- unity shader 中实现漫反射光照模型

时间:2021-02-02 20:01:36
在入门精要第六章,6.4中,如何实现逐定点光照模型。
代码和注释如下
//标识当前shader文件的路径和在shader选择列表中的名称 Shader "UnityShaderPratice/Chapter6/VertexDiffuse" //定义一个数形变量,变量名称是_Diffuse, 在编辑器中显示的变量名称为"Diffuse", 变量类型"Color", 变量的默认值是(1,1,1,1) Properties {      _Diffuse("Diffuse", Color) = (1,1,1,1) }
//Unity的着色器中都包含了一个或者多个子着色器,unity会根据当前设备显卡的配置和参数选择一个适合的subshader进行着色绘制 SubShader{     //顶点着色器或者片元着色器都需要在pass模块中书写      Pass{           //标签标示当前这个pass在光照流水线中的角色           Tags{"LightModel" = "ForwardBase"}                      //用来包围CG代码           CGPROGRAM                      //利用pragma指令来告诉unity我们分别定义了一个顶点着色器和片元着色器           #pragma vertex vert           #pragma fragment frag                      //为了使用unity内置的一些变量,           #include "Lighting.cginc"                      //为了在shader中使用属性模块中的_Diffuse,我们需要定义一个和该属性类型相匹配的变量           //材质的漫反射属性的取值范围一般在0 - 1,所以我们可以用fixed精度的变量来存储它。           fixed4 _Diffuse;                          //声明定义点着色器数据结构           struct a2v{               float4 vertex : POSITION;               float3 normal : NORMAL;            };                      //声明定义片元着色器数据结构           struct v2f{                float4 pos : SV_POSITION;                fixed3 color : COLOR;           };                      //定义顶点着色器           v2f vert (a2v v) {                //声明定义一个v2f片元着色器临时变量                v2f o;                fixed3 ambient =  UNITY_LIGHTMODEL_AMBIENT.xyz;                //获取定点的投影坐标                o.pos = UnityObjectToCliPos(v.vertex);                //获取定点法线的世界向量                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));                //获取定点法线的光照向量                fixed worldLight = normalize(_WorldSpaceLightPos0.xyz);                    fixed3 color = _LightColor0 * _Diffuse * saturate(dot(worldNormal, worldLight));                o.color = color + ambient ;                return o;                           }                      fixed4 frag(v2f v) : SV_Target{                return fixed4(v.color, 1.0);           }                      ENDCG      }
}

我遇到的问题。 法线向量B = 转换矩阵A-B * 法线向量A,即要将法线向量从模型空间转换到世界空间,但为了解决顶点坐标在转换过程中的非等比缩放问题,需要对法线向量的转换矩阵进行推导计算。推导的结果是将原有顶点转换矩阵进行逆转置操作。逆操作是为了还原比例缩放问题,但同样也还原了旋转,而旋转矩阵都是正交矩阵,那么可以在对还原后的矩阵进行转置操作,这样就可以在完成旋转的前提下还原费等比缩放问题。
(法线向量B是在世界空间下,法线向量A是在模型空间) 那么在实际转换的时候,按理说应该写成
mul ((float3x3)unity_ObjectToWorld, v.normal); 但笔者写成了mul(v.normal, (float3x3)unity_WorldToObject);我思考了好久,其实答案就在书中 4.9.2CG中的矢量和矩阵类型明确解释了
问题的本质在于: 在对顶点矩阵从模型空间转换到世界空间的时候,如果他的缩放比例没有做到等比缩放或者扩大,那么顶点坐标也会做不等比例的拉伸或者缩小,这样会导致当用顶点转换矩阵用在法线向量上后,推导后的法线向量不会再垂直顶点坐标。而非等比缩放矩阵本身不是正交矩阵,旋转矩阵本身是旋转矩阵。那么可以先对推导矩阵进行求逆,这样做将会还原顶点推导矩阵的缩放操作和旋转操作,但旋转矩阵本身是正交矩阵,矩阵的转置 == 矩阵的逆矩阵。所以可以对原有顶点推导矩阵进行求逆后在进行转置,这样一方面做到了还原缩放比例操作,另外对法线向量进行旋转操作。


等式1 :mul(v, m) == mul(transposed(m), v);等式2 :mul(m, v) == mul(v, transposed(m));

那么根据等式1可以得到 mul ((float3x3)unity_ObjectToWorld, v.normal); ==  mul(v.normal, (float3x3)unity_WorldToObject);



等式1 :mul(v, m) == mul(transposed(m), v);等式2 :mul(m, v) == mul(v, transposed(m));