光照模型的概念目前還不明晰,因為筆者也是一個初學者,所以請小心對待筆者介紹的內容。筆者認為光照模型是規定光照算法的模型,比如說前面提到的Lambert光照模型,規定了材質表面的光線的表達式為
環境光+散射光+反射高光+放射光
我們通過一組小實驗來說明如何自定義光照模型。
第1.1步:新建一個shader,兩個material
其中最后一個是采用了標准的diffuse
MEasyNormalMapping(Material)采用了EasyNormalMapping(shader)。
第1.2步:
▼代碼開始 Shader "Custom/EasyNormalMapping" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf CustomDiffuse //還記得嗎?這一句的語法是:#pragma surface 光線處理函數 光照模型 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; fixed4 _Color; //這個surf非常簡單,就是取出了主紋理罷了 void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Alpha = c.a; } /*這里有很多注意事項: a:下面的LightingCustomDiffuse是CUstomDiffuse這個光照模型的計算光照的實現。 b:聲明此光照計算實現函數時,名稱必須要按照:Lighting<光照模型名稱>,此處即LightingCustomDiffuse c:光照模型首先經過surf方法得到表面顏色,然后利用LightingCustomDiffuse計算光線,最后呈現在屏幕上 d:這里采用了一個典型的形參列表:(表面顏色 SurfaceOutput s, 光線的來源方向 fixed3 lightDir, 光衰減的系數 fixed atten) 請注意 lightDir是指從表面上的研究點到光源的方向,也就是指向光源方向,而不是入射方向(是入射方向的反方向) e:返回一個四維的小數,其實是指返回的光線 (其它的形參列表可以看:http://blog.csdn.net/henger_/article/details/52689080) */ inline float4 LightingCustomDiffuse(SurfaceOutput s, fixed3 lightDir, fixed atten) { //都做了些什么呢?首先是表面法向量和光線方向的點乘,此數目越靠近1,散射越明亮(是不是很符合常理!) float difLight = max(0, dot(s.Normal, lightDir)); //col表示結果的顏色 float4 col; //這里_LightColor0是由Unity根據場景中的光源得到的光源的光色,具體來由不清楚,簡單說就是光源的顏色。 //這個公式表明:散射光色 = 表面的反射系數 * 光源 * 散射強度 * 衰減系數(有許多博文說這里要乘以2) col.rgb = s.Albedo*_LightColor0.rgb*(difLight*atten); col.a = s.Alpha; return col; } ENDCG } FallBack "Diffuse" } ▲代碼結束
第1.3步:我們為兩個材料都選上石頭的紋理圖,最后可以在預覽圖中看出,它們的效果完全一樣。
總結:我們已經自定義了一個光照模型,這個光照模型和標准內置的diffuse模型效果一樣,因為我們的實現原理是一樣的。
本文參考了貓都能學會的Unity3D Shader入門指南(二)
第二組實驗
halfLambert模型:
將上面的代碼稍修改一下,得到HalfLambert模型,這是一個在低光線條件下增亮的做法,是將光強系數 * 0.5+0.5的做法,非常簡單。
inline float4 LightingCustomDiffuse(SurfaceOutput s, fixed3 lightDir, fixed atten)
{
float difLight = max(0, dot(s.Normal, lightDir));
float4 col;
//只增加這一行
difLight = difLight * 0.5 + 0.5;
col.rgb = s.Albedo*_LightColor0.rgb*(difLight*atten);
col.a = s.Alpha;
return col;
}
下圖中左邊應用了HalfLambert模型。
第三組實驗:實現積雪效果
第3.1步:新建一個Snow的shader,新建一個名為MSnow的材質。
第3.2步:Snow的代碼如下:
▼代碼開始 Shader "Custom/Snow" { Properties{ _MainTex("Albedo (RGB)", 2D) = "white" {} _Bump("Bump", 2D) = "bump"{} _Snow("Snow Level", Range(0,1)) = 0.2 _SnowColor("Snow Color", Color) = (1,1,1,1) _SnowDirection("Snow Direction", Vector) = (0,1,0) //注意U3D中的Y軸是反重力方向的,而不是Z軸。這和UE4不一樣。 } SubShader{ Tags{ "RenderType" = "Opaque" } LOD 300 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; sampler2D _Bump; struct Input { float2 uv_MainTex; float2 uv_Bump; float3 worldNormal; INTERNAL_DATA /*什么意思?這表示當前點在世界中的法方向,我們采用;INTERNAL_DATA來標記這個特殊的輸入*/ //更為詳細的參考:https://docs.unity3d.com/Manual/SL-SurfaceShaders.html }; float _Snow; float4 _SnowColor; float4 _SnowDirection; void surf(Input IN, inout SurfaceOutput o) { half4 c = tex2D(_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump)); //解釋:輸入IN表示輸入的點(包含一個特殊的輸入,就是世界坐標下的法方向) //WorldNormalVector(IN, o.Normal)表示IN點以及它的法向量在世界坐標下的向量表達, //和雪的方向的點擊,和閾值作比較 //插值函數:lerp(a,b,c)表示(b-a)*c+a if ( dot( WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) > lerp(1, -1, _Snow)) { o.Albedo = _SnowColor.rgb;//取雪色 } else { o.Albedo = c.rgb;//取原色 } o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } ▲代碼結束
第3.3步:注意配上法向量和主紋理。
效果:
第四組實驗:本組實驗修改了頂點,也就是對模型進行了頂點的修改。此前的操作都是在貼圖上進行的,頂點沒有發生過改動。本組實驗強調修改頂點。
第4.1步:在剛剛的Snow(Shader)代碼中稍微修改,如下:
▼代碼開始 // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' Shader "Custom/Snow" { Properties{ _MainTex("Albedo (RGB)", 2D) = "white" {} _Bump("Bump", 2D) = "bump"{} _Snow("Snow Level", Range(0,1)) = 0.2 _SnowColor("Snow Color", Color) = (1,1,1,1) _SnowDirection("Snow Direction", Vector) = (0,1,0) //注意U3D中的Y軸是反重力方向的,而不是Z軸。這和UE4不一樣。 _SnowDepth("Snow Depth", Range(0,0.3))=0.1 //改動1 } SubShader{ Tags{ "RenderType" = "Opaque" } LOD 300 CGPROGRAM #pragma surface surf Lambert vertex:vert //改動2:在末尾添加vertex:Fun表示頂點模型將修改,修改的方法為Fun sampler2D _MainTex; sampler2D _Bump; struct Input { float2 uv_MainTex; float2 uv_Bump; float3 worldNormal; INTERNAL_DATA /*什么意思?這表示當前點在世界中的法方向,我們采用;INTERNAL_DATA來標記這個特殊的輸入*/ //更為詳細的參考:https://docs.unity3d.com/Manual/SL-SurfaceShaders.html }; float _Snow; float4 _SnowColor; float4 _SnowDirection; float _SnowDepth; //改動3 void surf(Input IN, inout SurfaceOutput o) { half4 c = tex2D(_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump)); //解釋:輸入IN表示輸入的點(包含一個特殊的輸入,就是世界坐標下的法方向) //WorldNormalVector(IN, o.Normal)表示IN點以及它的法向量在世界坐標下的向量表達, //和雪的方向的點擊,和閾值作比較 //插值函數:lerp(a,b,c)表示(b-a)*c+a if ( dot( WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) > lerp(1, -1, _Snow)) { o.Albedo = _SnowColor.rgb;//取雪色 } else { o.Albedo = c.rgb;//取原色 } o.Alpha = c.a; } //改動4:vert函數處理頂點,對於每一個頂點v,進行如下修改。inout appdata_full v是頂點的固有寫法。 void vert(inout appdata_full v) { /*需求:我們希望vert函數將“法方向和雪方向近似相同的所有頂點都抬高一些” 數學做法: 1:將雪方向(也就是010)轉換到物體坐標系中; 這是什么意思?就是以物體來看,雪的方向是什么,如果物體是倒置的(腦子朝下的),那么物體看來的雪方向應該是0,-1,0, 如果物體時腦子靠一側的,那么物體看的雪的方向將會是水平方向的(它是這樣認為的),例如1,0,0, 如何將世界里的雪方向以物體的視角來看待呢?數學上只需要 M(world2object的轉換矩陣) * 雪的世界方向 = 雪的物體系下的方向 【語法上】 1.a:unity_ObjectToWorld是內置的一個矩陣,表示此物體到世界的轉換矩陣(也就是UE4中的Transform) 1.b:transpose(P)表示將P轉置 1.c:mul(P,v)表示矩陣P左乘向量v即Pv 2:上述得到的物體看待的雪方向記為sn,那么sn和此頂點的法方向的點乘,點乘值越大,表示越是容易積雪的情況 如果大於閾值,那么頂點的xyz在積雪方向上增加一定的量,此處的積雪方向是指法+雪的方向 */ //寫法A: float4 sn = mul(transpose(unity_ObjectToWorld), _SnowDirection); //寫法B:sn = _SnowDirection; /*寫法A是正確的,寫法B是錯誤的,它沒有將雪的方向轉換為物體看待的描述*/ if (dot(v.normal, sn.xyz) >= lerp(1, -1, (_Snow * 2) / 3)) { v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth; } } ENDCG } FallBack "Diffuse" } ▲代碼結束
效果可以看出:下面的球中,容易積雪的頂點被抬高了許多。
appdata_full擁有如下成員:
struct appdata_full {
float4 vertex : POSITION;
float4 tangent : TANGENT;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 texcoord2 : TEXCOORD2;
float4 texcoord3 : TEXCOORD3;
fixed4 color : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
以上頂點的信息中,前四個分別是:點的位置,點的正切方向(也就是定點的正切空間的指向,正切空間就是表面指向外的方向,也就是物理世界中嚴格的概念“法線方向”),點的法線方向(規定的法線方向,以正切空間為坐標系),紋理信息,以及最后的Color表示頂點的顏色。
——2017年7月28日16:21:45 小江村兒的文傑 zouwj5@qq.com