Unity Shader:復雜光照

本文同時發(fā)布在我的個人博客上:https://dragon_boy.gitee.io

Unity渲染路徑

在Unity中,渲染路徑?jīng)Q定了光照是如何應用到UnityShader中的舞吭。

Unity中支持4種渲染路徑,其中兩種是舊版本使用的橙数,常規(guī)使用的兩種是前向渲染路徑和延遲渲染路徑堕仔,默認情況下使用的是前向渲染路徑。

我們可以在Pass的標簽中設置渲染路徑蜗巧,"LightMode"標簽支持的渲染路徑設置如下:

前向渲染路徑

前向渲染路徑的原理

每進行一次完整的前向渲染睹簇,我們需要渲染該對象的渲染圖元奏赘,并計算兩個緩沖區(qū)的信息,一個是顏色緩沖區(qū)太惠,一個是深度緩沖區(qū)磨淌。

對于每個逐像素光源,我們都進行一次完整的前向渲染凿渊。如果一個物體在多個逐像素光源的影響區(qū)域內(nèi)梁只,那么該物體就需要執(zhí)行多個Pass,每個Pass計算一個逐像素光源的光照結果埃脏,然后在幀緩沖中把這些光照結果混合起來得到最終的顏色值敛纲。假設場景中有N個物體,每個物體受到N個光源的影響剂癌,那么渲染整個場景需要N*M個Pass淤翔。渲染引擎通常會限制每個物體的逐像素光照的數(shù)目。

Unity中的前向渲染

在Unity中佩谷,前向渲染有3種處理光照的方式:逐頂點處理旁壮、逐像素處理、球諧函數(shù)處理(SH)谐檀。決定一個光源使用哪種處理模式取決于它的類型和渲染模式抡谐。光源類型指的是該光源是平行光還是其他類型的光源,而光源的渲染模式指的是該光源是否是重要的桐猬。

在前向渲染種麦撵,當我們渲染一個物體時,Unity會根據(jù)場景中各個光源的設置以及這些光源對物體的影響程度(例如溃肪,距離該物體的遠近免胃、光源強度等)對這些光源進行一個重要度排序。其中惫撰,一定數(shù)目的光源會按照逐像素的方式處理羔沙,最多有4個光源按逐頂點的方式處理,剩下的光源可以按SH方式處理厨钻。Unity判斷規(guī)則如下:

  • 場景中最亮的平行光總是按逐像素處理的扼雏。
  • 渲染模式被設置為Not Important的光源坚嗜,會按照逐頂點或SH處理。
  • 渲染模式被設置為Important的光源诗充,會按照逐像素處理苍蔬。
  • 如果根據(jù)以上規(guī)則得到的逐像素光源數(shù)量小于Quality Setting中的逐像素光源數(shù)量,會有更多的光源以逐像素的方式進行渲染(默認是4)蝴蜓。

前向渲染有兩種Pass:Base Pass和Additional Pass碟绑,常規(guī)設置如下:


  • 首先衙傀,在渲染設置中堂湖,除了設置標簽外路克,還使用了#pragma multi_compile_fwdbase的編譯指令。這些編譯指令會保證Unity可以為相應類型的Pass生成所有需要的Shader變種汽抚,這些變種會處理不同條件下的渲染邏輯,同時也會在背后聲明相關的內(nèi)置變量并傳遞到Shader中伯病。通常情況下造烁,只有分別為Base Pass和Additional Pass使用這兩個編譯指令,我們才可以在相關的Pass中得到一些正確的光照變量午笛。
  • Base Pass中渲染的平行光默認是支持陰影的惭蟋,而Additional Pass中渲染的光源在默認情況下是沒有陰影的。我們可以在Addditional Pass中使用#pragma multi_compile_fwdadd_fullshadows指令來為點光源和聚光燈開啟陰影效果药磺。
  • 環(huán)境光和自發(fā)光在Base Pass中計算告组。因為我們希望這兩種效果只計算一次。
  • 在Additional Pass的渲染設置中癌佩,我們還開啟和設置了混合模式木缝。我們希望每個Additional Pass可以與上一次的光照結果在幀緩沖中進行疊加,從而得到最終有多個光照的渲染效果围辙。如果沒有開啟和設置混合模式我碟,那么Additional Pass的渲染結果會覆蓋掉之前的渲染結果。通常情況下我們選擇的混合模式是Blend One One姚建。
  • 對與前向渲染矫俺,一個Unity Shader通常會定義一個Base Pass(也可以定義多次,比如雙面渲染)以及一個Additional Pass掸冤。一個Base Pass僅會執(zhí)行一次厘托,其它每個逐像素光源會執(zhí)行一次Additional Pass。

延遲渲染路徑

前向渲染的問題是:當場景中包含大量實時光源時稿湿,前向渲染的性能會急速下降催烘。

