1. 程式人生 > >Unity3D學習筆記(三十六):Shader著色器(3)- 光照

Unity3D學習筆記(三十六):Shader著色器(3)- 光照

像素 環境 斜率 偏移 target off 分辨 屬性 思路

光照模型:用數學的方法模擬現實世界中的光照效果。 場景中模型身上的光反射到相機中的光線: 1.漫反射:產生明暗效果 2.高光反射:產生鏡面反射,物體中有最亮且比較耀眼的一部分 3.自發光: 4.環境光: 漫反射 光線被粗糙的表面無規則的向各個方向反射的現象。 漫反射的計算公式(蘭伯特光照模型)模擬漫反射 漫反射光 = 光照顏色和強度 * 漫反射顏色 * max(0, dot(法線方向, 光照方向)) 光照顏色和強度:Unity引擎提供的,CGIncludes/UnityLightingCommon.cginc,需要引用 漫反射顏色:從屬性面板獲取的 法線方向:世界空間下的:需要把模型空間下的法線轉換到世界空間下 光照方向:從頂點指向光源的CGIncludes/UnityCG.cginc 技術分享圖片

技術分享圖片

逐頂點漫反射光照:是把光照計算放在頂點函數裏,效果差(過渡區域),效率高

Shader "Lesson/VFVertDiffuse" {
    Properties{
        _DiffuseColor("漫反射顏色", Color) = (1,1,1,1)
    }
    SubShader{
        Pass
        {
            //設置正確的光照渲染模式,前項渲染裏的基礎
            Tags { "LightMode" = "ForwardBase" }
               //定義Cg的開始和結束
            CGPROGRAM
            
#pragma vertex vert//定義頂點函數名字 #pragma fragment frag//定義片元函數名字 #include "Lighting.cginc"//如果想要使用提供一些燈光變量和方法,需要引入文件 //漫反射光 = 光照顏色和強度 * 漫反射顏色 * max(0, dot(法線方向, 光照方向)) //漫反射顏色:從屬性面板獲取 _DiffuseColor //光照顏色:系統提供的,_LightColor0
//法線方向:世界空間下的:需要把模型空間下的法線轉換到世界空間下 worldNormal fixed4 _DiffuseColor;//重定義屬性變量 struct m2v { //需要獲取頂點坐標和法線向量 float4 vex : POSITION; float3 normal : NORMAL; }; struct v2f { //把光照計算放在頂點函數中 //所以需要把計算之後的漫反射光Color傳遞到片元函數中 float4 position : SV_POSITION; fixed4 color : COLOR0; }; //實現定點和片元函數 v2f vert(m2v v) { v2f f; f.position = UnityObjectToClipPos(v.vex);//轉換頂點坐標 float3 worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal));//normalize歸一化向量 float3 worldPosition = mul(UNITY_MATRIX_M, v.vex);//先獲取世界空間下的頂點坐標 float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPosition));//UnityWorldSpaceLightDir,獲取光照方向 fixed3 color = _LightColor0.xyz * _DiffuseColor.xyz * max(0, dot(worldNormal, lightDir));//漫反射顏色,光照不影響透明度,所以是.xyz沒有w f.color = fixed4(color, 1); return f; } fixed4 frag(v2f f) : SV_Target { return f.color; } ENDCG } } FallBack "Diffuse" }

逐像素漫反射光照:是把光照計算放在片元函數裏,效果好,效率低

Shader "Lesson/VFFragDiffuse" {
    Properties{
        _DiffuseColor("漫反射顏色", Color) = (1,1,1,1)
    }
    SubShader{
        Pass
        {
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            fixed4 _DiffuseColor;
              
            struct m2v
            {
                float4 vex : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 position : SV_POSITION;
                float3 worldNormal : COLOR0;//把世界空間下的法線傳遞過去
                float3 worldPosition : COLOR1;//世界空間下的頂點坐標
            };
            v2f vert(m2v v)
            {
                v2f f;
                f.position = UnityObjectToClipPos(v.vex);
                f.worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal));//轉換法線向量,變成世界空間下的
                f.worldPosition = mul(UNITY_MATRIX_M, v.vex);
                return f;
            }
            fixed4 frag(v2f f) : SV_Target
            {
                float3 lightDir = normalize(UnityWorldSpaceLightDir(f.worldPosition));
                fixed3 color = _LightColor0.xyz * _DiffuseColor.xyz * max(0, dot(f.worldNormal, lightDir));              
                return fixed4(color, 1);
            }
            ENDCG          
        }
    }
    FallBack "Diffuse"
}

Pass
{
    //設置正確的光照渲染模式,前項渲染裏的基礎
    Tags { "LightMode" = "ForwardBase" }
}

技術分享圖片

技術分享圖片

半蘭伯特光照模型,解決蘭伯特背光面毫無細節的問題。 漫反射光 = 光照顏色和強度 * 漫反射顏色 * (dot(法線方向, 光照方向) * 0.5 + 0.5) 實現半蘭伯特光照的逐像素渲染和逐頂點渲染
fixed3 color = _LightColor0.xyz * _DiffuseColor.xyz * (dot(f.worldNormal, lightDir) * 0.5 + 0.5);
高光反射 高光:物體上最亮的部分。 鏡面反射:當平行的入射光線射到物體光滑的表面,仍會平行的向一個方向反射出來。 高光(Specular) = 光照顏色和強度 * 高光顏色 * pow(max(0, dot(v, r)), 高光系數) V:頂點到相機的方向 R:反射光的方向
Shader "Lesson/VFVertSpecular" {
    Properties{
        _DiffuseColor("漫反射顏色", Color) = (1,1,1,1)
        _SpecularColor("高光顏色", Color) = (1,1,1,1)
        _Gloss("高光系數", Range(0, 20)) = 0.2
    }
    SubShader{
        Pass
        {
            Tags { "LightMode" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
           
            fixed4 _DiffuseColor;
            fixed4 _SpecularColor;
            float _Gloss;
              
            struct m2v
            {
                float4 vex : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 position : SV_POSITION;
                fixed4 color : COLOR0;               
            };
            v2f vert(m2v v)
            {
                v2f f;
                f.position = UnityObjectToClipPos(v.vex);
                //高光(Specular) = 光照顏色和強度 * 高光顏色 * pow(max(0, dot(v, r)), 高光系數)
                //V:頂點到相機的方向 UnityWorldSpaceViewDir(worldPosition)
                //R:反射光的方向 reflect(-lightDir, worldNormal)
                //光照顏色和強度 _LightColor0
                //高光顏色 _SpecularColor
                //高光系數 _Gloss
                //世界空間下的法線
                float3 worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal));
                //世界空間下的頂點坐標
                float3 worldPosition = mul(UNITY_MATRIX_M, v.vex);
                //頂點到光源的方向
                float3 lightDir = normalize(UnityWorldSpaceLightDir(worldPosition));
                //頂點到相機的方向
                float3 cameraDir = normalize(UnityWorldSpaceViewDir(worldPosition));
                //反射光的方向
                float3 refDir = reflect(-lightDir, worldNormal);
                //漫反射 半蘭伯特
                fixed3 diffuse = _LightColor0.xyz * _DiffuseColor.xyz * (dot(worldNormal, lightDir) * 0.5 + 0.5);
                //高光
                fixed3 specular = _LightColor0.xyz * _SpecularColor.xyz * pow(max(0, dot(cameraDir, refDir)), _Gloss);
                f.color = fixed4(diffuse + specular, 1);
                return f;
            }
            fixed4 frag(v2f f) : SV_Target
            {
                return f.color;
            }
            ENDCG          
        }
    }
    FallBack "Diffuse"
}

