在入門精要第六章,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));