Chapter9_光照

渲染路徑(Rendering Path)

渲染路徑決定了光照如何應用到Shader中,決定了把光源信息和處理後的光照信息放到那些數據中。

全局渲染路徑設置(Edit->ProjectSetting->Graphics)
單個攝像機設置渲染路徑

四種渲染路徑設置 :

  • Forward(前向渲染)
  • Deferred(延遲渲染)
  • Legacy Vertex Lit(遺留頂點照明渲染)
  • Legacy Deferred(light prepass)(遺留延遲渲染)

如果當前設備硬件不支持所選擇的渲染路徑,會自動使用更低一級的渲染路徑。例如設置了延遲渲染,硬件不支持,降低使用前向渲染路徑。不支持前向渲染,將使用遺留頂點照明渲染。

Shader指定某個Pass使用什麼渲染路徑,通過標籤LightMode設置。

標籤 描述
Always 不管使用什麼渲染路徑,該Pass都會被渲染,但不會計算任何光照
ForwardBase 用於前向渲染,該Pass會計算環境光,最重要的平行光,逐頂點/SH光源和Lightmaps
ForwardAdd 用於前向渲染,該Pass會計算額外的逐像素光源,每個Pass對應一個光源
Deferred 用於延遲渲染,該Pass會渲染G緩衝(G-buffer)
ShadowCaster 把物體的深度信息渲染到陰影映射紋理或一張深度紋理中
PrepassBase 用於遺留的延遲渲染,該Pass會渲染法線和高光反射的指數部分
PrepassFinal 用於遺留的延遲渲染,該Pass通過合併紋理,光照和自發光來渲染得到最後的顏色
Vertex,VertexLMRGBM,VertexLM 用於遺留的頂點照明渲染

Unity5.x後如果設置了前向渲染路徑,Pass沒有設置LightMode標籤,就會被當做遺留的頂點照明渲染路徑等同的Pass。


前向渲染路徑

1.前向渲染路徑原理

每進行一次完成的前向渲染,需要在一個Pass中渲染圖元,並計算兩個緩衝區。深度緩衝區決定片元是否可見,可見就更新顏色緩衝區顏色值。
對於每個逐像素光源,都需要一個Pass計算光照。一個物體如果受M個光源影響,就需要執行M個Pass,然後在幀緩衝中把這些光照結果混合得到最終顏色。如果一個場景有N個模型,每個模型受到M個光源影響,渲染這個場景需要 N * M 個Pass。因此渲染引擎會限制每個物體的逐像素光照數目。

2.Unity中的前向渲染

Unity中前向渲染有3中處理光照的方式:

  • 逐頂點處理
  • 逐像素處理
  • 球諧函數處理

光源的類型和渲染模式決定了它被怎麼處理。

  • 光源類型是指該光源是平行光還是其他類型光源。
  • 光源渲染模式是指該光源Render Mode屬性

Pixel Light Count數量(Quality Setting)的光源會按逐像素處理,然後最多四個光源按逐頂點處理,剩下的按SH方式處理。

  • 場景中最亮的平行光總是按逐像素處理
  • Render Mode被設置成Not Important的光源,按逐頂點或SH處理。
  • Render Mode被設置成Important的光源,按逐像素處理。
  • 根據以上規則,逐像素光源大於Pixel Light Count數量(Quality Setting),光源會被當做逐頂點或SH方式處理。
  • 最亮的平行光Render Mode被設置爲Not Important一樣會被當做逐頂點或SH方式處理。

前向渲染有兩種Pass:

  1. Base Pass(LightMode = "ForwardBase")     
  2. AdditionPass(LightMode = "ForwardBase")

Unity前向渲染(LightMode = ForwardBase/ForwardAdd)內置光照變量和函數 :

變量名稱 類型 描述
_LightColor0 float4 該Pass處理的逐像素光源的顏色
_WorldSpaceLightPos0 float4 _WorldSpaceLightPos.xyz是該Pass處理的逐像素光源的位置,如果該光源是平行光,那麼_WorldSpaceLightPos0.w是0,其他光源類型w值是1
_LightMatrix0 float4x4 從世界空間到光源空間的變換矩陣,可以用於採樣cookie和光強衰減紋理

