Unity - Manual: Graphics performance and profiling

CPU压力

Batching

在GPU渲染前,CPU会把数据按batch发送给GPU,每发送一次,都是一个drawcall,GPU在渲染每个batch的时候,会切换渲染状态,这里的渲染状态指的是:影响对象在屏幕上的外观的渲染属性或材质,比如:材质球,贴如,颜色,渲染模式(透明、半透明)等

  • Unity中的合批方式
    • 优先级:
      • SRP Batcher / Static Batching
      • GPU Instancing
      • Dynamic Batching
StaticBatching:
  • static batching 的目的不是减少drawcall ,而是减少渲染状态的改变,因为在渲染之前,要设置该物体的各种渲染属性,如果是同一个批次,只设置一次就好了。
  • static batching 之所以不会减少drawcall,是因为静态合批的物体是可以被裁剪的,它只是合并了顶点数组,但是顶点索引还是单独的,这样就可以根据索引值来决定绘制哪些submesh,比如10个网格 static batch 成了一个网格,但是第5个网格没在视锥体内,则有2个drawcall,第1-4个submesh,第6-10个submesh,第5个是被裁剪了,虽然是2个drawcall,但是渲染状态只设置1次
  • 如果不静态合批,虽然这10个mesh 的材质球、贴图都是一样的,也会分10个drawcall 去绘制,也就是10个batch ,要设置10次渲染状态。

细节补充:
  • 1. 在编辑器中静态合批,Unity不会使用任何运行时的CPU资源来生成网格数据
  • 2. 运行时进行静态合批会有一次较高的CPU峰值,可能会造/成一次卡顿
  • 3. 完成静态合批后,对象成为一个整体,且为静态,无法修改Transform属性
  • 4. 运行时可以对合批后的根对象staticBatchRoot进行Transfform属性的修改,
  • 5. 不过运行时合批的对象需要开启Read/Write选项
     

手动网格合并
原理:
  • 手动合并网格,和静态合批差不多,但是它不能裁剪submesh,如果视野中只有单个submesh,也会绘制整个mesh
  • 动态合批主要针对一些老旧的机型,它的原理是把许多小的模型,在CPU上收集他们的数据,比如渲染状态什么的,一次性提交到GPU,减少drawcall,但是在新的机型上,多个drawcall花费的时间,可能比收集数据还要少,动态合批的目的是为了减少CPU的耗时,但是合批本身就消耗CPU
  • 限制条件比较多:模型的顶点数不能超过900,如果包含了多个属性,比如uv1,uv2,顶点书就不能超过900/属性个数
  • 比如应用到:网格,粒子系统
GPU Instance
原理:
  • Unity对于所有符合要求的对象,将其位置、缩放、uv偏移、lightmappindex等相关信息一次性存到Constant Buffer常量缓冲区中,当一个对象作为实例进入渲染流程时,会根据传入的Instance ID来从显存中取出对应的信息,用于后续的渲染阶段,不用每次都发送数据到GPU,也就是减少了渲染状态的设置和drawcall的调用,适用于静态模型,比如场景中的树,花草
使用方法:
  • 在材质的Inspector面板中勾选Enable Instancing的选项
  • 使用Graphics.DrawMeshInstanced或Graphics.DrawMeshInstancedirdirect 手动调用GPU instance
  • 通过MaterialPropertyBlock给不同的instance,设置不同的表现
  • 和SRPBatcher不兼容,不支持Skinned Mesh Render,也就是不支持带骨骼动画的对象

MaterialPropertyBlock
  • 使用MaterialPropertyBlock设置随机颜色,不会打断合批,如果直接用material.setcolor 则会打断合批,因为那是一个单独的材质球,和GPU Instance适配,和SRP Batcher不适配
使用方法:
 MaterialPropertyBlock mpb = new MaterialPropertyBlock();

 mpb.SetColor("_Color", Color.red); // 设置颜色属性
 mpb.SetFloat("_Glossiness", 0.5f); // 设置浮点数属性
 mpb.SetTexture("_MainTex", someTexture); // 设置纹理属性

// 获取 Renderer 并应用 MaterialPropertyBlock
 Renderer renderer = GetComponent<Renderer>();
 renderer.SetPropertyBlock(mpb);


- 适用于需要频繁修改材质属性的情况,而不需要创建新的材质实例。
- 使用MaterialPropertyBlock不会影响材质的全局属性,只会影响应用了该MaterialPropertyBlock的特定Renderer
- 确保你的Shader中有对应的属性名,例如 _Color、_Glossiness、_MainTex 等。

缺点:
  • 优先级比较低、提交一次drawcall 耗时比平常要多一点
