Unity中使用模板测试模拟Mask组件效果

本文分享Unity中使用模板测试模拟Mask组件效果

在上一篇文章中, 我们分享了模板测试的基础知识, 在今天的分享中, 我们会尝试使用模板测试来模拟遮罩效果.

Unity中使用模板测试模拟Mask组件效果

准备工作

首先, 我们需要准备两张图片, 一张圆形的半透图片当做遮罩, 一张头像图片. 如图两张图片:
在这里插入图片描述

创建两个材质和一个shader(直接复制Unity的UI-Default做修改), 如下:

在这里插入图片描述

  • CustomMask.shader
  • CustomMask.mat
  • Object.mat

对应的shader代码为:

# CustomMask.shader
Shader "Custom/CustomMask"
{
	Properties
	{
		[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
		_Color ("Tint", Color) = (1,1,1,1)

		_Stencil ("Stencil ID", Float) = 0
		[Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp ("Stencil Comparison", Int) = 8
		[Enum(UnityEngine.Rendering.StencilOp)] _StencilOp ("Stencil Operation", Int) = 0
		_StencilWriteMask ("Stencil Write Mask", Range(0, 255)) = 255
		_StencilReadMask ("Stencil Read Mask", Range(0, 255)) = 255

		_ColorMask ("Color Mask", Range(0, 15)) = 15

		[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
	}

	SubShader
	{
		Tags
		{
			"Queue"="Transparent"
			"IgnoreProjector"="True"
			"RenderType"="Transparent"
			"PreviewType"="Plane"
			"CanUseSpriteAtlas"="True"
		}

		Stencil
		{
			Ref [_Stencil]
			Comp [_StencilComp]
			Pass [_StencilOp]
			ReadMask [_StencilReadMask]
			WriteMask [_StencilWriteMask]
		}


		Cull Off
		Lighting Off
		ZWrite Off
		ZTest [unity_GUIZTestMode]
		Blend SrcAlpha OneMinusSrcAlpha
		ColorMask [_ColorMask]

		Pass
		{
			Name "CustomMask"
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"
			#include "UnityUI.cginc"

			#pragma multi_compile __ UNITY_UI_ALPHACLIP

			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color    : COLOR;
				float2 texcoord  : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
				UNITY_VERTEX_OUTPUT_STEREO
			};

			fixed4 _Color;
			fixed4 _TextureSampleAdd;
			float4 _ClipRect;

			v2f vert(appdata_t IN)
			{
				v2f OUT;
				UNITY_SETUP_INSTANCE_ID(IN);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
				OUT.worldPosition = IN.vertex;
				OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

				OUT.texcoord = IN.texcoord;

				OUT.color = IN.color * _Color;
				return OUT;
			}

			sampler2D _MainTex;
			float _OneMinusSaturability;

			fixed4 frag(v2f IN) : SV_Target
			{
				half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd);
				if (IN.color.r < 0.0001 && IN.color.g < 0.0001 && IN.color.b > 0.0001 && IN.color.b < 0.004)
				{
					float gray = dot(color.rgb, float3(0.2125, 0.7154, 0.0721));
					color.rgb = float3(gray, gray, gray);
				}
				else
				{
					color *= IN.color;

					if (_OneMinusSaturability > 0.05)
					{
						float gray = dot(color.rgb, float3(0.2125, 0.7154, 0.0721));
						color.rgb = lerp(float3(gray, gray, gray), color.rgb, 1 - _OneMinusSaturability);
					}
				}

				color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);

				#ifdef UNITY_UI_ALPHACLIP
				clip (color.a - 0.001);
				#endif

				return color;
			}
		ENDCG
		}
	}
}

shader很简单, 直接从对应版本的UGUI的默认shader复制即可, 为了方便在属性面板中查看, 可以使用一些特性来做标记:

[Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp ("Stencil Comparison", Int) = 8
[Enum(UnityEngine.Rendering.StencilOp)] _StencilOp ("Stencil Operation", Int) = 0
_StencilWriteMask ("Stencil Write Mask", Range(0, 255)) = 255
_StencilReadMask ("Stencil Read Mask", Range(0, 255)) = 255

之后在属性面板上就可以直观看到信息:

在这里插入图片描述

然后构建三个RawImage, 一个显示原图, 一个是遮罩, 一个是头像, 如图:

在这里插入图片描述

它们的大小分别是,120x120, 100x100, 120x120, 此时看到的效果应该是:

在这里插入图片描述

好了, 现在开始正式工作, 通过调节模板参数来达到遮罩效果.

调节参数

首先来梳理一下思路.

我们的目标是将头像超出圆的部分截取掉, 只在圆的部分显示头像, 如图:

在这里插入图片描述

意思是我们需要在圆形的部分绘制头像, 根据模板测试的知识可以简单的得出结论, 只要在圆形的部分通过测试, 其它部分不通过即可达到目的.

首先我们绘制圆形遮罩, 指定一个参考值, 然后让其总是通过模板测试, 然后使用参考值来更新模板值, 设置如下:

在这里插入图片描述
此时圆形的图片所在的区域整个都会通过测试, 如图:

在这里插入图片描述

图中的绿色代表通过测试的区域. 此时绿色区域的模板值为参考值3.

接下来绘制头像, 指定一个相同的参考值, 然后设置为参考值相同才通过测试, 设置如下:

在这里插入图片描述

因为头像的大小为120x120, 而遮罩的大小为100x100, 所以超出的20x20无法通过测试, 如下:

在这里插入图片描述

图中红色部分代表未通过测试的区域, 此时的实际渲染结果就是超出的20x20部分被裁掉了:

在这里插入图片描述

那么如何裁掉圆形区域之外的部分呢?

直观的想法就是将圆形内部的参考值设置与圆形外部的不同. 但是参考值是针对整个图像来设置的, 我们无法手动指定不同区域. 另一个想法是在绘制圆形时, 过滤掉圆形之外的部分, 就是在片元处理的时候就直接丢弃, 只要那些片元不参与绘制, 当然也不参与模板测试, 对应的模板值肯定也只是0了, 这样, 模板缓存中, 圆形部分的模板值为3, 之外的部分为0. 我们继续绘制头像时, 圆形部分的模板值与头像相同, 可以绘制, 而圆形部分之外的部分与头像不同, 不能绘制, 这样就达到了我们的效果.

在这里插入图片描述

在这里插入图片描述

至于如何过滤掉圆形之外的片元, 因为那部分片元是透明的, 我们只需要使用透明度测试将其丢弃即可, 当然, 只需要简单的勾选Use Alpha Clip即可, 如图:

在这里插入图片描述

在片元着色器中对应的代码为:

#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif

即透明度小于0.001的片元就丢弃.

顺便说一下, 如果不想显示遮罩的图片, 只需要在绘制遮罩的时候将颜色掩码Color Mask设置为0即可, 对应的是Mask组件的ShowMaskGraphic属性, 效果如下:

在这里插入图片描述

以上就是整个模拟过程, Unity官方的Mask组件的核心运作原理就是如此, 不同的是因为在实际场景中可能存在多个物体需要进行模板测试, 所以需要做很多额外的工作来避免互相影响.

相信使用过Mask的同学知道这个组件有一个最大的问题就是渲染出来的效果存在比较明显的锯齿现象, 我们将在下一篇文章中尝试另一个方案来实现遮罩, 从而优化锯齿问题. 希望对大家有所帮助.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拂面清风三点水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值