1. 程式人生 > >Unity Shader入門精要筆記(十九):雙面渲染的透明效果

Unity Shader入門精要筆記(十九):雙面渲染的透明效果

本系列文章由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,只渲染正面。從而得到了上圖效果。