《Unity游戏优化》笔记(7)[21/02/08_周一][P81_96_第3章]

目录

第3章 批处理的优势 (P81)

3.1 Draw Call (P82)

3.2 材质和着色器 (P84)

3.3 Frame Debugger (P86)

3.4 动态批处理(P88)

3.4.1 顶点属性

3.4.2 网格缩放

3.4.3 动态批处理总结

3.5 静态批处理(P93)

3.6 本章小结


第3章 批处理的优势 (P81)

在3D图形和游戏中,批处理是一个非常通用的术语,它描述了将大量任意数据块组合在一起并将它们作为单个大数据块进行处理的过程。这对于CPU,特别是GPU是非常理想的,因为它可以使用多个内核同时处理多个任务。在内存中的不同位置来回切换内核是需要时间的,因此切换内核所花的时间越少越好。

在某些情况下,批处理的对象指的是网格、顶点、边、UV坐标和其他用于描述3D对象的不同数据类型的大集合。然后,该术语也可以简单代表批处理音频文件、精灵、纹理文件和其他大数据集的行为。

为了避免混淆,本文提到Unity中的批处理时,通常指的是两种用于批处理网格数据的主要机制:动态批处理和静态批处理。这两种方法本质上是几何体合并的两种不同形式,用于将多个对象的网格数据合并到一起,并在单一指令中渲染它们,而不是单独准备和绘制每个几何体。

将多个网格批处理为单个网格是可以实现的,因为没有规定网格对象必须是3D空间中连续的几何体。Rendering Pipeline(渲染管线)可以接受一系列没有共同边的顶点,因此可以将本来需要多个渲染指令的多个独立网格合并为单个网格,用单一指令渲染它。

在某些情况下,如果没有正确使用批处理,它的确会恶化性能。

本章内容

  • 渲染管线和Draw Call概念的简单介绍
  • Unity的材质和着色器如何一起工作,以渲染对象。
  • 使用Frame Debugger可视化渲染行为
  • 动态批处理的工作原理及优化方式
  • 静态批处理的工作原理及优化方式

3.1 Draw Call (P82)

批处理方法的主要目标是减少在当前视图中渲染所有对象所需的Draw Call数量。就最基本的形式而言,Draw Call只是一个从CPU发送GPU中用于绘制对象的请求。

Draw Call是这一过程的通用行业术语,但在Unity中有时也称为SetPass Call,因为一些底层方法也命名为SetPass Call。可以将Draw Call理解为初始化当前渲染过程之前的配置选项。

既然叫SetPass,Shader的基本组成是Pass,是不是一个Shader有2个Pass的情况下相当于有两个SetPass Call呢?

在请求Draw Call之前,需要完成一些工作。

CPU和GPU之间的通信任务是通过底层Graphics API进行的。Unity可以通过一个公共接口支持很多不同的Graphics API。

在渲染对象之前,必须为准备管线渲染而配置的大量设置常常统称为渲染状态(Render State)。除非这些渲染状态选项发生了变化,GPU将为所有传入的对象保持相同的渲染状态,并以类似的方式渲染它们。

更改渲染状态是一个耗时的过程。

用于渲染当前对象的纹理在Graphics API中实际上是一个全局变量,而在并行系统内修改全局变量说起来容易做起来难。请求改变渲染状态的次数越少,Graphics API越能更快地处理请求。

可以触发渲染状态同步的操作包括但不限于:立刻推送一张新纹理到GPU中,修改着色器、照明信息、阴影、透明度和其他任何图形设置。

一旦配置了渲染状态,CPU就必须决定绘制哪个网格,使用什么纹理和着色器,以及基于对象的的位置、旋转和缩放决定在何处绘制对象,然后发送指令到GPU以绘制它。为了使CPU和GPU之间的通信保持活跃,新指令被推入一个名为CommandBuffer的队列中。这个队列包含CPU创建的指令,以及GPU每次执行完前面的命令后从中提取的指令。

CommandBuffer的使用需要调研一下

批处理提升此过程的诀窍在于,新的DrawCall不一定意味着必须配置新的渲染状态。如果两个对象共享完全相同的渲染状态信息,那么GPU可以立刻开始渲染新对象。

