翻譯17 Mixed Lighting混合光照

只烘焙間接光
混合烘焙陰影和實時陰影
處理代碼的變化和問題
支持消減光照(subtractivelighting)html

unity 5.6.6f2緩存

1 烘焙間接光

光照貼圖能夠提供預計算光照:以紋理內存爲代價減小了GPU在實時中的工做量;還加入了間接光。函數

限制首先高光不能被烘焙其次烘焙光只經過光照探頭影響動態物體最後烘焙光不產生實時陰影性能

你能夠在下面的截圖中看到徹底實時光照和徹底烘焙光照之間的區別。這是前一篇教程中的一個場景,惟一的不一樣是我將全部的球體都設置爲動態並從新改變了一些球體的位置。其它一切都是靜態的。這是使用前向渲染的方法。優化

image image

徹底實時和徹底烘焙光照spa

1.1 混合模式

烘焙光照有間接光而沒有實時光照,由於間接光須要光照貼圖。因爲間接光能夠爲場景加入很大的真實感,若是咱們能夠將它和實時光照融合在一塊兒就再好不過了。這是能夠的,但也意味着着色的開銷會增長。咱們須要將混合光(Mixed Lighting)的光照模式(Lighting Mode)設置爲烘焙間接(Baked Indirect)。翻譯

image

混合光照,烘焙間接3d

咱們已經在前一篇教程中切換到這個模式了,可是以前咱們只使用了徹底烘焙光照。雖然表現結果與徹底烘焙光照相同,混合光照模式沒有任何區別。爲了使用混合光照,光源的模式必需要設置爲混合。代理

image

混合模式的主光源code

在將主定向光改成混合光後,兩件事會發生:首先,Unity會再次烘焙光照貼圖。這一次光照貼圖只會存儲間接光,因此它會比以前的暗不少。

image image

徹底烘焙的光照貼圖 vs 只有間接光的光照貼圖

另外,全部物體都會像主光源被設置爲實時那樣被照亮。只有一點不一樣:光照貼圖被用來爲靜態物體添加間接光,而不是球諧光或探頭。動態物體的間接光仍要使用光照探頭。

image

混合光照,實時直接光照烘焙間接光

咱們不須要改變咱們的着色器來支持這點,由於前向基礎通道(forward base pass)已經融合了光照貼圖數據和主定向光源。和往常同樣,額外的光照會獲得附加通道(additive pass)。當使用延遲渲染通道時,主光源也會獲得一個通道。

混合光能夠在運行時調整嗎?
是的,由於它們被用於實時光照。可是,它們的烘焙數據時靜態的。因此在運行時你只能稍微調整光照,好比稍微調整它的強度。更大的變化會令人明顯看出烘焙光照和實時光照之間的不一樣步。

1.2 更新着色器

剛開始一切彷佛正常運行。可是,定向光的陰影衰減發生了錯誤。咱們經過極大下降陰影距離觀察到陰影被剪掉了。

image image

陰影衰減,標準着色器vs咱們的着色器

雖然Unity很長一段時間都有混合光照模式,但實際上它在Unity5中就不起做用了。Unity5.6中新加入了一個混合光照模式,即咱們如今使用的這個。當該新模式被加入時,UNITY_LIGHT_ATTENUATION宏下面的代碼發生了變化。咱們在使用徹底烘焙光照或者實時光照時沒有注意到這一點,可是咱們必須更新咱們的代碼以適應混合光照的新方法。因爲這是最近的一個巨大的變化,咱們必需要注意它所帶來的問題。

咱們要改變的第一點是再也不使用SHADOW_COORDS宏來定義陰影座標的插值(interpolater)。咱們必須使用新的UNITY_SHADOW_COORDS宏來代替它。

struct Interpolators
{
    …
    //SHADOW_COORDS(5)
    UNITY_SHADOW_COORDS(5)
    …
};

一樣,TRANSFER_SHADOW應該替換爲UNITY_TRANSFER_SHADOW

