UGUI优化篇
- 1. 基础概念
- 2. 重要的类
- 1. MaskableGraphic类继承了IMaskable类
- 2. 两种遮罩的实现区别
- RectMask2D
- Mask
- 3. 渲染部分知识
- 深度测试
- 深度测试的工作原理
- 渲染队列
- 透明物体在渲染时怎么处理
- 为什么透明效果会造成性能问题
1. 基础概念
- 所有UI都由网格绘制的
- 如image由两个三角形构成,四个顶点
- drawcall(绘制调用)是指向GPU发送绘制指令的过程。每个draw call都是一次向图形处理器提交渲染命令的操作。具体来说,每当Unity需要绘制一个物体或一批物体时,它会生成一个或多个draw call。
- 填充率(Fill Rate)是指图形处理器(GPU)能够每秒填充像素的数量。它通常用来衡量GPU的渲染能力和性能。
- 屏幕的分辨率决定了需要渲染的像素数量。更高的分辨率意味着需要填充更多的像素,因此会影响填充率。
- Overdraw指的是在渲染过程中多次绘制同一个像素的现象。这通常发生在复杂的场景中,特别是当许多物体重叠或者遮挡彼此时。
- 在Overdrall模式下,可以看到重叠的部分,两次drawcall,同一像素被绘制两次
- 批处理(Batching)是一种优化技术,用于减少向图形处理器(GPU)发送的绘制调用(draw call)数量。每个draw call都是向GPU发送绘制命令的操作,而过多的draw call会增加CPU和GPU的负载,可能导致性能下降。
- Unity中有静态批处理和动态批处理。静态批处理适用于不会移动或改变的物体,而动态批处理适用于能够共享相同材质的移动物体。这些技术可以减少draw call数量,提高性能。
- 在这个例子中,两个image会有两次绘制调用drawcall,重叠的部分绘制的两次,可以将两个网格合并成一个网格,最后,在渲染时,只需要发送一个绘制调用drawcall来渲染一个网格,而不是每个image一个绘制调用。这样就大大减少了CPU发送绘制命令的开销,也减少了GPU处理多个绘制调用的开销。
2. 重要的类
1. MaskableGraphic类继承了IMaskable类
- 继承MaskableGraphic类的组件都可以被mask进行一个遮挡
2. 两种遮罩的实现区别
RectMask2D
IClipper接口是有一个剪切的方法
public interface IClipper
{
/// <summary>
/// Function to to cull / clip children elements.
/// </summary>
/// <remarks>
/// Called after layout and before Graphic update of the Canvas update loop.
/// </remarks>
void PerformClipping();
}
Mask
在Mask有一个方法GetModifiedMaterial,用模板缓存实现遮罩
/// Stencil calculation time!
public virtual Material GetModifiedMaterial(Material baseMaterial)
{
if (!MaskEnabled())
return baseMaterial;
var rootSortCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform);
var stencilDepth = MaskUtilities.GetStencilDepth(transform, rootSortCanvas);
if (stencilDepth >= 8)
{
Debug.LogWarning("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;
}
- 因为两种遮罩实现不同,进行UI优化也不同
3. 渲染部分知识
深度测试
深度测试(Depth Testing)是指用于确定哪些像素应该显示在其他像素之上的一种技术。起到剔除优化的作用。
深度测试的工作原理
通过比较每个像素的深度值(Depth Value)来确定像素是否应该被渲染。深度值是从相机到像素的距离的反映,通常是从0(最近)到1(最远)的范围。具体来说:
- 深度缓冲(Depth Buffer):
- 在渲染每个像素之前,Unity会将每个像素的深度值写入到一个称为深度缓冲(Depth Buffer)的特殊缓冲区中。
- 深度缓冲是一个与屏幕大小相同的二维数组,每个元素存储与像素位置对应的深度值。
- 深度测试的执行:
- 当一个物体的像素要渲染到屏幕上时,Unity会首先将该像素的深度值与深度缓冲中对应像素的深度值进行比较。
- 如果当前像素的深度值小于深度缓冲中的深度值(即当前像素更接近相机),则该像素被渲染,并更新深度缓冲。
- 如果当前像素的深度值大于深度缓冲中的深度值(即当前像素在深度上在后面),则该像素被丢弃,不会渲染到屏幕上。
渲染队列
渲染队列(Rendering Queue)是一个用来管理和排序需要被渲染的物体、几何体或者图形效果的机制。渲染队列的目的是为了优化渲染流程,确保图形处理单元(GPU)以最高效的方式处理和渲染场景中的各个元素。
透明物体在渲染时怎么处理
透明物体在渲染时主要采取从后向前(back-to-front)的渲染顺序。这是因为透明物体不会写入深度缓冲(depth buffer),也就是说它们不会遮挡其他物体,因此渲染的顺序决定了它们如何叠加在一起。
- 排序:首先,将所有需要渲染的透明物体按照它们在场景中的深度(通常是距离观察者的距离)进行排序。
- 从后向前渲染:按照排序后的顺序,从距离观察者最远的透明物体开始渲染,逐渐向前渲染到距离观察者最近的物体。
- 混合:在渲染每个透明物体时,使用Alpha混合(Alpha blending)技术将当前物体的颜色与已经渲染到屏幕上的颜色进行混合。这通常涉及到将当前物体的颜色和Alpha值(表示透明度的值)与屏幕上的颜色和Alpha值进行某种计算,得到新的颜色和Alpha值。
- 关闭深度写入:由于透明物体不会写入深度缓冲,因此需要确保在渲染透明物体时关闭深度写入功能,以避免它们错误地遮挡其他物体。
- 开启深度测试:虽然透明物体不会写入深度缓冲,但仍然需要开启深度测试来确保它们正确地叠加在其他不透明的物体之上。
通过这种方法,可以确保透明物体在渲染时能够正确地叠加在一起,并产生正确的视觉效果。
为什么透明效果会造成性能问题
透明效果会造成性能问题,主要是因为它们涉及到Alpha混合(Alpha blending)和渲染顺序(通常是从后向前)的处理,这会导致以下几个方面的性能开销:
- Overdraw(过度绘制):当一个像素被多次绘制时,就发生了overdraw。对于不透明物体,由于深度测试(depth testing)的存在,只有离观察者最近的物体会被绘制到屏幕上,因此overdraw通常不是问题。但对于透明物体,由于它们不会写入深度缓冲,并且需要按照从后向前的顺序渲染,所以每个透明物体都会与已经绘制在屏幕上的颜色进行混合,这可能导致一个像素被多次绘制和混合。
- 排序开销:为了正确渲染透明物体,需要将它们按照从后向前的顺序进行排序。这个排序过程可能涉及到大量的计算,特别是对于动态场景中的大量透明物体。
- Alpha混合的计算开销 :Alpha混合是一个相对复杂的计算过程,需要将当前像素的颜色和Alpha值与屏幕上的颜色和Alpha值进行混合。这个过程比简单的不透明渲染要复杂得多,因此会增加GPU的计算负担。