【Unity Shaders】學習筆記——SurfaceShader(十一)光照模型
轉載請注明出處:http://www.cnblogs.com/-867259206/p/5664792.html
-
如果你想從零開始學習Unity Shader,那么你可以看看本系列的文章入門,你只需要稍微有點編程的概念就可以。
-
水平有限,難免有謬誤之處,望指出。
LitSphere(Matcap)
發光球體光照模型就是將發光球體的紋理映射在球體上,來實現光照效果。這可以創造一些效果細膩的發光球體效果,但是它不受光照影響,改變光照的方向,球體的光照效果不變。如果要在固定視角的場景里制作細膩的球體光照,這會是一個不錯的選擇。
准備小球紋理貼圖:
-
定義Properties:
Properties {
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
}
-
編寫預編譯命令:
#pragma surface surf Unlit vertex:vert
#pragma target 3.0
-
在subshader里關聯properties:
float4 _MainTint;
sampler2D _MainTex;
sampler2D _NormalMap;
-
定義光照函數Unlit:
inline half4 LightingUnlit (SurfaceOutput s, fixed3 lightDir, fixed atten)
{
half4 c = half4(1,1,1,1);
c.rgb = s.Albedo;
c.a = s.Alpha;
return c;
}
這是一個無光照的光照函數,因為我們要用紋理上的光照效果,所以不需要計算光照。
-
定義Input結構體:
struct Input {
float2 uv_MainTex;
float2 uv_NormalMap;
float3 tan1;
float3 tan2;
};
-
定義頂點函數:
void vert (inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input,o);
TANGENT_SPACE_ROTATION;
o.tan1 = mul(rotation, UNITY_MATRIX_IT_MV[0].xyz);
o.tan2 = mul(rotation, UNITY_MATRIX_IT_MV[1].xyz);
}
解釋:
NITY_INITIALIZE_OUTPUT(Input,o);在HLSLSupport.cginc文件里是這樣定義的:
#if defined(UNITY_COMPILER_HLSL) || defined(SHADER_API_PSSL) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE)
#define UNITY_INITIALIZE_OUTPUT(type,name) name = (type)0;
#else
#define UNITY_INITIALIZE_OUTPUT(type,name)
#endif
如果是HLSL編譯器或某些版本的Shader API,則將變量賦值為0,否則什么都不做。這樣編譯器就不會報錯說變量未初始化。
TANGENT_SPACE_ROTATION;是Unity提供的一個宏,它定義了一個rotation矩陣用於從Object Space變換到Tangent Space。它在UnityCG.cginc里的定義如下:
// Declares 3x3 matrix 'rotation', filled with tangent space basis
#define TANGENT_SPACE_ROTATION \
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal ));
binormal是切空間的Y軸,tangent是切空間的X軸,normal是切空間的Z軸。為什么它定義了一個切空間旋轉看這篇。
UNITY_MATRIX_IT_MV是Object Space(or Model Space)變換到View Space(or Eye Space)的矩陣的逆轉置矩陣。View Space以攝像機為原點,攝像機朝向為Z軸。[]是取出矩陣的一列,UNITY_MATRIX_IT_MV[0].xyz是說將向量(1,0,0)(即X軸)左乘MV矩陣的逆轉置矩陣。UNITY_MATRIX_IT_MV[1].xyz則是向量(0,1,0)(即Y軸)左乘MV矩陣的逆轉置矩陣。
順便科普一下,OpenGL是列向量,變換矩陣應該左乘向量。如果要將法線從Object Space變換到View Space是不能用MV矩陣的,原因看這篇博文。要將法線從Object Space變換到View Space要用MV矩陣的逆轉置矩陣。那么,反過來,如果要將法線從View Space變換到Object Space只要將MV矩陣的逆轉置矩陣右乘法線即可。這里的代碼可以理解為將View Space的X軸和Y軸變換到了Object Space。變換到了Object Space以后再乘rotation變換到Tangent Space。
-
最后,編寫表面處理函數:
void surf (Input IN, inout SurfaceOutput o)
{
float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
o.Normal = normals;
float2 litSphereUV;
litSphereUV.x = dot(IN.tan1, o.Normal);
litSphereUV.y = dot(IN.tan2, o.Normal);
half4 c = tex2D (_MainTex, litSphereUV*0.5+0.5);
o.Albedo = c.rgb * _MainTint;
o.Alpha = c.a;
}
這部分需要的解釋不多。在表面處理函數里將法線和tan1、tan2點乘,相當於是把法線投影到了View Space的X軸和Y軸上。后面就是將法線當作UV來采樣。乘0.5加0.5是把區間變到[0,1]。
LitSphere有點像把我們能看見的小球的那一面當作一層皮扒下來,然后平鋪在紋理上,使紋理上的球嚴絲合縫地投影在了小球上。效果如下:

如果我們用紋理本身的UV坐標的話,小球圖片是矩形,四周還有黑邊,那么采樣到小球上會是一個變形的四周有黑邊的圓球圖案。相當於是把畫了個球的紋理貼在球身上。而用法線作UV的話,就把紋理上有球的那部分映射到了我們看得見的球的表面。
我覺得把這理解為把球的表面投影到紋理上更好理解一點。這樣要把法線從切空間變換到視空間,每個頂點都要變換,計算量太大,所以換過來,轉換到切空間計算會cheap一點。
這個光照模型也叫Matcap(Material Capture)。
這個光照模型只能用於比較圓的物體,比如Sphere和Capsule,Cube是不能用的,因為Cube能看見的法線就只有三個,所以沒辦法使用這個光照模型。
另一個版本的Matcap