UnityShader精要筆記七 透明效果 附RenderType

本文繼續(xù)對《UnityShader入門精要》——馮樂樂 第八章 透明效果 進行學習

一、深度測試和深度寫入

在之前的學習中抢蚀,我們從來沒有強調(diào)過渲染順序的問題。也就是說,當場景中包含很多模型時脑蠕,我們沒有考慮是先渲染A,再渲染B,最后再渲染C跪削,還是按照其他的順序來渲染谴仙。

事實上,對于不透明(opaque)物體碾盐,不考慮它們的渲染順序也能得到正確的排序效果晃跺,這是由于強大的深度緩沖(depth buffer,也被稱為z-buffer)的存在毫玖。

在實時渲染中掀虎,深度緩沖是用于解決可見性(visibility)問題的,它可以決定哪些物體哪些部分會被渲染在前面付枫,而哪些部分又會被其它物體遮擋烹玉。它的基本思想是:根據(jù)深度緩沖中的值來判斷該片元距離攝像機的距離,當渲染一個片元時阐滩,需要把它的深度值和已經(jīng)存在于深度緩沖區(qū)中的值進行比較(如果開啟了深度測試)二打,如果它的值距離攝像機更遠,那么說明這個片元不應該被渲染到屏幕上(有物體擋住了它)掂榔;否則這個片元應該覆蓋掉此時顏色緩沖中的像素值继效,并把它的深度值更新到深度緩沖中(如果開啟了深度寫入)。

借用一下第二章的圖

使用深度緩沖装获,可以使我們不必關心不透明物體的渲染順序瑞信,例如A擋住了B,即便我們先渲染A再渲染B也不用擔心B會遮蓋掉A穴豫,因為在進行深度測試時會判斷出B距離攝像機更遠喧伞,也就不會寫入到顏色緩沖中。但要實現(xiàn)透明效果,事情就不那么簡單了潘鲫,這是因為當使用透明度混合時翁逞,我們關閉了深度寫入(ZWrite)。

那么我們?yōu)槭裁匆P閉深度寫入呢溉仑?如果不關閉深度寫入挖函,一個半透明表面背后的表面本來是可以透過它被我們看到的,但由于深度測試時判斷結果是該半透明表面距離攝像機更近浊竟,導致后面的表面將會被剔除怨喘,我們也就無法透過半透明表面看到后面的物體了。但是振定,我們由此就破壞了深度緩沖的工作機制必怜,這是一個非常糟糕的事情,盡管我們不得不這么做后频。關閉深度寫入導致渲染順序?qū)⒆兊梅浅V匾?/strong>

1.不同渲染順序例一
圖8.1 場景中有兩個物體梳庆,其中A(黃色)是半透明物體,B(紫色)是不透明物體

我們先渲染B再渲染A卑惜。那么由于不透明物體開啟了深度測試和深度寫入膏执,而此時深度緩沖中沒有任何有效數(shù)據(jù),因此B首先會寫入顏色緩沖和深度緩沖露久。隨后更米,我們渲染A,透明物體仍然會進行深度測試毫痕,因此我們會發(fā)現(xiàn)和B相比A距離攝像機更近征峦,因此我們會使用A的透明度來和顏色緩沖區(qū)中的B的顏色進行混合,得到正確的半透明效果消请。

我們先渲染A栏笆,再渲染B。渲染A時梯啤,深度緩沖區(qū)中沒有任何有效數(shù)據(jù)竖伯,因此A直接寫入顏色緩沖,但由于對半透明物體關閉了深度寫入因宇,因此A不會修改深度緩沖七婴。等到渲染B時,B會進行深度測試察滑,它會發(fā)現(xiàn)打厘,“咦,深度緩存中還沒有人來過贺辰,那我就放心的寫入顏色緩沖了”户盯,結果就是B會直接覆蓋A的顏色嵌施。從視覺上來看,B就出現(xiàn)在了A的前面莽鸭,而這是錯誤的吗伤。

從這個例子可以看出,當關閉了深度寫入后硫眨,渲染順序是多么重要足淆。由此,我們知道礁阁,我們應該在不透明物體渲染完之后再渲染半透明物體巧号。那么如果都是半透明物體,渲染順序還重要嗎姥闭?答案是肯定的丹鸿。還是假設場景里有兩個物體A和B,如下圖所示棚品,其中A和B都是半透明物體靠欢。

2.不同渲染順序例二
圖8.2 場景中有兩個物體,其中A和B都是半透明物體

第一種情況南片,我們先渲染B掺涛,再渲染A庭敦。那么B會正常寫入顏色緩沖疼进,然后A會和顏色緩沖中的B顏色進行混合,得到正確的混合結果秧廉。