延遲渲染是一種更古老的渲染方法,但由于上述前向渲染可能造成的瓶頸問題缎罢,近幾年又流行起來伊群。除了前向渲染中使用的顏色緩沖和深度緩沖外考杉,延遲渲染還會利用額外的緩沖區(qū),這些緩沖區(qū)被稱為G緩沖舰始。G緩沖區(qū)存儲了我們所關心的表面信息崇棠,例如該表面的法線、位置丸卷、用于光照計算的材質(zhì)屬性等枕稀。

延遲渲染的原理

延遲渲染主要包含兩個Pass,在第一個Pass中谜嫉,我們不進行任何光照計算萎坷,而是僅僅計算哪些片元是可見的,利用深度緩沖沐兰。當一個片元可見時哆档,便將相關信息存儲在G緩沖中。第二個Pass中住闯,我們利用G緩沖中的片元信息來進行光照計算瓜浸。

延遲渲染使用的Pass數(shù)量就是通常是2個,與場景中的光源數(shù)無關比原。

Unity中的延遲渲染

延遲渲染路徑中的每個光源都可以按逐像素的方式處理插佛,但是,延遲渲染也有一些缺點:

  • 不支持MSAA量窘。
  • 不能處理半透明物體雇寇。
  • 對顯卡有要求,顯卡必須支持MRT等蚌铜。

使用延遲渲染時谢床,Unity要求提供兩個Pass。
(1)第一個Pass用于渲染G緩沖厘线。在這個Pass中识腿,我們會把物體的漫反射顏色、高光反射顏色造壮、平滑度渡讼、法線、自發(fā)光和深度等信息渲染到屏幕空間的G緩沖中耳璧。對于每個物體成箫,這個Pass僅會執(zhí)行一次。
(2)第二個Pass用于計算真正的光照模型旨枯。這個Pass會使用上一個Pass中渲染的數(shù)據(jù)來計算最終的光照顏色蹬昌,再存儲到幀緩沖中。

Unity的光照衰減

我們在Unity中使用一張紋理作為查找表來在片元著色器中計算逐像素光照的衰減攀隔。

用于光照衰減的紋理

Unity在內(nèi)部使用一張名為_LightTexture0的紋理來計算光照衰減皂贩。我們通常只關注對角線上的紋理顏色值栖榨,這些值表明了子啊光源空間中不同位置的點的衰減值。

為了對_LightTexture0紋理采樣得到給定點到光源的衰減值明刷,我們首先需要得到該點在光源空間中的位置婴栽,這里是通過_LightMatrix0變換矩陣得到的。_LightMatrix0可以將頂點從世界空間變換到光源空間辈末。因此我們像這樣獲取光源空間中頂點的位置:

float3 lightCoord = mul(_LightMatrix0, float4(i.worldPosition, 1)).xyz;

然后使用坐標模的平方對衰減紋理進行采樣愚争,得到衰減值:

fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

使用坐標模的平方進行采樣是為了避免進行開方操作。然后使用UNITY_ATTEN_CHANNEL來得到衰減紋理中衰減值所在的分量挤聘,以得到最終的衰減值轰枝。

使用數(shù)學公式計算衰減

查看http://www.reibang.com/p/b88a22022e7d

Unity的陰影

陰影實現(xiàn)原理

在實時渲染中,嘗試用陰影貼圖技術(Shadew Map)來實現(xiàn)陰影组去。簡單來說就是先把攝像機的位置放到與光源重合的位置鞍陨,攝像機看不到的地方就是陰影區(qū)域。

在前向渲染路徑中添怔,如果場景中最重要的平行光開啟了陰影湾戳,Unity就會為該光源計算它的陰影貼圖贤旷。陰影貼圖本質(zhì)上時深度圖广料,記錄了從光源位置觀察的能看到的最近的表面的深度信息。

Unity使用一個額外的Pass來專門更新光源的陰影貼圖幼驶,這個Pass就是LightMode標簽被設置為ShadowCaster的Pass艾杏,這個Pass的渲染目標時陰影貼圖。Unit首先把攝像機放置到光源的位置上盅藻,然后調(diào)用該Pass购桑,通過對頂點變換后得到光源空間的位置,并據(jù)此來輸出深度信息到陰影貼圖中氏淑。當開啟了光源的陰影效果后勃蜘,底層渲染引擎會在當前渲染物體的Unity Shader中找到LightModeShadwoCaster的Pass,如果沒有假残,就在Fallback中尋找缭贡,若還是沒有就不產(chǎn)生陰影。

在傳統(tǒng)的陰影貼圖的實現(xiàn)中辉懒,我們會在正常渲染的Pass中把頂點位置變換到光源空間下阳惹,以得到它在光源空間中的三維位置信息。然后我們使用xy分量對陰影貼圖進行采樣眶俩,得到陰影貼圖中該位置的深度信息莹汤。如果該深度值小于該頂點的深度值,說明該頂點在陰影中颠印。但對于支持MRT的顯卡纲岭,Unity使用屏幕空間的陰影映射技術抹竹。

當使用屏幕空間的陰影映射技術時,Unity會通過調(diào)用LightModeShadwoCaster的Pass來得到可投射陰影的光源的陰影貼圖以及攝像機的深度紋理荒勇。然后根據(jù)光源的陰影貼圖和攝像機的深度紋理來得到屏幕空間的陰影圖柒莉。如果攝像機的深度圖中記錄的表面深度大于轉換到陰影貼圖中的深度值,就說明該表面位于陰影中沽翔。由于陰影圖在屏幕空間下兢孝,因此,我們首先需要將表面坐標從模型空間變換到屏幕空間仅偎,然后使用這個坐標對陰影圖進行采樣跨蟹。

實現(xiàn)陰影和光照衰減的Blinn-Phong光照模型:

Shader代碼:

Shader "Unlit/Shadow"
{
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _BumpMap ("Normal Map", 2D) = "bump" {}
        _Specular ("Specular Color", Color) = (1, 1, 1, 1)
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        
        Pass { 
            Tags { "LightMode"="ForwardBase" }
        
            CGPROGRAM
            
            #pragma multi_compile_fwdbase   
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            
            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float4 texcoord : TEXCOORD0;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float4 TtoW0 : TEXCOORD1;  
                float4 TtoW1 : TEXCOORD2;  
                float4 TtoW2 : TEXCOORD3; 
                SHADOW_COORDS(4)
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
             
                o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                TANGENT_SPACE_ROTATION;
                
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
                
                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  
                o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  
                o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  
                
                TRANSFER_SHADOW(o);
                
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                
                fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
                bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));

                fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
                
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
                
                fixed3 halfDir = normalize(lightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
            
                UNITY_LIGHT_ATTENUATION(atten, i, worldPos);

                return fixed4(ambient + (diffuse + specular) * atten, 1.0);
            }
            
            ENDCG
        }
        
        Pass { 
            Tags { "LightMode"="ForwardAdd" }
            
            Blend One One
        
            CGPROGRAM
            
            //#pragma multi_compile_fwdadd
            // Use the line below to add shadows for point and spot lights
            #pragma multi_compile_fwdadd_fullshadows
            
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            
            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float4 texcoord : TEXCOORD0;
            };
            
            struct v2f {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float4 TtoW0 : TEXCOORD1;  
                float4 TtoW1 : TEXCOORD2;  
                float4 TtoW2 : TEXCOORD3;
                SHADOW_COORDS(4)
            };
            
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
             
                o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;  
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
    
                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  
                
                TRANSFER_SHADOW(o);
                
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target {
                float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                
                fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
                bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
                
                fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
                
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
                
                fixed3 halfDir = normalize(lightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
            
                UNITY_LIGHT_ATTENUATION(atten, i, worldPos);

                return fixed4((diffuse + specular) * atten, 1.0);
            }
            
            ENDCG
        }
    } 
        Fallback "Specular"
}

上述代碼中我們使用"AutoLight.cginc"文件中的衰減和陰影相關代碼。SHADOW_COORDS聲明一個名為_ShadowCoord的陰影紋理坐標變量橘沥。TRANSFER_SHADOW根據(jù)是使用傳統(tǒng)陰影貼圖技術還是屏幕陰影映射技術來判斷是將頂點坐標轉換到光源空間還是屏幕空間窗轩。SHADOW_ATTENUATION用來衰減陰影。注意座咆,要使用這些宏定義痢艺,輸入頂點著色器的坐標名稱需為vertex,輸入片元著色器的坐標名稱需為pos介陶。

我們使用UNITY_LIGHT_ATTENUATION來同時計算陰影和光照衰減堤舒。
實現(xiàn)效果:

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市哺呜,隨后出現(xiàn)的幾起案子舌缤,更是在濱河造成了極大的恐慌,老刑警劉巖某残,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件国撵,死亡現(xiàn)場離奇詭異,居然都是意外死亡玻墅,警方通過查閱死者的電腦和手機介牙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澳厢,“玉大人环础,你說我怎么就攤上這事∩退郑” “怎么了喳整?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長裸扶。 經(jīng)常有香客問我框都,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任魏保,我火速辦了婚禮熬尺,結果婚禮上,老公的妹妹穿的比我還像新娘谓罗。我一直安慰自己粱哼,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布檩咱。 她就那樣靜靜地躺著揭措,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刻蚯。 梳的紋絲不亂的頭發(fā)上绊含,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音炊汹,去河邊找鬼躬充。 笑死,一個胖子當著我的面吹牛讨便,可吹牛的內(nèi)容都是我干的充甚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼霸褒,長吁一口氣:“原來是場噩夢啊……” “哼伴找!你這毒婦竟也來了?” 一聲冷哼從身側響起傲霸,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤疆瑰,失蹤者是張志新(化名)和其女友劉穎眉反,沒想到半個月后昙啄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡寸五,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年梳凛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梳杏。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡韧拒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出十性,到底是詐尸還是另有隱情叛溢,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布劲适,位于F島的核電站楷掉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏霞势。R本人自食惡果不足惜烹植,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一斑鸦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧草雕,春花似錦巷屿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诫钓,卻和暖如春浓冒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尖坤。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工稳懒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慢味。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓场梆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纯路。 傳聞我的和親對象是個殘疾皇子或油,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355