翻譯15 Unity Deferred Lights - 延遲光照

自定義燈光渲染
解碼LDR顏色
增長獨立Pass渲染光
支持方向光、點光源、聚光燈
手動採樣陰影紋理
Unity 5.6.6f1
數組

1 Light Shader

在G-Buffers填充完畢後,而後渲染光。本篇先介紹Unity是如何渲染光,以及實現本身Shader的光渲染。在Edit / Project Settings / Graphics 去掉默認的Shader。cookie

1.1 Using a Custom Shader

每一個deferred光都是在一個獨立的Pass修改屏幕圖像(後處理Image)完成渲染。建立一個Shader而後指定到Built-In shader settingsapp

image

圖1 修改內置的Shader編輯器

1.2 A Second Pass

修改以後,編輯器大量報錯.ide

image

圖2 least 2 passes函數

先簡單複製第一個Pass解決錯誤,結果是屏幕內除了天空盒外全部物體被渲染成黑色了。這是由於使用了stencil-buffer。性能

報錯的緣由:爲何須要第二個Pass?
    當HDR禁用時,光照數據會被使用對數編碼計算,而後在(第二個)最終的pass解碼該數據。因此必需要增長Pass。當禁用HDR時就能調用第二個Pass,但此時天空也變黑了。優化

1.3 Avoiding the Sky

當在LDR(HDR禁用)模式,天空變黑了。這是由於轉換過程當中沒有正確使用stencil-buffer模板掩碼。在第二個Pass中配置:應該只渲染不屬於背景的片斷,可經過_StencilNonBackground提供適當的模板值。ui

Pass
{
    Stencil
    {
        Ref[_StencilNonBackground]
        ReadMask[_StencilNonBackground]
        CompBack Equal
        CompFront Equal
    }
}

1.4 Converting Colors

在第二個Pass的light-buffer轉換光照數據,方法就似Fog shader:用輸入源的Image UV座標採樣buffer來繪製一個覆蓋全屏的quad編碼

struct VertexData {
    float4 vertex : POSITION;
   float2 uv : TEXCOORD0;
};
struct Interpolators {
    float4 pos : SV_POSITION;
 float2 uv : TEXCOORD0;
};
Interpolators VertexProgram (VertexData v) {
    Interpolators i;
    i.pos = UnityObjectToClipPos(v.vertex);
    i.uv = v.uv;
    return i;
}

該light buffer經過名爲_LightBuffer變量提供給Shader

sampler2D _LightBuffer;
…
float4 FragmentProgram (Interpolators i) : SV_Target {
    return tex2D(_LightBuffer, i.uv);
}

LDR顏色使用指數編碼:2-C,使用對數解碼-log2C

return -log2(tex2D(_LightBuffer, i.uv));

2 Directional Lights

新增一個cginc文件,引入第一個pass。要把渲染的光照增長到圖像上,必須確保不能擦除已渲染的圖像,所以改變混合模式要徹底合併源顏色和目標顏色。

Blend One One

也須要全部可能的光照配置shader variants變體,該編譯指令:multi_compile_lightpass會建立全部包含的變體。而後再增長一個HDR_ON的指令。

#pragma multi_compile_lightpass
#pragma multi_compile _ UNITY_HDR_ON

2.1 G-Buffer UV Coordinates

須要用UV座標從G-buffers採樣,不幸的是,該light pass通道unity不支持提供該座標。解決辦法:從clip-space傳遞過來,使用ComputeScreenPos函數計算,返回一個float4的齊次座標。

v2f VertexProgram(appdata v)
{
     v2f o;
     o.pos = UnityObjectToClipPos(v.vertex);
     o.uv = ComputeScreenPos(o.pos);
     return o;
}

而後在fragment就能計算最終的2D座標。必須在fragment計算。見翻譯7

fixed4 FragmentProgram(v2f i) : SV_Target
{
    float2 uv = i.uv.xy / i.uv.w;
    return 0;
}

2.2 World Position

與上篇deferred fog中類似,須要計算從相機到片元的距離:從相機原點發射射線經過片元(給定方向)到達far-plane,而後再用fragment深度縮放射線。用該方法重建片元的世界座標

1首先。對於方向光,從quad的四頂點發出的射線做爲法向量提供。因此能夠經過頂點程序對射線進行插值。

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