第二種情況伞广,我們先渲染A,再渲染B疼电。那么A會先寫入顏色緩沖嚼锄,隨后B會和顏色緩沖中的A進行混合,這樣混合結果會完全反過來蔽豺,看起來好像B在A的前面区丑,得到的就是錯誤的半透明結構。

從這個例子可以看出修陡,半透明物體之間也是要符合一定的渲染順序的沧侥。

3.解決方案

基于上面兩點,渲染引擎一般都會先對物體進行排序魄鸦,再渲染宴杀,常用的方法是:
(1)先渲染所有不透明物體,并開啟它們的深度測試和深度寫入拾因。
(2)把半透明物體按它們距離攝像機的遠近進行排序旺罢,然后按照從后往前的順序渲染這些半透明物體旷余,并開啟它們的深度測試,但關閉深度寫入扁达。

那么正卧,問題都解決了嗎?不幸的是跪解,讓然沒有穗酥。在一些情況下,半透明物體還是會出現(xiàn)穿幫鏡頭惠遏。如果我們仔細想想的話砾跃,上面第二步中的渲染順序仍然是含糊不清的——按它們距離攝像機的遠近進行排序,那么它們距離攝像機的遠近是如何決定的呢节吮?讀者可能會馬上脫口而出抽高,“就是距離攝像的深度值嘛”。但是深度緩沖中的值其實是像素級別的透绩,即每個像素都有一個深度值翘骂,但是現(xiàn)在我們對單個物體級別進行排序,這意味著排序的結果是帚豪,要么物體A全部在B前面渲染碳竟,要么A全部在B后面渲染。但如果存在循環(huán)重疊的情況狸臣,那么使用這種方法就永遠無法得到正確的結果莹桅。圖8.3給出了3個物體循環(huán)重疊的情況。

圖8.3 循環(huán)重疊的半透明物體總是無法得到正確的半透明效果

在圖中烛亦,由于3個物體互相重疊诈泼,我們不可能得到一個正確的排序順序。這種時候煤禽,我們可以選擇把物體拆分成兩個部分铐达,然后再進行正確的排序。但即便我們通過了分割的方法解決了循環(huán)覆蓋的問題檬果,還會有其它情況來搗亂瓮孙,如下圖所給出的情況:


圖8.4 使用哪個深度對物體進行排序。紅色點分別標明了網(wǎng)格上距離攝像機最近的點选脊、最遠的點以及網(wǎng)格中點

這里的問題是如何排序杭抠?我們知道,一個物體的網(wǎng)格結構往往占據(jù)了空間中的某一塊區(qū)域知牌,也就是說祈争,這個網(wǎng)格上每一個點的深度值可能都是不一樣的,我們選擇哪個深度值來作為整個物體的深度值和其它物體進行排序呢角寸?是網(wǎng)格中點嗎菩混?還是最遠的點忿墅?還是最近的點?不幸的是沮峡,對于上圖的情況疚脐,選擇哪個深度值都會得到錯誤的結果,我們的排序結果總是A在B的前面邢疙,但實際上A有一部分被B遮擋了棍弄。這也意味著,一旦選定了一種判斷方式后疟游,在某些情況下半透明物體之間一定會出現(xiàn)錯誤的遮擋問題呼畸。這種問題的解決方法通常也是分割網(wǎng)格。

盡管結論是颁虐,總是會有一些情況打亂我們的陣腳蛮原,但由于上述方法足夠有效并且容易實現(xiàn),因此大多數(shù)游戲引擎都使用了這樣的方法另绩。為了減少錯誤排序的情況儒陨,我們可以盡可能讓模型是凸面體,并且考慮將復雜的模型拆分成可以獨立排序的多個子模型等笋籽。其實就算排序錯誤結果有時也不會非常糟糕蹦漠,如果我們不想分割網(wǎng)格,可以試著讓透明通道更加柔和车海,使穿插看起來并不是那么明顯笛园。我們也可以使用開啟了深度寫入的半透明效果來近似模擬物體的半透明(詳見8.5節(jié))。

二容劳、透明度測試和透明度混合

在Unity中喘沿,我們通常使用兩種方式來實現(xiàn)透明效果:第一種是使用透明度測試(Alpha Test)闸度,這種方法其實無法得到真正的半透明效果竭贩;另一種是透明度混合(Alpha Blending)。

1.透明度測試

