书中的6.4节讲的是漫反射的逐顶点光照和逐片元光照。
前一种算法是根据漫反射公式计算顶点颜色(顶点着色器),对颜色插值(光栅化过程)返回每个像素的颜色值(片元着色器)。
第二种算法是获得顶点的法线(顶点着色器),对法线插值(光栅化过程),根据漫反射公式计算像素颜色(片元着色器)。
注:漫反射公式:(光源颜色 * 材质漫反射颜色)* (表面法线矢量 · 表面到光源的矢量)
书中对上两种算法的实现如下:
1 Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex Level" 2 { 3 Properties 4 { 5 _Diffuse("Diffuse", Color) = (1.0, 1.0, 1.0, 1.0) 6 } 7 SubShader 8 { 9 Pass 10 { 11 Tags { "LightMode"="ForwardBase" } 12 13 CGPROGRAM 14 15 #pragma vertex vert 16 #pragma fragment frag 17 18 #include "Lighting.cginc" 19 20 fixed4 _Diffuse; 21 22 struct a2v 23 { 24 float4 vertex : POSITION; 25 float3 normal : NORMAL; 26 }; 27 28 struct v2f 29 { 30 float4 pos : SV_POSITION; 31 fixed3 color : COLOR; 32 }; 33 34 v2f vert (a2v v) 35 { 36 v2f o; 37 o.pos = mul(UNITY_MATRIX_MVP, v.vertex); 38 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; 39 fixed3 worldNormal = normalize(mul(v.normal, (float3x3)_World2Object)); 40 fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz); 41 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight)); 42 o.color = ambient + diffuse; 43 44 return o; 45 } 46 47 fixed4 frag(v2f i) : SV_Target 48 { 49 return fixed4(i.color, 1.0); 50 } 51 52 ENDCG 53 } 54 } 55 56 Fallback "Diffuse" 57 }
逐顶点光照
1 Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel Level" 2 { 3 Properties 4 { 5 _Diffuse("Diffuse", Color) = (1.0, 1.0, 1.0, 1.0) 6 } 7 SubShader 8 { 9 Pass 10 { 11 Tags { "LightMode"="ForwardBase" } 12 13 CGPROGRAM 14 15 #pragma vertex vert 16 #pragma fragment frag 17 18 #include "Lighting.cginc" 19 20 fixed4 _Diffuse; 21 22 struct a2v 23 { 24 float4 vertex : POSITION; 25 float3 normal : NORMAL; 26 }; 27 28 struct v2f 29 { 30 float4 pos : SV_POSITION; 31 fixed3 worldNormal : TEXCOORD0; 32 }; 33 34 v2f vert (a2v v) 35 { 36 v2f o; 37 o.pos = mul(UNITY_MATRIX_MVP, v.vertex); 38 o.worldNormal = mul(v.normal, (float3x3)_World2Object); 39 return o; 40 } 41 42 fixed4 frag(v2f i) : SV_Target 43 { 44 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; 45 fixed3 worldNormal = normalize(i.worldNormal); 46 fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); 47 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir)); 48 fixed3 color = ambient + diffuse; 49 return fixed4(color.rgb, 1.0); 50 } 51 52 ENDCG 53 } 54 } 55 56 Fallback "Diffuse" 57 }
逐片元光照
刚开始看这两段代码有点懵,觉得并没有什么区别,不理解为什么将漫反射的计算挪到片元着色器中就能区别上面两种算法。
查了一些帖子了解到,顶点着色器是对每个顶点数据进行处理的,包括空间转换和颜色计算等,并不是对每个像素进行处理。而片元着色器有时又叫做像素着色器,它是对每个片元处理一次。片元着色器得到的片元数据(像素数据),是在光栅化过程中对顶点数据进行插值得到的,并不只是顶点数据,
“属性插值的方法可以得到片元的颜色、法向量、深度值、纹理坐标等。”(https://wenku.baidu.com/view/219ecc7daef8941ea66e0519.html?pn=50NaN),即在片元着色器中使用的法线数据,其实已经是插值后的,因此在片元着色器中计算漫反射时,使用的就是逐片元光照的算法。这也是为什么逐片元光照会比逐顶点光照效率低的原因(计算漫反射的次数更多)。
“但是,由于逐顶点光照依赖于线性插值来得到像素光照,因此,当光照模型中有非线性的计算(例如计算高光反射时)时,逐顶点光照就会出问题。而且,由于逐顶点光照会在渲染图元内部对顶点颜色进行插值,这会导致渲染图元内部的颜色总是暗于顶点处的最高颜色值,这在某些情况下会产生明显的棱角现象。”