当前位置: 首页 > article >正文

【Unity3D】UGUI Canvas画布渲染流程

目录

Screen Space - Overlay

Screen Space - Camera

World Space  

UI合批分析(建议不看 直接看FrameDebugger测试)

优化UI合批

1、Image图片纹理不同导致合批失败

2、文本和图片相交以及排序对合批的影响

3、Mask对合批的影响(情况很复杂)

4、Canvas对合批的影响


参考文档:画布 - Unity 手册 

Canvas组件:画布组件是进行 UI 布局和渲染的抽象空间。所有 UI 元素都必须是附加了画布组件的游戏对象的子对象。

Screen Space - Overlay


        参数:
        Render Mode 渲染模式:Screen Space - Overlay、Screen Space - Camera、World Space。
        Pixel Perfect:是否应该无锯齿精确渲染 UI?
        Sort Order:渲染层级
        Target Display:输出屏幕目标Display 1
        Additional Shader Channels:额外的着色器通道

        画布渲染于所有物体上方,即最后渲染。并且不归于摄像机渲染,因此即使没有摄像机也能渲染出UI。
        
        画布宽高跟随屏幕宽高,画布大小固定(1,1,1),位置屏幕居中,覆盖整个屏幕。
        画布下的子UI需使用锚点适配来适应不同大小的屏幕,因屏幕变化后画布也会变化。

Screen Space - Camera

        画布内容归于摄像机进行渲染。与正常渲染物体一样。       
        画布宽度和高度跟随屏幕。大小会根据UI摄像机参数而适配变化。

Render Camera: UI摄像机
Plane Distance:画布距离UI摄像机的长度
Sorting Layer:可自定义大层级
Order In Layer:子层级

观察Frame Debugger分析
        
        由于有2个摄像机,因此有2个Render.OpaqueGeometry以及2个Camera.RenderSkybox。若将UI摄像机的Clear Flags从Skybox改为DepthOnly,则会减少1个Camera.RenderSkybox。
        UI渲染主要位于Render.TransparentGeometry中
        

Image默认材质着色器UI/Default会将渲染目标交到TempBuffer 355 1920*1080目标,着色器是支持SrcAlpha OneMinusSrcAlpha的常见透明因子混合,并存在深度测试小于等于(<=),不写入深度。(一般情况透明物体是不开启深度测试的,而这里开启的目的是为了能被3D物体遮挡)

规范做法:
主摄像机屏蔽UI层渲染

UI摄像机仅渲染UI层

将3D物体设置到UI层

此时若想把Cube渲染在UI之上,那么就是直接放到Canvas物体前面即可。
Canvas距离UI摄像机的距离由下图参数Plane Distance决定(默认100)



若放在Canvas后面则是被遮挡。


UI摄像机不一定是正交的,即使换成透视视角,依然是保持正常的UI显示(画布会缩放大小)并3D物体以透视视角渲染出来。

但透视视角会有更大的开销用于裁剪,一般情况下都是正交视角节省开销。
注意事项:不要试图用主摄像机去渲染在参与UI排序的3D物体,若使用主摄像机渲染,这个3D物体是绝对位于UI之下的,因为主摄像机的深度缓冲区被UI摄像机清空了,UI摄像机开始渲染时所有UI像素都会正常通过深度测试,所以就肯定会渲染在3D物体之上。正常就应该是交给UI摄像机渲染,UI摄像机渲染时,正常3D物体会先被渲染,深度写入后,UI物体再参与渲染时就会正常通过深度测试将被3D物体遮挡的像素过滤掉,呈现出3D物体在UI之上的。

World Space  

        它同样可以指定一个摄像机专门负责渲染画布。但区别于Screen Space - Camera,画布的位置、旋转、缩放均不会随着屏幕、摄像机变化而变化,它就变成和普通的3D平面物体一样看待。

UI合批分析(建议不看 直接看FrameDebugger测试)

实际上我们不太需要这个合批分析工具,因为太难理顺各种UGUI的合批规则了,而且很多参数都很难获取到,即使获取到了也不一定准确,总之最好的分析工具还是打开Frame Debugger 然后手动调下就好了。 (已放弃维护)

原理及工具参考:【Unity】静态优化工具支持UGUI合批分析、AB包冗余分析、预制体使用资源情况分析_ab包资源冗余-CSDN博客

合批分析使用必须运行游戏之后再使用才正常,因为有很多数据都是运行后Unity才正常设置的,比如图集纹理,不运行是不会使用图集的。 

