欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Unity中Mask和RectMask2D組件的對比與測試

 更新時間:2021年06月25日 16:16:31   作者:ryougi_  
本篇文章給大家介紹Unity中Mask和RectMask2D組件的對比與測試,包括組件用法及RectMask2D的基本用法,通過Mask的原理分析實例代碼相結合給大家講解的非常詳細,需要的朋友參考下吧

組件用法

Mask組件可以實現(xiàn)遮罩的效果,將一個圖像設為擁有mask組件圖像的子物體,最后就會隱藏掉子圖像和mask圖像不重合的部分。例如:

在這里插入圖片描述在這里插入圖片描述

(藍色的圓形名為mask,數(shù)字圖片名為image)

在“mask”圖片上添加mask組件后的結果(可以選擇是否隱藏mask圖像):

在這里插入圖片描述在這里插入圖片描述在這里插入圖片描述

RectMask2D的基本用法

RectMask2D的用法和mask大致相同,不過RectMask2D只能裁剪一個矩形區(qū)域,同時RectMask2D可以選擇邊緣虛化

在這里插入圖片描述在這里插入圖片描述在這里插入圖片描述

原理分析

Mask的原理分析

  1. Mask會賦予Image一個特殊的材質,這個材質會給Image的每個像素點進行標記,將標記結果存放在一個緩存內(這個緩存叫做 Stencil Buffer)
  2. 當子級UI進行渲染的時候會去檢查這個 Stencil Buffer內的標記,如果當前覆蓋的區(qū)域存在標記(即該區(qū)域在Image的覆蓋范圍內),進行渲染,否則不渲染

那么,Stencil Buffer 究竟是什么呢?

1 StencilBuffer

簡單來說,GPU為每個像素點分配一個稱之為StencilBuffer的1字節(jié)大小的內存區(qū)域,這個區(qū)域可以用于保存或丟棄像素的目的。

我們舉個簡單的例子來說明這個緩沖區(qū)的本質。

在這里插入圖片描述

如上圖所示,我們的場景中有1個紅色圖片和1個綠色圖片,黑框范圍內是它們重疊部分。一幀渲染開始,首先綠色圖片將它覆蓋范圍的每個像素顏色“畫”在屏幕上,然后紅色圖片也將自己的顏色畫在屏幕上,就是圖中的效果了。

這種情況下,重疊區(qū)域內紅色完全覆蓋了綠色。接下來,我們?yōu)榫G色圖片添加Mask組件。于是變成了這樣:

在這里插入圖片描述

此時一幀渲染開始,首先綠色圖片將它覆蓋范圍都涂上綠色,同時將每個像素的stencil buffer值設置為1,此時屏幕的stencil buffer分布如下:

在這里插入圖片描述

然后輪到紅色圖片“繪畫”,它在涂上紅色前,會先取出這個點的stencil buffer值判斷,在黑框范圍內,這個值是1,于是繼續(xù)畫紅色;在黑框范圍外,這個值是0,于是不再畫紅色,最終達到了圖中的效果。

所以從本質上來講,stencil buffer是為了實現(xiàn)多個“繪畫者”之間互相通信而存在的。由于gpu是流水線作業(yè),它們之間無法直接通信,所以通過這種共享數(shù)據(jù)區(qū)的方式來傳遞消息。

理解了stencil的原理,我們再來看下它的語法。在unity shader中定義的語法格式如下
(中括號內是可以修改的值,其余都是關鍵字):

Stencil
{
	Ref [_Stencil]//Ref表示要比較的值;0-255
	Comp [_StencilComp]//Comp表示比較方法(等于/不等于/大于/小于等);
	Pass [_StencilOp]// Pass/Fail表示當比較通過/不通過時對stencil buffer做什么操作
			// Keep(保留)
			// Replace(替換)
			// Zero(置0)
			// IncrementSaturate(增加)
			// DecrementSaturate(減少)
	ReadMask [_StencilReadMask]//ReadMask/WriteMask表示取stencil buffer的值時用的mask(即可以忽略某些位);
	WriteMask [_StencilWriteMask]
}

翻譯一下就是:將stencil buffer的值與ReadMask與運算,然后與Ref值進行Comp比較,結果為true時進行Pass操作,否則進行Fail操作,操作值寫入stencil buffer前先與WriteMask與運算。

2 mask的源碼實現(xiàn)

了解了stencil,我們再來看mask的源碼實現(xiàn)

