翻譯12 Unity 半透明陰影


支持鏤空陰影
噪聲
粗略的半透明陰影
鏤空陰影和半透明陰影之間切換

使用Unity 5.6.6f1

1 鏤空陰影

翻譯11介紹了鏤空渲染,可能也注意到了,物體的投射的陰影是物體本身的形狀,跟鏤空形狀完全不一致。這是因為我們之前的Shader投射陰影只是簡單的采樣了光的方向和到達物體表面的距離,沒有區分表面形狀。

image

圖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

image

圖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))

image image

圖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))

image

圖2.2 近似模擬半透

        抖動紋理密度大小可以縮放VPOS指定的紋理坐標實現,Unity使用了0.25值來縮放。

tex3D(_DitherMaskLOD, float3(i.vpos.xy * 0.25, alpha * 0.9375))

image

圖2.3 縮放后

         縮放后效果還行但不完美,這取決於抖動紋理的分辨率。越高的分辨率填充的越密集,效果越好。同時,抖動紋理在Hard和Soft陰影模式下的效果也不相同

image image

圖2.4 soft vs. hard

        這個陰影模式在物體移動時有一個視覺錯誤,整個陰影都在動。密集恐懼。

dither_pattern

圖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);
                }
            }
        }

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM