深度緩衝(Depth Buffer)html
透明度混合時應關閉深度寫入(ZWrite Off)緩存
若是不關閉深度寫入,一個半透明表面背後的表面本就是透過它被咱們看到的,但因爲深度測試時判斷結果是該半透明表面)距離攝像機更近,致使後面的表面會被剔除掉,也就沒法經過半透明面觀察到後面的物體。app
另外注意關閉深度寫入後要考慮物體的渲染順序。以下圖,一個半透明物體A和一個不透明物體B,B在A後方,若是先渲染A再渲染B會出現A被B遮擋的狀況,這是錯誤的。所以渲染順序在關閉深度寫入的狀況下極爲重要。函數
爲了保證渲染順序正確,渲染引擎通常會對物體進行排序,再渲染,經常使用的方法是測試
但這種方法沒法解決物體重疊的狀況,所以須要額外的解決方案,好比分割網格等。可是也能夠試着讓透明通道更柔和,是穿插重疊看起來不那麼明顯。spa
Unity的渲染順序3d
Unity經過一組Queue標籤來決定模型歸於哪一個渲染隊列,隊列由整數索引表示,索引號越小越先被渲染。orm
渲染隊列 | 渲染隊列描述 | 渲染隊列值 |
Background | 這個隊列被最早渲染。它被用於skyboxes等。 | 1000 |
Geometry | 這是默認的渲染隊列。它被用於絕大多數對象。不透明幾何體使用該隊列。 | 2000 |
AlphaTest | 通道檢查的幾何體使用該隊列。它和Geometry隊列不一樣,對於在全部立體物體繪製後渲染的通道檢查的對象,它更有效。 | 2450 |
Transparent | 該渲染隊列在Geometry和AlphaTest隊列後被渲染。任何通道混合的(也就是說,那些不寫入深度緩存的Shaders)對象使用該隊列,例如玻璃和粒子效果。 | 3000 |
Overlay | 該渲染隊列是爲疊加效果服務的。任何最後被渲染的對象使用該隊列,例如鏡頭光暈。 | 4000 |
透明度測試htm
只要有一個片元的透明度不知足條件(一般是小於某個閾值),那麼它對應的片元便會被捨棄,不作任何處理。對象
在Unity中是用以下函數來進行透明度測試:
clip(texColor.a - _Cutoff);
texColor.a爲紋理的alpha值,_Cutoff爲閾值,該函數等價於
if(texColor.a - _Cutoff < 0) discard;
完整的代碼
Shader "Unity Shader Book/Chapter 8/AlphaTest" { Properties { _Color("Color Tint",Color) = (1,1,1,1) _MainTex ("Texture", 2D) = "white" {} _Cutoff("Alpha Cutoff",Range(0,1)) = 0.5 } SubShader { Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" "IgnoreProjector"="True" "LightMode"="ForwardBase"} LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" struct appdata { float4 vertex : POSITION; float3 normal:NORMAL; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float3 worldNormal:TEXCOORD0; float3 worldPos:TEXCOORD1; float2 uv : TEXCOORD2; }; fixed4 _Color; fixed _Cutoff; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; return o; } fixed4 frag (v2f i) : SV_Target { fixed3 worldNormal = normalize(i.worldNormal); fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos); fixed4 texColor = tex2D(_MainTex,i.uv); //Alpha Test clip(texColor.a - _Cutoff); //equal to //if(texColor.a - _Cutoff < 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(diffuse + ambient,1.0); } ENDCG } } }
效果以下,能夠看到只是單純的顏色剔除,並不算正常的透明效果。
爲了獲得正確的透明效果,能夠使用透明度混合。
透明度混合
透明度混合即將透明物體的源顏色與其表面後方的目標顏色混合,其基本公式爲:
DstColor-new(混合後的顏色) = SrcAlpha * SrcColor + (1- SrcAlpha) * DstColor。(SrcAlpha爲混合因子)
該公式能夠在Unity的ShaderLab語義中表示爲
Blend SrcAlpha OneMinusSrcAlpha
更多語義能夠參考官方文檔
完整代碼實現
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' Shader "Unity/Chapter 8/AlphaBlend" { Properties { _Color("Color Tint",Color) = (1,1,1,1) _MainTex ("Texture", 2D) = "white" {} _AlphaScale("Alpha Scale",Range(0,1)) = 0.5 } SubShader { Tags { "RenderType"="Transparent" "IgnoreProjector"="True" "Queue"="Transparent" } LOD 100 Blend SrcAlpha OneMinusSrcAlpha ZWrite On Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" struct appdata { float3 normal:NORMAL; float4 vertex : POSITION; float4 texcoord : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD2; float3 worldNormal : TEXCOORD0; float3 worldPos : TEXCOORD1; float4 pos : SV_POSITION; }; fixed4 _Color; fixed _AlphaScale; sampler2D _MainTex; float4 _MainTex_ST; v2f vert (appdata v) { v2f o; o.worldNormal = UnityObjectToWorldNormal(v.normal); o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz; 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" }
該代碼獲得的效果以下
嗯,這纔是正常的透明效果
開啓深度寫入的半透明效果
上述的透明混合並無開啓深度寫入(ZWrite On),所以若是物體有重疊,那麼會產生錯誤的效果,以下
這時候須要在上文的透明度混合代碼中的Pass代碼塊以前,插入一個新的Pass代碼塊
Pass { ZWrite On ColorMask 0 //用於設置顏色通道的寫掩碼,0表示不寫入任何通道 }
這樣就會獲得正確的效果
雙面渲染的透明效果
仔細觀察上文的透明效果能夠發現,咱們並不能透過半透明物體觀察它們的內部狀況,這是不合乎常理的,所以咱們須要進行雙面渲染。
在Unity中,咱們能夠經過Cull指令來控制剔除哪一面的渲染圖元
Cull Back //剔除背面,只渲染前面,引擎的默認設置 Cull Front //剔除前面,僅渲染背面 Cull Off //關閉剔除
透明度測試的雙面渲染
僅須要在Tags標籤後插入一行
Cull Off
效果對比
透明度混合的雙面渲染
將Pass代碼塊複製一份,一個Pass負責渲染前面,一個Pass負責渲染後面就行,格式以下
Shader "........."{ propeties { /*.....*/ } SubShader{ Pass { Tags{.......} Cull Front //剔除前面 /*其他代碼保持不變 .... */ } Pass { Tags{.......} Cull Back //剔除後面 /*其他代碼保持不變 .... */ } } }
實現的效果以下