由于裁切需要同時裁切圖片和文本,所以Image和Text都會派生自MaskableGraphic。

如果要讓Mask節(jié)點下的元素裁切,那么它需要占一個DrawCall,因為這些元素需要一個新的Shader參數(shù)來渲染。

如下代碼所示,MaskableGraphic實現(xiàn)了IMaterialModifier接口, 而StencilMaterial.Add()就是設置Shader中的裁切參數(shù)。

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;
            }
            // 如果我們用了Mask,它會生成一個mask材質,
            Mask maskComponent = GetComponent<Mask>();
            if (m_StencilValue > 0 && (maskComponent == null || !maskComponent.IsActive()))
            {
                //設置模板緩沖值,并且設置在該區(qū)域內的顯示,不在的裁切掉
                var maskMat = StencilMaterial.Add(toUse,  // Material baseMat
                    (1 << m_StencilValue) - 1,            // 參考值
                    StencilOp.Keep,                       // 不修改模板緩存
                    CompareFunction.Equal,                // 相等通過測試
                    ColorWriteMask.All,                   // ColorMask
                    (1 << m_StencilValue) - 1,            // Readmask
                    0);                                   //  WriteMas
                StencilMaterial.Remove(m_MaskMaterial);
                //并且更換新的材質
                m_MaskMaterial = maskMat;
                toUse = m_MaskMaterial;
            }
            return toUse;
        }

Image對象在進行Rebuild()時,UpdateMaterial()方法中會獲取需要渲染的材質,并且判斷當前對象的組件是否有繼承IMaterialModifier接口,如果有那么它就是綁定了Mask腳本,接著調用GetModifiedMaterial方法修改材質上Shader的參數(shù)。

Image.cs 
   protected virtual void UpdateMaterial()
   {
       if (!IsActive())
           return;
       //更新剛剛替換的新的模板緩沖的材質
       canvasRenderer.materialCount = 1;
       canvasRenderer.SetMaterial(materialForRendering, 0);
       canvasRenderer.SetTexture(mainTexture);
   }
   public virtual Material materialForRendering
   {
       get
       {
           //遍歷UI中的每個Mask組件
           var components = ListPool<Component>.Get();
           GetComponents(typeof(IMaterialModifier), components);
           //并且更新每個Mask組件的模板緩沖材質
           var currentMat = material;
           for (var i = 0; i < components.Count; i++)
               currentMat = (components[i] as IMaterialModifier).GetModifiedMaterial(currentMat);
           ListPool<Component>.Release(components);
           //返回新的材質,用于裁切
           return currentMat;
       }
   }

因為模板緩沖可以提供模板的區(qū)域,也就是前面設置的圓形圖片,所以最終會將元素裁切到這個圓心圖片中。

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);
           // stencil只支持最大深度為8的遮罩
           if (stencilDepth >= 8)
           {
               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);
               StencilMaterial.Remove(m_MaskMaterial);
               m_MaskMaterial = maskMaterial;

               var unmaskMaterial = StencilMaterial.Add(baseMaterial, 1, StencilOp.Zero, CompareFunction.Always, 0);
               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, m_ShowMaskGraphic ? ColorWriteMask.All : 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
           StencilMaterial.Remove(m_MaskMaterial);
           m_MaskMaterial = maskMaterial2;

           graphic.canvasRenderer.hasPopInstruction = true;
           var unmaskMaterial2 = StencilMaterial.Add(baseMaterial, desiredStencilBit - 1, StencilOp.Replace, CompareFunction.Equal, 0, desiredStencilBit - 1, desiredStencilBit | (desiredStencilBit - 1));
           StencilMaterial.Remove(m_UnmaskMaterial);
           m_UnmaskMaterial = unmaskMaterial2;
           graphic.canvasRenderer.popMaterialCount = 1;
           graphic.canvasRenderer.SetPopMaterial(m_UnmaskMaterial, 0);

           return m_MaskMaterial;
       }

Mask 組件調用了模板材質球構建了一個自己的材質球,因此它使用了實時渲染中的模板方法來裁切不需要顯示的部分,所有在 Mask 組件的子節(jié)點都會進行裁切。

我們可以說 Mask 是在 GPU 中做的裁切,使用的方法是著色器中的模板方法。

RectMask2D的原理分析

RectMask2D的工作流大致如下:

①C#層:找出父物體中所有RectMask2D覆蓋區(qū)域的交集(FindCullAndClipWorldRect)
②C#層:所有繼承MaskGraphic的子物體組件調用方法設置剪裁區(qū)域(SetClipRect)傳遞給Shader
③Shader層:接收到矩形區(qū)域_ClipRect,片元著色器中判斷像素是否在矩形區(qū)域內,不在則透明度設置為0(UnityGet2DClipping )
④Shader層:丟棄掉alpha小于0.001的元素(clip (color.a - 0.001))

CanvasUpdateRegistry.cs
        protected CanvasUpdateRegistry()
        {
            Canvas.willRenderCanvases += PerformUpdate;
        }
        private void PerformUpdate()
        {
            //...略
            // 開始裁切Mask2D
            ClipperRegistry.instance.Cull();
            //...略
        }
ClipperRegistry.cs
        public void Cull()
        {
            for (var i = 0; i < m_Clippers.Count; ++i)
            {
                m_Clippers[i].PerformClipping();
            }
        }

RectMask2D會在OnEnable()方法中,將當前組件注冊ClipperRegistry.Register(this);

這樣在上面ClipperRegistry.instance.Cull();方法時就可以遍歷所有Mask2D組件并且調用它們的PerformClipping()方法了。

PerformClipping()方法,需要找到所有需要裁切的UI元素,因為Image和Text都繼承了IClippable接口,最終將調用Cull()進行裁切。

RectMask2D.cs
    protected override void OnEnable()
    {
        //注冊當前RectMask2D裁切對象,保證下次Rebuild時可進行裁切。
        base.OnEnable();
        m_ShouldRecalculateClipRects = true;
        ClipperRegistry.Register(this);
        MaskUtilities.Notify2DMaskStateChanged(this);
    }
        public virtual void PerformClipping()
        {
            //...略
            bool validRect = true;
            Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
            bool clipRectChanged = clipRect != m_LastClipRectCanvasSpace;
            if (clipRectChanged || m_ForceClip)
            {
                foreach (IClippable clipTarget in m_ClipTargets)
                    //把裁切區(qū)域傳到每個UI元素的Shader中[劃重點?。。
                    clipTarget.SetClipRect(clipRect, validRect);
                m_LastClipRectCanvasSpace = clipRect;
                m_LastValidClipRect = validRect;
            }
            foreach (IClippable clipTarget in m_ClipTargets)
            {
                var maskable = clipTarget as MaskableGraphic;
                if (maskable != null && !maskable.canvasRenderer.hasMoved && !clipRectChanged)
                    continue;
                // 調用所有繼承IClippable的Cull方法
                clipTarget.Cull(m_LastClipRectCanvasSpace, m_LastValidClipRect);
            }
        }
MaskableGraphic.cs
        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();
            }
        }

性能區(qū)分

Mask組件需要依賴一個Image組件,裁剪區(qū)域就是Image的大小。

Mask會在首尾(首=Mask節(jié)點,尾=Mask節(jié)點下的孩子遍歷完后)drawcall,多個Mask間如果符合合批條件這兩個drawcall可以對應合批(mask1 的首 和 mask2 的首合;mask1 的尾 和 mask2 的尾合。首尾不能合)
Mask內的UI節(jié)點和非Mask外的UI節(jié)點不能合批,但多個Mask內的UI節(jié)點間如果符合合批條件,可以合批。
具體來說:
新建一個場景,默認drawcall是2個;
現(xiàn)在添加一個mask,

在這里插入圖片描述

drawcall+3,Mask導致2個drawcall(第1個和第3個,一頭一尾),Mask下的子節(jié)點Image導致1個drawcall(中間的)
再看下RectMask2D的情況

在這里插入圖片描述

只有新增1個子節(jié)點Image的drawcall, 而RectMask2D不會導致drawcall.

而這時增加一個mask,不要重疊:

在這里插入圖片描述

還是5個drawcall, 沒有變化.
Unity把2個Mask進行了網格合并, 3個drawcall, 分別為[2個Mask頭]、[2個Image]、[2個Mask尾].

這里可以看出, Mask之間是可以進行合并的, 從而不額外增加drawcall

而如果放到一起,

在這里插入圖片描述

**這是因為Unity的合批需要同渲染層級(depth), 同材質, 同圖集, 如果重疊了, depth就不同了, 6個drawcall分別為Mask頭、Mask的Image、Mask尾、Mask(1)頭、Mask(1)的Image、Mask(1)尾.

Mask小結:

1.多個Mask之間可以進行合批(頭和頭合批, 子對象和子對象合批, 尾和尾合批),需要同渲染層級(depth), 同材質, 同圖集.
2.Mask內外不能進行合批.
再試試RectMask2D
把RectMask2D復制一個出來, 然后把位置擺開.**

在這里插入圖片描述

drawcall為4, 因為RectMask2D本身不會導致drawcall, 所以RectMask2D之間不能進行合批.

RectMask2D小結:

1.RectMask2D本身不產生drawcall.
2.不同RectMask2D的子對象不能合批.

對比測試

下面放上我在手機端做的一個簡單的對比測試:

在這里插入圖片描述

可以大致看出,在圖像很大且cpu任務較重的的情況下,mask會對性能有明顯的影響,而在圖像數(shù)量較多時mask略好于RectMask2D
項目鏈接:https://git.woa.com/jnjnjnzhang/MaskVsRectmask2d

注:測試場景中自帶約60個batches。每個mask測試加入同樣的20個mask。圖像數(shù)量少的場景每個mask下掛一個圖像,面積大情況下mask大小不變圖像邊長放大1000倍,數(shù)量多情況下每個mask下掛同樣的100個圖像。瓶頸為drawcall時,每個物體僅有簡單的渲染,在物體上掛載了需要進行復雜運算的腳本。瓶頸為gpu時,去掉腳本,在場景中掛載了后處理渲染提高gpu負載。

參考文章

https://zhuanlan.zhihu.com/p/136505882

以上就是Unity中Mask和RectMask2D組件的對比與測試的詳細內容,更多關于Unity中Mask和RectMask2D的資料請關注腳本之家其它相關文章!

相關文章

  • Unity?UGUI的PointerEventData的介紹及使用

    Unity?UGUI的PointerEventData的介紹及使用

    這篇文章主要為大家介紹了Unity?UGUI的PointerEventData的介紹及使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • C# 使用Free Spire.Presentation 實現(xiàn)對PPT插入、編輯、刪除表格

    C# 使用Free Spire.Presentation 實現(xiàn)對PPT插入、編輯、刪除表格

    小編發(fā)現(xiàn)使用.NET組件——Free Spire.Presentation,在C#中添加該產品DLL文件,可以簡單快速地實現(xiàn)對演示文稿的表格插入、編輯和刪除等操作,具體實現(xiàn)代碼大家參考下本文吧
    2017-09-09
  • C#中緩存的基本使用方法

    C#中緩存的基本使用方法

    項目開發(fā)過程中緩存的應用到處可見,下面這篇文章主要給大家介紹了關于C#中緩存的基本使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-09-09
  • 解決C#中取消方向鍵對控件焦點控制的實現(xiàn)方法

    解決C#中取消方向鍵對控件焦點控制的實現(xiàn)方法

    本篇文章是對C#中取消方向鍵對控件焦點控制的解決方法進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • Unity編輯器資源導入處理函數(shù)OnPostprocessAudio使用案例

    Unity編輯器資源導入處理函數(shù)OnPostprocessAudio使用案例

    這篇文章主要為大家介紹了Unity編輯器資源導入處理函數(shù)OnPostprocessAudio使用案例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • c# 引用類型構造器

    c# 引用類型構造器

    引用類型構造器,是將類型的實例初始化為良好狀態(tài)的一種特殊方法
    2012-10-10
  • C#微信公眾號開發(fā)之自定義菜單

    C#微信公眾號開發(fā)之自定義菜單

    這篇文章介紹了C#微信公眾號開發(fā)之自定義菜單,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-06-06
  • C#中的for和foreach的性能對比

    C#中的for和foreach的性能對比

    這篇文章主要介紹了C#中的for和foreach的性能對比,在C#中,for和foreach是兩種常用的循環(huán)結構,用于迭代集合中的元素,盡管它們在功能上相似,但它們在性能、空間效率和垃圾回收(GC)方面有一些區(qū)別,需要的朋友可以參考下
    2023-10-10
  • 在C#中使用Channels的完整教程

    在C#中使用Channels的完整教程

    這篇文章主要介紹了在C#中使用Channels的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • C#程序優(yōu)化-有效減少CPU占用率

    C#程序優(yōu)化-有效減少CPU占用率

    本文給大家介紹的是C#程序優(yōu)化的小技巧,通過此方法可以有效的降低CPU的占用率,十分的簡單實用,有需要的小伙伴可以參考下。
    2015-06-06

最新評論