實現漫反射,高光,自發光、貼圖紋理混合影響的Shader

// Upgrade NOTE: replaced ‘mul(UNITY_MATRIX_MVP,*)‘ with ‘UnityObjectToClipPos(*)‘
Shader "Lesson/VFFragSpecular" {
    Properties{
        _MainTex("紋理", 2D) = "white"{}
        _DiffuseColor("漫反射顏色", Color) = (1,1,1,1)
        _SpecularColor("高光顏色", Color) = (1,1,1,1)
        _Emission("自發光顏色", Color) = (0.05,0.05,0.05,1)
        _Gloss("高光系數", Range(1, 100)) = 1.05
    }
    SubShader{
        Pass
        {
            Tags{ "LightMode" = "ForwardBase" }
            CGPROGRAM
       
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
       
            //重定義
            fixed4 _DiffuseColor;
            fixed4 _SpecularColor;
            fixed4 _Emission;
            float _Gloss;
            sampler2D _MainTex;
           
            struct m2v
            {
                float4 vex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };
           
            struct v2f
            {
                //轉換之後的坐標
                float4 position : SV_POSITION;
                //世界空間下法線向量
                float3 worldNormal : COLOR0;
                //世界空間下的頂點坐標
                float3 worldPosition : COLOR1;
                //uv
                float2 uv : TEXCOORD0;
            };
            v2f vert(m2v v)
            {
                v2f f;
                f.position = UnityObjectToClipPos(v.vex);
                f.worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal));
                f.worldPosition = mul(UNITY_MATRIX_M, v.vex);
                f.uv = v.uv;
                return f;
            }
           
            fixed4 frag(v2f f) : SV_Target
            {
                /*高光(Specular) = 光照顏色和強度 * 高光顏色 * pow(max(0, dot(v, r)), 高光系數)
                V: 頂點到相機的方向  UnityWorldSpaceViewDir(世界空間下的頂點坐標)
                R:反射光的方向  reflect(從光源指向頂點的方向, 世界空間下的法線方向)
                */
           
                //頂點到相機的方法
                float3 cameraDir = normalize(UnityWorldSpaceViewDir(f.worldPosition));
                //光照方向 從頂點指向光源方法
                float3 lightDir = normalize(UnityWorldSpaceLightDir(f.worldPosition));
                //反射光方向  -lightDir  從光源指向頂點方法
                float3 refDir = reflect(-lightDir, f.worldNormal);
                //漫反射 半蘭伯特
                fixed3 diffuse = _LightColor0.xyz * _DiffuseColor.xyz * (dot(f.worldNormal,lightDir) * 0.5 + 0.5);
                //高光
                fixed3 specular = _LightColor0.xyz * _SpecularColor.xyz * pow(max(0, dot(cameraDir, refDir)), _Gloss);
                //自發光 _Emission
                //環境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //光的顏色
                fixed3 lightColor = diffuse + specular + _Emission + ambient;
                //根據UV獲取紋理的顏色
                fixed4 texColor = tex2D(_MainTex, f.uv);
                texColor.xyz = texColor.xyz * lightColor;
                return texColor;
            }                   
                ENDCG
        }
    }
        FallBack "Diffuse"
}

