文章目錄
基本概念
深度緩衝
深度緩衝Depth Buffer也稱爲Z緩衝 Z-Buffer。
深度緩衝存儲三維圖像渲染時每個生成的像素深度(即z座標)。
其組織形式爲一個保存每個屏幕像素深度的X-Y二維數組。
深度緩衝是可見性問題的一個解決方法。
由於深度緩衝會判斷各物體與攝像機的距離,(在深度寫入開啓的情況下)用戶無需再關心物體的渲染順序。
Z消隱:當同一場景中多個物體在同一個像素生成渲染結果時,圖形處理卡比較物體的深度(深度測試),保留距離觀察者較近者,將保留的物體點深度保存到深度緩衝區中的過程(深度寫入)。Z消隱可以讓圖形卡由深度緩衝區數據正確地生成通常的深度感知效果:較近的物體遮擋較遠的物體。
透明通道
透明通道(α Channel 或 Alpha Channel)是指一張圖片的透明和半透明度。
透明度是透明效果物體渲染時,每個片元的屬性之一(其他屬性有:顏色、深度等)。
通常,有兩種方式實現透明效果:透明度測試和透明度混合。
名稱 | 過程 | 效果 | 讀寫 |
---|---|---|---|
透明度測試 | 要麼不透明,要麼完全透明 | 開啓深度測試;開啓深度寫入 | |
透明度混合 | 透明度作爲透明因子與顏色屬性混合得到新的顏色,然後進行深度測試決定是否渲染 | 半透明效果 | 開啓深度測試;關閉深度寫入 |
渲染順序
多物體渲染過程
定義物體A深度大於物體B,即B在A前
-
先渲染不透明物體A,再渲染半透明物體B
A進行深度測試,發現當前無數據;
A寫入顏色緩衝+深度緩衝;
使用A的顏色屬性實現不透明效果;
B進行深度測試,發現數據並比較出B離視點更近;
B寫入顏色緩衝;
使用B的透明度和顏色緩衝中(A)的顏色混合實現半透明效果。
結果實現A不透明效果,B透明效果 -
先渲染半透明物體B,再渲染不透明物體A
B進行深度測試,發現當前無數據;
B寫入顏色緩衝;
使用B的透明度和顏色緩衝中的顏色(–)混合實現半透明效果。
A進行深度測試,發現當前無數據;
A寫入顏色緩衝+深度緩衝;
使用A的顏色屬性實現不透明效果。
結果 不透明物體A會覆蓋半透明物體B的一部分,導致看似“A在B前”,與定義不符 -
AB皆半透明,先渲染A,再渲染B
A進行深度測試,發現當前無數據;
A寫入顏色緩衝;
使用A透明度和顏色緩衝中的顏色(–)混合實現半透明效果;
B進行深度測試,發現當前無數據;
B寫入顏色緩衝;
使用B的透明度和顏色緩衝中的顏色(A)混合實現半透明效果。
結果實現A和B的不透明效果 -
AB皆半透明,先渲染B,再渲染A
B進行深度測試,發現當前無數據;
B寫入顏色緩衝;
使用B透明度和顏色緩衝中的顏色(–)混合實現半透明效果;
A進行深度測試,發現當前無數據;
A寫入顏色緩衝;
使用A的透明度和顏色緩衝中的顏色(B)混合實現半透明效果。
結果A的效果與環境和B有關,B僅與環境有關,看似“A在前B在後”
多物體渲染順序
- 渲染不透明物體;
- 由深至淺渲染半透明/透明物體
渲染排序問題
由於同一物體的深度排位不是唯一的,如同一物體不同部位深度位置可能不同,對於這種情況,可供解決的方法有
- 分割網格,將子模型排序得到唯一深度
- 柔滑透明通道,使穿插不明顯
Unity渲染隊列
渲染隊列 Render Queue是Unity解決渲染順序問題的方案。
已定義的渲染隊列標籤
可以通過在SubShader的Queue標籤中指定渲染隊列類型從而決定渲染順序。
Tag | Index | Description |
---|---|---|
Background | 1000 | 背景上的物體 |
Geometry | 2000 | 默認tag / 不透明物體 |
AlphaTest | 2450 | 使用透明度測試的物體 |
Transparent | 3000 | 使用透明度混合的物體 |
Overlay | 4000 | 疊加效果 / 最後渲染的物體 |
渲染隊列索引值
可以通過在SubShader的Queue標籤中指定渲染隊列索引值決定渲染順序:索引號越小越先渲染。
透明度測試效果
核心原理
透明度測試原理:clip函數判斷閾值
/*clip函數的僞代碼*/
clip(float x) //傳入參數:裁剪時使用的標量或矢量條件
{
if(x.any<0) //如果參數任一分量爲負數,則捨棄參數
{
discard;
}
}
實現代碼
Shader "Custom/Chapter8_AlphaTest"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Main Text", 2D) = "white" {}
_Cutoff("Alpha Cutoff",Range(0,1))=0.5 // 控制透明度測試的閾值,透明度範圍[0,1],故閾值範圍[0,1]
}
SubShader
{
Tags{ "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}//使用AlphaTest隊列渲染透明度測試;Shader不受投影器projector影響;指明爲透明度測試
Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
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=UnityObjectToClipPos(v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
o.worldPos=mul(unity_ObjectToWorld,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);
clip(texColor.a-_Cutoff);
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);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}
資源設置
線框不透明度爲100%,其餘顏色塊不透明度如圖中標示
結果
1._Cutoff∈[0,0.5] :所有色塊不透明
2. _Cutoff∈(0.5,0.6]:50%色塊完全透明
3. _Cutoff∈(0.6,0.7]:50%,60%色塊完全透明
4. _Cutoff∈(0.7,0.8]:50%,60%,70%色塊完全透明
5. _Cutoff∈(0.8,1]:所有色塊完全不透明
問題分析
邊緣有鋸齒
原因:邊界紋理透明度變化的精度問題
解決辦法:用透明度混合柔化透明效果
透明度混合效果
核心原理
混合命令Blend工作原理:由混合因子 Factor 控制顏色影響的權重值,將源顏色 Src(當前片元生成的顏色)和目標顏色 Dst(已存放於顏色緩存的顏色)按一定方式混合,把得到的結果顏色存入顏色緩存中。
- Blend命令
Semantics | Description |
---|---|
Blend Off | 關閉混合模式 |
Blend BlendMode1 BlendMode2 | 開啓混合模式並設置源顏色混合因子爲BlendMode1、目標顏色混合因子爲BlendMode2,默認爲相加操作 |
Blend SrcFactor DstFactor, A B | 開啓混合模式並設置SrcFactor=A,DstFactor=B,默認爲相加操作 |
BlendOp BlendOption | 開啓混合模式並設置混合操作爲BlendOption |
- BlendMode屬性值
Properties | Description |
---|---|
Zero | Blend factor is (0, 0, 0, 0) |
One | Blend factor is (1, 1, 1, 1) |
DstColor | Blend factor is (Rd, Gd, Bd, Ad) |
SrcColor | Blend factor is (Rs, Gs, Bs, As) |
OneMinusDstColor | Blend factor is (1 - Rd, 1 - Gd, 1 - Bd, 1 - Ad) |
SrcAlpha | Blend factor is (As, As, As, As) |
OneMinusSrcColor | Blend factor is (1 - Rs, 1 - Gs, 1 - Bs, 1 - As) |
DstAlpha | Blend factor is (Ad, Ad, Ad, Ad) |
OneMinusDstAlpha | Blend factor is (1 - Ad, 1 - Ad, 1 - Ad, 1 - Ad) |
SrcAlphaSaturate | Blend factor is (f, f, f, 1); where f = min(As, 1 - Ad) |
OneMinusSrcAlpha | Blend factor is (1 - As, 1 - As, 1 - As, 1 - As) |
- BlendOp屬性值
Properties | Description |
---|---|
Add | Add (s + d) |
Subtract | Subtract |
ReverseSubtract | Reverse subtract |
Min | Min |
Max | Max |
… | … |
實現代碼
Shader "Custom/Chapter8_AlphaBlend"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Main Text", 2D) = "white" {}
_AlphaScale("Alpha Scale",Range(0,1))=1 // 控制整體透明度,透明度範圍[0,1]
}
SubShader
{
Tags{ "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}//使用Transparent隊列渲染;Shader不受投影器projector影響;把Shader歸入提前定義的Transparent組
Pass
{
Tags{"LightMode"="ForwardBase"} //按向前渲染路徑的方式提供光照變量
ZWrite Off //透明度混合關閉深度寫入
Blend SrcAlpha OneMinusSrcAlpha //開啓並設置混合模式:片元着色器產生顏色的混合因子設爲SrcAlpha,顏色緩衝中的顏色的混合因子設爲1-SrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
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=UnityObjectToClipPos(v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
o.worldPos=mul(unity_ObjectToWorld,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);
//clip(texColor.a-_Cutoff); //透明度測試代碼
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);//透明度混合關鍵:設置α通道
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
結果展示
問題分析
設置不透明度Alpha Scale=1時,色塊呈現半透明,邊框不透明
原因:百分百反映.psd文件中的不透明度情況
雙面渲染的透明效果
渲染引擎默認情況下剔除物體背面形狀,導致無法觀察到物體內部/內部情況(即下圖綠色線條標誌的正方體內部結構)。
核心原理
使用Cull指令
Culling is an optimization that does not render polygons facing away from the viewer. All polygons have a front and a back side. Culling makes use of the fact that most objects are closed; if you have a cube, you will never see the sides facing away from you (there is always a side facing you in front of it) so we don’t need to draw the sides facing away. Hence the term: Backface culling. (Unity Manual)
爲優化性能,默認情況下Unity不會渲染多面體中觀察不到的面。要想得到雙面渲染的效果,需要使用Cull指令控制渲染剔除(或保留)的面。
命令 | 描述 | 應用 |
---|---|---|
Cull Back | 剔除“背面” | 默認 |
Cull Front | 剔除朝向攝像機的面 | 與Back相反 |
Cull Off | 保留所有面都要渲染 | 雙面渲染,但消耗大 |
( 透明度測試下的Cull Front效果
)
透明度測試的雙面渲染
直接在光照模式設置標籤後加入關閉剔除的Cull Off命令即可。
代碼
Shader "Custom/Chapter8_AlphaTest"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Main Text", 2D) = "white" {}
_Cutoff("Alpha Cutoff",Range(0,1))=0.5
}
SubShader
{
Tags{ "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
Pass
{
Tags{"LightMode"="ForwardBase"}
Cull Off // 剔除關閉,雙面渲染,內部結構可見
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
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=UnityObjectToClipPos(v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
o.worldPos=mul(unity_ObjectToWorld,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);
clip(texColor.a-_Cutoff);
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);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}
結果
在做框架效果時,透明度測試+剔除關閉可被考慮的樣子【思忖】
透明度混合的雙面渲染
由於透明度混合關閉深度寫入,因此若Cull Off進行雙面渲染會出現順序混亂。如下圖雖然沒報錯但是視覺上前後色塊和線框的表現已經出現問題。
因此一個直接的辦法就是將物體分部分渲染。而根據渲染順序,先渲染遠的“背面”,再渲染近的“前面”。而根據Shader中Pass語句塊按代碼中出現的順序執行,故將第一個Pass語句塊中剔除模式置Front(剔除前面,渲染背面);第二個Pass語句塊中剔除模式置Back(剔除背面,渲染前面),其餘維持不變即可。
代碼
Shader "Custom/Chapter8_AlphaBlendBothSide"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Main Text", 2D) = "white" {}
_AlphaScale("Alpha Scale",Range(0,1))=1
}
SubShader
{
Tags{ "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
//第一個Pass:先渲染“背面”
Pass
{
Tags{"LightMode"="ForwardBase"}
Cull Front //剔除“正面”,渲染“背面”
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
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=UnityObjectToClipPos(v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
o.worldPos=mul(unity_ObjectToWorld,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);
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);
}
ENDCG
}
//第二個Pass:後渲染“正面”
Pass
{
Tags{"LightMode"="ForwardBase"}
Cull Back //剔除“背面”,渲染“正面”
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
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=UnityObjectToClipPos(v.vertex);
o.worldNormal=UnityObjectToWorldNormal(v.normal);
o.worldPos=mul(unity_ObjectToWorld,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);
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);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
結果
拓展
- 同一場景下多物件,不同透明類型(透明測試/混合混雜,深度寫入開關混雜,剔除面混雜……)會出現混亂嗎?可能不會?
- 同一Shader中多Pass語塊,不同透明類型(透明測試/混合混雜,深度寫入開關混雜,剔除面混雜……)會出現混亂嗎?待考證……
- 以及現在可以試試實現模擬魚缸效果了嗎?
待續……