前面(五)中,我们写了一个比较简单的shader用于处理模型纹理,模拟成为漫反射光照。
其实:光照除了可以选择不同的计算方式时,我们还有两种选择:逐顶点或者逐像素光照计算,计算方法相同,但硬件来处理时执行逐像素会更多一些(费时间一些)

先贴代码

Shader "Custom/Chapter 6/DiffuseVertexLevel" {
    Properties {
        _Diffuse ("Diffuse",Color) = (1,1,1,1)
    }
    SubShader {
        Pass {
            Tags {"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            fixed4 _Diffuse;
            // struct a2v {
            //     float4 vertex : POSITION;
            //     float3 normal : NORMAL; 
            // };
            // struct v2f {
            //     float4 pos : SV_POSITION;
            //     fixed3 color : COLOR0;
            // };
            // v2f vert(a2v v) {
            //     v2f o;
            //     fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
            //     fixed3 worldNormal = normalize(mul(v.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;
            // }
            // fixed4 frag(v2f i) : SV_Target {
            //     return fixed4(i.color,1.0);
            // }


            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0; // 获取纹理坐标
            };
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                // 世界空间法线传给着色器
                o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject); 
                return o;
            }
            fixed4 frag(v2f i) : SV_Target {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                  // 获取环境光
                fixed3 worldNormal = normalize(i.worldNormal);                  // 获取世界坐标的法线
                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);        // 光源方向
                // _LightColor0:光源颜色和强度,saturate:把参数限制在[0,1]
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
                fixed3 color = ambient + diffuse;
                return fixed4(color,1.0);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

整理思路

顶点处理和像素处理

注释掉的代码是逐顶点处理在vert函数中处理的,因为顶点数量远小于像素数量,所以处理起来会更快一些.
frag处理时,和vert采用了一样的计算方法。思考:这是什么计算办法呢?
计算公式如下:

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight))

光照模拟, Lambert

从上面的Shader发现了一样的计算方法,也叫做Lambert(中文:兰伯特)光照模型。关于这个光照模型的效果我们看下面一张图!

三个以此为: 逐顶点Lambert,逐像素Lambert,逐像素Lambert(英文:Half Lambert)
逐顶点Lambert:光照从右到左过渡比较突兀
逐像素Lambert:光照从右到左过渡良好,但最左时(受不到光照时),会有明显的变黑
逐像素半Lambert:最左时,并不太黑。公式如下:

fixed halfLambert = dot(worldNormal,worldLight) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;

计算入射光线和法线的夹角后,通过缩小一半[0,1]会变成[0,0.5],再加上0.5后变成[0.5,1]。
等于受到光照强度最高值影响不大,但光照强度最低处就限制为0.5了

颜色的数学

颜色做乘除法

当我们用白色在进行乘除,很容易理解, 光照强度的改变。比如(255,255,255)是白色,乘以0时得到的结果(0,0,0)就是黑色了。所以任意颜色只要乘以一个系数,得到的值就是这个点下的光照强度。下面让我们看下公式:

saturate(dot(worldNormal,worldLight)): 这个计算结果是0到1,fixed3 xyz做乘法后,这个点出的颜色变得更亮了。这就是我们通过Lambert模型来让物体受到光照(物体不同地方受到不一样的光照)

关于颜色的其他计算

你可以参考: 颜色中的数学