优点:
  • 相比静态合批不会带来额外的内存压力
  • 相比动态合批没有严格的顶点限制
  • 与MaterialPropertyBlock很适配,不会打断合批
适用场景:
  • 需要画大批相同Mesh的场景,如草海、树林之类的
SRP Batcher
  • 对于使用相同的着色器变体的材质,即使材质球不一致,只要shader一致就可以,CPU会把这些对象的渲染状态,一次性提交到GPU缓存起来,这次提交称为一个SRPBatcher
  • 只要该对象的材质球不改变,GPU缓存就不用更新,所以CPU就不用每次都设置这些对象的渲染状态了,只调用drawcall就可以了
  • 通过frame debugger 可以查看一共进行了多少次SRPBatcher,每一个SRPBatcher可能包含若干个drawcall,因为shader一样并不代表材质球一样
  • 一个shader,使用了关键字变量,就会生成多个变体,每一个变体可以认为是一个单独的shader
使用前提:
  • ​​​​​​​渲染管线要求:
    • 支持URP、HDRP、SRP,不支持Built-in管线
  • 游戏对象要求
    • 必须包含一个Mesh或者Skinned Mesh,不能是粒子
    • 不能使用MaterialPropertyBlock
    • Shader必须兼容SRP Batcher
       
优点:​​​​​​​
  • 节省UniformBuffer的写入操作,支持动态物体,支持的范围要比静态合批更广泛,同时内存上的代价会小很多,材质多的情况也适用
适用场景:​​​​​​​
  • Shader重复率高,但是要控制Shader变体的数量
     
GPUSkinning
原理:
  • GPUSkinning主要应用于带骨骼动画的mesh
  • 之前是CPU根据骨骼权重,计算顶点的位置,如果骨骼动画较多,CPU会比较吃力,通过把每一帧模型的骨骼权重信息,以及它的缩放平移旋转矩阵,通过RT记录下来,在GPU中计算顶点的位置,实现动画
  • 减少drawcall
  • 适用于大批量的带有骨骼动画的场景

Culling

在GPU进行渲染之前,需要CPU传递渲染数据给GPU,因此需要先将一部分不需要进行渲染的对象进行剔除,也就是Culling。Unity引擎原生就支持了视椎体剔除,即将视域体范围外的对象进行剔除,这部分对象的数据就不用传给GPU进行处理。

在Unity中,所有的可视内容都继承自Renderer,比如MeshRenderfer、SpriteRenderer、LineRenderer、SkinnedMesh Renderer、TrailRenderer等在Unity进行渲染的过程中会它们进行筛选,自动执行视锥体剔限的操作

如果场景中激活的相机数量多,那么Cullling的总耗时也相应增高,即使没有用来显示物体,也会执行culling 耗时,函数体现在  Render 线程中的->Camera.Renderer 

CullingGroup

CullingGroup是Unity提供的一个API接口,它本身和Unity自己的Cu系统以及LOD是同一体系,相当于开放了一些Cull底层的功能供用户使用
Unity - Manual: CullingGroup API

Occlusion

基本介绍
摄像机在每一帧中执行剔除操作,这些操作会检查场景中的渲染器,并排除
(剔除)那些不需要绘制的渲染器
默认情况下,摄像机执行视锥体剔除

工作原理
在Unity Editor中生成有关场景的数据,然后在运行时使用该数据确定摄像机可以看到的内容,生成数据的过程称为烘焙.
在对遮挡剔除数据进行烘焙时,Unity将场景划分为多个单元,并生成描述单元内几何体以及相邻单元之间可见性的数据,然后,Unity尽可能合并单元,以减小生成的数据的大小,在运行时,Unity会将这些烘焙的数据加载到内存中,并且对于每个启用了Occlusion Culling属性的摄像机,将会对数据执行查询以确定该摄像机可以看到的内容

在CullSendEvents的子线程下方会出现CullQueryPortalVisibilitylJmbra函数
测试中该函数也会出现在工作线程中

使用建议

遮挡物:

  • 大的遮挡物具有良好的遮挡质量,比如山
  • 组合起来大的遮挡物并不合适,因为遮挡无法累计,如森林
  • 不要有太多的缝隙,如奶酪
  • 建模时要注意避免无意造成的缝隙
  • 尽量不要让相机能进入遮挡物内部,可通过碰撞实现

被遮挡物:

  • 可以将大部分都设置为被遮挡物,便于被剔除
  • 非常大的物体不适合作为被遮挡物,因为它总会被看到,如地形,可以考虑将其分割为多个部分




 

Logo

更多推荐