RectMask2D源码简析

0. 版本

  • 源码版本:2017.3.0
  • 着色器版本:2017.3.0

1. PerformClipping调用顺序

  • CanvasUpdateRegistry.PerformUpdate()
    public class CanvasUpdateRegistry
    {
        protected CanvasUpdateRegistry()
        {
            // 每一帧都会调用
            Canvas.willRenderCanvases += PerformUpdate;
        }
        
        private void PerformUpdate()
        {
             // 更新Layout
             ......

             // now layout is complete do culling...
             ClipperRegistry.instance.Cull();

             // 更新Graphic
             ......
        }
    }
  • ClipperRegistry.Cull()
    public class ClipperRegistry
    {
        // RectMask2D实现了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实现了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的区域的交集。
            // vaildRect = 区域是否有交集。
            Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
            bool clipRectChanged = clipRect != m_LastClipRectCanvasSpace;
            if (clipRectChanged || m_ForceClip)
            {
                // 重点1:向MaskGraphic设置剪裁区域。
                // m_ClipTargets有哪些后面会分析。
                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;

                // 重点2:MaskGraphic进行剪裁。  
                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有交接,设置区域。修改材质的事被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变了,要重建网格?猜测是CanvasRender根据cull的情况,做了网格的优化,减少了一些Overdraw。可以实验看看。
            }
        }
    }

2. 剪裁区域

  • GetCanvasRect
    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坐标,怎么是World坐标呢?
    猜测:CanvasBuildBatch生成的Mesh,这个Mesh直接放到世界坐标系下了,Local坐标是World坐标是一样的。
// 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. 总结

  • RectMask2D在每帧都会检查剪裁区域是否变化了。PerformUpdate中执行。
  • 剪裁区域 = 当前RectMask2D到Parent路径上的所有有效的RectMask2D的剪裁区域的交集。
  • 如果剪裁区域变化了,会通过CanvasRenderer来间接修改MaskGraphic的材质参数,把剪裁区域传进去。
  • 如果剪裁区域变化了,还会为MaskGraphic调用SetVerticesDirty,重新生成网格。猜测是CanvasRenderer对剪裁的网格进行了优化,避免了OverDraw。代价就是剪裁区域变化的时候,要重新生成网格。
  • 每个MaskGraphic,会把自己注册到,最近的一个ParentMask上面。
  • OUT.worldPosition = v.vertex; 读取的是Local坐标。猜测CanvasBuildBatch生成的Mesh,这个Mesh直接放到世界坐标系下了,Local坐标是World坐标是一样的。
  • 放到CanvasRenderer里面的是Local坐标,怀疑CanvasRenderer,把这个Local坐标又转成World坐标,再放到_ClipRect里的。根据一些测试,直接跳过CanvasRenderer,向_ClipRect直接传入World坐标是对的,传入Local坐标是不对的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容

  • 更新:【面试题含答案】http://bbs.9ria.com/thread-288394-1-1.html 高频问...
    好怕怕阅读 4,739评论 3 52
  • 一:什么是协同程序?答:在主线程运行时同时开启另一段逻辑处理,来协助当前程序的执行。换句话说,开启协程就是开启一个...
    CrixalisAs阅读 2,068评论 1 7
  • Information about the pose, topology, and expression of a...
    loveFBI阅读 2,177评论 0 2
  • 《ilua》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 1...
    叶染柒丶阅读 10,651评论 0 11
  • Information about the position and orientation of a real-...
    loveFBI阅读 1,234评论 0 0