3.2 材质和着色器 (P84)

在Unity中,渲染状态本质上是通过材质呈现给开发者的。材质是着色器的容器,着色器是一种用于定义GPU应该如何渲染输入的顶点和纹理数据的简短程序。着色器本身没有必要的状态信息来完成任何有价值的工作。

着色器之所以如此命名,是因为多年前,它们原本仅实现为处理对象的光照和着色(应用阴影,原本是没有阴影的)。现在更通用的功能是作为访问各种不同并行任务的可编辑接口。

如果想要最小化渲染状态修改的频率,可以减少场景中使用的材质数量,这将同时提升两个性能:CPU每帧将花费更少的时间生成指令,并传输给GPU;而GPU不需要经常停止,重新同步状态的变更。

测试DrawCall,先禁用一些渲染选项,因为它们会产生一些额外的DrawCall。

1.Disable Shadows

2.禁用Static Batching和Dynamic Batching

Batching值严格等于渲染场景使用的DrawCall数量。

渲染场景的背景将消耗一个批处理。

如果所有对象都设置为使用相同的材质,性能依然没有任何提升。这是因为渲染状态的变更数没有真正减少,也没有高效地合并网格信息。遗憾的是,渲染管线不够智能,意识不到我们再重复写入完全相同的渲染状态,并要求它一次又一次的渲染相同的网格。

3.3 Frame Debugger (P86)

它能确定批处理是如何影响场景的。可以观察场景时如何构建的,每次执行一个DrawCall。左边面板带有GPU指令列表,右边面板包含了详细信息。

需要将摄像机的ClearFlags设置为SolidColor,临时禁用天空盒,才能在Game窗口中观察DrawCall一步步呈现的场景图。

左边面板中的每一项后面的数字表示一个Graphics API调用,其中DrawCall只是一种API调用。任何API调用和DrawCall的开销都差不多,但复杂场景中的大多数API调用都采用DrawCall的形式,因此通常最先关注DrawCall的最小化,再去担心诸如后期处理效果等API通信开销。

3.4 动态批处理(P88)

3个重要优势:

  • 批处理在运行时生成(批处理是动态产生的)。
  • 批处理中包含的对象在不同的帧之间可能有所不同,这取决于哪些网格在主摄像机视图中当前是可见的(批处理的内容是动态的)。
  • 能在场景中运动的对象也可以批处理(对动态对象有效)

动态批处理自动识别共享材质和网格信息的对象。

动态批处理的要求:

  • 所有网格实例必须使用相同的材质引用。
  • 只有ParticleSysem和MeshRenderer组件进行动态批处理。
  • 每个网格至多有300个顶点。
  • 着色器使用的顶点属性数不能大于900。
  • 所有网格实例要么使用等比缩放,要么使用非等比缩放,但不能两者混用。
  • 网格实例应该引用相同的光照纹理文件。
  • 材质的着色器不能依赖多个过程。
  • 网格实例不能接受实时投影。
  • 整个批处理中网格索引的总数有上线,一般索引值在32-64k之间。

3.4.1 顶点属性

顶点属性只是网格文件中基于每个顶点的一段信息,它包括但不限于顶点位置、法线向量、UV坐标、颜色。只有着色器使用的顶点属性总数小于900的网格才会进行动态批处理。

查看网格的原始数据文件,其中包含的顶点属性信息会比Unity载入内存的少,这是由于引擎会将网格数据从几个原始数据格式转换为内部格式。不要假设3D建模工具提供的顶点属性数量是最终的数量。在Project窗口中找到MeshFilter组件,在Inspector窗口的Preview子区域查看Verts值。

总共900个属性预算,每个顶点使用的属性数据越多,网格运行拥有的顶点数量越少。简单的漫反射着色器只有3个属性,支持总共300个顶点的网格。更复杂的着色器,每个顶点需要5个属性,只能支持不超过180个顶点的网格的动态批处理。即使每个顶点使用不到3个顶点属性,动态批处理仍然只支持最多300个顶点的网格。

只有相对简单的对象才支持动态批处理。

如果点击FrameDebugger中的一个DrawCall项,就会显示标签为"Why this draw call can't be batched with the previous one。

优势只有点击DynamicBatch项,才会显示真正的原因。

