- unity shader 中實現漫反射光照模型

在入門精要第六章,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));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章