天天看點

Unity Shader透明度測試、透明度混合、開啟深度寫入的半透明效果、雙面渲染的透明效果

在Unity中,我們通常使用兩種方式來實作透明效果:1.使用透明度測試 2.透明度混合.

透明度測試:隻要一個片元的透明度不滿足條件,它對應的片元就會被舍棄。被舍棄的片元将不會再進行任何處理,也不會對顔色緩沖産生任何英雄,不需要關閉深度寫入.

透明度混合: 使用目前片元的透明度作為混合因子,與以及存儲在顔色緩沖中的顔色值進行混合,得到新的顔色。需要關閉深度寫入,要非常小心物體的渲染順序.

問題1:在透明度混合中為什麼需要關閉深度寫入呢?

如果不關閉深度寫入,一個透明物體背後的物體本來是可以被我們看到的,但由于深度測試時判斷結果是該半透明物體表面距離錄影機更近,導緻後面的物體被剔除,也就無法透過透明物體看到後面的物體了。

問題2:在透明度混合中為什麼渲染順序很重要呢?

假設場景裡面有物體A和B,A是半透明物體,B是不透明物體,A在B的前面

考慮兩種情況:

1.先渲染B,在渲染A:

由于不透明物體開啟了深度測試和深度寫入,一開始深度緩沖中沒有任何資料,B首先會寫入顔色緩沖和深度緩沖,然後渲染A,透明物體仍然會進行深度測試,但是A比B距離錄影機更近,是以我們會使用A的透明度和顔色緩沖中的B的顔色進行混合,得到正确的半透明效果.

2.先渲染A,在渲染B:

首先先渲染A,此時深度緩沖中沒有任何有效資料,但是由于對半透明物體關閉了深度寫入,A不會修改深度緩沖,然後渲染B,B進行深度測試,由于深度緩沖沒有發生變化,B就直接寫入顔色緩沖和深度緩沖,造成的結果就是B出現在了A的前面.

渲染順序總結如下:

1.先繪制所有不透明的物體,并開啟它們的深度測試和深度寫入.

2.對所有透明的物體排序。

3.按從後往前的順序繪制所有透明的物體。

透明度測試

通常,我們會在片元着色器中使用

clip

函數來進行透明度測試,如果給定參數的任何一個分量是負數,就會舍棄目前像素的輸出顔色.它等同于下面的代碼:

void clip(float4 x)
{
	if(any(x < 0))
		discard;
}
           

第一步建立一個Shader,在屬性裡面添加一個變量_Cutoff用來控制透明度測試時使用的門檻值:

第二步在SubShader語義塊中定義一個Pass語義塊:

Tags{"Queue " = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
		//使用渲染隊列名為AlphaTest的隊列,RenderType 讓Unity把這個shader歸入到提前定義的組,以指明該shader是一個使用了
		//透明度測試的shader. ignoreProjector 意味着shader不會受到投影器的影響
		pass
		{
			Tags{"LightMode" = "ForwardBase"}
           

第三步在CG代碼塊中聲明對應屬性的變量:

然後定義頂點着色器的輸入和輸出結構體:

struct a2v 
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			struct v2f
			{
				float4 pos : SV_POSITION;
				float3 wordlNormal : TEXCOORD;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};
           

接着定義頂點着色器:

v2f vert(a2v v)
			{
				v2f o;
				o.pos =  UnityObjectToClipPos(v.vertex);
				o.wordlNormal = 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.wordlNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed4 texColor = tex2D(_MainTex, i.uv);
				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);
			}
           

效果如下:

Unity Shader透明度測試、透明度混合、開啟深度寫入的半透明效果、雙面渲染的透明效果

完整代碼如下:

Shader "AlphaTest"
{

	Properties
	{
		_Color("Color Tint",Color) = (1, 1, 1, 1)
		_MainTex("Main Tex",2D) = "white"{}
		_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5//用于決定調用clip進行透明度測試時使用的判斷條件,0-1像素透明度的範圍

	}
	SubShader
	{
		Tags{"Queue " = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
		//使用渲染隊列名為AlphaTest的隊列,RenderType 讓Unity把這個shader歸入到提前定義的組,以指明該shader是一個使用了透明度測試的Shader
		//透明度測試的shader. ignoreProjector 意味着shader不會受到投影器的影響
		pass
		{
			Tags{"LightMode" = "ForwardBase"}
			CGPROGRAM

			#pragma vertex vert 
			#pragma fragment frag 
			#include "Lighting.cginc" //使用Unity内置變量 如_LightColor0

			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 : TEXCOORD;
				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);
				//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);
			}
			ENDCG
		}
	}
	FallBack "Transparent/Cutout/VertexLit"
}

           

透明度混合

為了進行混合,需要使用Unity提供的混合指令-Blend

第一步聲明屬性:

用來控制整體的透明度.

第二步修改标簽:

第三步,關閉深度寫入,設定混合模式:

Write Off
Blend SrcAlpha OneMinusSrcAlpha
           

第四步,修改片元着色器:

fixed4 frag(v2f i) : SV_Target
			{
				fixed3 worldNormal = normalize(i.wordlNormal);
				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);
				//設定片元着色器傳回值中的透明通道
			}
           

效果如下:

Unity Shader透明度測試、透明度混合、開啟深度寫入的半透明效果、雙面渲染的透明效果

完整代碼如下:

Shader "Unlit/AlphaTest"
{

	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"}
	//使用透明度混合的物體使用Transparent隊列
		pass
		{
			Tags{"LightMode" = "ForwardBase"}
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			CGPROGRAM

			#pragma vertex vert 
			#pragma fragment frag 
			#include "Lighting.cginc" //使用Unity内置變量 如_LightColor0

			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 wordlNormal : TEXCOORD;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};

			v2f vert(a2v v)
			{
				v2f o;
				o.pos =  UnityObjectToClipPos(v.vertex);
				o.wordlNormal = 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.wordlNormal);
				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"
}

           

開啟深度寫入的半透明效果

關閉深度寫入時,會帶來一些問題,當模型本身有複雜的遮擋關系時,就會有各種各樣因為排序而産生的錯誤的透明效果,比如下面的:

Unity Shader透明度測試、透明度混合、開啟深度寫入的半透明效果、雙面渲染的透明效果

一種解決辦法是使用兩個Pass來渲染模型:第一個Pass開啟深度寫入,但不輸出顔色,它的目的僅僅是為了把模型的深度值寫入深度緩沖中。第二個Pass進行正常的透明度混合.該Pass可以按照像素級别的深度排序結果進行透明渲染

完整代碼如下

Shader "Unlit/AlphaTest"
{

	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"}
	//使用透明度混合的物體使用Transparent隊列
		
		pass//目的是把模型的深度資訊寫入深度緩沖中
		{
			ZWrite On//開啟深度寫入
			ColorMask 0 //ColorMask用于設定顔色通道的寫掩碼 語義 ColorMask RGB|A|0 
			//當ColorMask設為0時,意味着Pass不寫入任何顔色通道,不會輸出任何顔色。
		}


		pass
		{
			Tags{"LightMode" = "ForwardBase"}
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			CGPROGRAM

			#pragma vertex vert 
			#pragma fragment frag 
			#include "Lighting.cginc" //使用Unity内置變量 如_LightColor0

			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 wordlNormal : TEXCOORD;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};

			v2f vert(a2v v)
			{
				v2f o;
				o.pos =  UnityObjectToClipPos(v.vertex);
				o.wordlNormal = 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.wordlNormal);
				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通道,僅僅是為了進行把模型的深度資訊寫入深度緩沖中.

Unity Shader透明度測試、透明度混合、開啟深度寫入的半透明效果、雙面渲染的透明效果

雙面渲染的透明效果

在現實生活中,如果一個物體是透明的,意味着我們不僅可以透過它看到其他物體的樣子,也可以看到它内部的結構,然後在前面展示的例子中,效果如下:

Unity Shader透明度測試、透明度混合、開啟深度寫入的半透明效果、雙面渲染的透明效果

我們并沒有能看到物體的内部結構,仿佛它就是空的。這是因為預設情況下渲染引擎剔除了物體背面的渲染圖元。

透明度測試的雙面渲染

如果我們想要使用透明度測試的物體實作雙面渲染的效果,隻需要把剔除指令關閉就行了.在Pass通道裡面添加下面的代碼就行了:

Cull Off
           
Unity Shader透明度測試、透明度混合、開啟深度寫入的半透明效果、雙面渲染的透明效果

透明度混合的雙面渲染

相比于透明度測試,透明度混合會更複雜,因為透明度混合需要關閉深度寫入。想要得到正确的透明效果,渲染順序是非常重要的,要保證物體是從後往前渲染的.

為此,我們把雙面渲染分成兩個Pass,第一個Pass渲染背面,第二個Pass渲染正面,這樣可以保證背面總是在正面被渲染之前渲染,進而可以保證正确的深度渲染關系.

完整代碼如下:

Shader "Unlit/AlphaTest"
{

	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"}
	//使用透明度混合的物體使用Transparent隊列
		pass
		{
			Tags{"LightMode" = "ForwardBase"}
			Cull Front
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			CGPROGRAM

			#pragma vertex vert 
			#pragma fragment frag 
			#include "Lighting.cginc" //使用Unity内置變量 如_LightColor0

			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 wordlNormal : TEXCOORD;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};

			v2f vert(a2v v)
			{
				v2f o;
				o.pos =  UnityObjectToClipPos(v.vertex);
				o.wordlNormal = 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.wordlNormal);
				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" //使用Unity内置變量 如_LightColor0

			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 wordlNormal : TEXCOORD;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};

			v2f vert(a2v v)
			{
				v2f o;
				o.pos =  UnityObjectToClipPos(v.vertex);
				o.wordlNormal = 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.wordlNormal);
				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,并且每個Pass分别使用Cull指令剔除不同朝向的渲染圖元

效果如下:

Unity Shader透明度測試、透明度混合、開啟深度寫入的半透明效果、雙面渲染的透明效果

繼續閱讀