KaaPexei 发表于 2022-8-11 12:28

技术美术成长之路——UnityShader篇(五)、基础光照

本文参考:
相关文章汇总:
第六章、Unity中的基础光照

1、真实光照的研究

首先光来到物体后在一个点上只可能发生一件事:散射或者吸收。
对于散射,就是会改变光的方向其他能量都不变,吸收就是吸收能量,但是剩下的光方向不变。
散射分为两种,一种是在物体表面就改变方向的,这种我们叫做高光反射或者镜面反射。还有一种是会折射进入物体内部,进入内部之后,因为光线是光子么,所以会有一部分被吸收掉,而剩余一部分会反射出去,这种就叫漫反射,他这个反射出去的方向就是周围各个方向。
吸收就发生在直线传播的过程,拐弯了就是散射了。
一根光线可能会发生两个散射行为,因为光线是光子,所以算是一个整体的东西,各个光子可以有不同的行为。当然这个和照射谁是有关系的。


上边是我们谈的真实物理的光照行为,但是这节课并不是完全模拟真实的物理行为,而是进行一定程度的简化,真正符合物理的光照左右BRDF在后面会有说。
2、标准光照模型

只关心直接光照,也就是看到的光只考虑光直接照射进眼睛和光经过一次散射照射进眼睛的,对于自发光物体不考虑它对其他物体的照亮。
环境光就是一个常量,所有物体都一样。
自发光同理也是常量,对于自发光物体累计上就行。
漫反射接受的irradiance和夹角有关,出射的radiance和本身的材质吸收情况有关。
高光反射一般认为没有被吸收,所以原来的量反射出去,但是看到的多少和观察方向有关,也就是观察方向和反射光线之间的夹角。这里针对phong模型,blinn改进了一点,改成了半程向量和法线之间的夹角来衡量。
另外一开始接触这些shader的时候,我有一个疑问就是顶点着色器都是一些变换,为啥她也叫着色器,他着什么色了。后来就在shading中找到了答案,也就是下面我们要说的:
着色计算,我们可以针对顶点计算,然后像素的结果通过顶点之间的插值得到。我们也可以先对像素进行插值,获得像素的法线等信息,然后对像素进行计算。
对于前者,我们的着色计算就发生在顶点着色器中,但是这种方式很少用,所以在最开始的时候没有听说,也就有那么个疑问,但是后来的几何shader和计算shader等等的这些就不好解释了,估计是跟风起名字,这样也好统一起来。
其实前者用的少是因为,他相对不准确,而且她既然是插值来的,说明中间的颜色更加接近平均,所以就会出现中间的颜色总是暗于顶点处的最高颜色值,这样显示的结果就会出现一些棱角情况。


3、环境光和自发光

4、漫反射光的实现



我们需要的条件,入射光的能量,法线和光照方向,漫反射材质。
其中的max操作,可以用下面函数:


然后开始实现:


这里会使用到标签,但是没有详细说,知道同时使用内置光照变量的必要条件就行了。
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "unityShaderBook/chapter6/Phong"{
    Properties
    {
      _Diffuse("Diffuse", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
      Pass
      {
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM
            #include "Lighting.cginc"
            #pragma vertex vert
            #pragma fragment frag
            fixed4 _Diffuse;
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 color : COLOR;
            };
            v2f vert(a2v i)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = normalize(mul(i.normal, (float3x3)unity_WorldToObject));
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));

                o.color = ambient + diffuse;
                return o;
            }
            float4 frag(v2f i) : SV_Target
            {
                return fixed4(i.color, 1);
            }

            ENDCG
      }
    }
    Fallback "Diffuse"
}

实现的结果,锯齿是比较明显的!所以接下来考虑使用逐像素的实现。
无论是哪一种,计算都是在world空间下进行的!


Shader "unityShaderBook/chapter6/phongPerFragment"
{

Properties
{
    _Diffuse("Diffuse", Color) = (1, 1, 1, 1)
}

SubShader
{

    Tags{"LightMode" = "ForwardBase"}

    Pass
    {

      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag
      fixed4 _Diffuse;
      #include "Lighting.cginc"
      struct a2v
      {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
      };

      struct v2f
      {
            float4 pos : SV_POSITION;
            float3 normal : NORMAL;
      };

      v2f vert(a2v i)
      {
            v2f o;
            o.pos = UnityObjectToClipPos(i.vertex);
            o.normal = mul(i.normal, unity_WorldToObject);
            return o;
      }

      fixed4 frag(v2f i) : SV_Target
      {
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT;
            fixed3 light = _WorldSpaceLightPos0;
            fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * saturate(dot(light, i.normal));

            return fixed4(ambient + diffuse, 1);
      }
      ENDCG
    }
}
    Fallback "Diffuse"
}但是现在从没有光的一侧看过去:


缺乏体积感!
所以改用半兰伯特操作:




代码只需修改一行:
fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * (0.5*(dot(light, i.normal)) + 0.5);5、高光实现:



相关代码:
Shader "unityShaderBook/chapter6/phongSpecularPerVertex"
{

    Properties
    {
      _Diffuse("Diffuse", Color) = (1, 1, 1, 1)
      _Specular("Specular", Color) = (1, 1, 1, 1)
      _Gloss ("Gloss", Range(8, 256)) = 20
    }

    SubShader
    {

      Tags{"LightMode" = "ForwardBase"}

      Pass
      {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 color : COLOR;
            };

            v2f vert(a2v i)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT;
                fixed3 worldNormal = normalize(mul(i.normal, unity_WorldToObject));
                fixed3 worldLight = normalize(_WorldSpaceLightPos0);
                fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * (saturate(dot(worldNormal, worldLight)));
                fixed3 worldReflectDir = worldNormal * dot(worldLight, worldNormal) * 2 - worldLight;
                fixed3 worldEyeDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, i.vertex));
                fixed3 specular = _Specular.rgb * _LightColor0.rgb * pow(saturate(dot(worldEyeDir, worldReflectDir)), _Gloss);
                o.color = ambient + diffuse + specular;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return fixed4(i.color,1 );
            }

            ENDCG

      }
    }

    Fallback "Diffuse"
}上边并没有用到一个函数reflect:
所以求worldReflectDir 的时候,可以用reflect函数:
fixed3 worldReflectDir = reflect(-worldLight, worldNormal);上边是逐顶点的结果,仔细看侧面高光很不自然:


而对比逐像素的结果:


相关代码:
Shader "unityShaderBook/chapter6/phongSpecularPerFragment"
{

    Properties
    {
      _Diffuse("Diffuse", Color) = (1, 1, 1, 1)
      _Specular("Specular", Color) = (1, 1, 1, 1)
      _Gloss ("Gloss", Range(8, 256)) = 20
    }

    SubShader
    {

      Tags{"LightMode" = "ForwardBase"}

      Pass
      {

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 normal : NORMAL;
                float4 posWorld : TEXCOORD;
            };

            v2f vert(a2v i)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);
                o.normal = mul(i.normal, unity_WorldToObject);
                o.posWorld = mul(unity_ObjectToWorld, i.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT;
                fixed3 worldLight = normalize(_WorldSpaceLightPos0);
                fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * (saturate(dot(i.normal, worldLight)));
                fixed3 worldReflectDir = reflect(-worldLight, i.normal);
                fixed3 worldEyeDir = normalize(_WorldSpaceCameraPos.xyz - i.posWorld);
                fixed3 specular = _Specular.rgb * _LightColor0.rgb * pow(saturate(dot(worldEyeDir, worldReflectDir)), _Gloss);
               
                return fixed4(ambient + diffuse + specular,1 );
            }
            ENDCG
      }
    }

}
最后blinn-phong其实改一行即可:
fixed3 specular = _Specular.rgb * _LightColor0.rgb * pow(dot(normalize(worldEyeDir+worldLight), i.normal), _Gloss);把之前的specular注释掉,同时worldReflectDir也没用了,也可注释掉。
6、内置函数



我们求光照方向本来是用的内置变量,但是针对点光源可能就不是这么搞了,但是上边的函数直接不管啥光源都能用。然后对于eye方向也不用减法了,直接用上边函数就够了。
注意他们没有归一化求出的光照方向。
修改成利用以上函数的代码:
Shader "unityShaderBook/chapter6/phongLastBuildInFunction"
{

    Properties
    {
      _Diffuse("Diffuse", Color) = (1, 1, 1, 1)
      _Specular("Specular", Color) = (1, 1, 1, 1)
      _Gloss ("Gloss", Range(8, 256)) = 20
    }

    SubShader
    {

      Tags{"LightMode" = "ForwardBase"}

      Pass
      {

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 normal : NORMAL;
                float4 posWorld : TEXCOORD;
            };

            v2f vert(a2v i)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);
                o.normal = UnityObjectToWorldNormal(i.normal);
                o.posWorld = mul(unity_ObjectToWorld, i.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT;
                fixed3 worldLight = normalize(UnityWorldSpaceLightDir(i.posWorld));
                fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * (saturate(dot(i.normal, worldLight)));
                fixed3 worldEyeDir = normalize(UnityWorldSpaceViewDir(i.posWorld));
                fixed3 specular = _Specular.rgb * _LightColor0.rgb * pow(dot(normalize(worldEyeDir+worldLight), i.normal), _Gloss);

                return fixed4(ambient + diffuse + specular,1 );
            }

            ENDCG


      }

    }
}找了一些相关函数的总结:
现在对于函数调用还不是很熟悉!找了一些总结,还是五行缺练!
页: [1]
查看完整版本: 技术美术成长之路——UnityShader篇(五)、基础光照