Interpolators MyVertexProgram (VertexData v)
{
    …
    //TRANSFER_SHADOW(i);
    UNITY_TRANSFER_SHADOW(i);
    …
}

然而,這會產生一個編譯錯誤,由於該宏須要一個額外的參數。從Unity 5.6開始,只有定向陰影的屏幕空間座標中被放入一個插值。點光源和聚光源的陰影座標如今在片斷程序(fragment program)中進行計算。有個新變化:在一些狀況中光照貼圖的座標被用在陰影蒙版(shadow mask)中,咱們會在後面講解這一點。爲了該宏能正常工做,咱們必須爲它提供第二個UV通道中的數據,其中包含光照貼圖的座標。

UNITY_TRANSFER_SHADOW(i, v.uv1);

這樣會再次產生一個編譯錯誤。這是由於在一些狀況下UNITY_SHADOW_COORDS錯誤地建立了一個插值,儘管實際上並不須要。在這種狀況下,TRANSFER_SHADOW不會初始化它,於是致使錯誤。這個問題出如今5.6.0中,一直到5.6.2和2017.1.0beta版本中都有。

人們一般不會注意到這個問題,由於Unity的標準着色器使用UNITY_INITIALIZE_OUTPUT宏來徹底地初始化它的插值結構體。由於咱們不使用這個宏,因此出現了問題。爲了解決它,咱們使用UNITY_INITIALIZE_OUTPUT宏來初始化咱們的插值。

Interpolators MyVertexProgram (VertexData v)
{
    Interpolators i;
    UNITY_INITIALIZE_OUTPUT(Interpolators, i);
    …
}

UNITY_INITIALIZE_OUTPUT有什麼做用?

它只是爲變量分配數值0,將其轉換爲正確的類型。至少是當程序支持該宏時會這樣,不然它不會作任何事。

// Initialize arbitrary structure with zero values.
// Not supported on some backends
// (e.g. Cg-based particularly with nested structs).
// hlsl2glsl would almost support it, except with structs that have
arrays
// -- so treat as not supported there either :(
#if defined(UNITY_COMPILER_HLSL) || defined(SHADER_API_PSSL) || \
defined(UNITY_COMPILER_HLSLCC)
    #define UNITY_INITIALIZE_OUTPUT(type,name) name = (type)0;
#else
    #define UNITY_INITIALIZE_OUTPUT(type,name)
#endif

一般咱們傾向於只使用顯式賦值,不多使用這個初始化插值宏。

1.3 手動衰減陰影

如今咱們正確地使用了新的宏定義,可是主光源的陰影仍然沒有按照它們應該的那樣衰減。結果咱們發現當同時使用定向陰影和光照貼圖時,UNITY_LIGHT_ATTENUATION不會對光源進行衰減。使用混合模式的主定向光源就會產生這個問題。因此咱們必須手動設置。

爲何在這個例子中陰影沒有衰減?
一、UNITY_LIGHT_ATTENUATION宏以前是獨立使用的,可是自從Unity5. 6它開始和Unity的標準全局光照函數一同使用。咱們沒有采用一樣的方法,所以它不能正常工做。
二、至於爲何要作這個改動,惟一的線索就是AutoLight中的一段註釋:「爲了性能的緣由以GI函數的深度處理陰影」。因爲着色器編譯器會隨意地移動代碼。

對於咱們的延遲光照着色器,咱們已經有了進行陰影衰減的代碼。將相關代碼片斷從MyDeferredShading中複製到My Lighting中的一個新函數中。惟一實際的區別在於咱們必須使用視圖向量和視圖矩陣構建viewZ。咱們只須要Z份量,因此無需進行一次完整的矩陣乘法。

float FadeShadows (Interpolators i, float attenuation) {
    float viewZ =
        dot(_WorldSpaceCameraPos - i.worldPos, UNITY_MATRIX_V[2].xyz);
    float shadowFadeDistance =
        UnityComputeShadowFadeDistance(i.worldPos, viewZ);
    float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
    attenuation = saturate(attenuation + shadowFade);
    return attenuation;
}