unity_4LightPosX0,unity_4LightPosY0

unity_4LightPosZ0

float4 僅用於Base Pass,前4個非重要的點光源在世界空間中的位置
unity_4LightAtten0 float4 僅用於Base Pass,存儲了前4個非重要的點光源的衰減因子
unity_LightColor half[4] 僅用於Base Pass,存儲了前4個非重要的點光源的顏色
函數名 描述
float3 WorldSpaceLightDir(float4 v) 僅可用於前向渲染,輸入一個模型空間的頂點位置,返回世界空間中從該點到光源的光照方向。內部實現使用了UnityWorldSpaceLightDir函數,沒有歸一化。
float3 UnityWorldSpaceLightDir(float4 v) 僅可用於前向渲染,輸入一個世界空間下的頂點位置,返回世界空間中從該點到光源的光照方向,沒有被歸一化。
float3 ObjSpaceLightDir(float4 v) 僅可用於前向渲染,輸入一個模型空間下的頂點位置,返回模型空間中從該點到光源的光源方向,沒有被歸一化。
float3 Shade4PointLights(...) 僅可用於前向渲染,計算四個點光源的光照。它的參數是已經內置的光照數據。通常就像上表的 unity_4LightPosX0,  unity_4LightPosY0

unity_4LightPosZ0,unity_4LightAtten0,unity_LightColor等等。

通常使用這個函數計算逐頂點的光照

頂點照明渲染路徑

通常只使用一個Pass,進行逐頂點光照計算。

頂點照明渲染中,最多可以計算8個逐頂點光源的光照。如果某個物體所受光照小於8個,剩餘的光源顏色爲黑色。

頂點照明渲染路徑中可用的內置變量和函數

變量名稱 類型 描述
unity_LightColor half4[8] 光源顏色(如果某個物體所受光照小於8個,剩餘的光源顏色爲黑色。)
unity_LightPosition float4[8] xyz分量是視角空間中的光源位置,如果光源是平行光,那麼z分量值爲0,其他光源類型爲z分量值爲1.
unity_LightAtten half4[8] 光源衰減因子,如果光源是聚光燈,x分量是cos(spotAngle/2),y分量是1/cos(spotAngle/4);如果是其他類型的光源,x分量是-1,y分量是1。z分量是衰減的平方,w分量是光源範圍開根號的結果。
unity_SpotDirection float4[8] 如果光源是聚光燈,值爲視角空間的聚光燈的位置。如果是其他類型的光源,值爲(0,0,1,0)
函數名 描述
float3 ShadeVertexLights(float4 vertex,float3 normal) 輸入模型空間中的頂點位置和法線,計算四個逐頂點光源的光照以及環境光。內部實現實際上調用了ShadeVertexLightsFull函數
float3 ShadeVertexLightsFull(float4 vertex,float3 normal,int lightCount,bool spotLight) 輸入模型空間中的頂點位置和法線,計算lightCount個光源的光照以及環境光。如果spotLight值爲true。那麼這些光源會被當做聚光燈處理。雖然結果更精確,但是計算更加耗時。否則按點光源處理。

延遲照明渲染路徑(移動設備不支持)

當場景中包含大量實時光源時,前向渲染性能急速下降,這時候就可以使用延遲渲染路徑。除了前向渲染使用的顏色緩衝區和深度緩衝區外,延遲渲染還會利用額外的G緩衝區。G緩衝區存儲了表面法線,位置等用於光照計算的材質屬性等。

1.延遲渲染原理

  • Pass1(不進行光照計算,通過深度緩衝,計算片元可見性)

把物體的漫反射顏色,高光反射顏色,平滑度,法線,自發光和深度等信息渲染到屏幕空間的G緩衝區中。對於每個物體,這個Pass只會執行一次。

  • Pass2(將Pass1存儲到G緩衝區中可見片元,取出計算光照)

取出Pass1存到G緩衝區中的片元信息,計算光照,得到最終光照顏色。再存到幀緩衝區。