struct Interpolators {
    float4 pos : SV_POSITION;
    float4 uv : TEXCOORD0;
    float3 ray : TEXCOORD1;
};

Interpolators VertexProgram (VertexData v) {
    Interpolators i;
    i.pos = UnityObjectToClipPos(v.vertex);
    i.uv = ComputeScreenPos(i.pos);
    i.ray = v.normal;
    return i;
}

2其次。在fragment函數經過採樣_CameraDepthTexture紋理和線性化計算能夠獲得depth值,相似於deferred fog計算

//Unity提供的聲明函數,等於 sampler2D _CameraDepthTexture; 定義在UnityCG
UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);

float4 FragmentProgram (Interpolators i) : SV_Target {
    float2 uv = i.uv.xy / i.uv.w;
 float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv); depth = Linear01Depth(depth);
    return 0;
}

3而後。與deferred fog最大的不一樣:fog shader須要射線到達far plane;而本shader的射線只能到達near plane。因此必需要縮放射線以便它能達到far-plane:縮放射線使Z座標變爲1,並與遠平面距離相乘。

depth = Linear01Depth(depth);
float3 rayToFarPlane = i.ray * _ProjectionParams.z / i.ray.z;

4再接着。按深度值縮放射線一次獲得一個座標。該射線被定義在視圖空間,它是camera的本地空間。所以,射線也以片斷在視圖空間中的座標結束。

float3 rayToFarPlane = i.ray * _ProjectionParams.z / i.ray.z;
float3 viewPos = rayToFarPlane * depth;

5最後。再使用unity_CameraToWorld內置矩陣從view視圖空間轉換到world世界座標,該矩陣定義在ShaderVariables.cginc

float3 viewPos = rayToFarPlane * depth;
float3 worldPos = mul(unity_CameraToWorld, float4(viewPos, 1)).xyz;

2.3 Reading G-Buffer Data

獲取World Pos後。經過訪問G-buffer檢索properties,該buffer可從內置的_CamearGBufferTexture變量獲取

sampler2D _CameraGBufferTexture0;
sampler2D _CameraGBufferTexture1;
sampler2D _CameraGBufferTexture2;

在上一篇Defferred Shading中也手動計算過G-buffer,此次直接讀取_CameraGBufferTexture現成的albedo、specular、smoothness、normal

float3 worldPos = mul(unity_CameraToWorld, float4(viewPos, 1)).xyz;
float3 albedo = tex2D(_CameraGBufferTexture0, uv).rgb;
float3 specularTint = tex2D(_CameraGBufferTexture1, uv).rgb;//合併
float3 smoothness = tex2D(_CameraGBufferTexture1, uv).a;//合併
float3 normal = tex2D(_CameraGBufferTexture2, uv).rgb * 2 - 1;

2.4 Computing BRDF

引入BRDF函數,定義在UnityPBSLighting.cginc中

首先計算視野方向

float3 worldPos = mul(unity_CameraToWorld, float4(viewPos, 1)).xyz;
float3 viewDir = normalize(_WorldSpaceCameraPos - worldPos);
其次是表面反射,這可從specular顏色獲取,使用SpecularStrength函數提取。
//。。。
float oneMinusReflectivity = 1 - SpecularStrength(specularTint);

而後傳遞光照數據,初始化直接光和間接光

float oneMinusReflectivity = 1 - SpecularStrength(specularTint);
//。。。
UnityLight light;
light.color = 0;
light.dir = 0;

UnityIndirect indirectLight;
indirectLight.diffuse = 0;
indirectLight.specular = 0;

最後計算最終的顏色

indirectLight.specular = 0;
float4 color = UNITY_BRDF_PBS
(
    albedo, specularTint, oneMinusReflectivity, smoothness,
    normal, viewDir, light, indirectLight
);
return color;

2.5 Configuring the Light

由於間接光呈現的是黑色的,在這裏不適用。可是直接光必須被配置成與當前渲染的光相匹配。對於方向光,須要它的顏色和方向。這兩個變量能夠經過_LightColor和_LightDir變量得到。

float4 _LightColor, _LightDir;

