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用来控制透明度测试时使用的阈值:

_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5//用于决定调用clip进行透明度测试时使用的判断条件,0-1像素透明度的范围

第二步在SubShader语义块中定义一个Pass语义块:

		Tags{"Queue " = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
		//使用渲染队列名为AlphaTest的队列,RenderType 让Unity把这个shader归入到提前定义的组,以指明该shader是一个使用了
		//透明度测试的shader. ignoreProjector 意味着shader不会受到投影器的影响
		pass
		{
			Tags{"LightMode" = "ForwardBase"}

第三步在CG代码块中声明对应属性的变量:

fixed _Cutoff;

然后定义顶点着色器的输入和输出结构体:

			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);
			}

效果如下:
在这里插入图片描述
完整代码如下:

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

第一步声明属性:

_AlphaScale("Alpha Scale", Range(0, 1)) = 1

用来控制整体的透明度.
第二步修改标签:

Tags{"Queue " = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}

第三步,关闭深度写入,设置混合模式:

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);
				//设置片元着色器返回值中的透明通道
			}

效果如下:
在这里插入图片描述
完整代码如下:

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"
}

开启深度写入的半透明效果

关闭深度写入时,会带来一些问题,当模型本身有复杂的遮挡关系时,就会有各种各样因为排序而产生的错误的透明效果,比如下面的:
在这里插入图片描述

一种解决办法是使用两个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通道,仅仅是为了进行把模型的深度信息写入深度缓冲中.
在这里插入图片描述

双面渲染的透明效果

在现实生活中,如果一个物体是透明的,意味着我们不仅可以透过它看到其他物体的样子,也可以看到它内部的结构,然后在前面展示的例子中,效果如下:在这里插入图片描述
我们并没有能看到物体的内部结构,仿佛它就是空的。这是因为默认情况下渲染引擎剔除了物体背面的渲染图元。

透明度测试的双面渲染

如果我们想要使用透明度测试的物体实现双面渲染的效果,只需要把剔除命令关闭就行了.在Pass通道里面添加下面的代码就行了:

Cull Off

在这里插入图片描述

透明度混合的双面渲染

相比于透明度测试,透明度混合会更复杂,因为透明度混合需要关闭深度写入。想要得到正确的透明效果,渲染顺序是非常重要的,要保证物体是从后往前渲染的.
为此,我们把双面渲染分成两个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指令剔除不同朝向的渲染图元
效果如下:
在这里插入图片描述


版权声明:本文为qq_44800780原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。