延遲渲染效率跟場景中光源數量沒關係。而和我們的屏幕空間大小有關。我們需要的信息都存儲在G緩衝區中,而緩衝區可以理解成一張張2D圖片。屏幕越大,計算越耗時。

2.Unity中延遲渲染

Unity5.x之前使用遺留的的延遲渲染路徑。Unity5.x之後使用延遲渲染路徑。

延遲渲染缺點:

  • 不支持真正的抗鋸齒功能
  • 不能處理半透明物體
  • 對顯卡有要求

3.G緩衝區的渲染紋理

  • RT0: 格式ARGB32,RGB存儲漫反射顏色,A通道沒使用。
  • RT1: 格式ARGB32,RGB存儲高光反射顏色,A通道存儲高光反射的指數部分。
  • RT2: 格式ARGB2101010,RGB存儲法線,A通道沒使用。
  • RT3: 格式ARGB32(非HDR)或 ARGBHalf(HDR),存儲自發光+lightmap+反射探針。
  • 深度緩衝和模板緩衝

4.延遲渲染路徑可訪問內置變量和函數

名稱 類型 描述
_LightColor float4 光源顏色
_LightMatrix0 float4x4 從世界空間到光源空間的變換矩陣,可以用於採樣cookie和光前衰減紋理

4個渲染路徑對比

特性 延遲渲染路徑 正向渲染路徑 傳統延遲渲染 傳統頂點光照
逐像素照明(法線貼圖、light cookie) x
實時陰影 有注意事項 x
反射探頭(Reflection Probe) x x
深度和法線緩衝區 額外的渲染通道pass x
Soft Particles x x
半透明物體 x x
抗鋸齒 x x
Light Culling Masks Limited Limited
光照保真度 所有 per-pixel 一些 per-pixel 所有 per-pixel 所有 per-vertex

實踐:1.前向渲染路徑

Shader "Chan/Chapter9_ForwardRendering" {
    Properties
    {
        _Diffuse("Diffuse",Color) = (1,1,1,1)
        _Specular("Specular",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(8.0,256)) = 20
    }
    SubShader
    {
        //Base Pass
        Tags{"RenderType" = "Opaque"}
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            
            //保證光照衰減等光照變量可以被正確賦值
            #pragma multi_compile_fwdbase
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLightDir));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 halfDir = normalize(worldLightDir + viewDir);

                //Blinn-Phong計算高光光照
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
                fixed atten = 1.0;
                return fixed4(ambient + (diffuse + specular) * atten,1.0);
            }

            ENDCG
        }
        Pass
        {
            Tags{"LightMode" = "ForwardAdd"}
            //開啓並設置混合模式 因爲希望幀在緩存中與之前的pass得到的光照結果進行疊加
            Blend One One

            CGPROGRAM
            
            #pragma multi_compile_fwdadd
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                #ifdef USING_DIRECTIONAL_LIGHT //光源爲平行光,沒有座標點
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                #else
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
                #endif

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLightDir));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);

                #ifdef USING_DIRECTIONAL_LIGHT
                fixed atten = 1.0;//平行光不衰減
                #else
                float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
                #endif

                return fixed4((diffuse + specular) * atten,1.0);
            }

            ENDCG
        }
    }
    Fallback "Specular"
}

解析:

1.結構 

Base Pass (逐像素的平行光+逐頂點光源+SH光源)  自發光+環境光+逐像素的平行光計算

Add Pass(除平行光外的其他所有逐像素光源) 其他逐像素光源光照計算

2.逐像素光照衰減計算相關變量

_LightTexture0 - 預處理得到的,一張包含了光源衰減信息的衰減紋理。Unity中使用通過採樣_LightTexture0來計算逐像素光照的衰減,避免複雜的數學公式。

dot(lightCoord, lightCoord).rr,首先是由點積得到光源的距離平方,這是一個標量,我們對這個變量進行.rr操作相當於構建了一個二維矢量,這個二維矢量每個分量的值都是這個標量值,由此得到一個二維採樣座標。詳細解釋