UnityLight CreateLight () {
    UnityLight light;
    light.dir = _LightDir;
    light.color = _LightColor.rgb;
    return light;
}

    UnityLight light = CreateLight();
//    light.color = 0;
//    light.dir = 0;

image

光照方向錯誤

計算獲得最終的光照,但光的方向錯誤了。緣由:_LightDir是光到表面的方向。在CreateLight計算中須要表面到光的方向

light.dir = -_LightDir;

image

正確,沒有陰影

2.6 Shadows

在本身的cginc文件中,咱們依靠AutoLight中的宏來肯定由陰影引發的光衰減。 不幸的是,該文件在編寫時並無考慮到延遲的光線。 如今將本身進行陰影採樣,可經過_ShadowMapTexture變量訪問陰影貼圖。

sampler2D _ShadowMapTexture;

可是,咱們不能隨意聲明此變量。 它已經在UnityShadowLibrary中爲點和聚光燈陰影定義了它。 所以,咱們不該該本身定義它,除非使用方向光陰影。

#if defined (SHADOWS_SCREEN)
    sampler2D _ShadowMapTexture;
#endif

要應用方向光陰影,須要採樣陰影紋理並使用它來減弱光色便可。 在CreateLight中計算就須要把UV座標參數。

UnityLight CreateLight (float2 uv) {
    UnityLight light;
    light.dir = -_LightDir;
    float shadowAttenuation = tex2D(_ShadowMapTexture, uv).r;
    light.color = _LightColor.rgb * shadowAttenuation;
    return light;
}

UnityLight light = CreateLight(uv);

image

有陰影的方向光

固然,這僅在定向光啓用了陰影時纔有效。 若是不是,則陰影衰減始終爲1。

float shadowAttenuation = 1;
#if defined(SHADOWS_SCREEN)
    shadowAttenuation = tex2D(_ShadowMapTexture, uv).r;
#endif
light.color = _LightColor.rgb * shadowAttenuation;

2.7 Fading Shadows

陰影貼圖應該是有限的,它覆蓋的面積越大,陰影的分辨率越低。 Unity提供了繪製陰影的最大距離,此距離能夠經過Edit / Project Settings / Quality進行調整。

image

陰影距離配置

當陰影幾乎快達到了該限定距離就會淡出,Unity內置的shader是這樣設定並計算。因爲我將手動採樣該陰影紋理,當到達紋理的邊緣時陰影會被截取,結果是陰影雖然消失了,但有被急劇切割的生硬畫面。

image

長、短距離陰影對比

要漸隱陰影,首先要知道的是陰影徹底消失的距離。該距離又依賴於陰影投射方向。在Stable Fit模式下,以map的中心點呈球面形開始漸隱消失陰影;在Close Fit模式它是依賴於視野深度。

UnityComputeShadowFadeDistance函數能計算出正確距離,它須要兩個參數:world pos 和 view depth;而後返回距離A。 注意:該距離A是從陰影紋理的中心點位置或者未更改的視野深度開始計算的。

UnityLight CreateLight (float2 uv, float3 worldPos, float viewZ) {
    UnityLight light;
    light.dir = -_LightDir;
    float shadowAttenuation = 1;
    #if defined(SHADOWS_SCREEN)
        shadowAttenuation = tex2D(_ShadowMapTexture, uv).r;
        float shadowFadeDistance = UnityComputeShadowFadeDistance(worldPos, viewZ);
    #endif
    light.color = _LightColor.rgb * shadowAttenuation;
    return light;
}

陰影應該是快要接近漸隱距離時開始消失,一旦到達就徹底消失。UnityComputeShadowFade函數計算合適的消失因子。

float shadowFadeDistance =    UnityComputeShadowFadeDistance(worldPos, viewZ);
float shadowFade = UnityComputeShadowFade(shadowFadeDistance);

UnityComputeShadowFade定義在UnityShadowLibrary.cginc,見下:

float UnityComputeShadowFadeDistance (float3 wpos, float z) {
    float sphereDist = distance(wpos, unity_ShadowFadeCenterAndType.xyz);
    return lerp(z, sphereDist, unity_ShadowFadeCenterAndType.w);
}

half UnityComputeShadowFade(float fadeDist) {
    return saturate(fadeDist * _LightShadowData.z + _LightShadowData.w);
}
View Code

