0. 版本
- 源碼版本:2017.3.0
- 著色器版本:2017.3.0
1. PerformClipping調(diào)用順序
- CanvasUpdateRegistry.PerformUpdate()
public class CanvasUpdateRegistry
{
protected CanvasUpdateRegistry()
{
// 每一幀都會(huì)調(diào)用
Canvas.willRenderCanvases += PerformUpdate;
}
private void PerformUpdate()
{
// 更新Layout
......
// now layout is complete do culling...
ClipperRegistry.instance.Cull();
// 更新Graphic
......
}
}
public class ClipperRegistry
{
// RectMask2D實(shí)現(xiàn)了IClipper接口
readonly IndexedSet<IClipper> m_Clippers = new IndexedSet<IClipper>();
public void Cull()
{
for (var i = 0; i < m_Clippers.Count; ++i)
{
m_Clippers[i].PerformClipping();
}
}
}
- RectMask2D.PerformClipping()
public class RectMask2D : UIBehaviour, IClipper, ICanvasRaycastFilter
{
private List<RectMask2D> m_Clippers = new List<RectMask2D>();
// MaskableGraphic實(shí)現(xiàn)了IClippable接口
private List<IClippable> m_ClipTargets = new List<IClippable>();
public virtual void PerformClipping()
{
//TODO See if an IsActive() test would work well here or whether it might cause unexpected side effects (re case 776771)
// if the parents are changed
// or something similar we
// do a recalculate here
if (m_ShouldRecalculateClipRects)
{
// m_Clippers = this的Parent路徑上的暑塑,所有的RectMask2D組件。
MaskUtilities.GetRectMasksForClip(this, m_Clippers);
m_ShouldRecalculateClipRects = false;
}
// get the compound rects from
// the clippers that are valid
bool validRect = true;
// clipRect = m_Clippers中所有的RectMask2D的區(qū)域的交集。
// vaildRect = 區(qū)域是否有交集秒咐。
Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
bool clipRectChanged = clipRect != m_LastClipRectCanvasSpace;
if (clipRectChanged || m_ForceClip)
{
// 重點(diǎn)1:向MaskGraphic設(shè)置剪裁區(qū)域。
// m_ClipTargets有哪些后面會(huì)分析注竿。
foreach (IClippable clipTarget in m_ClipTargets)
clipTarget.SetClipRect(clipRect, validRect);
m_LastClipRectCanvasSpace = clipRect;
m_LastValidClipRect = validRect;
}
foreach (IClippable clipTarget in m_ClipTargets)
{
// hasMoved : True if any change has occured that would invalidate the positions of generated geometry.
var maskable = clipTarget as MaskableGraphic;
if (maskable != null && !maskable.canvasRenderer.hasMoved && !clipRectChanged)
continue;
// 重點(diǎn)2:MaskGraphic進(jìn)行剪裁搀罢。
clipTarget.Cull(m_LastClipRectCanvasSpace, m_LastValidClipRect);
}
}
- MaskGraphic.SetClipRect
- MaskGraphic.Cull
public abstract class MaskableGraphic : Graphic, IClippable, IMaskable, IMaterialModifier
{
public virtual void SetClipRect(Rect clipRect, bool validRect)
{
if (validRect) // Mask有交接,設(shè)置區(qū)域赤拒。修改材質(zhì)的事被CanvasRenderer做了,黑盒看不到诱鞠。
canvasRenderer.EnableRectClipping(clipRect);
else
canvasRenderer.DisableRectClipping(); // Mask沒有交集挎挖。
}
public virtual void Cull(Rect clipRect, bool validRect)
{
var cull = !validRect || !clipRect.Overlaps(rootCanvasRect, true);
UpdateCull(cull);
}
private void UpdateCull(bool cull)
{
var cullingChanged = canvasRenderer.cull != cull;
canvasRenderer.cull = cull;
if (cullingChanged)
{
UISystemProfilerApi.AddMarker("MaskableGraphic.cullingChanged", this);
m_OnCullStateChanged.Invoke(cull);
SetVerticesDirty(); // 疑問:為什么cull變了,要重建網(wǎng)格航夺?猜測(cè)是CanvasRender根據(jù)cull的情況蕉朵,做了網(wǎng)格的優(yōu)化,減少了一些Overdraw阳掐∈夹疲可以實(shí)驗(yàn)看看。
}
}
}
2. 剪裁區(qū)域
internal class RectangularVertexClipper
{
readonly Vector3[] m_WorldCorners = new Vector3[4];
readonly Vector3[] m_CanvasCorners = new Vector3[4];
public Rect GetCanvasRect(RectTransform t, Canvas c)
{
if (c == null)
return new Rect();
t.GetWorldCorners(m_WorldCorners);
var canvasTransform = c.GetComponent<Transform>();
for (int i = 0; i < 4; ++i)
m_CanvasCorners[i] = canvasTransform.InverseTransformPoint(m_WorldCorners[i]);
return new Rect(m_CanvasCorners[0].x, m_CanvasCorners[0].y, m_CanvasCorners[2].x - m_CanvasCorners[0].x, m_CanvasCorners[2].y - m_CanvasCorners[0].y);
}
}
- UI/Default
疑問:OUT.worldPosition = v.vertex; 讀取的是Local坐標(biāo)缭保,怎么是World坐標(biāo)呢汛闸?
猜測(cè):CanvasBuildBatch生成的Mesh,這個(gè)Mesh直接放到世界坐標(biāo)系下了艺骂,Local坐標(biāo)是World坐標(biāo)是一樣的诸老。
// 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" // 2D Mask 剪裁。
#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; // 2D Mask 剪裁彻亲。
UNITY_VERTEX_OUTPUT_STEREO
};
fixed4 _Color;
fixed4 _TextureSampleAdd;
float4 _ClipRect; // 2D Mask 剪裁孕锄。
v2f vert(appdata_t v)
{
v2f OUT;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
OUT.worldPosition = v.vertex; // 2D Mask 剪裁吮廉。
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); // 2D Mask 剪裁苞尝。
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
return color;
}
ENDCG
}
}
}
3. m_ClipTargets
// MaskGraphic.cs
protected override void OnEnable()
{
......
UpdateClipParent();
......
}
protected override void OnDisable()
{
......
UpdateClipParent();
......
}
protected override void OnTransformParentChanged()
{
......
UpdateClipParent();
......
}
protected override void OnCanvasHierarchyChanged()
{
......
UpdateClipParent();
......
}
public virtual void RecalculateClipping()
{
UpdateClipParent();
}
// MaskGraphic.cs
private void UpdateClipParent()
{
// 返回最近的ParentMask
var newParent = (maskable && IsActive()) ? MaskUtilities.GetRectMaskForClippable(this) : null;
// if the new parent is different OR is now inactive
if (m_ParentMask != null && (newParent != m_ParentMask || !newParent.IsActive()))
{
m_ParentMask.RemoveClippable(this);
UpdateCull(false);
}
// don't re-add it if the newparent is inactive
if (newParent != null && newParent.IsActive())
newParent.AddClippable(this);
m_ParentMask = newParent;
}
// RectMask2D.cs
public void AddClippable(IClippable clippable)
{
if (clippable == null)
return;
m_ShouldRecalculateClipRects = true;
if (!m_ClipTargets.Contains(clippable))
m_ClipTargets.Add(clippable);
m_ForceClip = true;
}
4. 總結(jié)
- RectMask2D在每幀都會(huì)檢查剪裁區(qū)域是否變化了。PerformUpdate中執(zhí)行宦芦。
- 剪裁區(qū)域 = 當(dāng)前RectMask2D到Parent路徑上的所有有效的RectMask2D的剪裁區(qū)域的交集宙址。
- 如果剪裁區(qū)域變化了,會(huì)通過CanvasRenderer來間接修改MaskGraphic的材質(zhì)參數(shù)调卑,把剪裁區(qū)域傳進(jìn)去抡砂。
- 如果剪裁區(qū)域變化了,還會(huì)為MaskGraphic調(diào)用SetVerticesDirty恬涧,重新生成網(wǎng)格注益。猜測(cè)是CanvasRenderer對(duì)剪裁的網(wǎng)格進(jìn)行了優(yōu)化,避免了OverDraw溯捆。代價(jià)就是剪裁區(qū)域變化的時(shí)候丑搔,要重新生成網(wǎng)格。
- 每個(gè)MaskGraphic,會(huì)把自己注冊(cè)到啤月,最近的一個(gè)ParentMask上面煮仇。
- OUT.worldPosition = v.vertex; 讀取的是Local坐標(biāo)。猜測(cè)CanvasBuildBatch生成的Mesh谎仲,這個(gè)Mesh直接放到世界坐標(biāo)系下了浙垫,Local坐標(biāo)是World坐標(biāo)是一樣的。
- 放到CanvasRenderer里面的是Local坐標(biāo)郑诺,懷疑CanvasRenderer夹姥,把這個(gè)Local坐標(biāo)又轉(zhuǎn)成World坐標(biāo),再放到_ClipRect里的间景。根據(jù)一些測(cè)試佃声,直接跳過CanvasRenderer,向_ClipRect直接傳入World坐標(biāo)是對(duì)的倘要,傳入Local坐標(biāo)是不對(duì)的圾亏。