[unity/shaderlab]陰影學(xué)習(xí)筆記

開篇胡扯

? ? ? 之前在學(xué)習(xí)入門精要的時(shí)候,看到陰影部分各種名字超長(zhǎng)的內(nèi)置宏和看不明白的采樣方式就想直接跳過(guò)這一章了(當(dāng)時(shí)想的是,反正用到的時(shí)候把這些內(nèi)置宏復(fù)制一遍出來(lái)就可以了)。直到前段時(shí)間面試被問(wèn)到unity內(nèi)部陰影到底是怎么實(shí)現(xiàn)的,才發(fā)現(xiàn)自己對(duì)陰影一無(wú)所知,剛好最近有時(shí)間,準(zhǔn)備再次認(rèn)真的學(xué)習(xí)一下陰影相關(guān)的知識(shí)。


1.陰影必須的三要素

? ? ? 想要產(chǎn)生陰影,至少需要三個(gè)物體的支持:

  • 光源(廢話)
  • 陰影產(chǎn)生(投射?)者
  • 陰影接收者
大概就是這樣的~

? ? ? 陰影的產(chǎn)生與消失與這三個(gè)物體息息相關(guān),所以當(dāng)場(chǎng)景中陰影消失時(shí)要先查找一下是不是這三個(gè)物體沒(méi)有打開對(duì)應(yīng)的開關(guān)。

組件中陰影的開關(guān)

? ? ? light組件中的mode和shadow type選項(xiàng)都會(huì)影響陰影的產(chǎn)生,mesh組件重的cast shadows表示是否向其他物體投射陰影,receive shadows表示這個(gè)mesh是否接收其他物體的陰影。

2.陰影是怎么產(chǎn)生的

? ? ? 我們知道,陰影是由于物體遮住了光的傳播,不能穿過(guò)不透明物體而形成的較暗區(qū)域。
? ? ? 而在unity中,陰影使用的是一種Shadow Map的技術(shù)。大概意思就是把相機(jī)放在光源位置,相機(jī)看不到的地方,就是這個(gè)光源的陰影區(qū)域。

3.陰影產(chǎn)生著(投射者)

? ? ? 上面提到過(guò)unity使用的是Shadow Map技術(shù),unity要把相機(jī)放在光源位置,然后計(jì)算一張?jiān)擖c(diǎn)的深度圖。如果按照正常流程來(lái)說(shuō),我們要把物體的渲染流程全都走一遍來(lái)寫入深度,得到shadowmap,但是這么做無(wú)疑會(huì)做很多多余的計(jì)算(很多和深度無(wú)關(guān)的計(jì)算如光照模型等)。
? ? ? unity使用了一個(gè)額外的pass來(lái)專門更新光源的shadowmap:這個(gè)pass就是LightMode標(biāo)簽被設(shè)置為ShadowCaster的pass。所以,在unity中,如果沒(méi)有這個(gè)pass,并且沒(méi)有指定Fallback或指定的Fallback中沒(méi)有LightMode標(biāo)簽為ShadowCaster的pass時(shí),該物體就無(wú)法向其他物體投射陰影。

Tags { "LightMode" = "ShadowCaster" }

FarmeDebug中渲染shadowmap的事件

? ? ? 對(duì)于不透明物體,我們一般可以使用unity中寫好的shader作為Fallback,這樣在渲染的時(shí)候unity會(huì)自己去Fallback中尋找ShadowCaster的pass來(lái)渲染陰影。但是對(duì)于透明物體或者是使用了透明度測(cè)試的材質(zhì),使用默認(rèn)的ShadowCaster就會(huì)得到一些錯(cuò)誤的結(jié)果,這個(gè)時(shí)候就要我們根據(jù)自己的需要來(lái)實(shí)現(xiàn)ShadowCaster的pass,比如在片元著色器進(jìn)行透明度測(cè)試等。

4.陰影接收者

? ? ? 在傳統(tǒng)的陰影映射紋理中,我們會(huì)在正常渲染的pass中把頂點(diǎn)轉(zhuǎn)換到光源空間下,然后對(duì)光源的shadowmap進(jìn)行采樣,再把采樣結(jié)果與頂點(diǎn)的深度進(jìn)行對(duì)比,如果頂點(diǎn)深度大于采樣結(jié)果,那么說(shuō)明該頂點(diǎn)在陰影內(nèi)。
? ? ? unity使用了不同的陰影采樣技術(shù):屏幕空間的陰影映射技術(shù)。但是不是所有平臺(tái)unity都會(huì)使用這種技術(shù),因?yàn)檫@種技術(shù)需要顯卡支持MRT。那么屏幕空間的陰影映射技術(shù)到底做了什么呢?
? ? ? unity首先會(huì)調(diào)用LightMode為ShadowCaster的pass得到光源的陰影映射紋理(shadowmap)攝像機(jī)的深度紋理。然后根據(jù)陰影映射紋理和相機(jī)的深度紋理得到屏幕空間的陰影圖。如果攝像機(jī)的深度圖中記錄的深度大于轉(zhuǎn)換到陰影映射紋理中的深度值,就說(shuō)明該表面可見但是處于陰影中。
? ? ? 如果我們想要某個(gè)物體接受來(lái)自其他物體的陰影,只需要在shader中對(duì)這張屏幕空間的陰影圖進(jìn)行采樣就可以了。因?yàn)殛幱皥D是基于屏幕空間的,所以我們?cè)诓蓸拥臅r(shí)候要把表面坐標(biāo)從模型空間變換到屏幕坐標(biāo)空間。

相機(jī)的深度紋理(左)與光源的陰影映射紋理(右)
基于屏幕空間的陰影圖

? ? ? 那么怎么對(duì)陰影圖進(jìn)行采樣呢?unity其實(shí)已經(jīng)幫我們封裝好了采樣的函數(shù)。我們可以直接使用三個(gè)宏指令,就可以完成對(duì)陰影的采樣。三個(gè)宏指令分別是:SHADOW_COORDS(用在頂點(diǎn)輸出結(jié)構(gòu)體內(nèi))、TRANSFER_SHADOW(用在頂點(diǎn)著色器)、SHADOW_ATTENUATION(用在片元著色器)。這三個(gè)指令都是在AutoLight.cginc中定義的,所以我們?cè)谑褂们耙浀锰砑觟nclude。

            struct v2f {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                //參數(shù)為下一個(gè)插值寄存器的索引
                SHADOW_COORDS(2)
            };

            v2f vert(a2v v) {
                v2f o;
                //你的定點(diǎn)著色器邏輯
                // Pass shadow coordinates to pixel shader
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                //片元著色器邏輯
                fixed shadow = SHADOW_ATTENUATION(i);
                //結(jié)果計(jì)算
            }

? ? ? AutoLight中定義的宏指令:

// ---- Screen space direction light shadows helpers (any version)
#if defined (SHADOWS_SCREEN)

    #if defined(UNITY_NO_SCREENSPACE_SHADOWS)
        UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
        #define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );
        inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
        {
            #if defined(SHADOWS_NATIVE)
                fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz);
                shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
                return shadow;
            #else
                unityShadowCoord dist = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, shadowCoord.xy);
                // tegra is confused if we use _LightShadowData.x directly
                // with "ambiguous overloaded function reference max(mediump float, float)"
                unityShadowCoord lightShadowDataX = _LightShadowData.x;
                unityShadowCoord threshold = shadowCoord.z;
                return max(dist > threshold, lightShadowDataX);
            #endif
        }

    #else // UNITY_NO_SCREENSPACE_SHADOWS
        UNITY_DECLARE_SCREENSPACE_SHADOWMAP(_ShadowMapTexture);
        #define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
        inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
        {
            fixed shadow = UNITY_SAMPLE_SCREEN_SHADOW(_ShadowMapTexture, shadowCoord);
            return shadow;
        }

    #endif

    #define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
    #define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
#endif

? ? ? 由于這些宏會(huì)使用上下文變量來(lái)進(jìn)行相關(guān)計(jì)算,我們?cè)诰帉憇hader時(shí)需要保證自己定義的變量名與宏使用的名稱相匹配:a2v結(jié)構(gòu)體(頂點(diǎn)輸入結(jié)構(gòu)體)的頂點(diǎn)坐標(biāo)變量名必須是vertex,頂點(diǎn)著色器中a2v(頂點(diǎn)輸入)結(jié)構(gòu)體的名字必須是v,且v2f(頂點(diǎn)輸出結(jié)構(gòu)體)的頂點(diǎn)位置必須為pos。

5.統(tǒng)一管理陰影與光照衰減

? ? ? 在片元著色器中我們使用了SHADOW_ATTENUATION宏進(jìn)行陰影處理(對(duì)屏幕空間陰影圖進(jìn)行采樣)。unity還封裝了一個(gè)宏,可以進(jìn)行統(tǒng)一的光照衰減計(jì)算與陰影計(jì)算,那就是UNITY_LIGHT_ATTENUATION宏,這個(gè)宏需要三個(gè)參數(shù),第一個(gè)是光照的衰減atten,這個(gè)參數(shù)我們不用在外部聲明,宏內(nèi)部會(huì)自己聲明該變量并填充衰減值后傳出,第二個(gè)參數(shù)是我們?cè)谄髦心玫降捻旤c(diǎn)輸出結(jié)構(gòu)體,第三個(gè)參數(shù)是世界空間的坐標(biāo),這個(gè)坐標(biāo)用來(lái)計(jì)算光源空間下的坐標(biāo)。這個(gè)宏針對(duì)不同類型的光源和情況聲明了多個(gè)版本,所以我們?cè)谑褂玫臅r(shí)候不需要在Additional Pass判斷光源類型,代碼也得以統(tǒng)一。

// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

總結(jié)

? ? ? 又看了一遍書中關(guān)于陰影的部分,確實(shí)收益良多。但是還有很多東西現(xiàn)在搞不清楚,比如為什么在生成光源陰影映射紋理的時(shí)候要繪制很多次同樣的物體,而且不能合批,是不是陰影映射紋理也默認(rèn)進(jìn)行了LOD處理呢?

補(bǔ)充:

? ? ? 在渲染陰影的時(shí)候,會(huì)繪制四次renderjobdir,這里的陰影也是使用了一種類似于LOD的手法進(jìn)行處理,生成四份光源空間下的深度圖。在游戲運(yùn)行時(shí)根據(jù)相機(jī)距離來(lái)決定最終要采樣哪一種質(zhì)量的陰影貼圖,這樣可以在游戲執(zhí)行時(shí)動(dòng)態(tài)的優(yōu)化效率。但是同樣付出的代價(jià)就是要多繪制幾次,也就是多一些DC,同樣用來(lái)存儲(chǔ)的空間也對(duì)應(yīng)的要增大一些。

shadowrender

? ? ? 那么這個(gè)LOD的設(shè)置在哪呢?在quality setting中我們可以看到有相關(guān)的shadows的設(shè)置信息,在這里就可以設(shè)置shadow cascades數(shù)量啦。

quality setting
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載自VR設(shè)計(jì)云課堂[http://www.itdecent.cn/u/c7ffdc4b379e]Unity S...
    水月凡閱讀 1,166評(píng)論 0 0
  • 我見過(guò)世上最美的風(fēng)景 它是你清澈的眼眸 那泛起的層層漣漪 都是被打碎了的 我的深情 每每有風(fēng)襲來(lái) 請(qǐng)您微閉雙眼 輕...
    虛度老太婆閱讀 157評(píng)論 0 0
  • 一夜喜雨至 滿池芍藥開 嬌顏沾雨露 只為待君來(lái)
    喜心閱讀 335評(píng)論 0 0
  • 一個(gè)主題
    pretty1994閱讀 327評(píng)論 0 0
  • 昨天,同事在辦公室說(shuō)起一名學(xué)生,什么都不感興趣。不學(xué)習(xí),不打瞌睡,不偷看課外書,不交友,不玩手機(jī)。 整個(gè)入定老僧,...
    錦瑟_db50閱讀 216評(píng)論 0 1

友情鏈接更多精彩內(nèi)容