Unity 渲染教程(十一):透明度

weixin_34007291發表於2017-10-23

剪下渲染

要建立一個透明的材質,我們必須知道每個片段的透明度。透明度資訊最常儲存在顏色的透明度通道中。在我們的例子中,我們使用的是主反射率紋理的透明度通道和顏色色調的透明度通道。

這裡有一個透明度貼圖的例子。 它是一個白色紋理,在透明度通道帶有平滑衰退噪聲。它是白色的,所以我們可以完全專注於透明度,而不會被反射率的模式所分心。

7240015-53146709d0d04073.png

黑色背景下的透明度貼圖。

將這個紋理分配給我們的材質只是使材質變成白色。透明度通道被忽略,除非你選擇使用透明度通道作為平滑源。但是當你用這種材質選擇一個四邊形的時候,你會看到一個大致是圓形的選擇輪廓。

7240015-78f765bd1e411fa4.jpg

在實四邊形上的選擇輪廓。

我如何能使用選擇輪廓?

Unity 5.5引入了一種新的選擇高亮方法。以前,你總是看到所選網格的線框。 現在,你還可以通過場景檢視的Gizmos選單選擇使用輪廓效果。

Unity使用替換著色器來建立輪廓,稍後我們將提到這個著色器。這個著色器取樣主紋理的透明度通道。輪廓會在那些透明度值變為零的地方進行繪製。

確定透明度值

要獲取透明度的值,我們可以通過新增一個GetAlpha函式到My Lighting匯入檔案。像反射率一樣,我們通過乘以色調和主紋理的透明度值來找到這個值。

floatGetAlpha (Interpolators i) {

return_Tint.a * tex2D(_MainTex, i.uv.xy).a;

}

然而,當我們不使用紋理的透明度通道來確定平滑度的時候,我們應該只使用紋理。如果我們沒有檢查這一點的話,那麼我們可能會曲解資料。

floatGetAlpha (Interpolators i) {

floatalpha = _Tint.a;

#if !defined(_SMOOTHNESS_ALBEDO)

alpha *= tex2D(_MainTex, i.uv.xy).a;

#endif

returnalpha;

}

剪孔

在不透明的材質情況下,每個通過深度測試的片段都會被渲染。所有片段是完全不透明的,並將結果寫入深度緩衝區中。透明度的加入使這一點變得複雜。

實現透明度的最簡單的方法是保持二進位制。一個片段要麼是完全不透明的,要麼是完全透明的。如果它是透明的話,那麼它根本不會被渲染。這使得可以在表面中剪出一個孔來。

要中止渲染片段,我們可以使用clip函式。 如果這個函式的引數為負,則片段將被丟棄。圖形處理器不會混合這個片段的顏色,它的結果不會寫入深度緩衝區中去。如果發生這種情況,我們不需要擔心材質的所有其他屬性。所以最有效的是儘早進行裁剪。在我們的例子中,這個事情放在MyFragmentProgram函式開始的地方。

我們將使用透明度值來確定我們是否應該裁剪。由於透明度值位於零和一之間,我們必須將其減去一些,使其為負。通過減去1/2,我們將使透明度值的範圍的下半部分變為負。這意味著至少有1/2透明度值對應的片段將被渲染,而所有其他的片段將被裁剪。

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {

floatalpha = GetAlpha(i);

clip(alpha - 0.5);

}

7240015-b4d9c50875ac3c23.jpg

剪下一切透明度值低於0.5的片段。

可變的截止閾值

從透明度值中減去1/2是我們隨便做的一個選擇。我們可以減去另一個數字。如果我們從透明度值中減去一個更高的值,那麼更大的範圍將被剪下。因此,這個值用作截止閾值。讓我們先把這個值變成一個變數。首先,在我們的著色器中新增Alpha Cutoff屬性。

Properties {

_AlphaCutoff ("Alpha Cutoff", Range(0, 1)) = 0.5

}

然後將相應的變數新增到My Lighting之中,並在剪裁之前從透明度值中減去這個變數的值,而不是減去½。

float_AlphaCutoff;

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {

floatalpha = GetAlpha(i);

clip(alpha - _AlphaCutoff);

}

最後,我們還必須向我們的自定義著色器的UI裡面新增截止閾值。標準著色器顯示了反射率線下面的截止閾值,所以我們也這樣做。 我們將顯示一個縮排的滑塊,就像我們對平滑度的滑塊所做的事情一樣。

voidDoMain () {

GUILayout.Label("Main Maps", EditorStyles.boldLabel);

MaterialProperty mainTex = FindProperty("_MainTex");

editor.TexturePropertySingleLine(

MakeLabel(mainTex,"Albedo (RGB)"), mainTex, FindProperty("_Tint")

);

DoAlphaCutoff();

}

voidDoAlphaCutoff () {

MaterialProperty slider = FindProperty("_AlphaCutoff");

EditorGUI.indentLevel += 2;

editor.ShaderProperty(slider, MakeLabel(slider));

EditorGUI.indentLevel -= 2;

}

7240015-40fa24a7e512e2f1.jpg

Alphacutoff屬性的滑塊。

現在,你可以根據需要來調整截止閾值。 你還可以對其進行動畫處理,例如建立新增材質或去除材質的效果。

Alpha cutoff屬性變化的效果請見:https://zippy.gfycat.com/SelfishCloudyAyeaye.mp4

7240015-550bc264b02a4fbd.png

著色器編譯器將剪輯轉換為丟棄指令。這裡是相關的OpenGL核心程式碼片段。

u_xlat10_0 = texture(_MainTex, vs_TEXCOORD0.xy);

u_xlat1.xyz = u_xlat10_0.xyz * _Tint.xyz;

u_xlat30 = _Tint.w * u_xlat10_0.w + (-_AlphaCutoff);

u_xlatb30 = u_xlat30<0.0;

if((int(u_xlatb30) *int(0xffffffffu))!=0){discard;}

這裡是相關的Direct3D11核心程式碼片段版本。

0: sample r0.xyzw, v1.xyxx, t0.xyzw, s1

1: mul r1.xyz, r0.xyzx, cb0[4].xyzx

2: mad r0.w, cb0[4].w, r0.w, -cb0[9].x

3: lt r0.w, r0.w, l(0.000000)

4: discard_nz r0.w

陰影怎麼辦?

我們將在下一個教程中處理剪下和半透明材質的陰影。在此之前,你可以使用對使用這些材質的物件關閉陰影。

渲染模式

剪裁不是免費的。它在桌面電腦的圖形處理器上不那麼糟糕,但使用分片渲染的移動圖形處理器不喜歡丟棄片段。所以如果我們真的渲染一個剪下材質的話,我們應該只包括clip語句。而完全不透明的材料不需要這一點。為此,讓我們依賴一個新的關鍵字_RENDERING_CUTOUT。

floatalpha = GetAlpha(i);

#if defined(_RENDERING_CUTOUT)

clip(alpha - _AlphaCutoff);

#endif

為這個關鍵字新增一個著色器特性,包括基本渲染通道和附加渲染通道。

#pragma shader_feature _RENDERING_CUTOUT

#pragma shader_feature _METALLIC_MAP

在我們的自定義UI指令碼中,新增一個RenderingMode列舉,提供不透明和剪下渲染兩種選擇。

enumRenderingMode {

Opaque, Cutout

}

新增單獨的方法來給渲染模式顯示一條線。我們將使用基於關鍵字的列舉彈出視窗,就像我們對平滑源所做的事情一樣。根據_RENDERING_CUTOUT關鍵字是否存在來設定模式。顯示彈出視窗,如果使用者更改渲染模式的話,會再次設定關鍵字。

voidDoRenderingMode () {

RenderingMode mode = RenderingMode.Opaque;

if(IsKeywordEnabled("_RENDERING_CUTOUT")) {

mode = RenderingMode.Cutout;

}

EditorGUI.BeginChangeCheck();

mode = (RenderingMode)EditorGUILayout.EnumPopup(

MakeLabel("Rendering Mode"), mode

);

if(EditorGUI.EndChangeCheck()) {

RecordAction("Rendering Mode");

SetKeyword("_RENDERING_CUTOUT", mode == RenderingMode.Cutout);

}

}

和標準著色器一樣,我們將在UI的頂部顯示渲染模式。

publicoverridevoidOnGUI (

MaterialEditor editor, MaterialProperty[] properties

) {

this.target = editor.targetasMaterial;

this.editor = editor;

this.properties = properties;

DoRenderingMode();

DoMain();

DoSecondary();

}

7240015-f324f057652d554e.jpg

渲染模式的選擇。

我們現在可以在完全不透明渲染和剪下渲染之間進行切換。但是,即使在不透明渲染模式下,透明度的截止閾值滑塊仍然可見。在理想情況下,透明度的截止閾值滑塊只應在需要的時候顯示。標準著色器也做到了這一點。要在DoRenderingMode和DoMain之間傳達此資訊,請新增一個布林欄位,指示是否應該顯示透明度的截止閾值滑塊。

boolshouldShowAlphaCutoff;

voidDoRenderingMode () {

RenderingMode mode = RenderingMode.Opaque;

shouldShowAlphaCutoff =false;

if(IsKeywordEnabled("_RENDERING_CUTOUT")) {

mode = RenderingMode.Cutout;

shouldShowAlphaCutoff =true;

}

}

voidDoMain () {

if(shouldShowAlphaCutoff) {

DoAlphaCutoff();

}

}

渲染佇列

雖然我們的渲染模式現在完全正常工作,但Unity的著色器還做了另外一件事。他們把剪下材質放在與不透明材質不同的渲染佇列之中。首先渲染不透明的物體,然後再是那些剪下的東西。這是因為剪下更昂貴。首先渲染不透明的物體意味著我們永遠不會渲染那些被不透明物體遮擋住的剪下物件。

在內部,每個物體都有一個與其佇列相對應的數字。預設佇列為2000。剪下佇列為2450。數字較低的佇列首先渲染。

你可以使用Queuetag來設定著色器渲染通道的佇列。你可以使用佇列名稱,還可以新增偏移量,以更精確地控制渲染物件什麼時候被渲染。讓我們舉個簡單的例子來說,"Queue"="Geometry+1"。

但是我們沒有固定佇列。它取決於渲染模式。 因此,不使用標記,我們將讓UI設定一個自定義的渲染佇列,這會覆蓋著色器的佇列。你可以在檢視器處於除錯模式的時候通過選擇材質來找出材質的自定義渲染佇列。你將能夠看到它的自定義渲染佇列欄位。其預設值為-1,表示沒有設定自定義值,因此應該使用著色器的Queue標記。

7240015-d3009bf98b1fe212.jpg

自定義渲染佇列。

我們真的不在乎佇列的確切數字是什麼。他們甚至可能在未來的Unity版本中有所改變。 幸運的是,UnityEngine.Rendering名稱空間包含了RenderQueueenum,RenderQueueenum會包含正確的值。所以,讓我們在我們的UI指令碼中使用這個名稱空間。

usingUnityEngine;

usingUnityEngine.Rendering;

usingUnityEditor;

publicclassMyLightingShaderGUI : ShaderGUI {

}

當在DoRenderingMode內部檢測到更改的時候,確定正確的渲染佇列。然後,遍歷所選的材質並更新它的渲染佇列。

if(EditorGUI.EndChangeCheck()) {

RecordAction("Rendering Mode");

SetKeyword("_RENDERING_CUTOUT", mode == RenderingMode.Cutout);

RenderQueue queue = mode == RenderingMode.Opaque ?

RenderQueue.Geometry : RenderQueue.AlphaTest;

foreach(Material mineditor.targets) {

m.renderQueue = (int)queue;

}

}

渲染型別標籤

另一個細節是RenderType標籤。這個著色器標籤本身不做任何事情。這是一個提示,告訴Unity它是什麼樣的著色器。這用於替換後的著色器,用於確定是否該渲染物件。

什麼是替換著色器?

它可以推翻哪些著色器用於渲染物件的規則。然後,你可以使用這些著色器手動渲染場景。這可以用於建立許多不同的效果。當需要深度緩衝區但深度緩衝區不可訪問的時候,Unity可能在某些情況下使用替換著色器來建立深度紋理。再舉另外一個例子,你可以使用著色器替換來檢視是否有任何物件在檢視中使用剪下著色器,著色器替換可以使得使用了剪下著色器的物件變成紅色或者其它東西。當然,這隻適用於具有合適的RenderType標籤的著色器。

要調整RenderType標籤,我們必須使用Material.SetOverrideTag方法。Material.SetOverrideTag方法的第一個引數是要覆蓋的標籤。第二個引數是包含標記值的字串。對於不透明渲染著色器,我們可以使用預設值,這是通過提供一個空字串來實現的。對於剪下渲染著色器,這個標記值是TransparentCutout。

RenderQueue queue = mode == RenderingMode.Opaque ?

RenderQueue.Geometry : RenderQueue.AlphaTest;

stringrenderType = mode == RenderingMode.Opaque ?

"":"TransparentCutout";

foreach(Material mineditor.targets) {

m.renderQueue = (int)queue;

m.SetOverrideTag("RenderType", renderType);

}

在將你的材質切換到剪下模式之後,它將在其字串標記對映列表中獲取一個條目,你可以通過除錯檢視器來檢視這個條目。

7240015-322aa16f427bf74f.jpg

渲染型別標籤。

工程檔案下載地址:unitypackage

半透明材質的渲染

當你想在一個物體上剪下一個洞的時候,剪下渲染就足夠了,但是當你需要半透明的時候,僅僅有剪下渲染就不行了。另外,剪下渲染是對每個片段進行處理,這意味著邊緣將被混疊。在表面的不透明部分和透明部分之間沒有平滑過渡。要解決這個問題,我們必須新增對另一種渲染模式的支援。這個模式將支援半透明。Unity的標準著色器命名此模式為淡入,所以我們將使用相同的名稱。將這個模式新增到我們的RenderingMode列舉。

enumRenderingMode {

Opaque, Cutout, Fade

}

我們將對這個模式使用_RENDERING_FADE關鍵字。

調整DoRenderingMode以使用此關鍵字。

voidDoRenderingMode () {

RenderingMode mode = RenderingMode.Opaque;

shouldShowAlphaCutoff =false;

if(IsKeywordEnabled("_RENDERING_CUTOUT")) {

mode = RenderingMode.Cutout;

shouldShowAlphaCutoff =true;

}

elseif(IsKeywordEnabled("_RENDERING_FADE")) {

mode = RenderingMode.Fade;

}

if(EditorGUI.EndChangeCheck()) {

RecordAction("Rendering Mode");

SetKeyword("_RENDERING_CUTOUT", mode == RenderingMode.Cutout);

SetKeyword("_RENDERING_FADE", mode == RenderingMode.Fade);

}

}

渲染設定

淡入模式有自己的渲染佇列和渲染型別。佇列號為3000,這是透明物件的預設值。渲染型別為Transparent。

不是讓DoRenderingMode變得更復雜,讓我們在UI類中定義一個結構體來儲存每個渲染型別的設定。

enumRenderingMode {

Opaque, Cutout, Fade

}

structRenderingSettings {

publicRenderQueue queue;

publicstringrenderType;

}

現在我們可以為所有的渲染型別建立一個靜態設定陣列。

structRenderingSettings {

publicRenderQueue queue;

publicstringrenderType;

publicstaticRenderingSettings[] modes = {

newRenderingSettings() {

queue = RenderQueue.Geometry,

renderType =""

},

newRenderingSettings() {

queue = RenderQueue.AlphaTest,

renderType ="TransparentCutout"

},

newRenderingSettings() {

queue = RenderQueue.Transparent,

renderType ="Transparent"

}

};

}

在DoRenderingMode的內部,使用模式來獲取正確的設定,然後配置所有的材質。

if(EditorGUI.EndChangeCheck()) {

RecordAction("Rendering Mode");

SetKeyword("_RENDERING_CUTOUT", mode == RenderingMode.Cutout);

SetKeyword("_RENDERING_FADE", mode == RenderingMode.Fade);

//                                           RenderQueue queue = mode == RenderingMode.Opaque ?

//                                                          RenderQueue.Geometry : RenderQueue.AlphaTest;

//                                           string renderType = mode == RenderingMode.Opaque ?

//                                                          "" : "TransparentCutout";

RenderingSettings settings = RenderingSettings.modes[(int)mode];

foreach(Material mineditor.targets) {

m.renderQueue = (int)settings.queue;

m.SetOverrideTag("RenderType", settings.renderType);

}

}

渲染透明的幾何體

你現在可以將你的材質切換到淡入渲染模式。因為我們的著色器不支援該模式,你的材質將恢復為不透明。但是,在使用幀偵錯程式檢視的時候你會注意到有所不同。

當使用不透明渲染模式或是剪下渲染模式的時候,使用我們的材質的物件是由Render.OpaqueGeometry方法進行渲染的。一直是這種情況。當使用淡入渲染模式的時候,情況會有所不同。使用我們的材質的物件是由Render.TransparentGeometry方法進行渲染的。這是因為我們使用了不同的渲染佇列。

7240015-73b3116dbd31de67.jpg
7240015-4b340c94747a249c.jpg

不透明渲染模式和半透明渲染模式的對比。

如果在檢視中同時具有不透明和透明的物件,則將呼叫Render.OpaqueGeometry和Render.TransparentGeometry方法。首先渲染的是不透明和剪下幾何體,然後渲染的是透明幾何體。所以被不透明物體遮擋的半透明物件從來不會被渲染。

片段的混合

要使淡入模式可以工作,我們首先要調整我們的渲染著色器的功能。我們現在支援兩個關鍵字、三種模式,對基本渲染通道和附加渲染通道都是如此。

#pragma shader_feature _ _RENDERING_CUTOUT _RENDERING_FADE

在淡入模式的情況下,我們必須將當前片段的顏色與已經渲染的任何片段的顏色進行混合。這種混合是在我們的片段程式之外由圖形處理器完成的。它需要片段的透明度值來做到這一點,所以我們必須輸出片段的透明度值,而不一個是常量值-這個常量值我們一直使用到現在。

color.rgb += GetEmission(i);

#if defined(_RENDERING_FADE)

color.a = alpha;

#endif

returncolor;

為了建立半透明的效果,我們必須使用與我們用於不透明材質和剪下材質的混合模式有所不同的混合模式。像是附加渲染通道,我們必須新增新的顏色到已存在的顏色中去。但是,我們不能簡單地將它們加在一起。混合的效果應該取決於我們的透明度值。

當透明度值是一的時候,那麼我們渲染的東西是完全不透明的。在這種情況下,我們應該像往常一樣對於基本渲染通道使用Blend One Zero,對於附加渲染通道使用Blend One。但是當透明度值是零的時候,我們渲染的東西是完全透明的。在這種情況下,我們不應該渲染任何東西。混合模式必須BlendZeroOne,對兩個渲染通道都是如此。如果透明度值是1/4的話,那麼我們需要像Blend0.250.75和Blend0.25Onee這樣的東西。

為了使這成為可能,我們可以使用SrcAlpha和OneMinusSrcAlpha這兩個混合關鍵字。

Pass {

Tags {

"LightMode"="ForwardBase"

}

Blend SrcAlpha OneMinusSrcAlpha

}

Pass {

Tags {

"LightMode"="ForwardAdd"

}

Blend SrcAlpha One

ZWrite Off

}

7240015-9eb16cc328ef27cf.jpg

一個半透明的四邊形。

雖然這樣可以工作,但是這些混合模式只適合淡入渲染模式。所以我們必須使這些混合模式成為可變化的變數。幸運的是,這是可能的。首先為源混合模式和目標混合模式新增兩個float屬性。

Properties {

_SrcBlend ("_SrcBlend", Float) = 1

_DstBlend ("_DstBlend", Float) = 0

}

由於這些屬性取決於渲染模式,我們不會在我們的UI中顯示這些屬性。如果我們不使用自定義UI,我們可以使用HideInInspector屬性來隱藏它們。我將新增這些屬性。

[HideInInspector] _SrcBlend ("_SrcBlend", Float) = 1

[HideInInspector] _DstBlend ("_DstBlend", Float) = 0

使用這些float屬性來代替那些必須要能變化的混合關鍵字。你必須把這些float屬性放在方括號內。這是舊的著色器語法來對圖形處理器做相應的配置。 我們不需要在頂點程式和片段程式中訪問這些屬性。

Pass {

Tags {

"LightMode"="ForwardBase"

}

Blend [_SrcBlend] [_DstBlend]

}

Pass {

Tags {

"LightMode"="ForwardAdd"

}

Blend [_SrcBlend] One

ZWrite Off

}

要控制這些引數,請將兩個BlendMode欄位新增到我們的RenderingSettings結構體中,並適當地對它們進行初始化。

structRenderingSettings {

publicRenderQueue queue;

publicstringrenderType;

publicBlendMode srcBlend, dstBlend;

publicstaticRenderingSettings[] modes = {

newRenderingSettings() {

queue = RenderQueue.Geometry,

renderType ="",

srcBlend = BlendMode.One,

dstBlend = BlendMode.Zero

},

newRenderingSettings() {

queue = RenderQueue.AlphaTest,

renderType ="TransparentCutout",

srcBlend = BlendMode.One,

dstBlend = BlendMode.Zero

},

newRenderingSettings() {

queue = RenderQueue.Transparent,

renderType ="Transparent",

srcBlend = BlendMode.SrcAlpha,

dstBlend = BlendMode.OneMinusSrcAlpha

}

};

}

在DoRenderingMode之中,我們必須直接設定材質的_SrcBlend和_DstBlend屬性。我們可以通過Material.SetInt方法來做到這一點。

foreach(Material mineditor.targets) {

m.renderQueue = (int)settings.queue;

m.SetOverrideTag("RenderType", settings.renderType);

m.SetInt("_SrcBlend", (int)settings.srcBlend);

m.SetInt("_DstBlend", (int)settings.dstBlend);

}

深度引起的麻煩

當在淡入模式下對單個物件進行工作的時候,一切似乎都能工作正常。但是,當你有多個半透明的物件靠近在一起的時候,你可能會得到奇怪的結果。舉個簡單的例子來說,部分重疊的兩個四邊形,將一個四邊形稍微放置在另一個四邊形之上。從一些視角看,其中一個四邊形似乎切掉了另一個四邊形的一部分。

7240015-5732f1c8ac65aae6.jpg

奇怪的結果。

Unity會首先繪製最靠近相機的不透明物體。這是渲染重疊幾何體的最有效的方式。不幸的是,這種方法不適用於半透明的幾何體,因為它必須與其後面的任何東西進行混合。因此,透明的幾何體必須以相反的方式繪製。首先繪製離相機最遠的物件,最後繪製最近的物件。這就是為什麼渲染透明的東西比渲染不透明的東西要更昂貴的原因。

要確定幾何體的繪製順序,Unity使用幾何體中心的位置。這對於相距很遠的小物體來說工作得很好。但是對於大型幾何體,或者靠近在一起的扁平幾何體來說,這種方法不是那麼好。在這些情況下,在更改檢視角度的時候,繪製順序可能會突然翻轉。這可能會導致重疊的半透明物件的外觀突然有改變。

沒有辦法解決這個限制,特別是在考慮相交的幾何體的時候。然而,這種瑕疵往往不明顯。但在我們的情況下,某些渲染命令會產生明顯錯誤的結果。這是因為我們的著色器仍然會對深度緩衝區進行寫入。深度緩衝區是二進位制的,不關心透明度。如果一個片段未被裁剪的話,那麼它的深度值最終會寫入深度緩衝區中。因為半透明物件的繪製順序不是完美的,這種方法是不可取的。不可見的幾何體的深度值可能會阻止其他可見的物體被渲染。因此,當使用淡入渲染模式的時候,我們必須禁用對深度緩衝區的寫入。

控制ZWrite

像混合模式一樣,我們可以使用屬性來控制ZWrite模式。我們需要使用屬性在基本渲染通道中顯式的設定這個模式。附加渲染通道不會寫入深度緩衝區,因此不需要更改。

[HideInInspector] _SrcBlend ("_SrcBlend", Float) = 1

[HideInInspector] _DstBlend ("_DstBlend", Float) = 0

[HideInInspector] _ZWrite ("_ZWrite", Float) = 1

Blend [_SrcBlend] [_DstBlend]

ZWrite [_ZWrite]

新增一個布林欄位RenderingSettings來指示是否應啟用寫入深度緩衝區。這隻適用於不透明渲染模式和剪下渲染模式。

structRenderingSettings {

publicRenderQueue queue;

publicstringrenderType;

publicBlendMode srcBlend, dstBlend;

publicboolzWrite;

publicstaticRenderingSettings[] modes = {

newRenderingSettings() {

queue = RenderQueue.Geometry,

renderType ="",

srcBlend = BlendMode.One,

dstBlend = BlendMode.Zero,

zWrite =true

},

newRenderingSettings() {

queue = RenderQueue.AlphaTest,

renderType ="TransparentCutout",

srcBlend = BlendMode.One,

dstBlend = BlendMode.Zero,

zWrite =true

},

newRenderingSettings() {

queue = RenderQueue.Transparent,

renderType ="Transparent",

srcBlend = BlendMode.SrcAlpha,

dstBlend = BlendMode.OneMinusSrcAlpha,

zWrite =false

}

};

}

在DoRenderingMode之中中包含_ZWrite屬性,同樣使用的是Material.SetIntmethod方法。

foreach(Material mineditor.targets) {

m.renderQueue = (int)settings.queue;

m.SetOverrideTag("RenderType", settings.renderType);

m.SetInt("_SrcBlend", (int)settings.srcBlend);

m.SetInt("_DstBlend", (int)settings.dstBlend);

m.SetInt("_ZWrite", settings.zWrite ? 1 : 0);

}

將我們的材質切換到另外一種渲染模式,然後切回到淡入模式。 雖然半透明物件的繪製順序仍然可以翻轉,我們不再會在我們的半透明幾何體中看到意外的孔出現。

7240015-bf508a0ea8495099.jpg

不再有消失的幾何體了。

工程檔案下載地址:unitypackage

淡入vs透明度

我們建立的半透明渲染模式基於其透明度值來淡入幾何。請注意,幾何體顏色的整體貢獻都是做了淡入處理的。它的漫反射和鏡面高光反射都做了淡入處理。這就是為什麼它被稱為淡入模式的原因。

7240015-d93a93f78c3347af.jpg

在白色高光情況下做了淡入處理的紅色。

這個模式適用於許多效果,但它不能正確地表示實心半透明的表面。舉個簡單的例子來說,玻璃實際上是完全透明的,但它也有明顯的亮點和反射。反射後的光被新增到任何通過的光裡面。為了支援這一點,Unity的標準著色器也有一個透明渲染模式。所以,讓我們新增這個模式。

enumRenderingMode {

Opaque, Cutout, Fade, Transparent

}

透明模式的設定與淡入模式的設定相同,除了我們必須能夠不考慮透明度的值來新增反射以外。因此,透明模式的源混合模式必須是一而不是取決於透明度的值。

publicstaticRenderingSettings[] modes = {

newRenderingSettings() {

queue = RenderQueue.Geometry,

renderType ="",

srcBlend = BlendMode.One,

dstBlend = BlendMode.Zero,

zWrite =true

},

newRenderingSettings() {

queue = RenderQueue.AlphaTest,

renderType ="TransparentCutout",

srcBlend = BlendMode.One,

dstBlend = BlendMode.Zero,

zWrite =true

},

newRenderingSettings() {

queue = RenderQueue.Transparent,

renderType ="Transparent",

srcBlend = BlendMode.SrcAlpha,

dstBlend = BlendMode.OneMinusSrcAlpha,

zWrite =false

},

newRenderingSettings() {

queue = RenderQueue.Transparent,

renderType ="Transparent",

srcBlend = BlendMode.One,

dstBlend = BlendMode.OneMinusSrcAlpha,

zWrite =false

}

};

我們必須使用另一個關鍵字,在這種情況下關鍵字為_RENDERING_TRANSPARENT。 調整DoRenderingMode,以便它可以檢測和設定此關鍵字。

voidDoRenderingMode () {

elseif(IsKeywordEnabled("_RENDERING_TRANSPARENT")) {

mode = RenderingMode.Transparent;

}

EditorGUI.BeginChangeCheck();

mode = (RenderingMode)EditorGUILayout.EnumPopup(

MakeLabel("Rendering Mode"), mode

);

if(EditorGUI.EndChangeCheck()) {

RecordAction("Rendering Mode");

SetKeyword("_RENDERING_CUTOUT", mode == RenderingMode.Cutout);

SetKeyword("_RENDERING_FADE", mode == RenderingMode.Fade);

SetKeyword(

"_RENDERING_TRANSPARENT", mode == RenderingMode.Transparent

);

}

}

將這個關鍵字新增到我們的兩個著色器功能指令中。

1

#pragma shader_feature _ _RENDERING_CUTOUT _RENDERING_FADE _RENDERING_TRANSPARENT

現在我們必須在淡入模式和透明模式中輸出透明度值。

#if defined(_RENDERING_FADE) || defined(_RENDERING_TRANSPARENT)

color.a = alpha;

#endif

returncolor;

將我們的材質切換到透明模式以後將再次使得整個四邊形可見。因為我們不再基於透明度來調製新顏色,所以四邊形將比使用不透明模式的時候更亮。片段後面會新增多少顏色仍然由透明度值進行控制。所以當透明度值是一的時候,它看起來就像是一個不透明的表面。

7240015-e1658ad0285d3b3f.jpg

新增而不是淡入的效果。

預乘透明度值

為了使透明度能夠再次工作,我們必須手動調整透明度值。我們應該只調整漫反射,而不要影響鏡面高光反射。我們可以通過將材質的最終反射率顏色乘以透明度值來做到這一點。

float4 MyFragmentProgram (Interpolators i) : SV_TARGET {

float3 specularTint;

floatoneMinusReflectivity;

float3 albedo = DiffuseAndSpecularFromMetallic(

GetAlbedo(i), GetMetallic(i), specularTint, oneMinusReflectivity

);

#if defined(_RENDERING_TRANSPARENT)

albedo *= alpha;

#endif

}

7240015-fc920f351f70e434.jpg

淡入的反射率。

因為我們是在圖形處理器進行混合之前乘以透明度值,所以這種技術通常稱為預乘透明度混合。許多影象處理應用程式內部以這種方式儲存顏色。紋理也可以包含預乘透明度值得顏色。這樣的話,他們或是不需要一個透明度通道,或是可以儲存一個與相關聯的RGB通道不同的透明度值。這將使得可以使用相同的資料(比如說是火和煙的組合)分別進行變亮和變暗處理。但是,在紋理中儲存顏色有一個缺點就是會造成精度的損失。

調整透明度值

如果一個物體既透明又反光,我們將看到這個物體背後的東西和它發生的反射。這在物件的兩側都是正確的。但同樣的光不能同時被反射和通過物體。這再次是能量守恆的問題。所以一個物體反射的光越多,那麼就越少的光能夠通過它,無論其固有的透明度是多少。

為了表示這一點,我們必須在圖形處理器執行混合之前,在我們改變反射率之後調整透明度值。如果表面沒有反射的話,它的透明度值不變。但當它反射所有的光的時候,它的透明度值會變成一。當我們確定片段程式中的反射率的時候,我們可以使用它來調整透明度值。 給定原始透明度值a和反射率r,修正後的透明度值變為1-(1-a)(1-r)。

請記住,我們在我們的著色器中使用一個負反射率,(1-r)可以用R來表示。 然後我們可以稍微簡化公式。1-(1-a)R = 1-(R-aR)= 1-R + aR。在調整反射率顏色後,使用這個表示式作為新的透明度值。

#if defined(_RENDERING_TRANSPARENT)

albedo *= alpha;

alpha = 1 - oneMinusReflectivity + alpha * oneMinusReflectivity;

#endif

得到的結果應該比以前的結果稍微更暗一些,用來模擬光反彈從我們的物件的背面反彈出去的效果。

7240015-a77a45a0d1fc0695.jpg

調整後的透明度值。

請記住,這是一個大致簡化的透明度,因為我們沒有把物件的實際體積考慮進來,只把可見表面考慮了進來。

單向鏡怎麼處理?

沒有真正的單向鏡。用於該目的的窗戶實際上是雙向鏡。這種窗戶非常反光。 當一側的房間非常明亮的時候,你不會注意到來自另一側的黑暗房間的光。但是,當兩個房間同樣亮的時候,你將能夠看到光在這個窗戶的兩個方向通過。

相關文章