HDRP下的空场景的FrameDebugger的内容差异好大,知识要大更新了吗?

3.4.2 网格缩放

文档建议,对象应使用统一的等比缩放,或每个对象都有不一样的非等比缩放,才能包含在动态批处理中。属于这两组的对象会被放到两个不同的批处理中。

使用负数缩放会对动态批处理产生奇怪的效果。

3.4.3 动态批处理总结

要渲染大量的简单网格时,动态批处理是非常有用的工具。使用大量外观几乎相同的简单物体时,该系统的设计是非常完美的。应用动态批处理的可能情况如下:

  • 到处是石头、树木和灌木的森林。
  • 有很多简单而常见的元素的建筑、工厂或空间站。
  • 一个游戏,包含很多动态的非动画对象,还包含简单的几何体和粒子特效。

如果阻止两个对象动态批处理的唯一条件是,它们使用了不同的纹理,就应该花点时间和精力合并纹理(通常称为图集),并重新生成网格UV。

简单地假设正在进行动态批处理,更可能给应该程序带来性能损失,而实际上我们忘记了其中一个必要条件。

为了使场景中动态批处理的数量保持合适的水平,需要连续不断地检查DrawCall数量,并观察FrameDebugger数据,以确保最新的修改不会意外取消对象的动态批处理资格。

总之,每种情况都是各不相同的,需要使用网格数据、材质和着色器进行实验,以确定能动态批处理什么,不能动态批处理什么,并对场景不时地执行一些测试,以确保使用数量合理的DrawCall。

动态批处理其实不需要做任何设置,只需要模型满足条件,系统就会自动动态批处理。

3.5 静态批处理(P93)

静态批处理的要求:

  • 标记为Batching Static。
  • 每个被静态批处理的网格都需要额外的内存。
  • 合并到静态批处理中的顶点数量是有上限的,一般为32-64k个顶点。
  • 网格实例可以来自任何网格数据源,但它们必须使用相同的材质引用。

3.5.1 Static标记

该标记的一个明显的副作用是不能修改对象的变换。因此,任何想要使用静态批处理的对象都不能通过任何方式移动、旋转和缩放。

3.5.2 内存需要

静态批处理的额外内存需求取决于批处理的网格中复制的次数。

通常,渲染一个、十个或一百万个相同对象,消耗的内存是相同的,因为它们都引用相同的网格数据。在这种情况下,对象之间的唯一区别是每个对象的变换。(测试一下)

使用静态批处理渲染1000个相同的树对象,消耗的内存是不使用静态批处理渲染相同树的1000倍。如果没有正确地使用静态批处理,将导致一些严重的内存消耗和性能问题。

3.5.3 材质引用

静态批处理渲染所有静态网格时,使用的DrawCall数量最多等于所需的材质数量。

3.5.4 静态批处理的警告

1.Editor模式调试

视图确定静态批处理在场景中的整体效果有一些困难,因为在Edit模式下静态批处理没有生效。这些处理在运行时生效。

最好在构建新场景的早期开始进行静态批处理优化。

如果有许多批处理要创建,和/或有许多大型对象要批处理,那么场景初始化时间可能会显著增加。

2.在运行时实例化静态网格

在运行时添加到场景中的任何新对象,即使它们标记为BatchingStatic对象,也不会由静态批处理系统自动合并到任何现有批处理中。这样做会在重写计算网格和与管线渲染同步之间找出巨大的运行时开销,所以Unity甚至不会尝试自动执行。

如果需要动态实例化,或者使用叠加方式加载场景,就可以使用StaticBatchUtility.Combine()方法控制静态批处理。(测试)

如果有许多顶点要合并,那么该操作将非常昂贵。

3.5.5 静态批处理总结

它可以用于不同形状和巨大尺寸的网格。

3.6 本章小结

静态批处理和动态批处理都不是银弹。

我是希望找到一种银弹的.....现在要做的模型合并功能感觉就是把静态批处理和动态批处理的优势结合起来,做一个能够点击、高亮、编辑的静态批处理系统,也能够处理巨大尺寸的网格。

第6章将学习关于管线渲染和性能提升技术的更多信息。

下一章管理艺术资源。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值