該手動衰減必須在使用了UNITY_LIGHT_ATTENUATION初始化完成以後。

UnityLight CreateLight (Interpolators i) {
    …
    UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos.xyz);
    attenuation = FadeShadows(i, attenuation);
    …
}

只有當HANDLE_SHADOW_BLENDING_IN_GI在UnityShadowLibrary.cginc文件中有定義時,FadeShadows纔會開始計算。

float FadeShadows (Interpolators i, float attenuation) {
    #if HANDLE_SHADOWS_BLENDING_IN_GI
        // UNITY_LIGHT_ATTENUATION doesn't fade shadows for us.
        float viewZ = dot(_WorldSpaceCameraPos - i.worldPos, UNITY_MATRIX_V[2].xyz);
        float shadowFadeDistance = UnityComputeShadowFadeDistance(i.worldPos, viewZ);
        float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
        attenuation = saturate(attenuation + shadowFade);
    #endif
    return attenuation;
}

最後,咱們的陰影如它們應該的那樣正常衰減了。

2 使用陰影蒙版(Shadowmask)

烘焙間接光的混合模式成本很高。它們須要實時光照外加間接光的光照貼圖那麼大的工做量。它和徹底烘焙光照相比最重要的是加入了實時陰影。幸運的是,有一個方法仍能夠將陰影烘焙到光照貼圖中,將其和實時陰影綜合起來。爲了開啓這個功能,咱們將混合光照模式改成Shadowmask。

image

Shadowmask模式

在這個模式中,混合光照的間接光和陰影衰減都存儲在了光照貼圖中陰影被存儲在一張額外的貼圖(即陰影蒙版)。當只有主定向光源時,紅色的陰影蒙版決定是否過濾被照亮的物體。紅色是由於陰影信息被存儲在紋理的R通道。事實上,貼圖中至多能夠儲存四個光照的陰影,由於它只有四個通道。

image

烘焙的強度以及陰影蒙版

在Unity建立了陰影蒙版後,靜態物體的陰影投射會消失。只有光照探頭仍會處理它們。動態物體的陰影不受影響。

image

沒有烘焙陰影

2.1 對陰影蒙版採樣-Sampling the Shadowmask

爲了從新獲得烘焙陰影,咱們必須對陰影蒙版採樣樣。Unity的宏已經對點光源和聚光源進行了取樣,不過咱們必須也要將它包含在咱們的FadeShadows函數中。爲此咱們可使用UnityShadowLibrary中的UnitySampleBakedOcclusions函數。它須要光照貼圖的UV座標和世界位置做爲輸入參數。

float FadeShadows (Interpolators i, float attenuation)
{
    #if HANDLE_SHADOWS_BLENDING_IN_GI
        …
        float bakedAttenuation = UnitySampleBakedOcclusion(i.lightmapUV, i.worldPos);
        attenuation = saturate(attenuation + shadowFade);
    #endif
    return attenuation;
}

UnitySampleBakedOcclusion是什麼樣子的?
它使用光照貼圖座標對陰影蒙版取樣,而後選擇適當的通道。unity_OcclusionMaskSelector變量是一個含有一個份量的向量,該份量被設置爲1以匹配當前正在被着色的光源。

fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos) {
    #if defined (SHADOWS_SHADOWMASK)
        #if defined(LIGHTMAP_ON)
            fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D_SAMPLER(
                unity_ShadowMask, unity_Lightmap, lightmapUV.xy
            );
        #else
            fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D(unity_ShadowMask, lightmapUV.xy);
        #endif
        return saturate(dot(rawOcclusionMask, unity_OcclusionMaskSelector));
    #else
        return 1.0;
    #endif
}

該函數還處理了光照探頭代理體積的衰減,可是咱們尚未支持這點因此我去掉了那部分的代碼。這就是爲何該函數有一個世界位置的參數。

當使用陰影蒙版時,UnitySampleBakedOcclusions提供給咱們烘焙陰影衰減,在其餘狀況下它的值都爲1。如今咱們必須將它和咱們已經有的衰減綜合起來而後對陰影進行衰減。UnityMixRealtimeAndBakedShadows函數爲咱們實現了這些。

float bakedAttenuation = UnitySampleBakedOcclusion(i.lightmapUV, i.worldPos);
//attenuation = saturate(attenuation shadowFade);
attenuation = UnityMixRealtimeAndBakedShadows
(
    attenuation, bakedAttenuation, shadowFade
);

UnityMixRealtimeAndBakedShadows是如何工做的?

它也是UnityShadowLibrary中的一個函數。它還處理光照探頭代理體積以及一些其餘極端狀況。那些狀況和咱們無關,因此我刪除了一些內容。

inline half UnityMixRealtimeAndBakedShadows (
    half realtimeShadowAttenuation, half bakedShadowAttenuation, half fade
) {
    #if !defined(SHADOWS_DEPTH) && !defined(SHADOWS_SCREEN) && \
        !defined(SHADOWS_CUBE)
        return bakedShadowAttenuation;
    #endif

    #if defined (SHADOWS_SHADOWMASK)
        #if defined (LIGHTMAP_SHADOW_MIXING)
            realtimeShadowAttenuation = saturate(realtimeShadowAttenuation + fade);
            return min(realtimeShadowAttenuation, bakedShadowAttenuation);
        #else
            return lerp(
                realtimeShadowAttenuation, bakedShadowAttenuation, fade
            );
        #endif
    #else //no shadowmask
        return saturate(realtimeShadowAttenuation + fade);
    #endif
}

若是沒有動態陰影,那麼結果將獲得烘焙的衰減。這意味着動態物體沒有陰影,以及被映射到光照貼圖上的物體沒有烘焙陰影。

當沒有使用陰影蒙版時,它會進行原來的衰減。不然,它會根據咱們是否作了陰影混合進行表現,咱們後面再講。如今,它只是在實時衰減和烘焙衰減之間進行一個插值

image

實時陰影和陰影蒙版陰影

如今靜態物體有了實時陰影和烘焙陰影,且它們正確地混合。實時陰影的衰減仍然超過了陰影距離,可是烘焙陰影沒有。

image

只有實時陰影衰減了

2.2 添加一個陰影蒙版G-Buffer

如今陰影蒙版可用於前向渲染路徑,可是咱們須要使它也可用於延遲渲染:添加陰影蒙版信息做爲一個額外的G-緩存。因此當SHADOWS_SHADOWMASK被定義時,在FragmentOutput結構體中添加一個緩存。

struct FragmentOutput {
    #if defined(DEFERRED_PASS)
        float4 gBuffer0 : SV_Target0;
        float4 gBuffer1 : SV_Target1;
        float4 gBuffer2 : SV_Target2;
        float4 gBuffer3 : SV_Target3;

        #if defined(SHADOWS_SHADOWMASK)
            float4 gBuffer4 : SV_Target4;
        #endif
    #else
        float4 color : SV_Target;
    #endif
};

添加的第五個G-緩存,會使顯存增大,並非全部的平臺(mobile)都支持它。Unity只在有足夠多的渲染目標可用時才支持陰影蒙版,所以咱們也應該這樣作。

#if defined(SHADOWS_SHADOWMASK) && (UNITY_ALLOWED_MRT_COUNT > 4)
    float4 gBuffer4 : SV_Target4;
#endif

咱們只需在G-緩存中存儲採樣獲得的陰影蒙版數據,並且沒有一個確切的光照,爲此咱們可使用UnityGetRawBakedOcclusions函數,它與UnitySampleBakedOcclusion類似,惟一不一樣在於它沒有選擇某個紋理通道。

FragmentOutput output;
#if defined(DEFERRED_PASS)
    #if !defined(UNITY_HDR_ON)
        color.rgb = exp2(-color.rgb);
    #endif
    output.gBuffer0.rgb = albedo;
    output.gBuffer0.a = GetOcclusion(i);
    output.gBuffer1.rgb = specularTint;
    output.gBuffer1.a = GetSmoothness(i);
    output.gBuffer2 = float4(i.normal * 0.5 + 0.5, 1);
    output.gBuffer3 = color;
    #if defined(SHADOWS_SHADOWMASK) && (UNITY_ALLOWED_MRT_COUNT > 4)
        output.gBuffer4 = UnityGetRawBakedOcclusions(i.lightmapUV, i.worldPos.xyz);
    #endif
#else
    output.color = ApplyFog(color, i);
#endif

爲了能夠在沒有光照貼圖的時候也能成功編譯,當光照貼圖座標不可用時咱們使用0代替它。

#if defined(SHADOWS_SHADOWMASK) && (UNITY_ALLOWED_MRT_COUNT > 4)
    float2 shadowUV = 0;
    #if defined(LIGHTMAP_ON)
        shadowUV = i.lightmapUV;
    #endif
    output.gBuffer4 = UnityGetRawBakedOcclusions(shadowUV, i.worldPos.xyz);
#endif

2.3 使用陰影蒙版G-緩存

調整MyDeferredShading延遲渲染着色器。

第一步先添加額外的一個G-buffer變量。

sampler2D _CameraGBufferTexture0;
sampler2D _CameraGBufferTexture1;
sampler2D _CameraGBufferTexture2;
sampler2D _CameraGBufferTexture4;

第二步,建立一個函數來獲得適當的陰影衰減。若是有了陰影蒙版,可經過對紋理採樣而後和unity_OcclusionMaskSelector進行一次顏色飽和點乘。這個變量是在UnityShaderVariables.cginc中定義的,包含了一個用於選擇當前正在被渲染的光照通道的向量。

float GetShadowMaskAttenuation (float2 uv)
{
    float attenuation = 1;
    #if defined (SHADOWS_SHADOWMASK)
        float4 mask = tex2D(_CameraGBufferTexture4, uv);
        attenuation = saturate(dot(mask, unity_OcclusionMaskSelector));
    #endif
    return attenuation;
}

在CreateLight中,即便當前光照沒有實時陰影,咱們在有陰影蒙版時也要衰減陰影。

UnityLight CreateLight (float2 uv, float3 worldPos, float viewZ) {
    …
    #if defined(SHADOWS_SHADOWMASK)
        shadowed = true;
    #endif
    if (shadowed) {
        …
    }
    …
}

爲了正確地包含烘焙陰影,再次使用UnityMixRealtimeAndBakedShadows代替以前的衰減計算。

if (shadowed) 
{
    float shadowFadeDistance = UnityComputeShadowFadeDistance(worldPos, viewZ);
    float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
//  shadowAttenuation = saturate(shadowAttenuation + shadowFade);
    shadowAttenuation = UnityMixRealtimeAndBakedShadows(
        shadowAttenuation, GetShadowMaskAttenuation(uv), shadowFade
    );
    …
}

如今也可使用自定義的延遲光照着色器獲得正確的烘焙陰影了。例外,即當咱們的優化分支被使用時會跳過陰影混合。該捷徑在陰影蒙版被使用時不可用。

if (shadowed) {
    …
    #if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING) && defined(SHADOWS_SOFT)
        #if !defined(SHADOWS_SHADOWMASK)
            UNITY_BRANCH
            if (shadowFade > 0.99) {
                shadowAttenuation = 1;
            }
        #endif
    #endif
}

2.4 陰影蒙版-距離模式 DIstance Shadowmask

雖然使用陰影蒙版模式咱們能夠獲得不錯的靜態物體的烘焙陰影,動態物體卻不能從中獲利。動態物體只能接收到實時陰影以及光照探頭數據。若是咱們但願獲得動態物體的陰影,那麼靜態物體必須也要投射實時陰影。這裏的混合光照模式咱們要用到距離陰影蒙版(Distance Shadowmask)了。

image

距離陰影蒙版模式

在2017及以上,使用哪一個陰影蒙版模式是經過質量設置進行控制

當使用DistanceShadowmask模式時,全部物體都使用實時陰影。第一眼看去,好像和Baked Indirect模式徹底同樣。

image

全部物體都有實時陰影

不過這裏仍有一個陰影蒙版。在這個模式中,烘焙陰影和光照探頭的使用超出了陰影距離。所以該模式是成本最高的模式,在陰影距離範圍內等價於烘焙間接模式,超出該範圍則等價於陰影蒙版模式。

近處爲實時陰影,遠處爲陰影蒙版和探頭

2.3節中已經支持這個模式了,由於咱們正在使用UnityMixRealtimeAndBakedShadows。爲了正確地混合徹底實時陰影和烘焙陰影,它像往常那樣衰減實時陰影,而後取其和烘焙陰影的最小值。

2.5 多重光照

由於陰影蒙版有四個通道,它能夠最多同時支持4個光照體積重疊在一塊兒

image image

四個光源,都是混合光

主方向光源的陰影仍存儲在R通道中。你還可以看到存儲在G通道和B通道中的聚光源的陰影,最後一個聚光源的陰影存儲在A通道中。

當光照體積不重疊時,它們使用相同的通道來存儲它們的陰影數據。因此你能夠有任意多個混合光照。可是你必須確保至多四個光照體積彼此重疊若是有太多個混合光影響同一篇區域,那麼一些就會改回到徹底烘焙模式。爲了說明這一點,下面這張截圖顯示的是在多加入一個聚光源之後的光照貼圖。你能夠在強度貼圖中清楚地看到其中一個已經變成了烘焙光。

image image

5個重疊的光照,其中一個爲徹底烘焙光

2.6 支持多個有蒙版的定向光

不幸的是,陰影蒙版只有當包含至多一個混合模式的方向光源存在時才能正常工做。對於額外的方向光,陰影衰減會發生錯誤,至少是在使用前向渲染通道時。延遲渲染倒沒有問題。

image

兩個方向光源產生錯誤的衰減

這是使用UNITY_LIGHT_ATTENUATION的新方法中的一個漏洞:Unity使用經過UNITY_SHADOW_COORDS定義的陰影插值來存儲方向陰影的屏幕空間座標,或者其它擁有陰影蒙版的光源的光照貼圖座標。

使用陰影蒙版的方向光還須要光照貼圖座標。在forward-render中,這些座標會被包含,由於LIGHTMAP_ON會在須要的時候被定義。然而,LIGHTMAP_ON在additional-pass中永遠不會被定義。這意味着多餘的方向光沒有可用的光照貼圖座標。結果UNITY_LIGHT_ATTENUATION在這種狀況下只會使用0,致使錯誤的光照貼圖採樣

因此咱們不能依靠UNITY_LIGHT_ATTENUATION額外得到使用陰影蒙版的方向光源。用屏幕空間的方向陰影

#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
    …
#endif
#if !defined(LIGHTMAP_ON) && defined(SHADOWS_SCREEN)
    #if defined(SHADOWS_SHADOWMASK) && !defined(UNITY_NO_SCREENSPACE_SHADOWS)
        #define ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS 1
    #endif
#endif

接下來,對那些額外有蒙版的定向陰影,咱們也要包含光照貼圖座標。

struct Interpolators {
    …
    #if defined(LIGHTMAP_ON) || ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
        float2 lightmapUV : TEXCOORD6;
    #endif
};
…
Interpolators MyVertexProgram (VertexData v) {
    …
    #if defined(LIGHTMAP_ON) || ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
        i.lightmapUV = v.uv1 * unity_LightmapST.xy + unity_LightmapST.zw;
    #endif
    …
}

當光照貼圖座標可用時,咱們能夠再次使用FadeShadows函數進行咱們本身控制的衰減。

float FadeShadows (Interpolators i, float attenuation) 
{
    #if HANDLE_SHADOWS_BLENDING_IN_GI || ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
        …
    #endif
    return attenuation;
}

可是,這仍然不正確,由於咱們爲其輸入了錯誤的衰減數據。咱們必須繞開UNITY_LIGHT_ATTENUATION,只獲得烘焙後的衰減,在這個狀況中咱們可使用SHADOW_ATTENUATION宏。

float FadeShadows (Interpolators i, float attenuation) 
{
    #if HANDLE_SHADOWS_BLENDING_IN_GI || ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
        //UNITY_LIGHT_ATTENUATION doesn't fade shadows for us.
        #if ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS
            attenuation = SHADOW_ATTENUATION(i);
        #endif
        …
    #endif
    return attenuation;
}

image

兩個定向光源正確的衰減

3 消減陰影-Subtractive Shadows

混合光照很好,可是它不像徹底烘焙光照那樣成本低廉。若是以低性能硬件爲目標,那麼混合光照不太可行。烘焙光照會管用,可是事實上你也許須要動態物體對靜態物體投射陰影。那樣的話,你可使用消減混合光照模式

image

消減模式

在切換到消減模式後,場景會亮不少。這是因爲靜態物體如今同時使用徹底烘焙的光照貼圖和方向光源。這是由於動態物體仍然會同時使用光照探頭和方向光源。

image

靜態物體受到兩次光照

消減模式只可用於前向渲染當使用延遲渲染路徑時,相關的物體會回到前向渲染路徑,就像透明物體那樣。

3.1 消減光照

在消減模式中,靜態物體經過光照貼圖被照亮,同時還將動態陰影考慮在內。這是經過下降光照貼圖在陰影區域的強度來實現的。爲此,着色器須要使用光照貼圖和實時陰影。它還須要使用實時光照來計算出要將光照貼圖調暗多少。這就是爲何咱們在切換到這個模式後獲得了雙重光照。

消減光照是一個近似,只在一個單必定向光下起做用,所以它只支持主方向光的陰影。另外,咱們必須以某種方式瞭解在動態着色區域內間接光的環境是什麼。因爲咱們使用的是一個徹底烘焙的光照貼圖,咱們沒有這個信息。Unity沒有包含一個額外的只有間接光的光照貼圖,而是使用了一個統一的顏色對環境光取近似值。即實時陰影顏色(Realtime Shadow Color),你能夠在混合光照選項中調整它。

在着色器中,咱們知道當LIGHTMAP_ONSHADOWS_SCREEN,和LIGHTMAP_SHADOW_MIXING關鍵詞被定義而SHADOWS_SHADOWMASK沒有被定義時咱們應該使用消減光照。若是這樣的話咱們定義SUBTRACTIVE_LIGHTING,以便更容易使用它。

#if !defined(LIGHTMAP_ON) && defined(SHADOWS_SCREEN)
    #if defined(SHADOWS_SHADOWMASK) && !defined(UNITY_NO_SCREENSPACE_SHADOWS)
        #define ADDITIONAL_MASKED_DIRECTIONAL_SHADOWS 1
    #endif
#endif

#if defined(LIGHTMAP_ON) && defined(SHADOWS_SCREEN)
    #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK)
        #define SUBTRACTIVE_LIGHTING 1
    #endif
#endif
在作其餘事情以前,咱們必須去除掉雙重陰影。爲此咱們能夠關閉動態光照,就像咱們對延遲通道所作的那樣。
UnityLight CreateLight (Interpolators i)
{
    UnityLight light;

    #if defined(DEFERRED_PASS) || SUBTRACTIVE_LIGHTING
        light.dir = float3(0, 1, 0);
        light.color = 0;
    #else
        …
    #endif

    return light;
}

image

靜態物體只有烘焙光

3.2 爲烘焙光打陰影

爲了應用消減陰影,咱們建立一個函數以在須要的時候調整間接光。一般它不會作任何事。

void ApplySubtractiveLighting (
    Interpolators i, inout UnityIndirect indirectLight
) {}

