UGUI optimization series (2) ------ Rendering concept

This series is to learn the overall solution of siki academy UGUI - optimization (Unity 2019.1.0f2) notes


github address: https://github.com/BlueMonk1107/UGUISolution
Atlas block algorithm address: https://github.com/DaVikingCode/UnityRuntimeSpriteSheetsGenerator
texturepacker official website: https://www.codeandweb.com/texturepacker
UGUI source code download address : https://bitbucket.org/Unity-Technologies/ui/downloads/?tab=downloads


1. Rendering of transparent objects

The rendering of transparent objects does not write the depth. Rendering translucent objects from the back to the front will cause the problem of over draw, which is why the UI is
image.png
drawn in the transparent queue, so the UI in the UI Everything has 28 blending, and all are drawn from the back to the front, so the UI part needs to optimize over draw. When drawing a large number of UI, it will exceed the mobile device CPU rendering capability. Depth value: the greater the depth, the farther away from the


camera
image.png
Small case of depth test: Objects using the same shader will be displayed at the front after ZWrite is turned on

Shader "My Shader/AlphaShadera"
{
    
    
  SubShader{
    
    
        //开启深度测试
        ZWrite on 
        ZTest Always
        Pass{
    
    
        
        Color(1,1,1,1)
        }
    
    }
}

2. Execution logic of grid reconstruction

The vast majority of UI issues boil down to mesh reconstruction issues

1. Rebatch

It is to batch all objects under the canvas for unified processing to generate mesh.
If any object under the canvas changes, it will cause a batch operation to redraw all the objects under the canvas. Therefore, if the UI of a project
with high all the grids under the canvas will be redrawn.

2. Rebuild

For the redrawing of a single object, before canvasbuildbatch will calculate which elements need to be rebuilt,
it is mentioned in the PerformUpdate method of CanvasUpdateRegistry that there are three main parts

1). Reconstruction of Layout

It involves a lot of calculations. For example, nested layout will cause greater performance loss, because it is calculated layer by layer. Only after the calculation of the previous layer can the calculation of the next layer be performed, especially when there are only two or three items. It is better to use code to do calculations and avoid using autosort components

2).Crop

3).Graphic reconstruction

The reason for generating dirty marks for vertices and materials
is very simple. Only when the material is changed, it will be
marked for many reasons. In the Graphic class, SetVerticesDirty (set the vertices in that mark. method), check its references, you can see that there are many references, for example, a small attribute change in image will cause the vertex dirty mark and cause reconstruction. SetAllDirty will be called
image.png
in Graphic's OnEnable and OnDisable, and it will set all Dirty marks are all turned on to
directly control components and the performance loss of objects is large because this method is called, so components and objects will be redrawn when they are displayed and hidden, and the SetActive method of objects should be avoided.

3. Reconstruction execution process

Example of setting color with Image

//  Graphic类中color属性,设置时会先调用SetPropertyUtility.SetColor,成功之后设置脏标记
public virtual Color color {
    
     
     get {
    
     return m_Color; } 
     set {
    
     
     	if (SetPropertyUtility.SetColor(ref m_Color, value)) 
            SetVerticesDirty(); 
     } 
 }
// SetPropertyUtility.SetColor : 颜色修改
 public static bool SetColor(ref Color currentValue, Color newValue)
 {
    
    
     //判断与之前的颜色是都一致,一致则不需要重建
     if (currentValue.r == newValue.r && currentValue.g == newValue.g && currentValue.b == newValue.b && currentValue.a == newValue.a)
         return false;

     currentValue = newValue;
     return true;
 }
//SetVerticesDirty : 调用CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild
//传入需要重建的Graphic
public virtual void SetVerticesDirty()
{
    
    
    if (!IsActive())
        return;

    m_VertsDirty = true;
    CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this);

    if (m_OnDirtyVertsCallback != null)
        m_OnDirtyVertsCallback();
}
//CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild : 将传入的Graphic加入队列中
public static void RegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
    
    
    instance.InternalRegisterCanvasElementForGraphicRebuild(element);
}
private bool InternalRegisterCanvasElementForGraphicRebuild(ICanvasElement element)
{
    
    
    if (m_PerformingGraphicUpdate)
    {
    
    
        Debug.LogError(string.Format("Trying to add {0} for graphic rebuild while we are already inside a graphic rebuild loop. This is not supported.", element));
        return false;
    }

    return m_GraphicRebuildQueue.AddUnique(element);
}
//CanvasUpdateRegistry构造函数注册了Canvas.willRenderCanvases += PerformUpdate
//下一帧canvasrender时会调用PerformUpdate就会进行加入的Graphic重建

