Unity Shader入门教程(九) :开启深度写入的半透明效果

在上篇文章有讲到实现透明度混合时需要关闭深度写入。而为了不造成错误的排序效果,渲染引擎一般都会先对物体进行排序,再渲染。常用的方法如下:(对于上篇文章shader代码中使用的渲染队列Transparent)

  1. 先渲染所有不透明物体,并开启他们的深度测试和深度写入;
  2. 把半透明物体按他们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明物体,并开启他们的深度测试,但关闭深度写入;

其实这样做并没有解决所有情况下的错误的排序问题。在一些情况下,根据“距离摄像机远近”并不能解决问题。这是因为深度缓冲中的值其实是像素级别的,即每个像素有一个深度值,但是现在我们对单个物体级别进行排序,这意味着排序结果是,要么物体A全部在B前面渲染,要么A全部在B后面渲染。但如果存在循环重叠的情况,那么使用这种方法就永远无法得到正确的结果。如下展示了无法使用上述方法解决的情况:

图8.3中由于3个物体互相重叠,我们不可能得到一个正确的排序顺序。这种时候,我们可以选择把物体拆分成2个部分,然后再进行正确的排序。但是如果遇到图8.4的情况,如何判断他们距离摄像机远近呢?我们知道,一个物体的网格结构往往占据了空间中的莫一块区域,也就是说网格上的每一个点的深度值都是不一样的,那我们选择图中哪个点来判断距离摄像机远近呢?答案是不管选择哪个点都不合理。

所以,为了解决这个问题,一种方法是使用2个Pass来渲染模型:第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;第二个Pass进行正常的透明度混合,由于上一个pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染。但这种方法的缺点是,多使用一个Pass会对性能造成一定影响,并且对于模型内部不会有半透明效果。

开始深度写入的半透明混合的shader代码如下:

Shader "Custom/AlphaBlendZWrite"
{
	Properties
	{
		_Color("Color", Color) = (1,1,1,1)
		_MainTex("Main Tex", 2D) = "white" {}
		_AlphaScale("Alpha Scale", Range(0, 1)) = 1
	}
	SubShader
	{
		//"Queue" = "Transparent"表示开启了透明度混合的都要使用此模式,"IgnoreProjector" = "True表示该shader不会受投影器(Projector)影响
		//"RenderType" = "TransparentCutout"表示让unity把这个shader归入到提前定义的组(这里指TransparentCutout)
		//一般开启透明度测试需要设置这三个标签
		Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}

		Pass
		{
			//开启深度写入
			ZWrite On
			//ColorMask语义有以下几种:ColorMask RGB|A|0|其他任何RGB组合
			//为0时代表该Pass不写入任何颜色通道,即不会输出任何颜色
			ColorMask 0
		}

		Pass
		{
			Tags{"LightMode" = "ForwardBase"}

			//关闭深度写入
			ZWrite Off
			//该博客中有讲
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			float4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _AlphaScale;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float3 texcoord : TEXCOORD0;
			};

			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				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);
				//纹理采样的结果和颜色变量的混合
				fixed3 albedo = texColor.rgb * _Color.rgb;
				//环境光计算
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				//漫反射光计算
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

				//texColor.a * _AlphaScale,表示纹理的透明通道与_AlphaScale变量混合
				return fixed4(ambient + diffuse, texColor.a* _AlphaScale);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

 该Shader代码的结果的如下图:

由此可见该透明效果并不能看到正方体的内部。而现实生活中,如果一个物体是透明的,意味着我们不仅可以透过它看到其他物体的样子,也可以看到它的内部结构。而造成这种结果的原因是,默认情况下渲染引擎剔除了物体背面的渲染图元,而只渲染了物体的正面。因此我们需要得到双面的渲染效果。Unity中可以用Cull指令来控制需要剔除哪个面的渲染图元。指令如下:

Cull Back | Front | Off

如果设置为Back,那么那些背对着相机的图元就不会得到渲染,也是默认情况下的剔除状态;如果设置为Front,那么那些朝向摄像机的渲染图元就不会得到渲染;如果设置为Off,就会关闭剔除功能,所有图元都将得到渲染,但此时需要渲染的图元成倍增加,影响性能。

因此,我们还需要实现透明度混合的双面渲染效果,使效果更接近现实情况。而实现此功能,只需要将正面和反面都渲染一遍,并保证反面的渲染在正面的渲染之前,以此来保证正确的深度渲染关系(原因(八)文章中有讲)。

unity shader中SubShader下的Pass会按照从上往下的顺序依次渲染,因此,我们第一个Pass只渲染背面,第二个Pass只渲染正面,从而保证背面总在正面之前被渲染。具体shader代码如下(两个Pass代码一致,唯一的区别只是一个关闭了正面的渲染,一个只关闭了背面的渲染):

Shader "Custom/AlphaBlendBothSided"
{
	Properties
	{
		_Color("Color", Color) = (1,1,1,1)
		_MainTex("Main Tex", 2D) = "white" {}
		_AlphaScale("Alpha Scale", Range(0, 1)) = 1
	}
	SubShader
	{
		//"Queue" = "Transparent"表示开启了透明度混合的都要使用此模式,"IgnoreProjector" = "True表示该shader不会受投影器(Projector)影响
		//"RenderType" = "TransparentCutout"表示让unity把这个shader归入到提前定义的组(这里指TransparentCutout)
		//一般开启透明度测试需要设置这三个标签
		Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}

		Pass
		{
			Tags{"LightMode" = "ForwardBase"}

			//关闭正面的渲染
			Cull Front

			//关闭深度写入
			ZWrite Off
			//该博客中有讲
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			float4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _AlphaScale;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float3 texcoord : TEXCOORD0;
			};

			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				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);
				//纹理采样的结果和颜色变量的混合
				fixed3 albedo = texColor.rgb * _Color.rgb;
				//环境光计算
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				//漫反射光计算
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

				//texColor.a * _AlphaScale,表示纹理的透明通道与_AlphaScale变量混合
				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"

			float4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _AlphaScale;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float3 texcoord : TEXCOORD0;
			};

			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				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);
				//纹理采样的结果和颜色变量的混合
				fixed3 albedo = texColor.rgb * _Color.rgb;
				//环境光计算
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				//漫反射光计算
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

				//texColor.a * _AlphaScale,表示纹理的透明通道与_AlphaScale变量混合
				return fixed4(ambient + diffuse, texColor.a* _AlphaScale);
			}
			ENDCG
		}
	}
	FallBack "Transparent/VertexLit"
}

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值