Unity Shader入門精要筆記(十九):雙面渲染的透明效果
阿新 • • 發佈:2019-02-13
本系列文章由Aimar_Johnny編寫,歡迎轉載,轉載請標明出處,謝謝。
前面文章介紹了透明度測試和透明度混合,基本滿足了透明的效果需求,但嚴格來說,現實中透明除了能看到後面物體的樣子,也應該能看到透明物體內部的樣子,但前面的方法我們都看不到其內部結構,這是因為預設情況下我們剔除了物體背面的渲染,只渲染了正面,所以可以用Cull Back / Front / Off命令來控制渲染剔除。
Cull Back是預設情況,剔除背面,Front是剔除正面,Off是關掉剔除,慎重關掉剔除,這樣會使渲染圖元成倍增加,除非特殊需求,不要關閉剔除。下面以透明度混合的例子看一下雙面渲染的效果:
有人會想實現起來很簡單,關掉剔除就可以了,但這樣不可以。透明度混合關閉了深度寫入,沒有深度資訊,如果雙面同時渲染,我們無法保證同一個物體的正面和背面圖元的渲染順序,這樣有可能得到錯誤的半透效果。解決辦法是把雙面渲染分成兩個Pass,第一個Pass只渲染背面,第二個Pass只渲染正面,因為SubShader中的Pass會被順序執行,所以我們總能保證背面是在正面之前渲染,從而保證正確的深度渲染關係。
下面上程式碼
Shader "CustomShader/Transparent/AlphaBlendBothSideShader" { Properties { _MainTex ("Texture", 2D) = "white" {} _Color ("Color Tint", Color) = (1, 1, 1, 1) _AlphaScale ("Alpha Scale", Range(0, 1)) = 1 } SubShader { Tags {"Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True"} Pass { Tags {"LightMode" = "ForwardBase"} Cull Front ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; float _AlphaScale; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _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 = 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 { Tags {"LightMode" = "ForwardBase"} Cull Back ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Lighting.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; float _AlphaScale; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _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 = 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" }
上面程式碼雖然很長,但半透的渲染部分和前面章節一樣,這裡不解釋,唯一有區別的就是第一個Pass我們加了Cull Front,只渲染背面,第二個Pass我們加了Cull Back,只渲染正面。從而得到了上圖效果。