合批工具问题:
1、相交算法有问题,原本用的是rect1.position 实际要用rect1.localPosition,兼容锚点和中心点不是中心的情况。 
2、解决问题1后,其实发现要真正计算相交的是网格,而不是RectTransform区域,不同组件的网格获取方式不同,如图片、文本,其他组件还未支持,比如Spine等等,所以这个工具很难维护...
RectTransformExtensions.cs 代码修改和新增如下部分。

   public static void CalcualteGraphicRect(Graphic graphic, ref Rect rect)
        {
            Vector2 size = Vector2.zero;

            Text textComponent = graphic as Text;
            if (textComponent != null)
            {
                List<UIVertex> textUIVertexList = new List<UIVertex>();
                textUIVertexList = textComponent.cachedTextGenerator.verts as List<UIVertex>;
                //textComponent.cachedTextGenerator.GetVertices(textUIVertexList);                
                float minX = float.MaxValue;
                float maxX = float.MinValue;
                float minY = float.MaxValue;
                float maxY = float.MinValue;

                //使用center计算出中心点后,你还需要转空间,这个转空间一直出问题,所以不采用这种办法
                //Vector3 center = Vector3.zero;

                Vector3 minPos = Vector3.zero;
                Vector3 maxPos = Vector3.zero;

                string str = "";
                for (int i = 0; i < textUIVertexList.Count; i++)
                {
                    Vector3 pos = textUIVertexList[i].position;
                    minX = Mathf.Min(minX, pos.x);
                    maxX = Mathf.Max(maxX, pos.x);
                    minY = Mathf.Min(minY, pos.y);
                    maxY = Mathf.Max(maxY, pos.y);

                    minPos = Vector3.Min(minPos, pos);
                    maxPos = Vector3.Max(maxPos, pos);

                    //center += pos;
                    str += pos + " ";
                }
                size.x = maxX - minX;
                size.y = maxY - minY;
                //center /= textUIVertexList.Count; 

                //转空间会有问题 不采用计算中心点 而是直接算出以文本空间(中心点是文本坐标点rect.position)的网格中心点。
                //转空间就是直接用 网格中心点 + 文本坐标点,将网格中心点转到 文本所在空间(Canvas空间系下) 将其作为Rect区域的中心点去计算网格相交

                Vector2 rawPos = rect.position;
                //解决偏移问题,根据MinX MaxX  MinY MaxY 求出局部中心点, 用文本中心点rect.position加上局部中心点就能得到真正的文本网格中心点
                Vector2 offset = new Vector2(((minX + maxX) / 2.0f), ((minY + maxY) / 2.0f));
                rect.position += offset;
                Debug.Log("rawPos:" + rawPos + " afterPos:" + rect.position + "offset:" + offset);
                Debug.Log(graphic.gameObject.name + " " + size + "\n" + str);
            }
            else
            {
                Image imageComponent = graphic as Image;
                RectTransform r = imageComponent.GetComponent<RectTransform>();

                //只能编辑器读资源来获取MeshType,其他方式都试过不行...
                string path = AssetDatabase.GetAssetPath(imageComponent.sprite);
                TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
                TextureImporterSettings settings = new TextureImporterSettings();
                textureImporter.ReadTextureSettings(settings);

                //if (imageComponent.overrideSprite.packingMode == SpritePackingMode.Tight) //不能用这个packingMode判断, 会严格按照Sprite的MeshType和图集的Tight Packing决定。
                if (settings.spriteMeshType == SpriteMeshType.Tight)
                {                    
                    Vector3[] worldPosArr = new Vector3[4];
                    r.GetWorldCorners(worldPosArr);

                    float minX = float.MaxValue;
                    float maxX = float.MinValue;
                    float minY = float.MaxValue;
                    float maxY = float.MinValue;
                    string str = "";
                    for (int i = 0; i < worldPosArr.Length; i++)
                    {
                        Vector3 pos = worldPosArr[i];
                        minX = Mathf.Min(minX, pos.x);
                        maxX = Mathf.Max(maxX, pos.x);
                        minY = Mathf.Min(minY, pos.y);
                        maxY = Mathf.Max(maxY, pos.y);
                        str += pos + " ";
                    }
                    size.x = (maxX - minX) * 100;
                    size.y = (maxY - minY) * 100;
                    Debug.Log("[Tight] " + graphic.gameObject.name + " " + size + "\n" + str);
                }
                else
                {
                    size = r.rect.size;
                    Debug.Log("[FullRect]" + graphic.gameObject.name + " " + size);
                }
            }

            rect.size = size;
        }

        /// <summary>
        /// 适用
        /// </summary>
        /// <param name="rect1"></param>
        /// <param name="rect2"></param>
        /// <returns></returns>
        public static bool IsRectTransformOverlap(RectTransform rect1, RectTransform rect2)
        {
            //获取真实网格大小宽度高度
            Vector2 size1 = rect1.rect.size;
            Vector2 size2 = rect2.rect.size;
            Vector2 pos1 = rect1.localPosition;
            Vector2 pos2 = rect2.localPosition;

            Rect r1 = new Rect(pos1, size1);
            Rect r2 = new Rect(pos2, size2);

            Graphic g1 = rect1.GetComponent<Graphic>();
            Graphic g2 = rect2.GetComponent<Graphic>();
            if (g1 as Text || g1 as Image)
            {
                CalcualteGraphicRect(g1, ref r1);
            }
            if (g2 as Text || g2 as Image)
            {
                CalcualteGraphicRect(g2, ref r2);
            }

            Debug.Log(rect1.gameObject.name + " rect1: " + r1);
            Debug.Log(rect2.gameObject.name + " rect2: " + r2);

  
            float rect1MinX = r1.x - r1.width / 2;
            float rect1MaxX = r1.x + r1.width / 2;
            float rect1MinY = r1.y - r1.height / 2;
            float rect1MaxY = r1.y + r1.height / 2;

            //Debug.Log($"r1 pos:{pos1}, rect:{rect1.rect}");


            float rect2MinX = r2.x - r2.width / 2;
            float rect2MaxX = r2.x + r2.width / 2;
            float rect2MinY = r2.y - r2.height / 2;
            float rect2MaxY = r2.y + r2.height / 2;

            //Debug.Log($"r2 pos:{pos2}, rect:{rect2.rect}");

            bool xNotOverlap = rect1MaxX <= rect2MinX || rect2MaxX <= rect1MinX;
            //Debug.Log($"r1 maxX:{rect1MaxX}, r2 minx:{rect2MinX}, rect1MaxX <= rect2MinX:{rect1MaxX <= rect2MinX}");
            //Debug.Log($"r2 maxX:{rect2MaxX}, r1 minx:{rect1MinX}, rect2MaxX <= rect1MinX:{rect2MaxX <= rect1MinX}");
            //Debug.Log($"xNotOverlap:" + xNotOverlap);

            bool yNotOverlap = rect1MaxY <= rect2MinY || rect2MaxY <= rect1MinY;
            //Debug.Log($"r1 maxY:{rect1MaxY}, r2 minY:{rect2MinY}, rect1MaxY <= rect2MinY:{rect1MaxY <= rect2MinY}");
            //Debug.Log($"r2 maxY:{rect2MaxY}, r1 minY:{rect1MinY}, rect2MaxY <= rect1MinY:{rect2MaxY <= rect1MinY}");
            //Debug.Log($"yNotOverlap:" + yNotOverlap);

            bool notOverlap = xNotOverlap || yNotOverlap;

            return !notOverlap;
        }

