本文分享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
的同学知道这个组件有一个最大的问题就是渲染出来的效果存在比较明显的锯齿现象, 我们将在下一篇文章中尝试另一个方案来实现遮罩, 从而优化锯齿问题. 希望对大家有所帮助.