_LightMatrix0 - 爲了得到某點的衰減值,需要知道把頂點座標轉化到光源空間,通過採樣_LightTexture0得到。_LightMatrix0爲從世界空間到光源空間的變換矩陣。

UNITY_ATTEN_CHANNEL - 採樣_LightTexture0得到的數據除了衰減值,還有別的數據。通過UNITY_ATTEN_CHANNEL取到存儲衰減值的分量。

使用數學公式計算衰減(線性衰減):

float distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);

atten = 1.0 / distance;   物體離開光源的照明範圍,Unity不會執行Additional Pass。效果會發生突變。。。


Unity的陰影

Base Pass,Additional Pass中有很多光照計算,而計算陰影只需要得到深度信息而已。因此爲了節約性能,單獨用一個LightMode = “ShadowCaster”的Pass。

1.物體投射陰影原理

1.1普通的Shadow Map技術分爲兩步:

  • 從光源視角渲染整個場景,獲得Shadow Map(陰影映射紋理),Shadow Map存放的是場景中距離光源最近的深度信息。
  • 實際攝像機渲染物體,將模型可見頂點座標轉換到光源視角下,得到的深度與Shadow Map採樣得到的深度信息比較,深度大於Shadow Map的說明頂點雖然可見,但是不能處在陰影中。準確來說,此步的描述只是針對Foward渲染模式——對物體做光照計算的同時做陰影計算。如下圖所示:

1.2屏幕空間的陰影映射技術(Screenspace Shadow Map) 

普通Shadow Map技術,對片段的光照計算(包括陰影)可能會被浪費掉(片元不可見被丟棄)。後來的延遲渲染模式(Deferred)消除了overdraw(通過深度緩衝,先計算片元可見性),屏幕空間的陰影映射技術採樣延遲渲染模式計算陰影。

  • 首先得到從當前攝像機處觀察到的深度紋理。在延遲渲染裏這張深度圖本來就有,如果是前向渲染的話就需要把場景整個渲染一遍,把深度渲染到深度圖中。然後再從光源出發得到從該光源處觀察到的深度紋理,也被稱爲這個光源的ShadowMap
  • 然後通過第一步的兩個深度紋理在屏幕空間做一次陰影收集計算(Shadows Collector),計算會得到一張屏幕空間陰影紋理。這個過程概括來說就是把每一個像素根據它在攝像機深度紋理中的深度值得到世界空間座標,再把它的座標從世界空間轉換到光源空間中,和光源的ShadowMap裏面的深度值對比,如果大於ShadowMap中的深度距離,那麼就說明光源無法照到,在陰影內。(原理跟普通ShadowMap一樣)
  • 最後,在正常渲染物體爲它計算陰影的時候,只需要按照當前處理的fragment在屏幕空間中的位置對上一步得到的屏幕空間陰影圖採樣就可以了。

2.物體投射陰影設置

當模型Cast Shadows屬性設置爲On時候, 可以自定義一個Pass,設置 LightMode = "ShadowCaster"自定義投射陰影。但是一般這個Pass比較通用,因此直接使用Fallback中Shader定義好的Pass。如果要一個物體投射陰影,需要將該物體加入到光源的陰影映射紋理中。

3.物體接收陰影

如果要一個物體接收陰影,Shader中需要對陰影映射採樣,把採樣結果和最後的光照結果相乘來產生陰影效果。