In summary:
it is the modification of the object that causes it to be marked with a dirty mark, and then rebuild the Graphic of the marked object on that mark

4. Optimization ideas

The reconstruction of the canvas is the most time-consuming. There are a lot of things underneath, so modifying one of the components will rebuild all the components on it. If there are many other components that will not be modified, such as the background, but Due to the modification of other components, the changes of components that do not need to be modified frequently will cause unreasonable performance consumption
or there are many texts on the screen, then controlling the display and hiding of one of the texts will cause all the texts to be rebuilt, which will cause stuck Dayton, because there are many faces and vertices on the text

3. UI ray part execution logic

The main component is GraphicRaycaster (on Canvas).
Compared with ordinary rays, the biggest difference is that it only responds to objects that inherit the Graphic class and is treated as UI rays.

//主要是Raycast方法 : 代码太多,只截取部分代码
//获取当前canvas所有Graphics组件
 var canvasGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
//响应点击事件的摄像机
var currentEventCamera = eventCamera; 
//获取canvas上设置的targetDisplay : 显示在第几个显示器上
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
    displayIndex = canvas.targetDisplay;
else
    displayIndex = currentEventCamera.targetDisplay;
//将点击位置转换成相对于显示器屏幕的坐标(计算Pos属性)
 //判断点击Pos转换到屏幕坐标的结果是否超出了屏幕
if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
    return;
//根据GraphicRaycaster上的Blocking Mask属性判断射线击中的距离(计算hitDistance属性)
//射线的主要逻辑 : 对当前Graphics的状态进行筛选
Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);

 //graphic深度为-1时不会被绘制在屏幕上,即物体的activeInHierarchy为false
//graphic.raycastTarget : UI元素是否进行射线碰撞响应
//graphic.canvasRenderer.cull : 当前canvasrender是否被剔除,如果为true也不会绘制
if (graphic.depth == -1 || !graphic.raycastTarget || graphic.canvasRenderer.cull)
    continue;
//筛选点击时间是否在graphic的rectTransform上
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera))
    continue;
//筛选Z的租表是否超出了相机的最远面
if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
    continue;
//根据深度对数组进行逆序
s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
totalCount = s_SortedGraphics.Count;
for (int i = 0; i < totalCount; ++i)
    results.Add(s_SortedGraphics[i]);

 //判断当前是否是一个反面目标
if (ignoreReversedGraphics)
//过滤摄像机背后渲染的元素
if (appendGraphic)
//再判断距离
if (distance >= hitDistance)
	continue;
//得到真正的击中目标
resultAppendList.Add(castResult);

Four. UGUI batch

Batching means that the UIs that can merge meshes are combined together. The UI is built from the back to the front, and the order on the panel is the rendering level.

1. Batch rules

First sort all the UIs according to UI Depth, Material ID, Texture ID, and Render Order, and then perform batch processing, that is, judge whether the Material and Texture are the same, and if they are the same, they can be batched. Note here: Those that can be
batched The elements must be next to each other. UI1 and UI2 can be merged, but UI1 and UI3 cannot be batched, because UI2 interrupts the batching, so the
rendering order of the UI is very important to prevent the batching from being interrupted.

2. Example analysis of batching rules

The smaller the depth, the first to render
image.png


1). From the first white object, judge whether the depth is -1 (whether the object is displayed). If it is not displayed, continue to judge the depth of the next object. If it is displayed, judge whether it covers other objects. The picture above shows that white is not covered on other objects, so its depth is 0
2). Text is covered on white. The special thing about text is that the frame may be larger than the mesh,
image.png
so the condition for covering here is not that the outer frames overlap. Instead, it depends on whether the meshes of the two UIs overlap
and if they are covered, determine whether the two can be combined (whether the Material and Texture are the same), here it is obvious that they cannot be combined, so the depth value of the text depth = white depth value + 1 = 1
If many are covered, then the depth value of text = the largest depth value in the covered element + 1
3). red is covered on top of white and text, and red and text cannot be combined, the depth value of text and white The larger one is text, so red depth = text depth + 1 = 2
4). yellow is covered on top of red, and can be batched, and its depth is 2 (note that this is only a batch test, not a real batch operation )
5). blue is covered on top of yellow, and can be batched, and its depth is 2
6). According to the depth, there are three arrays with a depth of 2. At this time, sort the ones with the same depth, Material ID, Texture The IDs are all the same, and finally sorted according to the Render Order, so the final array is: white - text - red - yellow - blue
7). The array will be passed to the batch processing part. After passing in, it will be judged from the first element whether it can be combined with adjacent elements to judge whether it can be combined (where the batch is actually performed), and W and T cannot be combined. , its batch number is 0, similarly the batch number of T is 1, R and Y can be batched together, continue to judge that the next B can also be batched together, so the batch number of RYB is 2