陰影漸隱值範圍是[0, 1],該值決定了陰影要消失多少。實際的消失值能夠加到陰影衰減之上並限定在[0, 1]以內

float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
shadowAttenuation = saturate(shadowAttenuation + shadowFade);

最後,提供世界座標和視圖深度在片元程序中建立光照。視圖深度是片元在視圖空間中的位置的Z份量。

UnityLight light = CreateLight(uv, worldPos, viewPos.z);

image

陰影漸隱

2.8 Light Cookies

支持Cookies紋理,使用變量_LightTexture0訪問;同時還要從world-space轉換到light-space,最後採樣。轉換矩陣使用unity_WorldToLight矩陣變量

sampler2D _LightTexture0;
float4x4 unity_WorldToLight;

CreateLight,使用上述矩陣變量轉換world-space到light-space;而後使用轉換後的座標採樣cookie紋理。cookie也要衰減,須要單獨定義並使用。

light.dir = -_LightDir;
float attenuation = 1;
float shadowAttenuation = 1;
#if defined(DIRECTIONAL_COOKIE)
    float2 uvCookie = mul(unity_WorldToLight, float4(worldPos, 1)).xy;
    attenuation *= tex2D(_LightTexture0, uvCookie).w;
#endif
    …
light.color = _LightColor.rgb * (attenuation * shadowAttenuation);

image

帶有cookie的方向光

總體結果彷佛能夠,可是觀察邊緣彷佛有硬邊

image

硬邊過渡

相鄰片元的cookie座標的巨大差別就會致使該問題出現。在這種狀況下,GPU選擇的mipmap級別對於最近的表面是low level。解決辦法之一就是:在採樣mip映射時應用偏移。大神的總結

attenuation *= tex2Dbias(_LightTexture0, float4(uvCookie, 0, -8)).w;

image

偏移採樣

2.9 Supporting LDR

上述只支持HDR,如今來支持LDR。步驟以下:

首先,編碼後的LDR顏色要乘如light-buffer,而不是加法。這能夠用:Blend DstColor Zero實現。注意只用該Blend mode會引發HDR的錯誤。因此須要靈活配置:Blend [_SrcBlend] [_DstBlend]

而後,使用2-c函數解碼

float4 color = UNITY_BRDF_PBS(
        albedo, specularTint, oneMinusReflectivity, smoothness,
        normal, viewDir, light, indirectLight
);
    #if !defined(UNITY_HDR_ON)
        color = exp2(-color);
    #endif
return color;

3 Spotlights

由於方向光會影響到場景內全部物體,因此被畫成全屏quad。相比之下,聚光燈只會影響位於圓錐體內的部分物體。一般不須要計算整個圖像的聚光燈光照,將繪製一個與聚光燈的影響範圍相匹配的金字塔體。

3.1 Drawing a Pyramid

禁用方向燈,改用聚光燈。由於着色器只對方向光正確工做,那麼如今的結果會出現錯誤。可是它仍可讓你看到金字塔的哪些部分被渲染了。

image

渲染範圍

根據上圖,金字塔是做爲一個普通的3D對象呈現的。它的背面被剔除,因此咱們能夠看到金字塔的正面。只有當它前面沒有東西的時候,它纔會被畫出來。除此以外,還添加了一個pass,用於設置模板緩衝區,以將繪圖限制爲位於金字塔卷內的片斷。您能夠經過frame-debugger來驗證。

image

剔除方式

這意味着咱們的着色器的culling和z-test設置被否棄了。 所以將其從着色器中刪除。

Blend [_SrcBlend] [_DstBlend]
//Cull Off
//ZTest Always
ZWrite Off

當聚光燈的體積距離相機足夠遠時,此方法適用。 可是,當聚光燈離攝像機太近時,它會失敗。 發生這種狀況時,相機可能會進入了該體積內。 甚至有可能將近平面的一部分置於其內部,而將其他部分置於其外部,與近平面相交了。 在這些狀況下,模板緩衝區不能用於限制渲染。

仍然渲染光照的技巧是繪製金字塔的內表面,而不是金字塔的外表面。 這是經過渲染其背面而不是其正面來完成的。 並且,僅當這些表面最終位於已渲染的表面以後時才渲染它們。 這種方法還涵蓋了聚光燈體積內的全部片斷。 但這最終致使渲染了太多的碎片,由於一般金字塔的一般隱藏部分也將被渲染。 所以,僅在必要時執行。