Shader "Chan/Chapter9_Shadow" {
    Properties
    {
        _Diffuse("Diffuse",Color) = (1,1,1,1)
        _Specular("Specular",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(8.0,256)) = 20
    }
    SubShader
    {
        //Base Pass
        Tags{"RenderType" = "Opaque"}
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            
            //保證光照衰減等光照變量可以被正確賦值
            #pragma multi_compile_fwdbase
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
                //聲明一個存放陰影紋理採樣座標的插值寄存器 
                //2爲插值寄存器索引值 0被worldNormal佔用,1被worldPos佔用
                SHADOW_COORDS(2)
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                //計算輸出結構體聲明的陰影紋理座標
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLightDir));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 halfDir = normalize(worldLightDir + viewDir);

                //Blinn-Phong計算高光光照
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
                fixed atten = 1.0;//衰減值
                //對陰影紋理採樣
                fixed shadow = SHADOW_ATTENUATION(i);
                return fixed4(ambient + (diffuse + specular) * atten * shadow,1.0);
            }

            ENDCG
        }
        Pass
        {
            Tags{"LightMode" = "ForwardAdd"}
            //開啓並設置混合模式 因爲希望幀在緩存中與之前的pass得到的光照結果進行疊加
            Blend One One

            CGPROGRAM
            
            #pragma multi_compile_fwdadd
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                #ifdef USING_DIRECTIONAL_LIGHT //光源爲平行光,沒有座標點
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                #else
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
                #endif

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLightDir));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);

                #ifdef USING_DIRECTIONAL_LIGHT
                fixed atten = 1.0;//平行光不衰減
                #else
                float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
                #endif

                return fixed4((diffuse + specular) * atten,1.0);
            }

            ENDCG
        }
    }
    Fallback "Specular"
}

SHADOW_COORDS(),TRANSFER_SHADOW(o),SHADOW_ATTENUATION(i)  陰影計算三賤客

  • SHADOW_COORDS() 輸出結構體聲明一個插值寄存器用於存放陰影映射紋理座標
  • TRANSFER_SHADOW(o) 計算輸出結構體聲明的陰影紋理座標
  • SHADOW_ATTENUATION(i)  對陰影紋理採樣

4.光照衰減因子和陰影統一管理

爲啥能統一?

因爲光照衰減和陰影對物體最終渲染結果的影響,本質上是一樣的。都是把一個值乘以光照計算結果得到最終值。

怎麼統一?

使用AutoLight.cginc內置的宏定義 UNITY_LIGHT_ATTENUATION。

Shader "Chan/Chapter9_AttenuationAndShadowUseBuinldInFunction" {
    Properties
    {
        _Diffuse("Diffuse",Color) = (1,1,1,1)
        _Specular("Specular",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(8.0,256)) = 20
    }
    SubShader
    {
        //Base Pass
        Tags{"RenderType" = "Opaque"}
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            
            //保證光照衰減等光照變量可以被正確賦值
            #pragma multi_compile_fwdbase
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
                //聲明一個存放陰影紋理採樣座標的插值寄存器 
                //2爲插值寄存器索引值 0被worldNormal佔用,1被worldPos佔用
                SHADOW_COORDS(2)
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                //計算輸出結構體聲明的陰影紋理座標
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLightDir));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 halfDir = normalize(worldLightDir + viewDir);

                //Blinn-Phong計算高光光照
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);
                
                //fixed atten = 1.0;//衰減值
                //對陰影紋理採樣
                //fixed shadow = SHADOW_ATTENUATION(i);
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
                return fixed4(ambient + (diffuse + specular) * atten,1.0);
            }

            ENDCG
        }
        Pass
        {
            Tags{"LightMode" = "ForwardAdd"}
            //開啓並設置混合模式 因爲希望幀在緩存中與之前的pass得到的光照結果進行疊加
            Blend One One

            CGPROGRAM
            
            #pragma multi_compile_fwdadd
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                #ifdef USING_DIRECTIONAL_LIGHT //光源爲平行光,沒有座標點
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                #else
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
                #endif

                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldLightDir));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);

                // #ifdef USING_DIRECTIONAL_LIGHT
                // fixed atten = 1.0;//平行光不衰減
                // #else
                // float3 lightCoord = mul(unity_WorldToLight,float4(i.worldPos,1)).xyz;
                // fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
                // #endif
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

                return fixed4((diffuse + specular) * atten,1.0);
            }

            ENDCG
        }
    }
    Fallback "Specular"
}

UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);

接受三個參數   1.隨便一個變量名 例如:WoKao  2.輸出結構體  3.世界座標系下的頂點座標