Summary:
1). Traversing all UI elements
2). Sorting the array according to UI Depth, Material ID, Texture ID, and Render Order
3). Filtering out all UI elements with a depth value of -1
4). Judging whether adjacent elements are Combine batches to get the batch number

3.Frame Debug (rendering order viewing tool)

image.png

4. UI module in Profiler: you can check the reason for interrupting batching

image.png
Gameobject Count : The number of gameobjects contained in the current batch

5. Special example of batching

image.png
Batch results: three batches of text, white, and blue. Because the sorted array is W -T -B, this is because the texture ID of W is smaller, so when T and W are sorted, W is in the front. Solve The method is to assign the same texture to the image components of W and B, so that T will not interrupt the batching of W and B

6. Mask cannot be batched together

1).mask will generate 2 drawcalls, which is the reason for its performance consumption

1. Set the template cache
to add a special material to the mask, which is why the batching is interrupted. Because the material is different, set the template cache value of the pixel that needs to be hidden to 0, and set the template cache value of the pixel that needs to be displayed 1, this is the first drawcall
2. After traversing the sub-objects under the mask, the stencil cache will be restored, so another drawcall
material is generated. After the sub-object is rendered, it is judged whether the stencil cache value of the pixel is 1 to decide whether to render , the restoration is the second drawcall

2). The mask will interrupt the batching, because the mask will be given a special material

3). Masks can be batched together (provided that the IDs of the pictures are the same)

Because the mask generates two drawcalls, the two masks can be batched in the set drawcall, and the restored drawcall can be batched together. In general, there are still two drawcalls. Note:


1
). The camera will generate a drawcall
2). The mask component is hung on There must be an image component on the object, otherwise it will not take effect
3). The occluded object is still drawn, which will take up a drawcall, but the mask will remove the drawn pixels
4). Sub-objects under the mask can be batched normally 5
). Two masks can be batched, and the sub-objects of the two masks that can be batched can also be batched.
image.png
6). When the next mask overlaps with the hidden sub-objects of the previous mask, because The sub-object hidden by the previous mask interrupted the batching of the two masks, so the next mask will calculate the drawcall separately and cannot be batched
image.png

7.RectMask2D

It is more performance-saving than mask
1). RectMask2D itself does not occupy drawcall. In fact, it only uses the area of ​​​​the current object to crop the sub-object. It does not occupy drawcall itself.
2). The vertices and faces of the object that are completely clipped in the mask are still drawn normally. However, objects that are completely clipped by RectMask2D will not draw vertices and faces, and will not occupy drawcalls
3). Objects that are completely clipped by RectMask2D will not participate in the depth calculation between other UIs, and cannot be batched.
4). Subclasses under RectMask2D Objects can be batched, or can be batched with a single object (there is no RectMask2D restriction above), but it cannot be batched with another sub-object under RectMask2D

Compared with mask:
The biggest problem with RectMask2D is that two RectMask2Ds cannot be batched together, so different masking methods should be selected according to different usage scenarios
1. There are multiple masks, and the sub-objects under the mask are You can choose to use mask 2 in batches
. If there is only one mask and it is only a mask for the UI, using RectMask2D will save performance

Five. Filling rate

The more times a pixel is drawn, the brighter the color
Try to avoid UI coverage, and also avoid separating all content.
Use special-shaped pictures for UI pictures, because this can reduce the effective area and coverage. For example, a circular picture is usually cut into a rectangle. , there is no need to display a transparent area around it, but this will increase the effective area and increase the possibility of UI coverage

1). You can use Jiugongge to hollow out the middle part to reduce the filling rate

image.png


The Image component needs to set the image type:
image.png
irregular hollowing requires the use of scripts, which will be explained in detail later

2). Deprecate the Text effect component

For example, outline and shadow will increase a large number of vertices and faces, and the effect is not ideal.
image.png
Change the value to a larger value and you can see that there are four new texts on the left, which means that he has increased the number of vertices and faces by 4 times in order to achieve the stroke effect. The number of faces
can use the Text Mesh Pro plug-in to achieve stroke and other effects

Guess you like

Origin blog.csdn.net/dengshunhao/article/details/105404086