多光情況

技術分享圖片

// Upgrade NOTE: replaced ‘mul(UNITY_MATRIX_MVP,*)‘ with ‘UnityObjectToClipPos(*)‘
Shader "Lesson/VFMoreLightDiffuse" {
    Properties{
        _DiffuseColor("漫反射顏色", Color) = (1,1,1,1)
    }
    SubShader{
        Pass
        {
            Tags{ "LightMode" = "ForwardBase" }
            CGPROGRAM
       
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
           
            //重定義
            fixed4 _DiffuseColor;
       
            struct m2v
            {
                float4 vex : POSITION;
                float3 normal: NORMAL;
            };
           
            struct v2f
            {
                float4 position : SV_POSITION;
                fixed4 color : COLOR0;
            };
           
            v2f vert(m2v v)
            {
                v2f f;
                f.position = UnityObjectToClipPos(v.vex);
           
                //世界空間下的法線
                float3 worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal));
                //光照方向
                float3 lightDir = normalize(UnityWorldSpaceLightDir(mul(UNITY_MATRIX_M, v.vex)));
           
                fixed3 diffuse = _LightColor0.xyz * _DiffuseColor.xyz * max(0, dot(worldNormal, lightDir));
           
                f.color = fixed4(diffuse, 1);
                return f;
            }
           
            fixed4 frag(v2f f) : SV_Target
            {
                return f.color;
            }
            ENDCG
        }
        Pass
        {
            Blend One One//把點光源(新)和太陽光(舊)混合
            Tags{ "LightMode" = "ForwardAdd" }//最多添加4盞光源
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
           
            //重定義
            fixed4 _DiffuseColor;
            struct m2v
            {
                float4 vex : POSITION;
                float3 normal: NORMAL;
            };
            struct v2f
            {
                float4 position : SV_POSITION;
                fixed4 color : COLOR0;
            };
            v2f vert(m2v v)
            {
                v2f f;
                f.position = UnityObjectToClipPos(v.vex);
                //世界空間下的法線
                float3 worldNormal = normalize(mul(UNITY_MATRIX_M, v.normal));
                //光照方向
                float3 lightDir = normalize(UnityWorldSpaceLightDir(mul(UNITY_MATRIX_M, v.vex)));
                fixed3 diffuse = _LightColor0.xyz * _DiffuseColor.xyz * max(0, dot(worldNormal, lightDir));
                f.color = fixed4(diffuse, 1);
                return f;
            }
            fixed4 frag(v2f f) : SV_Target
            {
                return f.color;
            }
                ENDCG
        }
    }
    FallBack "Diffuse"
}