image image

當靠近相機時,要繪製背面才正確

3.2 Supporting Multiple Light Types

目前,CreateLight只能用於方向光。讓咱們確保特定於方向燈的代碼只在適當的時候使用。

UnityLight CreateLight (float2 uv, float3 worldPos, float viewZ) {
    UnityLight light;

   
   
   
   
     // light.dir = -_LightDir; 
   
    float attenuation = 1;
    float shadowAttenuation = 1;

    #if defined(DIRECTIONAL) || defined(DIRECTIONAL_COOKIE)
        light.dir = -_LightDir;

        #if defined(DIRECTIONAL_COOKIE)
            float2 uvCookie = mul(unity_WorldToLight, float4(worldPos, 1)).xy;
            attenuation *= tex2Dbias(_LightTexture0, float4(uvCookie, 0, -8)).w;
        #endif

        #if defined(SHADOWS_SCREEN)
            shadowAttenuation = tex2D(_ShadowMapTexture, uv).r;
            float shadowFadeDistance = UnityComputeShadowFadeDistance(worldPos, viewZ);
            float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
            shadowAttenuation = saturate(shadowAttenuation + shadowFade);
        #endif
    #else
        light.dir = 1;
    #endif

    light.color = _LightColor.rgb * (attenuation * shadowAttenuation);
    return light;
}

儘管陰影衰落基於方向陰影貼圖,可是其餘類型的陰影也應該會被漸隱。 這樣能夠確保全部陰影都以相同的方式漸隱,而不只僅是某些陰影。 所以,只要有陰影,陰影淡入淡出代碼便適用於全部燈光。 所以,讓咱們將該代碼移到特定於光源的塊以外。

咱們可使用布爾值來控制是否使用陰影淡出代碼。因爲布爾值是一個常數值,若是它仍然爲假,代碼將被刪除。

UnityLight CreateLight (float2 uv, float3 worldPos, float viewZ) {
    UnityLight light;
    float attenuation = 1;
    float shadowAttenuation = 1;
    bool shadowed = false;
    #if defined(DIRECTIONAL) || defined(DIRECTIONAL_COOKIE)
        …省略代碼
        #if defined(SHADOWS_SCREEN)
            shadowed = true;
            shadowAttenuation = tex2D(_ShadowMapTexture, uv).r;
//          float shadowFadeDistance = UnityComputeShadowFadeDistance(worldPos, viewZ);
//          float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
//          shadowAttenuation = saturate(shadowAttenuation + shadowFade);
        #endif
    #else
        light.dir = 1;
    #endif

    if (shadowed) {
        float shadowFadeDistance = UnityComputeShadowFadeDistance(worldPos, viewZ);
        float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
        shadowAttenuation = saturate(shadowAttenuation + shadowFade);
    }

    light.color = _LightColor.rgb * (attenuation * shadowAttenuation);
    return light;
}

非方向燈光都有一個position變量。它經過內置的_LightPos提供。

float4 _LightColor, _LightDir, _LightPos;

如今能夠肯定聚光燈的光向量得出光方向。

#else
    float3 lightVec = _LightPos.xyz - worldPos;
    light.dir = normalize(lightVec);
#endif

3.3 World Position Agin

結果爲黑色,彷佛光線方向不正確。 發生這種狀況是由於聚光燈的世界位置計算不正確。 當咱們在場景中的某個地方渲染金字塔時,不像方向光那樣渲染全屏quad將光線存儲在normal通道中。 而必須是經由Vertex-Program從頂點的位置發射射線,經過將頂點的pos轉換到view-space完成計算,爲此,咱們可使用UnityObjectToViewPos函數。

i.ray = UnityObjectToViewPos(v.vertex);

然而,這會產生方向錯誤的光線。咱們要消去它們的X和Y座標。

i.ray = UnityObjectToViewPos(v.vertex) * float3(-1, -1, 1);

image

正確的世界位置

再次看看UnityObjectToViewPos內部實現

inline float3 UnityObjectToViewPos (in float3 pos) {
    return mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, float4(pos, 1.0))).xyz;
}