返回值 = fixed類型的名爲WoKao的變量(光照衰減&&陰影值相乘後的結果

來段宏定義理解下:

#ifdef POINT
sampler2D_float _LightTexture0;
unityShadowCoord4x4 unity_WorldToLight;
#   define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
        unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \
        fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
        fixed destName = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r * shadow;
#endif

5.透明物體的陰影

Shader "Chan/Chapter9_AlphaTestWithShadow" {
    Properties
    {
        _Color("Main Tint",Color) = (1,1,1,1)
        _MainTex("Main Tex",2D) = "white"{}
        _Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
    }
    SubShader
    {
        //Queue = 定義該SubShader的渲染隊列  IgnoreProject = 不受投影影響   RenderType = 提前定義組???
        Tags{"Queue" = "AlphaTest" "IgnoreProject" = "True" "RenderType" = "TransparentCutout"}
        Pass
        {
            Tags{"LightMode" = "Forwardbase"}

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "Autolight.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Cutoff;

            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float4 texcoord:TEXCOORD0;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
                float2 uv:TEXCOORD2;
                SHADOW_COORDS(3)
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
                TRANSFER_SHADOW(o)
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed4 texColor = tex2D(_MainTex,i.uv);

                /*
                if(texColor.a - _Cutoff) 爲負
                {
                    discard;//丟棄片元
                }
                */
                //同上 紋理A通道值小於_Cutoff,該片元被丟棄
                clip(texColor.a - _Cutoff);

                fixed3 albedo = texColor.rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos)
                return fixed4(ambient + diffuse * atten,1.0);
            }

            ENDCG
        }
    }
    Fallback "Transparent/Cutout/VertexLit"
}

1.怎麼讓透明物體獲得正確的投影?

    爲了讓透明物體有正確的投影,需要對攝像機觀察到的深度紋理和光源方向出發得到的陰影映射紋理進行透明度測試。

2.怎麼對深度紋理和陰影映射紋理進行透明度測試? 

    帶有Tags { "LightMode" = "ShadowCaster" }的Pass會對通過透明度測試的物體。

3.如何寫帶有Tags { "LightMode" = "ShadowCaster" }的Pass?

     使用內置,減少代碼量。當前Shader中沒有 Tags { "LightMode" = "ShadowCaster" },會去Fallback中查找帶有此Tags的Pass,進行投射陰影的計算。

Transparent/Cutout/VertexLit 對應Unity內置的文件名爲:AlphaTest_VertexLit的Shader。此Shader中有

4.注意事項:

      Tags { "LightMode" = "ShadowCaster" }的Pass,計算中用到名爲_Cutoff的變量。上邊的Shader必須有定義。

       Alphatest Greater [_Cutoff]   =    當alpha大於_Cutoff渲染

5.還有問題?

默認情況下,Unity不會計算背對光源的面,爲了讓背對光源的深度信息加入陰影映射紋理計算中,需要設置Mesh Renderer組件中的Cast Shadows屬性設置爲Two Sided。 

只計算正面
計算雙面

 

6.透明物體產生陰影:

Shader "Chan/Chapter9_AlphaBlendWithShadow" {
    Properties
    {
        _Color("Color Tint",Color) = (1,1,1,1)
        _MainTex("Main Tex",2D) = "white"{}
        _AlphaScale("Alpha Scale",Range(0,1.0)) = 1.0
    }
    SubShader
    {
        //透明度操作必有的三個標籤
        Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}

        Pass
        {
            Tags{"LightMode" = "Forwardbase"}
            Zwrite Off //關閉深度寫入
            Blend SrcAlpha OneMinusSrcAlpha //設置混合因子

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _AlphaScale;

            struct a2v
            {
                float4 vertex:POSITION;
                float3 normal:NORMAL;
                float4 texcoord:TEXCOORD0;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
                float2 uv:TEXCOORD2;
                SHADOW_COORDS(3)
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed4 texColor = tex2D(_MainTex,i.uv);
                fixed3 albedo = texColor.rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
                UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos)             
                return fixed4(ambient + diffuse * atten,texColor.a * _AlphaScale); 
            }

            ENDCG
        }
    }
    //強制半透明物體生成陰影
    Fallback "VertexLit"
}

 和AlphaBlend幾乎一樣。只是加了陰影計算。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章