翻譯11 Unity 透明渲染


剪紙鏤空shader
渲染隊列
半透明材質
合並反射和透明

11之前的shader僅能支持不透明渲染,現在增加透明渲染。

1 Cutout-鏤空渲染

要創建透明的材質,首先要了解每個片元的透明度。透明度信息存儲在顏色的alpha通道,在我們的shader里是主紋理的alpha通道和調色值的alpha通道。

1.1 確定Alpha值

獲取alpha值

//采樣alpha
float GetAlpha(Interpolators i){
    return _Tint.a * tex2D(_MainTex, i.uv.xy).a;
}

但是前面講了SMOOTHNESS_ALBEDO值可能被用來確定平滑度,所以要在該值不被使用時才能采樣該紋理的alpha值,避免錯誤

//采樣alpha
float GetAlpha(Interpolators i){
    float alpha = _Tint.a;
#if !defined(_SMOOTHNESS_ALBEDO)
    alpha *= tex2D(_MainTex, i.uv.xy).a;
#endif
    return alpha;
}

1.2 剪切鏤空

在不透明的材質,每個通過深度測試的片元都會被渲染,所有的片元都是不透明的並且寫入深度緩沖。所以實現透明最簡單快捷的方法是:在深度測試時,要么它完全不透明,要么它完全透明。如果它是透明就不渲染。

CG提供了一個Clip函數,若參數<0,那么該像素所在的片元整個就會被丟棄(粗暴處理),同時不會寫入深度緩沖。可以將得到的Alpha減去一個給定值(視效果)來決定是否渲染。

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
    float alpha = GetAlpha(i);
    clip(alpha - 0.5);
    //...
}

image

圖1.1 Transparency

1.3 自定義裁剪范圍

1.2中給定0.5不太靈活,我們需要一個任意值來代替。

Properties {
    …
    _AlphaCutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}

float _AlphaCutoff;
//…
float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
    float alpha = GetAlpha(i);
    clip(alpha - _AlphaCutoff);
    //…
}

DX11匯編

   0: sample r0.xyzw, v1.xyxx, t0.xyzw, s2//采樣
   1: mul r0.xyz, r0.xyzx, cb0[6].xyzx      //r0 * cb0[6]
   2: mad r0.w, cb0[6].w, r0.w, -cb0[8].y //cb0[6] * r0 + (-cb0[8])
   3: lt r0.w, r0.w, l(0.000000)      //浮點比較:r0.w < 0 ?  0xFFFFFFFF : 0x0000000
   4: discard_nz r0.w

1.4 渲染模式

Clip性能問題:桌面GPU尚可,但是對移動GPU來講性能就很糟糕。而且對於不透明物體不需要執行該函數。增加預定義的關鍵字

void SetRenderMode()
{
    RenderMode mode = RenderMode.Opaque;
    if (IsKeyEnable("_RENDER_CUTOUT"))
    {
           mode = RenderMode.Cutout;
    }
       EditorGUI.BeginChangeCheck();
    GUIContent gc = new GUIContent("RenderMode");
    mode = (RenderMode)EditorGUILayout.EnumPopup(gc, mode);
    if (EditorGUI.EndChangeCheck())
    {
        SetKeyword("_RENDER_CUTOUT", mode == RenderMode.Cutout);
    }
}
float alpha = GetAlpha(i);
#if defined(_RENDERING_CUTOUT)
    clip(alpha - _AlphaCutoff);
#endif

1.5 渲染隊列

UnityShader提供了隊列Queue標簽

    //Determine in which order objects are renderered.
    public enum RenderQueue
    {
        //This render queue is rendered before any others.
        Background = 1000,
        //Opaque geometry uses this queue.
        Geometry = 2000,
        //Alpha tested geometry uses this queue.
        AlphaTest = 2450,
        //Last render queue that is considered "opaque".
        GeometryLast = 2500,
        //This render queue is rendered after Geometry and AlphaTest, in back-to-front
        //order.
        Transparent = 3000,
        //This render queue is meant for overlay effects.
        Overlay = 4000
    }

我們可以用自定義UI來決定渲染隊列

if (EditorGUI.EndChangeCheck())
{
    SetKeyword("_RENDER_CUTOUT", mode == RenderMode.Cutout);
    RenderQueue queue = mode == RenderMode.Opaque? RenderQueue.Geometry:RenderQueue.AlphaTest;
    targetMaterial.renderQueue = (int)queue;
}

1.6 渲染類型標簽

標簽在用攝像機的replacement shaders時非常有用,可以自定義渲染效果。

string renderType = mode == RenderMode.Opaque ? "" : "TransparentCutout";
targetMaterial.SetOverrideTag("RenderType", renderType);

2 半透明-淡入淡出渲染

Cutout渲染局限性
    1是對每一個像素計算,不滿足alpha值的片元整個會被丟棄
    2是渲染結果要么全不透明要么全透明
    3是在不透明和透明區間沒有過渡,硬邊嚴重
    4是鏤空邊緣鋸齒嚴重