當渲染方向光時,應該只使用頂點法線。當渲染非方向燈之外的光幾什麼時候,須要把頂點pos轉到view-space計算。Unity經過_LightAsQuad變量告訴咱們正在處理哪一種狀況。

若是_LightAsQuad被設爲1,則處理的是方向光quad而且可使用法線。不然,咱們必須使用UnityObjectToViewPos。插值好過if ==> from + (to – from)*t,t爲1直接使用法線,爲0直接計算到view-space

i.ray = lerp
(
    UnityObjectToViewPos(v.vertex) * float3(-1, -1, 1),
    v.normal,
    _LightAsQuad
);

3.4 Cookie Attenuation

聚光燈的錐形衰減是經過cookie紋理建立的,不管是默認的圓形仍是定製的cookie。咱們能夠從複製定向光的cookie代碼,仿照着寫。也是存儲在_LightTexture0

float3 lightVec = _LightPos.xyz - worldPos;
light.dir = normalize(lightVec);
float2 uvCookie = mul(unity_WorldToLight, float4(worldPos, 1)).xy;
attenuation *= tex2Dbias(_LightTexture0, float4(uvCookie, 0, -8)).w;

可是,聚光燈Cookie越遠離燈光位置,它就會變得越大。 這是因爲經過透視變換形成的。 所以,矩陣乘法會產生4D齊次座標。 爲了獲得規則的2D座標,咱們必須將X和Y除以W。

float4 uvCookie = mul(unity_WorldToLight, float4(worldPos, 1));
uvCookie.xy /= uvCookie.w;
attenuation *= tex2Dbias(_LightTexture0, float4(uvCookie.xy, 0, -8)).w;

image

cookie衰減

上圖實際上產生了兩個光錐,一個向前一個向後。 後向圓錐一般在渲染區域以外結束,但這並不能保證。咱們只須要前向錐,它對應於負的W座標。

attenuation *= tex2Dbias(_LightTexture0, float4(uvCookie.xy, 0, -8)).w;
attenuation *= uvCookie.w < 0;

3.5 Distance Attenuation

聚光燈發出的光也會根據距離衰減。此衰減存儲在查找紋理中,可經過_LightTextureB0使用該紋理。

sampler2D _LightTexture0, _LightTextureB0;

紋理被設計成必須使用光的距離的平方,並按光的範圍進行縮放,做爲UV進行採樣。範圍存儲在_LightPos的第四個份量中。採樣獲得的紋理應該使用哪一個通道在不一樣的平臺,由UNITY_ATTEN_CHANNEL宏定義。

light.dir = normalize(lightVec);
attenuation *= tex2D
(
    _LightTextureB0,
    (dot(lightVec, lightVec) * _LightPos.w).rr
).UNITY_ATTEN_CHANNEL;
float4 uvCookie = mul(unity_WorldToLight, float4(worldPos, 1));

image

cookie 和 distance衰減

3.6 Shadows

當聚光燈有陰影時,定義SHADOWS_DEPTH關鍵字。

//在CreateLight中
float4 uvCookie = mul(unity_WorldToLight, float4(worldPos, 1));
uvCookie.xy /= uvCookie.w;
attenuation *= tex2Dbias(_LightTexture0, float4(uvCookie.xy, 0, -8)).w;

#if defined(SHADOWS_DEPTH)
    shadowed = true;
#endif

聚光燈和方向燈使用相同的變量來採樣陰影貼圖。在聚光燈的狀況下,可使用內置UnitySampleShadowmap來處理採樣硬陰影或軟陰影的細節。參數:陰影空間中的片元位置。unity_WorldToShadow(4x4)矩陣中第一個數組能夠用來將世界空間轉換爲陰影空間。

shadowed = true;
shadowAttenuation = UnitySampleShadowmap(
    mul(unity_WorldToShadow[0], float4(worldPos, 1))
);

4 Point Lights