咱們在獲取光照貼圖數據後要調用該函數。

UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir) {
    …

    #if defined(FORWARD_BASE_PASS) || defined(DEFERRED_PASS)
        #if defined(LIGHTMAP_ON)
            indirectLight.diffuse = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.lightmapUV));

            #if defined(DIRLIGHTMAP_COMBINED)
                …
            #endif

            ApplySubtractiveLighting(i, indirectLight);
        #else
            indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
        #endif
        …
    #endif

    return indirectLight;
}

若是有消減光照,那麼咱們必須獲取陰影衰減。咱們能夠簡單地從CreateLight中將代碼複製過來。

void ApplySubtractiveLighting (
    Interpolators i, inout UnityIndirect indirectLight
) {
    #if SUBTRACTIVE_LIGHTING
        UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos.xyz);
        attenuation = FadeShadows(i, attenuation);
    #endif
}

下一步,咱們要計算出若是使用實時光照的話咱們能夠接收到多少光。咱們假設該信息和烘焙在光照貼圖中的信息相吻合。因爲光照貼圖只包含漫射光,咱們只需計算定向光的Lambert。

#if SUBTRACTIVE_LIGHTING
    UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos.xyz);
    attenuation = FadeShadows(i, attenuation);
    float ndotl = saturate(dot(i.normal, _WorldSpaceLightPos0.xyz));
#endif

爲了達到陰影光照的強度,咱們必須將蘭伯特項乘以衰減。可是咱們已經有了徹底不含陰影的烘焙光照。所以咱們估算一下有多少光被陰影擋住了。

float ndotl = saturate(dot(i.normal, _WorldSpaceLightPos0.xyz));
float3 shadowedLightEstimate = ndotl * (1 - attenuation) * _LightColor0.rgb;

經過從烘焙光中減去該估值,咱們最終獲得了調整好的光照。

float3 shadowedLightEstimate = ndotl * (1 - attenuation) * _LightColor0.rgb;
float3 subtractedLight = indirectLight.diffuse – shadowedLightEstimate;
indirectLight.diffuse = subtractedLight;

image

減去後獲得的光照

不管在什麼環境光場景中,這總會產生純黑色陰影。爲了更好地符合場景的須要,咱們可使用咱們的消減陰影顏色,能夠經過unity_ShadowColor實現。陰影區域不該比這個顏色更暗,不過它們能夠更亮些。因此咱們取計算出的光照和陰影顏色的最大值。

float3 subtractedLight = indirectLight.diffuse - shadowedLightEstimate;
subtractedLight = max(subtractedLight, unity_ShadowColor.rgb);
indirectLight.diffuse = subtractedLight;

咱們還要考慮到陰影強度被設置爲小於1這個狀況。爲了應用陰影強度,在有陰影和無陰影光照之間基於_LightShadowData的X份量作插值。

subtractedLight = max(subtractedLight, unity_ShadowColor.rgb);
subtractedLight = lerp(subtractedLight, indirectLight.diffuse, _LightShadowData.x);
indirectLight.diffuse = subtractedLight;

image

有顏色的陰影

由於咱們的場景的環境強度(ambient intensity)被設置爲0,因此默認的陰影顏色和場景不太搭配。可是能夠很輕鬆地發現消減陰影,所以我沒有調整它。還有一點很是明顯,即陰影顏色如今覆蓋了全部的烘焙陰影,而實際不該該這樣。它應該隻影響那些接收動態陰影的區域,不該該使烘焙陰影變亮。爲此,使用消減光照和烘焙光照的最小值。

//indirectLight.diffuse = subtractedLight;
indirectLight.diffuse = min(subtractedLight, indirectLight.diffuse);

image

正確的消減陰影

如今只要咱們使用適當的陰影顏色,咱們就會獲得正確的消減陰影。可是記住這只是一個近似,並且它不太適用於多重光照。例如,其它的烘焙光會產生錯誤的陰影。

image

多重光照錯誤的消減

相關文章
相關標籤/搜索