為了解決上述問題,Unity自帶的StandardShader使用了一種Fade模式。增加關鍵字_RENDERING_FADE略。

2.1 渲染設置

Fade模式有它自己的渲染隊列和渲染類型。Queue始值是3000,標識透明物體。RenderType是Transparent.

替換SetRenderMode函數,定義一個通用的渲染設置

class RenderingSettings
{
    public RenderQueue Queue;
    public string RenderType;
    public static RenderingSettings[] modes =
    {
        new RenderingSettings(){Queue = RenderQueue.Geometry, RenderType = ""},
        new RenderingSettings() { Queue = RenderQueue.AlphaTest, RenderType = "TransparentCutout"},
        new RenderingSettings() { Queue = RenderQueue.Transparent, RenderType = "Transparent"}
    };
}
void SetRenderMode()
{
    RenderMode mode = RenderMode.Opaque;
    if (IsKeyEnable("_RENDERING_CUTOUT"))
    {
        mode = RenderMode.Cutout;
        AlphaCutOffShow();
    }else if (IsKeyEnable("_RENDERING_FADE"))
       {
        mode = RenderMode.Fade;
    }
    EditorGUI.BeginChangeCheck();
    GUIContent gc = new GUIContent("RenderMode");
    mode = (RenderMode)EditorGUILayout.EnumPopup(gc, mode);
    if (EditorGUI.EndChangeCheck())
    {
        SetKeyword("_RENDERING_CUTOUT", mode == RenderMode.Cutout);
        SetKeyword("_RENDERING_FADE", mode == RenderMode.Fade);
        //RenderQueue queue = mode == RenderMode.Opaque? RenderQueue.Geometry:RenderQueue.AlphaTest;
        //string renderType = mode == RenderMode.Opaque ? "" : "TransparentCutout";
        RenderingSettings settings = RenderingSettings.modes[(int)mode];
        targetMaterial.renderQueue = (int)settings.Queue;
        targetMaterial.SetOverrideTag("RenderType", settings.RenderType);
    }
}

2.2 渲染透明物體

打開FrameDebugger觀察,選擇Fade模式,然后對比前后的變化:
    當使用Opaque或Cutout模式,使用該材質的對象通過Render.OpaqueGeometry渲染。
    當使用Fade模式,對象通過Render.TransparentGeometry渲染。

image image

圖2.1 Opaque vs Transparent

注意到圖2.1右側圖片,也調用了Render.OpaqueGeometry方法。注意這個調用先后順序,Opaque和Cutout先被渲染,Transparent后渲染。因此該渲染順序,確保了半透明物體不會在它們之后渲染。

2.3 Blending Fragments - 融合

公式:Blend src dst
舉例:源:透明度為a,顏色值Ac;目標顏色為Bc.
混合后顏色 = Ac*a + (1-a)*Bc

實現Fade渲染模式。用兩個關鍵字在base和additive pass支持三種渲染模式。

#pragma shader_feature _ _RENDERING_CUTOUT _RENDERING_FADE

在Fade模式下,我們需要對當前片元的顏色與colorBuffer已存在的顏色融合。這一步雖在GPU完成,但需要現在Fragment程序預先計算提供。

為了創建一個透明效果,我們需要使用不同於Opaque和Cutout的融合模式。例如在additive Pass,把新顏色加到現有的顏色。然而不能簡單的相加在一起,這個多通道融合需要取決於alpha值:
    當alpha為1,物體將會渲染成完全不透明。對於上述舉例,通常在basePass使用Blend One Zero,在additivePass使用Blend One One;
    當alpha為0,物體將會渲染成完全透明。這兩個pass的融合只能使用Blend Zero One;
    當alpha為0.25,使用Blend 0.25 0.75 和 Blend 0.25 One;
    以此類推…

為了達成上述效果,可以使用SrcAlpha和OneMinusSrcAlpha關鍵字

image圖2.2 半透明效果

這兩個關鍵字只是對Fade渲染半透明效果的近似估計,我們也可以自定義屬性變量代替。

_SrcBlend("SrcBlend", float) = 1
_DstBlend("DstBlend", float) = 0

由於這兩個屬性依賴渲染模式,因此不能直接顯示在Inspector面板。

[HideInInspector] _SrcBlend("SrcBlend", float) = 1
[HideInInspector] _DstBlend("DstBlend", float) = 0

使用自定義的屬性,必須放入中括號,這是舊Shader語法要求。

Blend [_SrcBlend] [_DstBlend]

為了能控制這些變量,在RenderingSettings增加兩個變量

public static RenderingSettings[] modes = {
    new RenderingSettings() {
        queue = RenderQueue.Geometry,
        renderType = "",
        srcBlend = BlendMode.One,
        dstBlend = BlendMode.Zero
    },
    new RenderingSettings() {
        queue = RenderQueue.AlphaTest,
        renderType = "TransparentCutout",
        srcBlend = BlendMode.One,
        dstBlend = BlendMode.Zero
    },
    new RenderingSettings() {
        queue = RenderQueue.Transparent,
        renderType = "Transparent",
        srcBlend = BlendMode.SrcAlpha,
        dstBlend = BlendMode.OneMinusSrcAlpha
    }
};

