Unity Shader入門教程(三)自制光照模型


光照模型的概念目前還不明晰,因為筆者也是一個初學者,所以請小心對待筆者介紹的內容。筆者認為光照模型是規定光照算法的模型,比如說前面提到的Lambert光照模型,規定了材質表面的光線的表達式為

環境光+散射光+反射高光+放射光

 

我們通過一組小實驗來說明如何自定義光照模型。

1.1步:新建一個shader,兩個material

其中最后一個是采用了標准的diffuse

 

MEasyNormalMappingMaterial)采用了EasyNormalMappingshader)。

 

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步:新建一個Snowshader,新建一個名為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表示頂點的顏色。

 

——201772816:21:45 小江村兒的文傑 zouwj5@qq.com

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM