自定義燈光渲染
解碼LDR顏色
增長獨立Pass渲染光
支持方向光、點光源、聚光燈
手動採樣陰影紋理
Unity 5.6.6f1數組
在G-Buffers填充完畢後,而後渲染光。本篇先介紹Unity是如何渲染光,以及實現本身Shader的光渲染。在Edit / Project Settings / Graphics 去掉默認的Shader。cookie
每一個deferred光都是在一個獨立的Pass修改屏幕圖像(後處理Image)完成渲染。建立一個Shader而後指定到Built-In shader settingsapp
圖1 修改內置的Shader編輯器
修改以後,編輯器大量報錯.ide
圖2 least 2 passes函數
先簡單複製第一個Pass解決錯誤,結果是屏幕內除了天空盒外全部物體被渲染成黑色了。這是由於使用了stencil-buffer。性能
報錯的緣由:爲何須要第二個Pass?
當HDR禁用時,光照數據會被使用對數編碼計算,而後在(第二個)最終的pass解碼該數據。因此必需要增長Pass。當禁用HDR時就能調用第二個Pass,但此時天空也變黑了。優化
當在LDR(HDR禁用)模式,天空變黑了。這是由於轉換過程當中沒有正確使用stencil-buffer模板掩碼。在第二個Pass中配置:應該只渲染不屬於背景的片斷,可經過_StencilNonBackground提供適當的模板值。ui
Pass { Stencil { Ref[_StencilNonBackground] ReadMask[_StencilNonBackground] CompBack Equal CompFront Equal } }
在第二個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));
新增一個cginc文件,引入第一個pass。要把渲染的光照增長到圖像上,必須確保不能擦除已渲染的圖像,所以改變混合模式要徹底合併源顏色和目標顏色。
Blend One One
也須要全部可能的光照配置shader variants變體,該編譯指令:multi_compile_lightpass會建立全部包含的變體。而後再增長一個HDR_ON的指令。
#pragma multi_compile_lightpass #pragma multi_compile _ UNITY_HDR_ON
須要用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; }
與上篇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;
獲取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;
引入BRDF函數,定義在UnityPBSLighting.cginc中
首先計算視野方向
float3 worldPos = mul(unity_CameraToWorld, float4(viewPos, 1)).xyz; float3 viewDir = normalize(_WorldSpaceCameraPos - worldPos);
//。。。 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;
由於間接光呈現的是黑色的,在這裏不適用。可是直接光必須被配置成與當前渲染的光相匹配。對於方向光,須要它的顏色和方向。這兩個變量能夠經過_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;
光照方向錯誤
計算獲得最終的光照,但光的方向錯誤了。緣由:_LightDir是光到表面的方向。在CreateLight計算中須要表面到光的方向
light.dir = -_LightDir;
正確,沒有陰影
在本身的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);
有陰影的方向光
固然,這僅在定向光啓用了陰影時纔有效。 若是不是,則陰影衰減始終爲1。
float shadowAttenuation = 1; #if defined(SHADOWS_SCREEN) shadowAttenuation = tex2D(_ShadowMapTexture, uv).r; #endif light.color = _LightColor.rgb * shadowAttenuation;
陰影貼圖應該是有限的,它覆蓋的面積越大,陰影的分辨率越低。 Unity提供了繪製陰影的最大距離,此距離能夠經過Edit / Project Settings / Quality進行調整。
陰影距離配置
當陰影幾乎快達到了該限定距離就會淡出,Unity內置的shader是這樣設定並計算。因爲我將手動採樣該陰影紋理,當到達紋理的邊緣時陰影會被截取,結果是陰影雖然消失了,但有被急劇切割的生硬畫面。
長、短距離陰影對比
要漸隱陰影,首先要知道的是陰影徹底消失的距離。該距離又依賴於陰影投射方向。在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); }
陰影漸隱值範圍是[0, 1],該值決定了陰影要消失多少。實際的消失值能夠加到陰影衰減之上並限定在[0, 1]以內
float shadowFade = UnityComputeShadowFade(shadowFadeDistance);
shadowAttenuation = saturate(shadowAttenuation + shadowFade);
最後,提供世界座標和視圖深度在片元程序中建立光照。視圖深度是片元在視圖空間中的位置的Z份量。
UnityLight light = CreateLight(uv, worldPos, viewPos.z);
陰影漸隱
支持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);
帶有cookie的方向光
總體結果彷佛能夠,可是觀察邊緣彷佛有硬邊
硬邊過渡
相鄰片元的cookie座標的巨大差別就會致使該問題出現。在這種狀況下,GPU選擇的mipmap級別對於最近的表面是low level。解決辦法之一就是:在採樣mip映射時應用偏移。大神的總結
attenuation *= tex2Dbias(_LightTexture0, float4(uvCookie, 0, -8)).w;
偏移採樣
上述只支持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;
由於方向光會影響到場景內全部物體,因此被畫成全屏quad。相比之下,聚光燈只會影響位於圓錐體內的部分物體。一般不須要計算整個圖像的聚光燈光照,將繪製一個與聚光燈的影響範圍相匹配的金字塔體。
禁用方向燈,改用聚光燈。由於着色器只對方向光正確工做,那麼如今的結果會出現錯誤。可是它仍可讓你看到金字塔的哪些部分被渲染了。
渲染範圍
根據上圖,金字塔是做爲一個普通的3D對象呈現的。它的背面被剔除,因此咱們能夠看到金字塔的正面。只有當它前面沒有東西的時候,它纔會被畫出來。除此以外,還添加了一個pass,用於設置模板緩衝區,以將繪圖限制爲位於金字塔卷內的片斷。您能夠經過frame-debugger來驗證。
剔除方式
這意味着咱們的着色器的culling和z-test設置被否棄了。 所以將其從着色器中刪除。
Blend [_SrcBlend] [_DstBlend] //Cull Off //ZTest Always ZWrite Off
當聚光燈的體積距離相機足夠遠時,此方法適用。 可是,當聚光燈離攝像機太近時,它會失敗。 發生這種狀況時,相機可能會進入了該體積內。 甚至有可能將近平面的一部分置於其內部,而將其他部分置於其外部,與近平面相交了。 在這些狀況下,模板緩衝區不能用於限制渲染。
仍然渲染光照的技巧是繪製金字塔的內表面,而不是金字塔的外表面。 這是經過渲染其背面而不是其正面來完成的。 並且,僅當這些表面最終位於已渲染的表面以後時才渲染它們。 這種方法還涵蓋了聚光燈體積內的全部片斷。 但這最終致使渲染了太多的碎片,由於一般金字塔的一般隱藏部分也將被渲染。 所以,僅在必要時執行。
當靠近相機時,要繪製背面才正確
目前,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
結果爲黑色,彷佛光線方向不正確。 發生這種狀況是由於聚光燈的世界位置計算不正確。 當咱們在場景中的某個地方渲染金字塔時,不像方向光那樣渲染全屏quad將光線存儲在normal通道中。 而必須是經由Vertex-Program從頂點的位置發射射線,經過將頂點的pos轉換到view-space完成計算,爲此,咱們可使用UnityObjectToViewPos函數。
i.ray = UnityObjectToViewPos(v.vertex);
然而,這會產生方向錯誤的光線。咱們要消去它們的X和Y座標。
i.ray = UnityObjectToViewPos(v.vertex) * float3(-1, -1, 1);
正確的世界位置
再次看看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
);
聚光燈的錐形衰減是經過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;
cookie衰減
上圖實際上產生了兩個光錐,一個向前一個向後。 後向圓錐一般在渲染區域以外結束,但這並不能保證。咱們只須要前向錐,它對應於負的W座標。
attenuation *= tex2Dbias(_LightTexture0, float4(uvCookie.xy, 0, -8)).w;
attenuation *= uvCookie.w < 0;
聚光燈發出的光也會根據距離衰減。此衰減存儲在查找紋理中,可經過_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));
cookie 和 distance衰減
當聚光燈有陰影時,定義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)) );
點光源使用與聚光燈相同的光向量、方向和距離衰減。這樣他們就能夠共享代碼。應該只在定義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
這已經足夠讓點光源工做了。它們被渲染成和聚光燈同樣的效果,除了渲染範圍使用的是球形而不是錐形。
高亮
點光源的陰影存儲在一個CubeMap。內置UnitySampleShadowmap可採樣。參數:光的方向。一個從光到表面的向量。它是光的相反方向。
#if defined(SPOT) … #else #if defined(SHADOWS_CUBE) shadowed = true; shadowAttenuation = UnitySampleShadowmap(-lightVec); #endif #endif
點光源陰影
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
點光源cookie
如今,咱們可使用本身的着色器渲染全部動態光源。 儘管咱們目前並未對優化進行太多關注,但仍有一項潛在的大型優化值得考慮:最終超出陰影漸隱距離的片元將不會被陰影化。 可是如今仍在採樣它們的陰影,這可能很昂貴。 咱們能夠經過基於陰影衰落因子進行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