通過Material.SetInt函數直接為_SrcBlend_DstBlend指定值

targetMaterial.SetInt("_SrcBlend", (int)settings.SrcBlend);
targetMaterial.SetInt("_DstBlend", (int)settings.DstBlend);

2.4 深度沖突問題

把兩個半透明物體一高一低放置在一起(不是重疊但很接近),互相重合區域會出現一個遮擋問題。不能透過透明區域看見另一個物體。

image

圖2.3 奇怪現象

Unity首先試圖繪制距離攝像機最近的物體,這是對重疊幾何繪制最有效的方法。不幸的是它對半透明物體失效了。解決方法就是反過來繪制,最遠的物體先繪制寫入深度緩沖,最近的物體后繪制與深度緩沖比較。這也是繪制半透明物體代價更昂貴的原因。

為了確定幾何圖形的繪制順序,Unity使用了物體的中心位置。這對於相距很遠的小物體來說很有效。但是對於較大物體,或者是緊靠在一起的平面物體,它就不好使了。在這種情況下,當你改變視角時,繪制順序會突然變化。這可能會導致重疊的半透明物體外觀的突然變化。

無法繞過此限制,尤其是在繪制相交幾何時。  但是在我們的例子中,某些繪制順序產生了明顯錯誤結果。 發生這種錯誤是因為我們的着色器仍會寫入深度緩沖區。 深度緩沖區是二進制數,並不關心透明度。 如果片段沒有被裁剪,其深度最終將寫入緩沖區。 由於半透明對象的繪制順序並不完美,若開啟深度寫入,不可見物體的深度值最終可能會覆蓋可見的物體的深度值。 因此在使用Fade渲染模式時,我們必須禁用對深度緩沖區的寫入。

2.5 ZWrite 控制

類似融合自定義屬性,ZWrite模式也可自定義

[HideInInspector] _SrcBlend ("_SrcBlend", Float) = 1
[HideInInspector] _DstBlend ("_DstBlend", Float) = 0
[HideInInspector] _ZWrite ("_ZWrite", Float) = 1
…
Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]

GUI拓展略

image

圖2.4 重合正確

3 全透明渲染-逼真的材質

Fade缺點:
    我們創建的半透明渲染模式會根據物體alpha值淡出顯示。 請注意,物體顏色的顯示呈褪色。 它的漫反射和鏡面反射以及高光都會被淡化了。 這就是為什么它被稱為淡入淡出模式。
    Fade模式適用於大多數效果,但它不能正確渲染一個半透明的固體物體。例如玻璃。

Transparent優點:
    玻璃材質實際上是完全透明的,並且也有清晰的高光和反射光,反射光也會加入到其他任何經過的光的路徑鍾參與着色。


image

圖3.1 紅色過渡趨勢

enum RenderingMode {
    Opaque, Cutout, Fade, Transparent
}

設置Transparent模式與Fade相同,不管Alpha值是多少我們必須添加反射。因此源混合模式必須為1而不能依賴Alpha值。然后增加關鍵字_RENDERING_TRANSPARENT.

3.1 預乘Alpha

預乘Alpha:紋理的RGB通道分別預乘Alpha得到新的顏色,然后就可以不需要Alpha通道。

優點
    它們可存儲與RGB通道相關聯的不同Alpha值,這樣可以使用相同數據來實現多種組合效果。
    少計算一次乘法:混合后顏色 = Ac*a + (1-a)*Bc => Ac` + (1-a)*Bc.提交效率
    在有透明像素的邊緣進行正確插值:如果RGB不預乘Alpha,插值時的權重過大,邊緣會產生奇怪的顏色。例如出現黑邊問題

缺點:
    RGB預乘后,RGB每個通道值會變小,顏色變暗
    紋理精度下降。每個通道都乘以了alpha因子
    不能直觀知道原RGB顏色值

3.2 調節Alpha

如果一個物體既透明又反光,我們就能看到它背后的東西,也能看到反射回來的東西。這對物體的兩面觀察都成立。但是同樣的光既不能被反射也不能穿過物體。這又是一個能量守恆的問題。所以一個物體的反射性越強,穿過它的光就越少,不管它固有的透明度如何。

表面沒有反射,它的alpha值是0。當反射所有的光時,它的alpha實際上變成了1。當我們在Fragment Program中確定反射率時,我們可以使用它來調整alpha值。給定初始alpha和反射率:

調節alpha = 1 – (1 - α)(1 - r)

(1 – r)在Shader定義為oneMinusReflectivity

調節alpha = 1 – oneMinusReflectivity + α * oneMinusReflectivity

#if  defined(_RENDERING_TRANSPARENT)
    albedo *= alpha;
    alpha = 1 - oneMinusReflectivity + alpha * oneMinusReflectivity;
#endif
效果更暗了。


免責聲明!

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



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