第七章 透明效果(2)

@學習

1. 透明度測試

咱們來看一下如何在Unity中實現透明度測試的效果。在上面咱們已經知道了透明度測試的原理。
透明度測試:只要一個片元的透明度不知足條件(一般是小於某個閾值),那麼它對應的片元就會被捨棄。被捨棄的片元將不會再進行任何處理,也不會對顏色緩衝產生任何影響;不然就會按照普通的不透明物體的處理方式來處理它。
一般,咱們會在片元着色器中使用clip函數來進行透明度測試。clip是Cg中的一個函數,它的定義以下:
函數:void clip(float4 x);void clip(float3 x);void clip(float2 x);void clip(float1 x);void clip(float x);
參數:裁剪時使用的標量或矢量條件。
描述:若是給定參數的任何一個份量是負數,就會捨棄當前像素的輸出顏色。它等同於下面的代碼:測試

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

在本節中,咱們使用圖中的半透明紋理來實現透明度測試。該透明紋理在不一樣區域的透明度也不一樣,咱們經過它來查看透明度測試的效果。
在這裏插入圖片描述
在學習完本節後,咱們能夠獲得相似下圖的效果。
在這裏插入圖片描述
步驟:
(1)爲了在材質面板中控制透明度測試時使用的閾值,咱們在Properties語義塊中聲明一個範圍在[0,1]之間的屬性_Cutoff:spa

Properties{
_Color("Main Tint",Color)=(1,1,1,1)
_MainTex("Main Tex",2D)="white"{}
_Cutoff("Alpha Cutoff",Range(0,1))=0.5
}

_Cutoff參數用於決定咱們調用clip進行透明度測試時使用的判斷條件。它的範圍是[0,1],這是由於紋理像素的透明度就是在此範圍內。
(2)而後,咱們在SubShader語義塊中定義了一個Pass語義塊:3d

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

咱們在前面已經知道了渲染的重要性,而且知道在Unity中透明度使用的渲染隊列是名爲AlphaTest的隊列,所以咱們須要把Queue標籤設置爲AlphaTest。而RenderType標籤可讓Unity把這個shader納入到提早定義的組(這裏就是TransparentCutout組)中,以指明該shader是一個使用了透明度測試的shader。RenderType標籤一般被用於着色器替換功能。咱們還把IgnoreProjector設置爲True,這意味着這個Shader不會受到投影器(Projectors)的影響。一般,使用了透明度測試的Shader都應該在SubShader中設置這三個標籤。最後,LightMode的標籤是Pass標籤中的一種,它用於定義該Pass在Unity的光照流水線中的角色。
(3)爲了和Properties語義塊中聲明的屬性創建聯繫,咱們須要定義和各個屬性類型相匹配的變量:code

fixed4 _Color;
sampler2D _Maintex;
float4 _MainTex_ST;
fixed _Cutoff;

因爲_Cutoff的範圍在[0,1],所以咱們可使用fixed精度來存儲它。
(4)而後咱們定義頂點着色器輸入和輸出結構體,接着定義頂點着色器:orm

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){
v2f o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
o.worldPos=mul(_Object2World,v.vertex).xyz;
o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}

上面的代碼咱們已經見過不少次了,咱們在頂點着色器計算出世界空間的法線方向和頂點位置以及變換後的紋理座標,再把他們傳遞給片元着色器。
(5)最重要的透明度測試的代碼寫在片元着色器中:blog

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

(6)最後,咱們須要爲這個UnityShader設置合適的Fallback:排序

Fallback"Transparent/Cutout/VertexLit"

和以前使用的Diffuse和Specular不一樣,此次咱們使用內置的Transparent/Cutout/VertexLit來做爲回調Shader。這不只可以保證咱們編寫的SubShader沒法在當前顯卡上工做時能夠有合適的替代Shader,還能夠保證使用透明度測試的物體能夠正確的向其餘物體投射陰影,具體原理咱們後面講到。
材質面板中的Alpha cutoff參數用於調整透明度測試時使用的閾值,當紋理像素的透明度小於該值時,對應的片元就會被捨棄。當咱們逐漸調大該值時,立方體上的網格就會消失,以下圖所示:隊列

在這裏插入圖片描述
從上圖能夠看出,透明度測試獲得的透明效果很極端——要麼徹底透明,要麼徹底不透明,它的效果每每像在一個不透明的物體上挖了一個洞。並且獲得的透明效果在邊緣處每每良莠不齊,有鋸齒,這是由於在邊界處紋理的透明度的變化精度問題。爲了獲得更加柔滑的透明效果,就可使用透明度混合。

2.透明度混合

