支持鏤空陰影
噪聲
粗略的半透明陰影
鏤空陰影和半透明陰影之間切換
使用Unity 5.6.6f1
1 鏤空陰影
翻譯11介紹了鏤空渲染,可能也注意到了,物體的投射的陰影是物體本身的形狀,跟鏤空形狀完全不一致。這是因為我們之前的Shader投射陰影只是簡單的采樣了光的方向和到達物體表面的距離,沒有區分表面形狀。
圖1.1 不符合現實的半透明陰影
1.1 重寫陰影
為了將透明度考慮在內,需要在陰影投射pass通道訪問alpha值。這意味着我們要采樣albedo紋理。然而,當僅用opaque渲染模式時就不需要采樣,為此需要適配Shader變體。
現在的Shader已經集成了兩個變體陰影,一是針對PointLight立方體陰影,二是針對其他類型燈光。現在增加第三個變體。改造一下之前的Shadow.cginc-略
1.2 裁剪陰影片段
就像渲染鏤空效果時,陰影的鏤空也根據alpha值來決定是否丟棄片段。因此算法上大致相似,必要變量也相似。拷貝tint、albedo、renderingsettings。
#include "UnityCG.cginc" float4 _Tint; sampler2D _MainTex; float4 _MainTex_ST, _AlphaCutOff;
當使用Cutout渲染模式就需要采樣albedo紋理,但是只有當不使用albedo紋理alpha值作為平滑度時才能這樣做(之前是把albedo`s alpha作為了平滑值,避免沖突)。如果滿足上述條件,就可把uv坐標傳遞給片元程序。
寫一個宏_SHADOW_NEED_UV
#if defined(_RENDERING_CUTOUT) && !defined(_SMOOTHNESS_ALBEDO)
#define _SHADOW_NEED_UV 1
#endif
struct Interplotars { float4 position : SV_POSITION; #if defined(_SHADOW_NEED_UV) float2 uv : TEXCOORD0; #endif #if defined(SHADOWS_CUBE) float3 lightVec : TEXCOORD1; #endif };
實現宏_SHADOW_NEED_UV ,需要傳遞uv坐標時就采樣
#if defined(_SHADOW_NEED_UV)
i.uv = TRANSFORM_TEX(v.uv, _MainTex);
#endif
拷貝GetAlpha函數,把厲害的宏替換為_SHADOW_NEED_UV
//采樣alpha float GetAlpha(Interpolators i) { float alpha = _Tint.a; #if defined(_SHADOW_NEED_UV) alpha *= tex2D(_MainTex, i.uv.xy).a; #endif return alpha; }
現在可以在fragment程序中獲取alpha值,然后在Cutout渲染模式下使用clip裁切。
float alpha = GetAlpha(i); #if defined(_RENDERING_CUTOUT) clip(alpha - _AlphaCutOff); #endif
圖1.2 鏤空陰影
2 局部陰影
為了同時支持Fade和Transparent渲染模式的陰影,必須將其關鍵字添加到shadowCaster通道的着色器鍾並定義ShaderFeature。
#pragma shader_feature _ _RENDERING_CUTOUT _RENDERING_FADE _RENDERING_TRANSPARENT
在Fade和Transparent模式下陰影也應該是半透明的,定義宏SHADOWS_SEMITRANSPARENT
#if defined(_RENDERING_FADE) || defined(_RENDERING_TRANSPARENT)
#define SHADOWS_SEMITRANSPARENT 1
#endif
再次調整Shadeow_need_uv
#if SHADOWS_SEMITRANSPARENT || defined(_RENDERING_CUTOUT) #if !defined(_SMOOTHNESS_ALBEDO) #define SHADOWS_NEED_UV 1 #endif #endif
這個時候由於是使用了Clip,還不能正確投射半透明陰影。繼續!
2.1 抖動陰影
陰影貼圖包含了光線到表面的距離。光線阻擋結果信息存在陰影貼圖,存儲的結果是:0或1。因此是沒有辦法指定光被半透明表面部分阻擋。
目前能做到的就是將陰影表面的一部分剪掉(鏤空)。這就是為鏤空陰影所做的。但是,除了基於閾值進行裁剪外,我們還可以裁剪片段。例如,如果一個表面讓一半的光通過,並通過使用特定抖動紋理裁剪所有其他片段。就可以把生成的陰影顯示為完整陰影的一半。
依靠紋理的alpha值,我們可以使用帶有更多或更少孔的圖案,就不會出現重復的模式。而且,如果我們混合使用這些模式,則可以對陰影的密度進行平滑過渡。基本上,我們僅使用兩種狀態來近似漸變。這種技術被稱為抖動。
Unity包含一個抖動模式圖集,我們可以使用。它包含16種4×4像素的不同圖案。它從一個完全空的模式開始。每個連續的模式填充一個額外的像素,直到有七個像素被填充。然后模式被反轉和反轉,直到所有像素都被填充。
2.2 VPOS
要對我們的陰影應用抖動模式,我們必須對其進行采樣。 不能使用網格的UV坐標,因為它們在陰影空間中不一致。 相反,我們需要使用片段的屏幕空間坐標。 陰影是從光的視角角度渲染貼圖的,這會使圖案與陰影貼圖對齊。
片段的屏幕空間坐標可以在片元程序中訪問,方法是添加一個帶有VPOS語義的參數。這些坐標不是由頂點程序顯式輸出的,但GPU可以程序用使用它們。
然而不幸的是,VPOS和SV_POSITION語義,在一些平台上,它們最終可能會映射到相同的語義位置。所以我們不能同時在我們的結構體中使用它們。幸運的是,我們只需要在頂點程序中使用SV_POSITION,而VPOS只需要在fragment程序中使用。所以分別為Vertex和Fragment程序定義一個單獨的結構體。
2.3 抖動處理
要訪問Unity的抖動模式紋理,需要增加變量_DitherMaskLOD,不同模式存儲在3D紋理的不同layer中,必須使用sampler3D聲明。
sampler3D _DitherMaskLOD;
如果需要半透明陰影,就在片元程序采樣該紋理。通過tex3D采樣,參數是_DitherMaskLOD和一個3D坐標。由於該紋理有16種模式,模式選擇由Z坐標決定,Z的范圍是0-1。以0.0625增量步進。
#if SHADOWS_SEMITRANSPERANT tex3D(_DitherMaskLOD, float3(i.vpos.xy, 0.0625)); #endif
采樣該紋理的目的是取得改紋理的alpha通道,再使用它減去一個較小值,然后用clip裁剪掉
#if SHADOWS_SEMITRANSPERANT float dither = tex3D(_DitherMaskLOD, float3(i.vpos.xy, 0.0625)).a;
clip(dither – 0.01); #endif
要真正看到該模式紋理顯示,需要調整顯示密度大小,這可以通過將紋理的坐標位置乘以0.01來實現的。聚光燈的陰影下觀察它。Z值從0-1,紋理將從不渲染到完全渲染。
tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.1, 0.0625))
圖2.1 Dither抖動紋理 0.0625 vs. 0.9375
2.4 近似半透明
根據tex3D采樣函數,可知Z坐標值決定是否要渲染片元。那么只需將該片元的alpha值與最高LOD層級(15/16=0.9375)相乘,得出該片元alpha所在的LOD層級。
tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.1, alpha * 0.9375))
圖2.2 近似模擬半透
抖動紋理密度大小可以縮放VPOS指定的紋理坐標實現,Unity使用了0.25值來縮放。
tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.25, alpha * 0.9375))
圖2.3 縮放后
縮放后效果還行但不完美,這取決於抖動紋理的分辨率。越高的分辨率填充的越密集,效果越好。同時,抖動紋理在Hard和Soft陰影模式下的效果也不相同
圖2.4 soft vs. hard
這個陰影模式在物體移動時有一個視覺錯誤,整個陰影都在動。密集恐懼。
圖2.5 抖動
3 組合剪影與淡出效果
可能Dither模式太難堪了,我們可能既想要投射陰影,也要半透明的效果。這就可以組合Cutout與Fade渲染。在陰影pass中增加一個新的關鍵字:
_SEMITRANSPARENT_SHADOWS。若沒有啟用淡出半透陰影,回退到裁剪陰影。
#pragma shader_feature _ _SEMITRANSPARENT_SHADOWS #if defined(_RENDERING_FADE) || defined(_RENDERING_TRANSPARENT) //#define SHADOWS_SEMITRANSPARENT 1 #if _SEMITRANSPARENT_SHADOWS #define SHADOWS_SEMITRANSPARENT 1
#elif #define _RENDERING_CUTOUT
#endif #endif
上面這段代碼加入后,會自動切換為裁剪陰影。
3.1 啟用自定義關鍵字
3.2 合並顯示
void DoSimetransparentShadow(RenderMode mode) { if (mode == RenderMode.Fade || mode == RenderMode.Transparent) { EditorGUI.BeginChangeCheck(); bool enable = EditorGUILayout.Toggle ( new GUIContent("Semitransparent Shadow"), IsKeyEnable("_SEMITRANSPARENT_SHADOWS") ); if(!enable) isShowCutoffAlpha = true; if (EditorGUI.EndChangeCheck()) { SetKeyword("_SEMITRANSPARENT_SHADOWS", enable); } } }