它是一種“霸道極端”的機制莺禁,只要一個片元的透明度不滿足條件(通常是小于某個閾值)留量,那么它對應的片元就會被舍棄。被舍棄的片元不會再進行任何處理哟冬,也不會對顏色緩沖產(chǎn)生任何影響楼熄;否則就會按照普通的不透明物體的處理方式來處理它,即進行深度測試浩峡、深度寫入等可岂。也就是說,透明度測試是不需要關閉深度寫入的翰灾,它和其它不透明物體最大的不同是它會根據(jù)透明度來舍棄一些片元缕粹。雖然簡單稚茅,但是它產(chǎn)生的效果也很極端,要么完全透明平斩,即看不到亚享,要么完全不透明,就像不透明物體那樣绘面。

2.透明度混合

這種方法可以得到真正的半透明效果欺税。它會使用當前片元的透明度作為混合因子,與已經(jīng)存儲在顏色緩沖中的顏色進行混合揭璃,得到新的顏色晚凿。但是透明度混合需要關閉深度寫入,這使我們要非常小心物體的渲染順序瘦馍。需要注意的是晃虫,透明度混合只關閉了深度寫入,但沒有關閉深度測試扣墩。這意味著當使用透明度深度混合渲染一個片元時哲银,還是會比較它的深度值與當前深度緩沖區(qū)中的深度值,如果它的深度值距離攝像機更遠呻惕,那么就不會再進行混合操作荆责。這一點決定了,當一個不透明物體出現(xiàn)在一個透明物體前面亚脆,而我們先渲染了不透明物體做院,它仍然可以正常地遮住不透明物體。也即是說濒持,對于透明混合度來說键耕,深度緩沖是只讀的。

三柑营、UnityShader的渲染隊列

Unity為了解決渲染順序的問題提供了渲染隊列(render queue)這一解決方案屈雄。我們可以使用SubShader的Queue標簽來決定我們的模型將歸于哪個渲染隊列。Unity在內(nèi)部中使用一系列整數(shù)索引來表示每個渲染隊列官套,且索引號越小表示越早被渲染酒奶。在Unity5中,Unity提前定義了5個渲染隊列(與Unity5之前的版本相比多了一個AlphaTest渲染隊列)奶赔,當然在每個隊列中間我們可以使用其他隊列惋嚎。下表給出了這5個提前定義的隊列以及它們的描述。

image.png

因此站刑,如果我們想要通過透明度測試來實現(xiàn)透明效果另伍,代碼中應該包含類似下面的代碼:

SubShader{
   Tags{"Queue"="AlphaTest"}
   Pass{
      ...
   }
}

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

SubShader{
   Tags{"Queue"="Transparent"}
   Pass{
      ZWrite Off
      ......
   }
}

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

四中贝、透明度測試Alpha Test

我們來看一下如何在Unity中實現(xiàn)透明度測試的效果。在上面我們已經(jīng)知道了透明度測試的原理臼朗。

透明度測試:只要一個片元的透明度不滿足條件(通常是小于某個閾值)邻寿,那么它對應的片元就會被舍棄。被舍棄的片元將不會再進行任何處理视哑,也不會對顏色緩沖產(chǎn)生任何影響绣否;否則就會按照普通的不透明物體的處理方式來處理它。

通常挡毅,我們會在片元著色器中使用clip函數(shù)來進行透明度測試蒜撮。clip是Cg中的一個函數(shù),它的定義如下:

void clip(float4 x)跪呈;
void clip(float3 x)段磨;
void clip(float2 x);
void clip(float1 x)耗绿;
void clip(float x)苹支;

參數(shù):裁剪時使用的標量或矢量條件。
描述:如果給定參數(shù)的任何一個分量是負數(shù)误阻,就會舍棄當前像素的輸出顏色债蜜。它等同于下面的代碼:

void clip(float4 x)
{
   if(any(x<0))
      discard;
   }
}

在本節(jié)中,我們使用圖中的半透明紋理來實現(xiàn)透明度測試究反。該透明紋理在不同區(qū)域的透明度也不同寻定,我們通過它來查看透明度測試的效果。


圖8.5 一張透明紋理精耐,其中每個方格的透明度都不同

圖8.6 透明度測試效果

示例代碼參考Chapter8-AlphaTest.shader狼速,這個例子并沒有使用法線貼圖韩容,就是簡單的單紋理肢簿,大部分代碼可以參考UnityShader精要筆記六 基礎紋理,下面將不同的地方展示一下。

1._Cutoff
Shader "Unity Shaders Book/Chapter 8/Alpha Test" {
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
    }

_Cutoff參數(shù)用于決定我們調(diào)用clip進行透明度測試時使用的判斷條件沫浆。它的范圍是[0,1],這是因為紋理像素的透明度就是在此范圍內(nèi)滚秩。

2.Queue
SubShader{
   Tags{"Queue"="AlphaTest""IgnoreProjector"="True" "RenderType"="TransparentCutout"}
   Pass{
      Tags{"LightMode"="ForwardBase"}
   }
}

我們在前面已經(jīng)知道了渲染的重要性专执,并且知道在Unity中透明度使用的渲染隊列是名為AlphaTest的隊列,因此我們需要把Queue標簽設置為AlphaTest郁油。

而RenderType標簽可以讓Unity把這個shader歸入到提前定義的組(這里就是TransparentCutout組)中本股,以指明該shader是一個使用了透明度測試的shader攀痊。RenderType標簽通常被用于著色器替換功能。

我們還把IgnoreProjector設置為True拄显,這意味著這個Shader不會受到投影器(Projectors)的影響苟径。

通常,使用了透明度測試的Shader都應該在SubShader中設置這三個標簽躬审。

3.frag中的clip
fixed4 frag(v2f i):SV_Target{
fixed3 worldNormal = normalize(i.worldNormal)棘街;
fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor=tex2D(_MainTex,i.uv);
//Alpha text
clip(TeXColor.a-_Cutoff);
//Equal to
//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 fixed(ambient+diffuse博助,1.0)险污;
}

前面我們已經(jīng)提到過clip函數(shù)的定義,它會判斷它的參數(shù)富岳,即TeXColor.a-_Cutoff是否為負數(shù)蛔糯,如果是就會舍棄該片元的輸出。也就是說窖式,當texColor.a小于材質(zhì)參數(shù)_Cutoff時渤闷,該片元就會產(chǎn)生完全透明的效果。

4.VertexLit

最后脖镀,我們需要為這個UnityShader設置合適的Fallback:

Fallback"Transparent/Cutout/VertexLit"

和之前使用的Diffuse和Specular不同飒箭,這次我們使用內(nèi)置的Transparent/Cutout/VertexLit來作為回調(diào)Shader。這不僅能夠保證我們編寫的SubShader無法在當前顯卡上工作時可以有合適的替代Shader蜒灰,還可以保證使用透明度測試的物體可以正確的向其他物體投射陰影弦蹂,具體原理可以參見9.4.5節(jié)。

材質(zhì)面板中的Alpha cutoff參數(shù)用于調(diào)整透明度測試時使用的閾值强窖,當紋理像素的透明度小于該值時凸椿,對應的片元就會被舍棄。當我們逐漸調(diào)大該值時翅溺,立方體上的網(wǎng)格就會消失脑漫,如下圖所示:


圖8.7 隨著Alpha cutoff參數(shù)的增大,更多的像素由于不滿足透明度測試條件而被剔除

從上圖可以看出咙崎,透明度測試得到的透明效果很極端——要么完全透明优幸,要么完全不透明,它的效果往往像在一個不透明的物體上挖了一個洞褪猛。而且得到的透明效果在邊緣處往往參差不齊网杆,有鋸齒,這是因為在邊界處紋理的透明度的變化精度問題。為了得到更加柔滑的透明效果碳却,就可以使用透明度混合队秩。

五、透明度混合Alpha Blending

透明度混合的實現(xiàn)要比透明度測試復雜一些昼浦,這是因為我們在處理透明度測試時馍资,實際上跟對待普通的不透明的物體幾乎是一樣的,只是在片元著色器中增加了對透明度的判斷并裁剪片元的代碼关噪。而想要實現(xiàn)透明度混合就沒有這么簡單了鸟蟹。我們回顧之前提到的透明度混合的原理:

透明度混合:這種方法可以得到真正的半透明效果。它會使用當前片元的透明度作為混合因子色洞,與已經(jīng)存儲在顏色緩沖區(qū)中的顏色值進行混合戏锹,得到新的顏色。但是火诸,透明度混合需要關閉深度寫入锦针,設使得我們要非常小心物體的渲染順序。

為了進行混合置蜀,我們需要使用Unity提供的混合命令——Blend奈搜。Blend是Unity提供的設置混合模式的命令。想要實現(xiàn)半透明的效果就需要把當前自身的顏色和已經(jīng)存在于顏色緩沖中的顏色值進行混合盯荤,混合時使用的函數(shù)就是由該指令決定的馋吗。下表給出了Blend命令的語義:


image.png

在本節(jié)里,我們會使用第二種語義秋秤,即Blend SrcFactor DstFactor來進行混合宏粤。需要注意的是,這個命令在設置混合因子的同時也開啟了混合模式灼卢。這是因為只有開啟了混合之后绍哎,設置片元的透明通道才有意義,而Unity在我們使用Blend命令的時候就自動幫我們打開了鞋真。很多初學者總是抱怨為什么自己的模型沒有任何透明的效果崇堰,這往往是因為他們沒有在pass中使用Blend命令,一方面是沒有設置混合因子涩咖,但更重要的是海诲,根本沒有打開混合模式。我們會把源顏色的混合因子SrcFactor設為SrcAlpha檩互,而目標顏色的混合因子DstFactor設置為OneMinusSrcAlpha特幔。這意味著混合后新的顏色是:


image.png

注:OneMinusSrcAlpha英文單詞的意思就是1-SrcAlpha

通常透明度的混合使用的就是這樣的混合命令,在8.6節(jié)盾似,我們會看到更多混合語義用法敬辣。
我們使用和8.3節(jié)中同樣的透明紋理雪标,可以得到下面的效果:


圖8.8 透明度混合
Shader "Unity Shaders Book/Chapter 8/Alpha Blend" {
    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" }

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

我們使用一個新的屬性_AlphaScale來代替原先的_Cutoff屬性零院。_AlphaScale用于在透明紋理的基礎上控制整體的透明度溉跃。參考最后一句:return fixed4(ambient + diffuse, texColor.a * _AlphaScale);

別的代碼和之前類似,不說了告抄。

我們可以調(diào)節(jié)材質(zhì)面板上的AlphaScale參數(shù)撰茎,以控制整體透明度。下圖給出了不同AlphaScale參數(shù)下的半透明效果打洼。


圖8.9 隨著Alpha Scale參數(shù)的增大龄糊,模型變得越來越透明

我們在以前解釋了由于關閉深度寫入帶來的各種問題。當模型本身有復雜的遮擋關系或是包含了復雜的非凸格網(wǎng)格時候募疮,就會有各種各樣因為排序錯誤而產(chǎn)生的錯誤的透明效果炫惩。下圖給出了使用UnityShader渲染Knot模型時得到的效果。


圖8.10 當模型網(wǎng)格之間有互相交叉的結構時阿浓,往往會得到錯誤的半透明效果

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

六侈百、開啟深度寫入的半透明效果

在上一節(jié)瓮下,我們給出了一種由于關閉深度寫入而造成的錯誤排序情況。一種解決方法是使用兩個Pass來渲染模型:

  • 第一個Pass開啟深度寫入钝域,但不輸出顏色讽坏,它的目的僅僅是為了把該模型的深度值寫入深度緩沖中;
  • 第二個Pass進行正常的透明度混合网梢,由于上一個Pass已經(jīng)得到了逐像素的正確的深度信息震缭,該Pass就可以按照像素級別的深度排序結果進行透明渲染。

但這種方法的缺點在于战虏,多使用一個Pass會對性能造成一定的影響拣宰。在本節(jié)最后,我們可以得到類似下圖的效果:


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

可以看出烦感,使用這種方法巡社,我們?nèi)匀豢梢詫崿F(xiàn)模型與它后面的背景混合的效果,但模型內(nèi)部之間不會有任何真正的半透明效果手趣。

參考Chapter8-AlphaBlendZWrite.shader晌该,代碼和AlphaBlend幾乎一樣肥荔,我們只需在原來的基礎上再增加一個新的Pass即可。

    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        
        // Extra pass that renders to depth buffer only
        Pass {
            ZWrite On
            ColorMask 0
        }

這個新添加的Pass的目的僅僅是為了把模型的深度信息寫入深度緩沖中朝群,從而剔除模型中被自身遮擋的片元燕耿。因此,Pass的第一行開啟了深度寫入姜胖。在第二行誉帅,我們使用了一個新的渲染命令——ColorMask。在ShaderLab中右莱,ColorMask用于設置顏色通道的寫掩碼(write mask)蚜锨。它的語義如下:

ColorMask RGB|A|0|其它任何R、G慢蜓、B亚再、A的組合

當ColorMask設為0時,意味著該Pass不寫入任何顏色通道晨抡,即不會輸出任何顏色氛悬。這正是我們需要的——該Pass只需寫入深度緩存即可。

七凄诞、ShaderLab的混合命令

在前面圆雁,我們已經(jīng)看到如何利用Blend命令進行混合。實際上帆谍,混合還有很多其他的用處伪朽,不僅僅是用于透明度混合。在本節(jié)里汛蝙,我們將更加詳細的了解混合中的細節(jié)問題烈涮。

我們首先來看一下混合時如何實現(xiàn)的。當片元著色器產(chǎn)生一個顏色的時候窖剑,可以選擇與顏色緩沖中的顏色進行混合坚洽。這樣一來,混合就和兩個操作數(shù)有關:源顏色(source color)和目標顏色(destination color)西土。源顏色讶舰,我們用S表示,指的是由片元著色器產(chǎn)生的顏色值需了;目標顏色跳昼,我們用D表示,指的是從顏色緩沖中讀取到的顏色值肋乍。對它們進行混合后得到的輸出顏色鹅颊,我們用O表示,它會重新寫入到顏色緩沖中墓造。我們需要注意的是堪伍,當我們談及混合中的源顏色锚烦、目標顏色和輸出顏色時,它們都包含了RGBA四個通道的值帝雇,而并非僅僅是RGB通道涮俄。


圖2.16 混合操作的簡化流程圖

想要使用混合,我們必須首先開啟它摊求。在Unity中禽拔,我們使用Blend(Blend Off命令除外)命令時刘离,除了設置混合狀態(tài)外也開啟了混合室叉。但是,在其他圖形API中我們是需要手動開啟的硫惕。例如在OpenGl中茧痕,我們需要使用glEnable(GL_BLEND)來開啟混合。但在Unity中恼除,它已經(jīng)在背后為我們做了這些工作踪旷。

1.混合等式和參數(shù)

現(xiàn)在,我們已知兩個操作數(shù):源顏色S和目標顏色D豁辉,想要得出輸出顏色O就必須使用一個等式來計算令野。我們把這個等式稱為混合等式(blend equation)。進行混合時徽级,我們需要兩個混合等式:一個用于混合RGB通道气破,一個用于混合A通道。當設置混合狀態(tài)時餐抢,我們實際上設置的就是混合等式中的操作和因子现使。在默認情況下,混合等式使用的操作都是加操作(我們也可以使用其它操作)旷痕,我們只需再設置一下混合因子即可碳锈。由于需要混合兩個等式(分別用于混合RGB通道和A通道),每個等式有兩個因子(一個用于和源顏色相乘欺抗,一個用于和目標顏色相乘)售碳,因此一共需要四個因子。下表給出了ShaderLab中設置混合因子的命令绞呈。


image.png

可以發(fā)現(xiàn)贸人,第一個命令只提供了兩個因子,這意味著將使用同樣的混合因子來混合RGB通道和A通道报强,即此時SrcFactorA將等于SrcFactor灸姊,DstFactorA將等于DstFactor。下面就是使用這些因子進行加法混合時使用的混合公式:


image.png

那么秉溉,這些混合因子可以由哪些值呢力惯?下表給出了ShaderLab支持的幾種混合因子:
image.png

使用上面的指令進行設置時碗誉,RGB通道的混合因子和A通道的混合因子都是一樣的,有時我們希望可以使用不同的參數(shù)混合A通道父晶,這時就可以利用Blend SrcFactor DstFactor哮缺,SrcFactorA DstFactorA指令。例如甲喝,我們想要在混合后尝苇,輸出顏色的透明度值就是源顏色的透明度,就可以使用下面的指令:

Blend SrcAlpha OneMinusSrcAlpha埠胖, One Zero
2.混合操作

在上面涉及的混合等式中糠溜,當把源顏色和目標顏色與它們對應的混合因子相乘后,我們都是把它們的結果加起來作為輸出顏色的直撤。那么可不可以選擇不使用加法非竿,而使用減法呢?答案是肯定的谋竖,我們可以使用ShaderLab的BlendOp BlendOperation命令红柱,即混合操作命令。下表給出了ShaderLab中支持的混合操作蓖乘。


image.png

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

3.常見的混合類型

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

//正常(Normal)握牧,即透明度混合
Blend SrcAlpha OneMinusSrcAlpha
//柔和相加(soft Additive)
Blend OneMinusDstColor One
//正片疊底(Multiply),即相乘
Blend DstColor Zero
//兩倍相乘(2x Multiply)
Blend DstColor SrcColor
//變暗(Darken)
BlendOp Min
Blend One One
//變亮(Lighten)
BlendOp Max
Blend One One
//濾色(Screen)
Blend OneMinusDstColor One
//等同于
Blend One OneMinusSrcColor
//線性減淡(Linear Dodge)
Blend One One
圖8.12 不同混合狀態(tài)設置得到的效果

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

八、雙面渲染的透明效果

在現(xiàn)實生活中芦缰,如果一個物體是透明的企巢,意味著我們不僅可以透過它看到其它物體的樣子,也可以看到它的內(nèi)部結構让蕾。但在前面實現(xiàn)的透明效果中浪规,無論是透明度測試還是透明度混合或听,我們都無法觀察到正方體內(nèi)部及其背面的形狀,導致物體看起來好像只有半個一樣笋婿。

這是因為誉裆,默認情況下,渲染引擎剔出了物體背面(相對于攝像機的方向)的渲染圖元缸濒,而只渲染了物體的正面足丢。如果我們想要得到雙面渲染的效果,可以使用Cull指令來控制需要剔除哪個面的渲染圖元庇配。在Unity中斩跌,Cull指令的語法如下:

Cull Back| Front | Off

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

cull這個單詞就是剔除的意思

1.透明度測試的雙面渲染

非常簡單奄容,只需在Pass的渲染設置中使用Cull指令來關閉剔除即可冰更。

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

如上所示,這行代碼的作用是關閉剔除功能昂勒,是的該物體的所有渲染圖元都會被渲染蜀细。由此,我們可以得到下圖的效果:


圖8.13 雙面渲染的透明度測試的物體
2.透明度混合的雙面渲染

和透明度測試相比戈盈,想要讓透明度混合實現(xiàn)雙面渲染會更復雜一些奠衔,這是因為透明度混合需要關閉深度寫入,而這是“一切混亂的開端”塘娶。我們知道归斤,想要得到正確的透明效果,渲染順序是非常重要的——我們想要保證圖元是從后往前渲染的刁岸。對于透明度測試來說脏里,由于我們沒有關閉深度寫入,因此可以利用深度緩沖按逐像素的粒度進行深度排序虹曙,從而保證渲染的正確性迫横。然而一旦關閉了深度寫入鸦难,我們就需要小心的控制渲染順序來得到正確的深度關系,如果我們?nèi)匀徊蓸由厦娴姆椒ㄔ币苯雨P閉剔除功能合蔽,那么我們就無法保證同一個物體的正面和背面的渲染順序,就有可能得到錯誤的半透明效果介返。

為此拴事,我們選擇把雙面渲染的工作分成兩個Pass——第一個Pass只渲染背面,第二個Pass只渲染正面圣蝎,由于Unity會順序執(zhí)行SubShader中的各個Pass刃宵,因此我們可以保證背面總是在正面渲染之前渲染,從而可以保證正確的深度渲染關系徘公。

參考Chapter8-AlphaBlendBothSided.shader

Properties{
_Color("Main 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"

這里我交換了順序坦袍,先Cull Back再Cull Front,就是右邊那個效果等太,可以發(fā)現(xiàn)變暗了捂齐。


image.png

這個渲染順序是有影響的,雖然是一個透明效果缩抡,但仍然應該先渲染背面奠宜,再渲染正面。

九瞻想、RenderType

在上面的內(nèi)容中压真,出現(xiàn)了RenderType標簽,但馮樂樂介紹的比較簡短:

RenderType標簽可以讓Unity把這個shader歸入到提前定義的組(這里就是TransparentCutout組)中蘑险,以指明該shader是一個使用了透明度測試的shader滴肿。RenderType標簽通常被用于著色器替換功能。

參考UnityShader RenderType&Queue 理解

RenderType通常使用的值包括:

  • Opaque(不透明的): most of the shaders (Normal, Self Illuminated自發(fā)光, Reflective, terrain shaders).
  • Transparent: most semitransparent(半透明) shaders (Transparent, Particle, Font, terrain additive pass shaders).
  • TransparentCutout: masked transparency shaders (Transparent Cutout, two pass vegetation shaders).
  • Background: Skybox shaders.
  • Overlay: GUITexture, Halo, Flare shaders.
  • TreeOpaque: terrain engine tree bark.
  • TreeTransparentCutout: terrain engine tree leaves.
  • TreeBillboard: terrain engine billboarded trees.
  • Grass: terrain engine grass.
  • GrassBillboard: terrain engine billboarded grass.

這些RenderType的類型名稱實際上是一種約定漠其,用來區(qū)別這個Shader要渲染的對象嘴高,當然你也可以改成自定義的名稱,只不過需要自己區(qū)別場景中不同渲染對象使用的Shader的RenderType的類型名稱不同和屎,也就是說RenderType類型名稱使用自定義的名稱并不會對該Shader的使用和著色效果產(chǎn)生影響拴驮。

指定RenderType的名稱,主要是為了配合使用替代渲染的方法:

Camera.SetReplacementShader("shader","RenderType")  

在使用替代渲染方法時柴信,相機會使用指定的 shader 來代替場景中的其他 shader 對場景進行渲染套啤。比如現(xiàn)在有 shader1:

Shader "shader1"{
    Properties{...}
    SubShader{
    Tags{"RenderType"="Opaque"}
    Pass{...}   
    }
    SubShader{
    Tags{"RenderType"="Transparent"}
    Pass{...}   
    }
}

場景中一部分物體當前使用的是 shader2:

Shader "shader2"{
    Properties{...}
    SubShader{
    Tags{"RenderType"="Opaque"}
    Pass{...}   
    }
}

另一部分使用的是 shader3:

Shader "shader3"{
    Properties{...}
    SubShader{
    Tags{"RenderType"="Transparent"}
    Pass{...}   
    }
}

調(diào)用替代渲染的方法:

Camera.SetReplacementShader("shader1","")   

這種情況下,場景中所有的物體就都使用shader1進行渲染(當Shader中包含多個SubShader,在渲染時顯卡根據(jù)性能從上到下選擇第一個能支持的shader)
如果在調(diào)用時潜沦,第二個參數(shù)不為空字符串萄涯,即:

Camera.SetReplacementShader("shader1","RenderType")   

這種情況下,首先在場景中找到標簽中包含該字符串(這里為"RenderType")的shader唆鸡,再去看標簽中的該字符串的值與shader1中包含該字符串的值是否一致涝影,一致的話,替換渲染争占,否則不渲染燃逻;由于shader2中包含"RenderType"="Opaque",而且shader1中的第一個SubShader中包含"RenderType"="Opaque"臂痕,因此將shader1中的第一個SubShader替換場景中的所有shader2伯襟,同理,將shader1中的第二個SubShader替換場景中的所有的shader3握童。

如果shader1為:

Shader "shader1"{
    Properties{...}
    SubShader{
    Tags{"RenderType"="Opaque" "A"="On"}
    Pass{...}   
    }
    SubShader{
    Tags{"RenderType"="Transparent"  "A"="Off"}
    Pass{...}   
    }
}   

shader2為:

Shader "shader2"{
    Properties{...}
    SubShader{
    Tags{"RenderType"="Opaque" "A"="On"}
    Pass{...}   
    }
}  

shader3為:

Shader "shader3"{
    Properties{...}
    SubShader{
    Tags{"RenderType"="Transparent" "A"="On"}
    Pass{...}   
    }
}      

替代渲染的調(diào)用方式為:

Camera.SetReplacementShader("shader1","A")      

最后的結果是姆怪,shader1的第一個SubShader將會替換shader2和shader3

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市澡绩,隨后出現(xiàn)的幾起案子稽揭,更是在濱河造成了極大的恐慌,老刑警劉巖英古,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淀衣,死亡現(xiàn)場離奇詭異,居然都是意外死亡召调,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門蛮浑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唠叛,“玉大人,你說我怎么就攤上這事沮稚∫照樱” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵蕴掏,是天一觀的道長障般。 經(jīng)常有香客問我,道長盛杰,這世上最難降的妖魔是什么挽荡? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮即供,結果婚禮上定拟,老公的妹妹穿的比我還像新娘。我一直安慰自己逗嫡,他們只是感情好青自,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布株依。 她就那樣靜靜地躺著,像睡著了一般延窜。 火紅的嫁衣襯著肌膚如雪恋腕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天逆瑞,我揣著相機與錄音荠藤,去河邊找鬼。 笑死呆万,一個胖子當著我的面吹牛商源,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谋减,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼牡彻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了出爹?” 一聲冷哼從身側(cè)響起庄吼,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎严就,沒想到半個月后总寻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡梢为,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年渐行,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铸董。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡祟印,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粟害,到底是詐尸還是另有隱情蕴忆,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布悲幅,位于F島的核電站套鹅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏汰具。R本人自食惡果不足惜卓鹿,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望郁副。 院中可真熱鬧减牺,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至稚失,卻和暖如春栋艳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背句各。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工吸占, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人凿宾。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓矾屯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親初厚。 傳聞我的和親對象是個殘疾皇子件蚕,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 為什么透明效果的渲染順序很重要 書上已經(jīng)解釋的很清楚了,這邊說一下产禾,為什么對于循環(huán)重疊的半透明物體需要在意渲染順序...
    爛醉花間dlitf閱讀 799評論 0 1
  • ●透明是游戲中經(jīng)常使用的一種效果排作,在實時渲染透明效果,通常會在渲染模型時控制它的透明通道亚情。在unity中實現(xiàn)透明效...
    黒可樂閱讀 1,107評論 0 0
  • 透明效果 前言 Unity中通常使用兩種方法來實現(xiàn)透明 :透明度測試(AlphaTest)和透明度混合(Alpha...
    BacteriumFox閱讀 633評論 0 0
  • 0.本文示例代碼地址 GitHub[https://github.com/aslash/Unity_Shader_...
    太刀閱讀 1,416評論 0 1
  • 本文同時發(fā)布在我的個人博客上:https://dragon_boy.gitee.io 在Unity中妄痪,我們通常使用...
    Dragon_boy閱讀 6,823評論 0 2