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)把半透明物體按攝像機遠近排序,按照從后往前的順序渲染這些半透明物體,開啟他們的深度測試,但關閉深度寫入。
光有順序的保障也是不夠全面的。有時候會有相互重復穿插、覆蓋的情況。
遇到上面的這種情況,一般是選擇分割網(wǎng)格,我們盡可能讓模型是凸面體,考慮將復雜的模型拆分成可以獨立排序的多個子模型。如果不想分割網(wǎng)絡,可以試著讓透明通道更加柔和,使得穿插不是那么明顯。也可以開始深度寫入的半透明效果來模擬物體半透明。
Unity Shader的渲染順序
Unity對順序的問題提供了 渲染隊列( render queue )的解決方案,使用SubShader的Queue來決定渲染隊列。Unity 在內(nèi)部使用一系列整數(shù)索引來表示每個渲染隊列,且索引號越小表示越早被渲染。
//類似這樣的聲明,注意不是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)
得到的效果會如下
直接上代碼
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 .
透明度混合
原理:這種方法可以得到真正的半透明效果。它會使用當前片元的透明度作為混合因子,與已經(jīng)存儲在顏色緩沖中的顏色值進行混合,得到新的顏色。但是,透明度混合需要關閉深度寫入,這使得我們要非常小心物體的渲染順序。
為了進行混合,我們需要使用Unity 提供的混合命令——Blend。 Blend 是Unity 提供的設置混合模式的命令。想要實現(xiàn)半透明的效果就需要把當前自身的顏色和已經(jīng)存在于顏色緩沖中的顏色值進行混合,混合時使用的函數(shù)就是由該指令決定的。
這里 使用第二種語義 Blend SrcFactor DstFactor 來進行混合。 這個命令在設置混合因子的同時打開了混合模型。只有開啟混合,設置片元的透明通道才有意義。
我們把源顏色的混合因子SrcFactor 設置為SRCAlpha, 而目標顏色的混合因子 DstFactor 設為OneMinusSrcAlpha,這就是說混合后的顏色是
這是常用的混合命令。
這次用上面的素材 所以改動部分代碼
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"
}
}
但是當模型本身有復雜的遮擋關系或者包含了復雜的非凸網(wǎng)格的時候,就會有各種排序錯誤產(chǎn)生的錯誤的透明效果。
這都是由于我們關閉了深度寫入造成的,因為這樣我們就無法對模型進行像素級別的深度排序。在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)部的半透明。如圖
上代碼 實現(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 個因子。
其實這些混合因子呢其實也已經(jīng)定義好,直接調(diào)用機可以。我原來的帖子詳細寫過而且各種表現(xiàn)也有展示
調(diào)用時候的命令
Blend SrcAlpha OneMinusSrcAlpha, One Zero
混合操作
所謂真正的混合不是說和因子相乘的那一刻,是結果相加的操作混合。這樣可以是加法也可以是減法。減法就是 命令** BlendOp BlendOperation**
混合操作命令通常是與混合因子命令一起工作的。但需要注意的是, 當使用 Min 或Max 混合操作時, 混合因子實際上是不起任何作用的,它們僅會判斷原始的源顏色和目的顏色之間的比較結果。
Blend SrcColor OneMinusSrcColor
BlendOp Sub //等等操作 ,寫法就是這樣
常見的混合類型
通過混合操作和混合因子命令的組合, 我們可以得到一些類似Photoshop 混合模式中的混合效果:
需要注意的是,雖然上面使用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
透明度混合的雙面渲染
這個比較麻煩,因為關閉了深度寫入,所以先后順序其實不知道。必須控制渲染順序。
這個的解決辦法就是寫兩個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"
}