8章 透明效果

Unity中兩種方法實現(xiàn)透明效果: 1.透明度測試(Alpha Test),無法得到真正半透明效果,另外一種是透明度混合(Alpha Blending )
前面的渲染都不考慮渲染順序,對于不透明的物品這樣也可以渲染正確。這是因為強大的深度緩存(depth buffer 也叫z-buffer),在實時渲染中,深度緩存用于解決可見(visibility)的問題?;舅悸肪褪牵焊鶕?jù)深度緩存中的值來判斷該片元距離攝像機的距離。 需要開啟深度測試和深度寫入。
使用深度緩存,可以讓我們不關心渲染順序,但是要實現(xiàn)透明效果,就不那么簡單了,因為在實現(xiàn)透明度混合時,我們關閉了深度寫入(ZWrite)。

透明度測試

只要一個片元的透明度不滿足條件(通常小于某個閥值),就會舍棄片元,被舍棄的片元不會再進行任何處理,也不會對顏色緩存產(chǎn)生影響。否則,就會按照普通不透明的物體來處理它,即進行深度測試,深度寫入等。也就是說透明度測試是不需要關閉深度寫入的。它和其他不透明物體最大的不同是根據(jù)透明度舍棄一些片元。。簡單但是效果很極端,完全透明看不到。要不完全不透明。

透明度混合

  使用片元的透明度作為混合因子,與已經(jīng)存儲在顏色緩沖中的顏色進行混合,得到新顏色。但是透明度混合要關閉深度寫入,使得要關心物體渲染順序。注意的是**只關閉了深度寫入,沒有關閉深度測試**。當使用透明度混合渲染一個片元時,會比較它的深度值與當前深度緩沖中的深度值。如果不透明在透明的前面就不會再進行混合,舍棄這個混合。**深度緩沖是只讀的。判斷需要渲染么**

渲染順序的重要性

透明度融合中 最重要的就是渲染順序,不光是透明和不透明需要從后到前,兩個透明的物體在渲染的時候也需要注意從后到前的順序,不然就會看起來反了。
透明和不透明一起 會因為不透明有深度寫入 會蓋住透明物體。 兩個透明的物體會進行顏色的混合,如果順序不會也會得到錯誤的表現(xiàn)
總結方法就是
(1)先渲染所有不透明物體,并開啟它們的深度測試和深度寫入
(2)把半透明物體按攝像機遠近排序,按照從后往前的順序渲染這些半透明物體,開啟他們的深度測試,但關閉深度寫入。
光有順序的保障也是不夠全面的。有時候會有相互重復穿插、覆蓋的情況。

image

遇到上面的這種情況,一般是選擇分割網(wǎng)格,我們盡可能讓模型是凸面體,考慮將復雜的模型拆分成可以獨立排序的多個子模型。如果不想分割網(wǎng)絡,可以試著讓透明通道更加柔和,使得穿插不是那么明顯。也可以開始深度寫入的半透明效果來模擬物體半透明。

Unity Shader的渲染順序

Unity對順序的問題提供了 渲染隊列( render queue )的解決方案,使用SubShader的Queue來決定渲染隊列。Unity 在內(nèi)部使用一系列整數(shù)索引來表示每個渲染隊列,且索引號越小表示越早被渲染。

image

//類似這樣的聲明,注意不是LightMode
SubShader {
        Tags { ”Queue”=”Alpha Test”}
        Pass {
        ……
        }
}

如果我們想要通過透明度混合來實現(xiàn)透明效果,代碼中應該包含類似下面的代碼:

SubShader {
        Tags { ”Queue”=”Transparent”}
        Pass {
            ZWrite Off   //深度寫入關閉
            ……
        }
    }

其中, ZWrite Off 用于關閉深度寫入,在這里我們選擇把它寫在Pass 中。我們也可以把它寫在SubShader 中,這意味著該SubShader 下的所有Pass 都會關閉深度寫入。

透明度測試

原理: 一個片元透明度不滿足條件(通常小于某個閥值),那么它對應的片元就會被舍棄。被舍棄的片元不會再進行任何處理,也不會對顏色緩沖產(chǎn)生任何影響(就等于啥都沒了,不存在);否則的話就會按照不透明的物體來進行處理
通常的判斷是用片元著色器中的** clip函數(shù)來進行透明度測試**。這是一個CG函數(shù)
函數(shù): void clip(float4 x); void clip(float3 x); void clip(float2 x); void clip(float1 x); void clip(float x);
參數(shù):裁剪時使用的標量或矢量條件。
描述:如果給定參數(shù)的任何一個分量是負數(shù),就會舍棄當前像素的輸出顏色。
示例用這樣一張圖,有4個不同的透明度 便于觀察表現(xiàn)


image

得到的效果會如下


image

直接上代碼

Shdaer "Alpha Test"{
     Properties{
          _Color("Color Tint", Color) = (1,1,1,1)
          _MainTex("Main Tex",2D) ="white"{}
           _Cutoff("Alpha Cutoff", Range(0,1)) =0.5    // 這個是判斷是否裁剪掉的 標準值,范圍[0,1]透明度的范圍也是這個
     }
      SubShader{
            Tags{ "Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
            Pass{
                Tags{ "LightMode" ="ForwardBase"}
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "Lighting.cginc"

                fixed4 _Color;
                sampler2D _MainTex;
                float4 _MainTex_ST;
                fixed _Cutoff;   //精度在[0,1] 所以用fixed

                struct a2v{
                    float4 vertex :POSITION;
                    float3 normal :NORMAL;
                    float4 texcoord: TEXCOORD0;
                };
                struct v2f{
                    float4 pos: SV_POSITION;
                    float3 worldNormal :TEXCOORD0;
                    float3 worldPos :TEXCOORD1;
                    float2 uv:TEXCOOrd2;
                };
                v2f vert(a2v v){
                    //把頂點的位置和法線 轉(zhuǎn)到世界坐標系,以及變換后的紋理坐標 傳給片元
                    v2f o;
                    o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
                    o.worldNormal= UnityObejctToWorldNormal(v.normal);
                    o.worldPos= mul(_Object2World, v.vertex).xyz;
                    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                    return o;
                }
                fixed4 frag(v2f i):SV_Target{
                    fixed3 worldNormal = normalize(i.worldNormal);
                    fixed3 worldLightDir= normalize(UnityWorldSpaceLightDir(i.worldPos));

                    fixed4 texColor = tex2D(_MainTex,i.uv);
                    //Alpha test
                    clip(texColor.a -_Cutoff);
                    //等同代碼 if(texColor.a -_Cutoff<0.0){discard;} 舍棄
                    
                    fixed3 albedo = texColor.rgb *_Color.rgb;
                    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz *albedo;
                    fixed3 diffuse = _LightColor0.rgb *albedo * max(0, dot(worldNormal,worldLightDir));

                    return fixed4(ambient +diffuse,1.0);

                }
                Fallback "Transparent/Cutout/VertexLit"
            }    
      }
      
}

這里的Tags是新的知識點
使用透明度測試 就需要Queue 設置為** AlphaTest 。**
RenderType 標簽讓 Unity把Shader 歸入到提前定義的組(這里就TransparentCount)中,以指明該Shader 是一個使用了透明度測試的Shader。
RenderType 標簽通常被用于著色器替換功能。我們還
把IgnoreProjector 設置為True,這意味著這個Shader 不會受到投影器(Projectors )的影響
。
通常,使用了透明度測試的Shader 都應該在SubShader 中設置這三個標簽。
LightMode 標簽是Pass標簽中的一種,它用于定義該Pass 在Unity 的光照流水線中的角色。只有定義了正確的LightMode,我們才能正確得到一些Unity 的內(nèi)置光照變量,例如 _LightColor0 .

image

透明度混合

原理:這種方法可以得到真正的半透明效果。它會使用當前片元的透明度作為混合因子,與已經(jīng)存儲在顏色緩沖中的顏色值進行混合,得到新的顏色。但是,透明度混合需要關閉深度寫入,這使得我們要非常小心物體的渲染順序。
為了進行混合,我們需要使用Unity 提供的混合命令——Blend。 Blend 是Unity 提供的設置混合模式的命令。想要實現(xiàn)半透明的效果就需要把當前自身的顏色和已經(jīng)存在于顏色緩沖中的顏色值進行混合,混合時使用的函數(shù)就是由該指令決定的。


image

這里 使用第二種語義 Blend SrcFactor DstFactor 來進行混合。 這個命令在設置混合因子的同時打開了混合模型。只有開啟混合,設置片元的透明通道才有意義。
我們把源顏色的混合因子SrcFactor 設置為SRCAlpha, 而目標顏色的混合因子 DstFactor 設為OneMinusSrcAlpha,這就是說混合后的顏色是


image

這是常用的混合命令。
這次用上面的素材 所以改動部分代碼

Properties{
      _Color("Main Tint", Color) = (1,1,1,1)
      _MainTex("Main Tex", 2D) = "white"{}
      _AlphaScale("Alpha Scale",Range(0,1)) =1  //在紋理透明度的基礎上控制整體透明度
}
        fixed _AlphaScale;  //屬性名稱對應修改
      SubShader{
          //透明度混合queue 是 Transparent
          Tags{"Queue" = "Transparent" "IgnoreProjector"="True" "RenderType"  ="Transparent"}
          Pass{
                Tags{ "LightMode"="ForwardBase"}
                ZWrite off
                Blend SrcAlpha OneMinusSrcAlpha
                //片元著色器修改
                fixed4 frag(v2f i): SV_Target{
                      fixed3 worldNormal = normalize(i.worldNormal);
                      fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                      
                      fixed4 texColor = tex2D(_MainTex, i.uv);
                      fixed3 albedo = texColor.rgb * _Color.rgb;
                      fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz *albedo;
                      fixed3 diffuse = _LightColor0.rgb *albedo *max(0,dot(worldNormal, worldLightDir));
                      return fixed4(ambient + diffuse, texColor.a *_AlphaScale);
                }
                FallBack "Transparent/VertexLit"    
           }
    }
image

但是當模型本身有復雜的遮擋關系或者包含了復雜的非凸網(wǎng)格的時候,就會有各種排序錯誤產(chǎn)生的錯誤的透明效果。

image

這都是由于我們關閉了深度寫入造成的,因為這樣我們就無法對模型進行像素級別的深度排序。在8.1 節(jié)中我們提到了一種解決方法是分割網(wǎng)格,從而可以得到一個“質(zhì)量優(yōu)等”的網(wǎng)格。但是很多情況下這往往是不切實際的。這時, 我們可以想辦法重新利用深度寫入,讓模型可以像半透明物體一樣進行淡入淡出。這就是我們下面要講的內(nèi)容。

開啟深度寫入的半透明效果

上面的錯誤表現(xiàn)是由于深度寫入關閉造成的。一種解決辦法:使用兩個pass來渲染模型,第一Pass 開啟深度寫入,但不輸出顏色,它的目的僅僅是為了把模型的深度值寫入深度緩沖中;第二個Pass進行正常的透明度混合,由于上個Pass得到逐像素的深度信息,該Pass就可以按照像素級別的深度排序結果進行透明渲染。
缺點在于,多用一個Pass 對性能影響比較大,而且使用這種方式可以實現(xiàn)與背后場景的混合。但是模型內(nèi)部的前后沒有了透明混合就是沒了內(nèi)部的半透明。如圖

image

上代碼 實現(xiàn)過程

//我們需要在前面的代碼里 加一個pass用了深度寫入,測試
Shader "Unity Shaders Book/Chapter 8/Alpha Blending With ZWrite" {
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
    }
    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        
        // Extra pass that renders to depth buffer only
        Pass {
            ZWrite On
            ColorMask 0
        }
        
        Pass {
            // 和8.4 節(jié)同樣的代碼
        }
    } 
    FallBack "Transparent/VertexLit"
}

這個pass雖然沒有什么具體計算,但是依然有深度消耗
pass的目的僅僅是為了把模型的深度信息寫入深度緩沖中,從而剔除模型被自身遮擋的片元。 所以第一行ZWrite On。 第二行一個新的渲染命令----ColorMask。這里是用于設置顏色通道的寫掩碼(write mask)

ColorMask RGB |A | 0 | 其他任何R、G、B、A 的組合

當ColorMask 設為0時,表示Pass 不會寫入任何顏色通道,不會輸出任何顏色。個人認為 這個就是強行設置顏色值,RGBA設置某個值 這個pass就對應只會輸出這個值。

ShaderLab的混合命令

我們首先來看一下混合是如何實現(xiàn)的。當片元著色器產(chǎn)生一個顏色的時候,可以選擇與顏色緩存中的顏色進行混合。這樣一來,混合就和兩個操作數(shù)有關:源顏色(source color)和目標顏色( destination color )。源顏色,我們用S 表示,指的是由片元著色器產(chǎn)生的顏色值; 目標顏色,我們用D 表示,指的是從顏色緩沖中讀取到的顏色值。對它們進行混合后得到的輸出顏色,我們用O表示,它會重新寫入到顏色緩沖中。需要注意的是,當我們談及混合中的源顏色、目標顏色和輸出顏色時,它們都包含了RGBA 四個通道的值,而并非僅僅是RGB 通道。
想要使用混合,我們必須首先開啟它。在Unity 中,當我們使用Blend (Blend Off 命令除外〉命令時,除了設置混合狀態(tài)外也開啟了混合。但是, 在其他圖形API 中我們是需要手動開啟的。例如在OpenGL 中,我們需要使用glEnable(GL_BLEND)來開啟混合。
這個我專門寫過一篇帖子來講這個Blend http://www.itdecent.cn/p/9f5066eca31f 主要是說的Alpha的混合
這里主要講一下命令和使用

混合等式的參數(shù)

等式一般都是加的操作(我們也可以使用其他操作)這個混合過程雖然是不可以編程的 但是是一個高度可以配置的過程。混合中 主要影響 RGB通道和A通道(分開計算)??梢詡魅胂胍幕旌弦蜃?,每個等式有兩個因子(一個用于和源顏色相乘, 一個用于和目標顏色相乘〉,因此一共需要4 個因子。


image

其實這些混合因子呢其實也已經(jīng)定義好,直接調(diào)用機可以。我原來的帖子詳細寫過而且各種表現(xiàn)也有展示


image

調(diào)用時候的命令
Blend SrcAlpha OneMinusSrcAlpha, One Zero

混合操作

所謂真正的混合不是說和因子相乘的那一刻,是結果相加的操作混合。這樣可以是加法也可以是減法。減法就是 命令** BlendOp BlendOperation**

image

image

混合操作命令通常是與混合因子命令一起工作的。但需要注意的是, 當使用 Min 或Max 混合操作時, 混合因子實際上是不起任何作用的,它們僅會判斷原始的源顏色和目的顏色之間的比較結果。

Blend SrcColor OneMinusSrcColor
BlendOp Sub //等等操作 ,寫法就是這樣

常見的混合類型

通過混合操作和混合因子命令的組合, 我們可以得到一些類似Photoshop 混合模式中的混合效果:


image

image

需要注意的是,雖然上面使用Min 和Max 混合操作時仍然設置了混合因子,但實際上它們并不會對結果有任何影響,因為Min 和Max 混合操作會忽略混合因子。另一點是,雖然上面有些混合模式并沒有設置混合操作的種類,但是它們默認就是使用加法操作,相當于設置了BlendOp Add。

雙面渲染的 透明效果

前面的透明渲染呢 只有第一層,內(nèi)部結構看不到這是因為開啟了物體背景剔除。只渲染正面,背面不管。 如果要爭取的效果,可以使用Cull 指令來控制需要剔除哪個渲染圖元

Cull Back  |Front  | Off

如果設置為Back,那么那些背對著攝像機的渲染圖元就不會被渲染,這也是默認情況下的剔除狀態(tài);如果設置為Front , 那么那些朝向攝像機的渲染圖元就不會被撞染; 如果設置為Off,就會關閉剔除功能, 那么所有的渲染圖元都會被渲染,但由于這時需要渲染的圖元數(shù)目會成倍增加,因此除非是用于特殊效果, 例如這里的雙面渲染的透明效果,通常情況是不會關閉剔除功能的。

透明度測試的雙面渲染

代碼很簡單就是在 原來透明徹底是代碼里面加一句 Cull off

Pass {
        Tags { "LightMode"="ForwardBase" }
            
        // Turn off culling
        Cull Off
image

透明度混合的雙面渲染

這個比較麻煩,因為關閉了深度寫入,所以先后順序其實不知道。必須控制渲染順序。
這個的解決辦法就是寫兩個Pass(好傻)SubShader會順序執(zhí)行Pass。第一個Pass只渲染背面,第二個渲染正面,這樣保證了背面的肯定在前面閑渲染,這樣雖然兩次Pass 但其實計算沒重復,不算性能浪費。

Shader "Unity Shaders Book/Chapter 8/Alpha Blend With Both Side" {
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _AlphaScale ("Alpha Scale", Range(0, 1)) = 1
    }
    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        
        Pass {
            Tags { "LightMode"="ForwardBase" }
            
            // First pass renders only back faces 
            Cull Front
            
            // 和之前一樣的代碼
        }
        
        Pass {
            Tags { "LightMode"="ForwardBase" }
            
            // Second pass renders only front faces 
            Cull Back
            
            // 和之前一樣的代碼
        }
    } 
    FallBack "Transparent/VertexLit"
}

image
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容