版本
- 源码版本:2017.3.0
- 着色器版本:2017.3.0
Mask使用方法
https://jingyan.baidu.com/article/91f5db1b261d911c7f05e393.html
层级关系
MaskSample.pngGraphic.UpdateMaterial()
Image和Text都会设置材质到CanvasRenderer上。
// Graphic.cs
protected virtual void UpdateMaterial()
{
if (!IsActive())
return;
canvasRenderer.materialCount = 1;
canvasRenderer.SetMaterial(materialForRendering, 0);
canvasRenderer.SetTexture(mainTexture);
}
Graphic.materialForRendering
用IMaterialModifier来修改材质。Image找到的IMaterialModifier有Image和Mask。Text找到的IMaterialModifier只有Text。
// Graphic.cs
public virtual Material materialForRendering
{
get
{
var components = ListPool<Component>.Get();
GetComponents(typeof(IMaterialModifier), components);
var currentMat = material;
for (var i = 0; i < components.Count; i++)
currentMat = (components[i] as IMaterialModifier).GetModifiedMaterial(currentMat);
ListPool<Component>.Release(components);
return currentMat;
}
}
Mask.GetModifiedMaterial
- 最多支持8层Mask,靠近Canvas最近的是第1层,次之第2层,以此类推。
- 不同的层使用模板缓存不同的位。
第1层Mask,写入0000 0001。
第2层Mask,与第1层有交集的地方,写入0000 0011。
第3层Mask,与第2层有交集的地方,写入0000 0111。
..... - 通过CanvasRenderer.SetPopMaterial(),清空模板缓存。
例子中Frame以及Frame的所有的ChildObject都渲染完毕之后,Frame的CanvasRenderer会清空模板缓存。
再举个例子:
Frame1(Mask + Image) // 第1层的Mask,写入0000 0001。
Text1(Text) // 根据0000 0001进行模板测试。
Frame2(Mask + Image) // 第2层的Mask,与第1层有交集的地方,写入0000 0011。
Text2(Text) // 根据0000 0011进行模板测试
// 在渲染Frame3之前,清空了0000 0010的模板缓存。
Frame3(Mask + Image) // 第2层的Mask,设置模板缓存的方式与Frame2相同。
Text3(Text) // 模板测试的方式与Text2相同。
// Mask.cs
/// Stencil calculation time!
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
if (!MaskEnabled())
return baseMaterial;
var rootSortCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
var stencilDepth = MaskUtilities.GetStencilDepth(transform, rootSortCanvas);
if (stencilDepth >= 8)
{
// 模板缓存一共有8位,在Parent到Canvas之间已经有8个Mask了,当前Mask无法启用。
Debug.LogError("Attempting to use a stencil mask with depth > 8", gameObject);
return baseMaterial;
}
int desiredStencilBit = 1 << stencilDepth;
// if we are at the first level...
// we want to destroy what is there
if (desiredStencilBit == 1)
{
var maskMaterial = StencilMaterial.Add(
baseMaterial,
1, // 写入值
StencilOp.Replace, // 写入操作
CompareFunction.Always, // 检测总是成功
m_ShowMaskGraphic ? ColorWriteMask.All : 0 // ColorMask
);
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = maskMaterial;
var unmaskMaterial = StencilMaterial.Add(
baseMaterial,
1, // 清空操作,模板参考值应该是没有作用了吧?
StencilOp.Zero, // 清空Buffer
CompareFunction.Always, // 检测总是成功
0); // ColorMask
StencilMaterial.Remove(m_UnmaskMaterial);
m_UnmaskMaterial = unmaskMaterial;
graphic.canvasRenderer.popMaterialCount = 1;
graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);
return m_MaskMaterial;
}
//otherwise we need to be a bit smarter and set some read / write masks
var maskMaterial2 = StencilMaterial.Add(
baseMaterial,
desiredStencilBit | (desiredStencilBit - 1), // 写入值
StencilOp.Replace,
CompareFunction.Equal, // 与上一层Mask的值相同才能通过检测
m_ShowMaskGraphic ? ColorWriteMask.All : 0,
desiredStencilBit - 1, // ReadMask,
desiredStencilBit | (desiredStencilBit - 1)); // WriteMask
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = maskMaterial2;
// 清除当前层,保留其他层。假设当前层是3。
graphic.canvasRenderer.hasPopInstruction = true;
var unmaskMaterial2 = StencilMaterial.Add(
baseMaterial,
desiredStencilBit - 1, // RefValue = 0000 0011
StencilOp.Replace,
CompareFunction.Equal,
0,
desiredStencilBit - 1, // ReadMask = 0000 0011
desiredStencilBit | (desiredStencilBit - 1)); // WriteMask = 0000 0111
StencilMaterial.Remove(m_UnmaskMaterial);
m_UnmaskMaterial = unmaskMaterial2;
graphic.canvasRenderer.popMaterialCount = 1;
graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);
return m_MaskMaterial;
}
MaskableGraphic.GetModifiedMaterial
- m_StencilValue = 在Parent到Canvas之间有效的Mask数量
- 举例中的Image组件,因为与Mask在同一个GameObejct下面,Mask组件会修改Image组件的材质(修改模板缓存),Image就不再修改自己的材质了。
- 举例中的Text组件,因为GameObject下没有Mask组件,并且Text的ParentGameObject到Canvas之间有Mask组件,所以要修改Text的材质(进行模板检测)。
// MaskableGraphic.cs
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
var toUse = baseMaterial;
if (m_ShouldRecalculateStencil)
{
var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0;
m_ShouldRecalculateStencil = false;
}
// if we have a enabled Mask component then it will
// generate the mask material. This is an optimisation
// it adds some coupling between components though :(
Mask maskComponent = GetComponent<Mask>();
if (m_StencilValue > 0 && (maskComponent == null || !maskComponent.IsActive()))
{
// 例子中的Text组件,会进入到这里,会进行模板测试。
var maskMat = StencilMaterial.Add(
toUse, // Material baseMat
(1 << m_StencilValue) - 1, // 参考值
StencilOp.Keep, // 不修改模板缓存
CompareFunction.Equal, // 相等通过测试
ColorWriteMask.All, // ColorMask
(1 << m_StencilValue) - 1, // Readmask
0); // WriteMask
StencilMaterial.Remove(m_MaskMaterial);
m_MaskMaterial = maskMat;
toUse = m_MaskMaterial;
}
return toUse;
}
Shader代码
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
Shader "UI/Default"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 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 "Default"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#pragma multi_compile __ UNITY_UI_CLIP_RECT
#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 v)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.worldPosition = v.vertex;
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);
OUT.texcoord = v.texcoord;
OUT.color = v.color * _Color;
return OUT;
}
sampler2D _MainTex;
fixed4 frag(v2f IN) : SV_Target
{
half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
#ifdef UNITY_UI_CLIP_RECT
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
return color;
}
ENDCG
}
}
}
网友评论