補充內容

技術分享圖片

補充:在障礙物後面顯示輪廓陰影

Shader "Lesson/PlayerBody" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _OcculusionColor ("被擋住以後的顏色", Color) = (1,1,1,1)
    }
    SubShader {
        Tags { "Queue"="Geometry+200" }
        Pass
        {
            ZTest Greater
            Color[_OcculusionColor]
        }
        Pass
        {
            ZTest Less
            Color[_Color]
        }
    }
    FallBack "Diffuse"
}

補充:物體多個材質球渲染,unity mobile shader:解決眼鏡和身體的遮擋關系

Shader "Lesson/Glass" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _OcculusionColor ("被擋住以後的顏色", Color) = (1,1,1,1)
    }
    SubShader {
        Tags { "Queue"="Geometry+100" }
        Pass
        {
            ZTest Greater
            ZWrite off
            Color[_OcculusionColor]
        }
        Pass
        {
            ZTest Less
            Color[_Color]
        }
    }
    FallBack "Diffuse"
}

技術分享圖片

邊緣發光

// Upgrade NOTE: replaced ‘_World2Object‘ with ‘unity_WorldToObject‘
// Upgrade NOTE: replaced ‘mul(UNITY_MATRIX_MVP,*)‘ with ‘UnityObjectToClipPos(*)‘
Shader"Lesson/OutLine1"
{
    Properties
    {
        _MainTex("main tex",2D) = "black"{}
        _RimColor("rim color",Color) = (1,1,1,1)//邊緣顏色
        _RimPower("rim power",range(1,10)) = 2//邊緣強度
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include"UnityCG.cginc"
            struct v2f
            {
                float4 vertex:POSITION;
                float4 uv:TEXCOORD0;
                float4 NdotV:COLOR;
            };
            sampler2D _MainTex;
            float4 _RimColor;
            float _RimPower;
            v2f vert(appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                float3 V = WorldSpaceViewDir(v.vertex);
                V = mul(unity_WorldToObject,V);//視方向從世界到模型坐標系的轉換
                o.NdotV.x = saturate(dot(v.normal,normalize(V)));//必須在同一坐標系才能正確做點乘運算
                return o;
            }
            half4 frag(v2f IN) :COLOR
            {
                half4 c = tex2D(_MainTex,IN.uv);
                //用視方向和法線方向做點乘,越邊緣的地方,法線和視方向越接近90度,點乘越接近0.
                //用(1- 上面點乘的結果)*顏色,來反映邊緣顏色情況
                c.rgb += pow((1 - IN.NdotV.x) ,_RimPower)* _RimColor.rgb;
                return c;
            }
            ENDCG
        }
    }
    FallBack"Diffuse"
}
輪廓 思路:兩次渲染,第一次渲染背面,剔除正面,把模型頂點沿法線方向擴伸一定距離(用來表現描邊的粗細) 第二次渲染,渲染正面,剔除背面
Shader "Lesson/OutLine2"
{
    Properties
    {
        _MainTex("main tex",2D) = ""{}
        _Factor("factor",Range(0,0.1)) = 0.01//描邊粗細因子
        _OutLineColor("outline color",Color) = (0,0,0,1)//描邊顏色
    }
    SubShader
    {
        Pass
        {
            Cull Front //剔除前面
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
       
            struct v2f
            {
                float4 vertex :POSITION;
            };
           
            float _Factor;
            half4 _OutLineColor;
           
            v2f vert(appdata_full v)
            {
                v2f o;
                //v.vertex.xyz += v.normal * _Factor;
                //o.vertex = mul(UNITY_MATRIX_MVP,v.vertex);
           
                //變換到視坐標空間下,再對頂點沿法線方向進行擴展
                float4 view_vertex = mul(UNITY_MATRIX_MV,v.vertex);
                float3 view_normal = mul(UNITY_MATRIX_IT_MV,v.normal);
                view_vertex.xyz += normalize(view_normal) * _Factor; //記得normalize
                o.vertex = mul(UNITY_MATRIX_P,view_vertex);
                return o;
            }
           
            half4 frag(v2f IN) :COLOR
            {
                //return half4(0,0,0,1);
                return _OutLineColor;
            }
            ENDCG
        }
        Pass
        {
            Cull Back //剔除後面
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
       
            struct v2f
            {
                float4 vertex :POSITION;
                float4 uv:TEXCOORD0;
            };
           
            sampler2D _MainTex;
           
            v2f vert(appdata_full v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP,v.vertex);
                o.uv = v.texcoord;
                return o;
            }
           
            half4 frag(v2f IN) :COLOR
            {
                //return half4(1,1,1,1);
                half4 c = tex2D(_MainTex,IN.uv);
                return c;
            }
            ENDCG
        }
    }
        FallBack "Diffuse"
}
描邊 思路:兩次渲染。第一次渲染背面,剔除正面。利用Offset指令,向離攝像機更近的方式偏移 第二次正常渲染物體(不剔除),(或者可以渲染正面,剔除背面)。 Offset:深度偏移 Offset Factor,Units Factor參數表示 Z縮放的最大斜率的值。 Units參數表示可分辨的最小深度緩沖區的值。 我的理解:深度偏移,Unity是左手坐標系,Z的正方向是朝屏幕裏面。 沿Z的負方向偏移就是離攝像機更近, 沿Z的正方向偏移就是離攝像機更遠。 作用:可以強制使位於同一位置上的兩個幾合體中的一個幾何體繪制在另一個的上層 比如:幾何體A的Z值比幾何體B更遠,此時B是在A的上層。 但是,給A設置了Offset 往Z的負方向偏移,此時A比B更近了,A就在B上層了。 在深度測試(Z test)的時候,會去比較Z值,近的會繪制在遠的上面。 所以上面的思路,要麽第一次渲染背面,往Z的負方向偏移 要麽第二次渲染的,往Z的正方向偏移,都是可以的。
Shader "Lesson/OutLine3"
{
    Properties
    {
        _MainTex("main tex", 2D) = ""{ }
        _OutLineColor("outline color",Color) = (0,0,0,1)//描邊顏色
    }
    SubShader
    {
        //描邊
        pass
        {
            Cull Front
            Offset -5,-1 //深度偏移
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
       
            sampler2D _MainTex;
            half4 _OutLineColor;
       
            struct v2f
            {
                float4  pos : SV_POSITION;
            };
       
            v2f vert(appdata_base v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
                return o;
            }
       
            float4 frag(v2f i) : COLOR
            {
                return _OutLineColor;
            }
            ENDCG
        }
        //正常渲染物體
        pass
        {
            //Cull Back
            //Offset 5,-1
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
       
            sampler2D _MainTex;
            float4 _MainTex_ST;
       
            struct v2f
            {
                float4  pos : SV_POSITION;
                float2  uv : TEXCOORD0;
            };
       
            v2f vert(appdata_base v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
                return o;
            }
       
            float4 frag(v2f i) : COLOR
            {
                float4 c = tex2D(_MainTex,i.uv);
                return c;
            }
            ENDCG
        }
    }
}


Unity3D學習筆記(三十六):Shader著色器(3)- 光照