透明度混合的實現要比透明度測試複雜一些,這是由於咱們在處理透明度測試時,實際上跟對待普通的不透明的物體幾乎是同樣的,只是在片元着色器中增長了對透明度的判斷並裁剪片元的代碼。而想要實現透明度混合就沒有這麼簡單了。咱們回顧以前提到的透明度混合的原理:
透明度混合:這種方法能夠獲得真正的半透明效果。它會使用當前片元的透明度做爲混合因子,與已經存儲在顏色緩衝區中的顏色值進行混合,獲得新的顏色。可是,透明度混合須要關閉深度寫入,設使得咱們要很是當心物體的渲染順序。
爲了進行混合,咱們須要使用Unity提供的混合命令——Blend。Blend是Unity提供的設置混合模式的命令。想要實現半透明的效果就須要把當前自身的顏色和已經存在於顏色緩衝中的顏色值進行混合,混合時使用的函數就是由該指令決定的。下表給出了Blend命令的語義:

在本節裏,咱們會使用第二種語義,即Blend SrcFactor DstFactor來進行混合。須要注意的是,這個命令在設置混合因子的同時也開啓了混合模式。這是由於只有開啓了混合以後,設置片元的透明通道纔有意義,而Unity在咱們使用Blend命令的時候就自動幫咱們打開了。不少初學者老是抱怨爲何本身的模型沒有任何透明的效果,這每每是由於他們沒有在pass中使用Blend命令,一方面是沒有設置混合因子,但更重要的是,根本沒有打開混合模式。咱們會把源顏色的混合因子SrcFactor設爲SrcAlpha,而目標顏色的混合因子DstFactor設置爲OneMinusSrcAlpha。這意味着混合後新的顏色是:

在這裏插入圖片描述

一般透明度的混合使用的就是這樣的混合命令,在後面,咱們會看到更多混合語義用法。
使用和上小結一樣的紋理,咱們能夠獲得下面的效果:
在這裏插入圖片描述
步驟:
(1)修改Properties語義塊:

Properties{
_Color("Main Tint",Color)={1,1,1,1}
_MainTex("Main Tex", 2D)="white"{}
_AlphaScale("Alpha Scale",Range(0,1))=1
}

咱們使用一個新的屬性_AlphaScale來代替原先的_Cutoff屬性。_AlphaScale用於在透明紋理的基礎上控制總體的透明度。相應的,咱們也須要在Pass中修改和屬性對應的變量:

fixed4  _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _AlphaScale;

(2)修改SubShader使用的標籤:

SubShader{
Tags{"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
}

(3)與透明度測試不一樣的是,咱們還須要在Pass中爲透明度混合進行合適的混合狀態設置:

Pass{
Tags{"LightMode"="ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
}

Pass的標籤仍和以前的同樣,這是爲了讓Unity可以按向前渲染路徑的方式爲咱們正確提供各個光照變量。除此以外,咱們還該把該Pass的深度寫入(ZWrite)設置爲關閉狀態(Off),咱們在以前已經講過爲何要這樣作了。這是很是重要的。而後咱們開啓並設置了該Pass的混合模式。如在本節開頭所講的,咱們將源顏色(該片元着色器產生的顏色)的混合因子設爲SrcAlpha,把目標顏色(已經存在於顏色緩衝區中的顏色)的混合因子設爲OneMinusSrcAlpha,以獲得合適的半透明效果。
(4)修改片元着色器:

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 fixed(ambient+diffuse,texColor.a*_AlphaScale);
}

上述代碼和之前的幾乎同樣,只是移除了透明度測試的代碼,並設置了該片元着色器返回值中的透明通道,它是紋理像素的透明通道和材質參數_AlphaScale的乘積。正如本節一開始所說,只有使用Blend命令打開混合後,咱們在這裏設置的透明通道纔有意義,不然這些透明度並不會對片元的透明效果有任何影響。
(5)最後,修改UnityShader的Fallback:

Fallback"Transparent/VertexLit"

咱們能夠調節材質面板上的AlphaScale參數,以控制總體透明度。下圖給出了不一樣AlphaScale參數下的半透明效果。
在這裏插入圖片描述
咱們在之前解釋了因爲關閉深度寫入帶來的各類問題。當模型自己有複雜的遮擋關係或是包含了複雜的非凸格網格時候,就會有各類各樣由於排序錯誤而產生的錯誤的透明效果。下圖給出了使用UnityShader渲染Knot模型時獲得的效果。
在這裏插入圖片描述 這都是因爲咱們關閉了深度寫入形成的,由於這樣咱們就沒法對模型進行像素級別的深度排序。在上面咱們提出了一種解決方法就是分割網格,從而能夠獲得一個「質量優等」的網格。可是不少狀況下這每每時不切實際的。這是咱們能夠想辦法從新利用深度寫入,讓模型能夠像半透明物體同樣進行淡入淡出。這就是咱們的下一節內容。

相關文章
相關標籤/搜索