點光源使用與聚光燈相同的光向量、方向和距離衰減。這樣他們就能夠共享代碼。應該只在定義SPOT關鍵字時使用spotlight代碼的其他部分。

    #if defined(DIRECTIONAL) || defined(DIRECTIONAL_COOKIE)
        …
    #else
        float3 lightVec = _LightPos.xyz - worldPos;
        light.dir = normalize(lightVec);

        attenuation *= tex2D(
            _LightTextureB0,
            (dot(lightVec, lightVec) * _LightPos.w).rr
        ).UNITY_ATTEN_CHANNEL;

        #if defined(SPOT)
            float4 uvCookie = mul(unity_WorldToLight, float4(worldPos, 1));
            uvCookie.xy /= uvCookie.w;
            attenuation *=
                tex2Dbias(_LightTexture0, float4(uvCookie.xy, 0, -8)).w;
            attenuation *= uvCookie.w < 0;

            #if defined(SHADOWS_DEPTH)
                shadowed = true;
                shadowAttenuation = UnitySampleShadowmap(
                    mul(unity_WorldToShadow[0], float4(worldPos, 1))
                );
            #endif
        #endif
    #endif

這已經足夠讓點光源工做了。它們被渲染成和聚光燈同樣的效果,除了渲染範圍使用的是球形而不是錐形。

image

高亮

4.1 Shadows

點光源的陰影存儲在一個CubeMap。內置UnitySampleShadowmap可採樣。參數:光的方向。一個從光到表面的向量。它是光的相反方向。

#if defined(SPOT)
…
#else
    #if defined(SHADOWS_CUBE)
        shadowed = true;
        shadowAttenuation = UnitySampleShadowmap(-lightVec);
    #endif
#endif

image

點光源陰影

4.2 Cookies

Point light cookie也能夠經過_LightTexture0得到。須要的是一個cubeMap映射,而不是常規的紋理。

//sampler2D _LightTexture0, _LightTextureB0;
#if defined(POINT_COOKIE)
    samplerCUBE _LightTexture0;
#else
    sampler2D _LightTexture0;
#endif

sampler2D _LightTextureB0;
float4x4 unity_WorldToLight;

要對cookie進行採樣,請將片斷的world-space轉換爲light-space,並使用光照空間對立方體映射進行採樣。

#else
    #if defined(POINT_COOKIE)
        float3 uvCookie = mul(unity_WorldToLight, float4(worldPos, 1)).xyz;
        attenuation *= texCUBEbias(_LightTexture0, float4(uvCookie, -8)).w;
    #endif

    #if defined(SHADOWS_CUBE)
        shadowed = true;
        shadowAttenuation = UnitySampleShadowmap(-lightVec);
    #endif
#endif

image

點光源cookie

4.3 Skipping Shadows

如今,咱們可使用本身的着色器渲染全部動態光源。 儘管咱們目前並未對優化進行太多關注,但仍有一項潛在的大型優化值得考慮最終超出陰影漸隱距離的片元將不會被陰影化。 可是如今仍在採樣它們的陰影,這可能很昂貴。 咱們能夠經過基於陰影衰落因子進行UNITY_BRANCH分支來避免這種狀況。 它接近1,那麼咱們能夠徹底跳過陰影衰減。

if (shadowed) {
    float shadowFadeDistance = UnityComputeShadowFadeDistance(worldPos, viewZ);
    float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
    shadowAttenuation = saturate(shadowAttenuation + shadowFade);
    UNITY_BRANCH
    if (shadowFade > 0.99) {
        shadowAttenuation = 1;
    }
}

可是,即便用了UNITY_BRANCH分支它自己也很昂貴。除了靠近陰影區域的邊緣,全部碎片都落在陰影區域的內部或外部。 但這僅在GPU能夠利用這一點的狀況下才重要。 在這種狀況下,使用HLSLSupport.cginc定義UNITY_FAST_COHERENT_DYNAMIC_BRANCHING宏。

#if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING)
    UNITY_BRANCH
    if (shadowFade > 0.99) {
        shadowAttenuation = 1;
    }
#endif

即便這樣,僅當陰影須要多個紋理樣本時才值得使用。 對於柔和的聚光燈和點光源陰影,進一步使用用SHADOWS_SOFT關鍵字指示。 而方向光陰影始終只須要單個紋理,所以它性能很便宜。

#if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING) && defined(SHADOWS_SOFT)
    UNITY_BRANCH
    if (shadowFade > 0.99) {
        shadowAttenuation = 1;
    }
#endif
相關文章
相關標籤/搜索