图片网格就是注意Tight紧凑(Mesh Type)情况,网格区域是比RectTransform小的(大部分情况),而文本网格则是肯定会比RectTransform小,而且要特别注意不要只盯着一个文本符号网格的高度看,要看整体符号网格,比如下图:你只有发现全部都没相交图片网格才是不相交,否则只要有1个相交了,那就打断合批(节点交叉摆放情况才会,下面优化部分说明)

优化UI合批

1、Image图片纹理不同导致合批失败

0/0/0/0 代表 合批ID/深度/材质ID/贴图ID(仅需关注合批ID)


解决办法:将2张图片使用图集合并成一张图集图片(SpriteAtlas)然后Unity就会是使用图集图片去渲染,而不是2张图片,至于具体渲染图集图片的那张图片由Unity分析出图片所在图集图片的UV范围而采集纹理。
需要开启Sprite Packer Mode是Always Enabled
位于Project Settings - Editor 

2、文本和图片相交以及排序对合批的影响

仅有文本放到图片上且网格相交会打断文本合批,如果文本位于图片下面(被图片遮挡)则不会有影响。
原本文本和图片网格不相交(注意是网格,不是RectTransform的区域Rect)

一旦相交网格,就会打断合批,检查发现打断的是文本合批,图片合批正常执行。

其实也就是常见的按钮就是文本放到了图片上面的,如果有多个会如下。。。

解决办法:将文本全部挪到图片节点的下面,不要出现交叉图片和文本节点的形式。(图文节点交叉形式)

3、Mask对合批的影响(情况很复杂)

Show Mask Graphic:是否显示裁剪图片,勾选显示,否则不显示。这个不会影响裁剪、以及裁剪合批情况。

情况一:正常裁剪(Mask与其他Mask或图片不相交;Mask内图片与其他不相交)(渲染4次)

一共2个图集图片Image、其中一个图片(树)是白色图片Mask的子物体被裁剪。
Mask裁剪会将Mask内与Mask外图片合批打断,所以2张图片需要2次渲染;
1个Mask组件自身会进行一次Mask遮罩模板写入渲染,一次Mask遮罩模板清除渲染,共2次。
每个Mask组件之间的这个模板写入渲染和模板清除渲染也是可以合批的,稍后说明。
FrameDebugger情况如下:

自上而下分别是:
Mask图片自身渲染+Mask模板写入渲染
花图片渲染
树图片渲染
Mask模板清除渲染

情况二:将花图片拖拽到树和Mask图片的位置,不过花图片层级是低于树和Mask图片的,与情况一相比没有任何合批影响。(即不会增加DC次数)(渲染4次)

但是,渲染顺序自上而下变化:

Mask写入

Mask清除

情况三:将花图片也使用一个新的Mask图片裁剪(渲染3次)


2个Mask模板写入(合批进行)、2个Mask下同图集图片合批、2个Mask模板清除(合批进行)共3次渲染。也就是增加Mask反而能让DC下降。

情况四:花图片网格与其他Mask图片网格相交时,合批将发生巨大变化,由3变6(渲染6次)

严格来说是低层Mask下的图片网格与高层Mask网格相交

自上而下:
花Mask裁剪模板写入
花图片
树Mask裁剪模板写入
花Mask裁剪模板清除
树图片
树Mask裁剪模板清除

也就是说,全部都没有进行合批,目前已知情况三是最好的,情况四是最差的。

情况五:将花的Mask网格与另一个Mask下的树网格相交。(渲染5次)
严格来说是低层Mask网格与高层Mask下的图片网格相交


自上而下:
2个Mask模板写入

花Mask模板清除

树Mask模板清除

情况六:花的Mask网格与树的Mask网格相交(渲染6次)

自上而下:
花Mask裁剪模板写入
花图片
花Mask裁剪模板清除
树Mask裁剪模板写入
树图片
树Mask裁剪模板清除

情况七:将花转移到树的Mask下(作为子物体),花的Mask图片物体删除。(渲染3次)

只要Mask下的图片网格与Mask网格相交,则都会合批图片。
自上而下:Mask模板写入、图集图片合批、Mask模板清除

情况八:在情况七基础上,将花的网格不与Mask网格相交(将花移动到Mask裁剪区域之外)(渲染4次)

自上而下:花、mask写入、树、mask清除

情况九:将Mask下的所有图片移动到Mask网格之外。(渲染3次)

自上而下:mask写入、图集图片合批、mask清除

总结:情况三、情况七、情况九是最好的,仅3次,除了情况四、情况五、情况六是比较差的。
需要着重处理比较差的情况,往最好的情况去处理。

4、Canvas对合批的影响

与Mask的影响类似,但不会有Canvas自身的渲染(例如:模板写入、模板清除操作)但它会打断非Canvas内的上一个物体与下一个物体的合批。而每个Canvas内的元素合批情况与原本一样。

自上而下:(最差的情况)
花Canvas_Flwoer(1)
Canvas_Flower(1) 的 文本1 Text(1)
Canvas_Flower(1) 的 Image
Canvas_Flower(1) 的 文本2 Text(2)
树 Tree
花Canvas_Flower(2)
花Canvas_Flower(2) 的 文本 Text
树 Tree(1)

可发现Canvas和Canvas之间的Tree虽然是同样的图片,但没有合批,因为被Canvas打断,而Canvas_Flower(1)内的文本2个也没有合批,因为被Image打断。

优化后如下:

不要有交叉Canvas和Canvas之外物体的节点情况。
Canvas内的直接使用原本优化UI合批方法进行优化即可,即将文本和图片节点不要出现交叉。


http://www.kler.cn/a/469421.html

相关文章:

  • 使用图像过滤器在 C# 中执行边缘检测、平滑、浮雕等
  • asammdf python库解析MF4文件(一)cut and filter
  • 解锁编程智慧:23种设计模式案例分享
  • 《Mcal》--MCU模块
  • 形态学:图像处理中的强大工具
  • Docker图形化界面工具Portainer最佳实践
  • 【NLP高频面题 - Transformer篇】Transformer编码器有哪些子层?
  • 【蓝桥杯】43709.机器人繁殖
  • (初学者)STM32 MP157中LED触发器笔记
  • Go小技巧易错点100例(二十一)
  • BGP(Border Gateway Protocol)路由收集器
  • 下载word报表
  • reactor中的并发
  • Java(day3)
  • 使用JMeter对Linux生产服务器进行压力测试
  • Golang中的大端序和小端序
  • 五类推理(逻辑推理、概率推理、图推理、基于深度学习的推理)的开源库 (二)
  • 51单片机——蜂鸣器模块
  • SpringCloud源码-nacos
  • 图片验证码
  • 解锁kafka组件安全性解决方案:打造全方位安全防线
  • 解决TortoiseGit 在Windows系统中文件不显示状态图标的问题
  • Elasticsearch操作笔记版
  • HarmonyOS学习大纲
  • 2.5万字 - 用TensorFlow和PyTorch分别实现五种经典模型
  • Go语言的 的接口(Interfaces)核心知识