我们可以在美术资源生产的过程中做很多渲染批次方面的优化。通常我们可以将一些使用相同材质的物体模型合并成一个模型,在游戏渲染的时候一次提交给渲染API进行绘制,降低了Draw call的数量。但是这样带来了一个问题,所有合并的模型必须一次全部绘制。哪怕这个模型只有一个边边角角在摄像机的视野里面,引擎的场景管理系统也要将整个模型提交给渲染API进行绘制。虽然管线中后续的计算会剔除不可见三角形,但是这样还是造成了一定的计算资源浪费。

对于场景中摆放位置非常相近的一些物体,这些物体通常可以被摄像机同时看到,我们可以通过合并模型的方法来达到降低Draw call数量的目的而不会带来不必要的计算代价。

如果一些模型引用的材质除了Texture其他参数都相同,我们可以将这些模型引用的Texture进行合并,将它们合并成一张更大的Texture。这样所有的模型都可以引用相同的材质,然后通过只设置一次渲染状态进行绘制了。虽然Draw call数量没有减少,但是避免了渲染状态的切换,同样达到了合批渲染的目的(原理请参看上一篇文章《图形渲染及优化—Batch》)。这项技术称为Texture atlasing,这是美术工作中最常用的方法,具体做法有大量内容可查。

Unity引擎内建了两种合批渲染技术:Static batching(静态合批)和Dynamic batching(动态合批)。

Static Batching

如果我们的游戏场景中有一些共享同一材质的模型存在,并且这些模型一直都不会移动、旋转和缩放。我们可以将这些模型设置为Static:

在Build的时候Unity会自动地提取这些共享材质的静态模型的Vertex buffer和Index buffer。根据其摆放在场景中的位置等最终状态信息,将这些模型的顶点数据变换到世界空间下,存储在新构建的大Vertex buffer和Index buffer中。并且记录每一个子模型的Index buffer数据在构建的大Index buffer中的起始及结束位置。

在后续的绘制过程中,一次性提交整个合并模型的顶点数据,根据引擎的场景管理系统判断各个子模型的可见性。然后设置一次渲染状态,调用多次Draw call分别绘制每一个子模型。

Static batching并不减少Draw call的数量,但是由于我们预先把所有的子模型的顶点变换到了世界空间下,并且这些子模型共享材质,所以在多次Draw call调用之间并没有渲染状态的切换,渲染API会缓存绘制命令,起到了渲染优化的目的(原理请参看上一篇文章《图形渲染及优化—Batch》) 。另外,在运行时所有的顶点位置处理不再需要进行计算,节约了计算资源。一般的游戏场景中存在着大量的静态物体,所以我们可以尽量将不会移动、旋转和缩放的模型设置为“Static”来提高渲染的效率。

Static batching相比于我们前面说的,在美术资源生产阶段合并模型的办法更加灵活。我们可以在最终游戏场景设计阶段随意修改场景,并且在Unity中构建完各个场景之后,由Unity将模型打包合并。另外如果在美术资源生产阶段合并模型,后续在引擎里渲染的时候无法剔除不可见的子模型,造成了计算资源的浪费,这点Static batching根据可见性进行渲染,相对节省了计算资源。

Static batching也会带来一些性能的负面影响。Static batching会导致应用打包之后体积增大,应用运行时所占用的内存体积也会增大。见下图:

在很多不同的GameObject引用同一模型的情况下,如果不开启Static batching,GameObject共享的模型会在应用程序包内或者内存中只存在一份,绘制的时候提交模型顶点信息,然后设置每一个GameObjec的材质信息,分别调用渲染API绘制。

开启Static batching,在Unity执行Build的时候,场景中所有引用相同模型的GameObject都必须将模型顶点信息复制,并经过计算变化到最终在世界空间中,存储在最终生成的Vertex buffer中。这就导致了打包的体积及运行时内存的占用增大。

所以有时我们不得不对某些GameObject避免使用Static batching来节省一些运行时内存空间。

Dynamic Batching

Dynamic batching是专门为优化场景中共享同一材质的动态GameObject的渲染设计的。为了合并渲染批次,Dynamic batching每一帧都会有一些CPU性能消耗。如果我们开启了Dynamic batching,Unity会自动地将所有共享同一材质的动态GameObject在一个Draw call内绘制。

Dynamic batching的原理也很简单,在进行场景绘制之前将所有的共享同一材质的模型的顶点信息变换到世界空间中,然后通过一次Draw call绘制多个模型,达到合批的目的。模型顶点变换的操作是由CPU完成的,所以这会带来一些CPU的性能消耗。并且计算的模型顶点数量不宜太多,否则CPU串行计算耗费的时间太长会造成场景渲染卡顿,所以Dynamic batching只能处理一些小模型。

目前Unity限制能进行Dynamic batching的模型最高能有900个顶点属性。这里注意不是900个顶点,而是900个定点属性。如果我们在Shader中使用了Vertex Position,Normal and single UV,那么能够进行Dynamic batching的模型最多只能够有300个顶点。如果我们在Shader中使用了Vertex Position、Normal、UV0、UV1 and Tangent那么顶点的数量就减少到180个。这个顶点属性数量的要求是Unity自己设计的,未来随着硬件性能的提升,这个值也会调整。

Dynamic batching是否能够执行还有一些需要注意的地方:

1.Unity的文档中说,GameObject之间如果有镜像变换不能进行合批,例如,"GameObject A with +1 scale and GameObject B with –1 scale cannot be batched together"。

2.使用Multi-pass Shader的物体会禁用Dynamic batching,因为Multi-pass Shader通常会导致一个物体要连续绘制多次,并切换渲染状态。这会打破其跟其他物体进行Dynamic batching的机会。

3.Unity的Forward Rendering Path中如果一个GameObject接受多个光照那么光照的绘制会按照下面的流程进行:

如果一个GameObject接受一个以上的per-pixel light那么就会为每一个per-pixel light产生多余的模型提交和绘制。我们可以在Quality Settings中将的per-pixel light数量限制为1.

这样设置之后Unity通过各种条件判断只会选择出一个Light执行per-pixel lighting。GameObject的绘制在一个Pass内就可以完成,避免了附加的Pass,提高了可合批的概率。

4.在Unity的渲染管线中无论采用Forward Rendering或者是Deferred Rendering,半透明物体的渲染都要在最后采用Forward Rendering的方式渲染(Deferred Rendering无法支持半透明渲染)。所有的半透明物体在绘制之前要进行严格的排序,绘制操作按序执行。因为物体绘制的顺序被严格限定,所以物体间能够进行动态合批的概率很小。

5.我们知道能够进行合批的前提是多个GameObject共享同一材质,但是对于Shadow casters的渲染是个例外。仅管Shadow casters使用不同的材质,但是只要它们的材质中给Shadow Caster Pass使用的参数是相同的,他们也能够进行Dynamic batching。例如,场景中我们摆放了许多的箱子,他们的材质中引用的Texture不同, 但是在做Shadow Caster Pass渲染的时候Texture会被忽略,这些箱子是可以合批渲染的。这是因为Shadow渲染如果使用ShadowMap或其他的衍生算法,场景中的物体要绘制两遍。第一遍绘制Shadow pass要做一次深度渲染,只提取场景中Shadow casters物体的深度值。下面是Unity文档中的一段话:

Shadow Caster Pass在做Depth buffer渲染的时候我们不需要Fragment Shader关于颜色相关的结果,仅仅一个简单的能够输出深度值的Fragment Shader足以。而且对于几乎所有物体而言使用的Shadow Caster Pass又是相同的,所以这些Shadow caster物体就可以进行Dynamic batching了。

在目前比较主流的游戏引擎中,进行其他绘制之前都会首先执行一次Depth buffer渲染,尤其是在采用Deferred Rendering的时候。这一步不仅仅是为Shadow的绘制,更是为了后续执行其他像素计算相关Shader的时候能够在Early-Z阶段剔除不必要执行的Fragment计算,降低填充率,提高效率。

Dynamic batching在降低Draw call的同时会导致额外的CPU性能消耗,所以仅仅在合批操作的性能消耗小于不合批,Dynamic batching才会有意义。而新一代图形API( Metal、Vulkan)在批次间的消耗降低了很多,所以在这种情况下使用Dynamic batching很可能不能获得性能提升。Dynamic batching相对于Static batching不需要预先复制模型顶点,所以在内存占用和发布的程序体积方面要优于Static batching。但是Dynamic batching会带来一些运行时CPU性能消耗,Static batching在这一点要比Dynamic batching更加高效。所以我们在实践中可以根据具体的场景灵活地平衡两种合批技术的使用。

Static Batching In Runtime

Unity还提供了一种灵活度很高的运行时静态合批方法。我们可以在运行时调用StaticBatchingUtility.Combine实现将一些模型合并成一个完整模型。

这个函数的实现有两个版本:

// StaticBatchingUtility.Combine prepares all children of the staticBatchRoot for static batching.

// Once combined, children cannot change their Transform properties; however, staticBatchRoot can be moved.

public static void Combine(GameObject staticBatchRoot);

// StaticBatchingUtility.Combine prepares all gos for static batching.staticBatchRoot is treated as their parent.

// Once combined, gos cannot change their Transform properties; however, staticBatchRoot can be moved.

// The GameObject in gos must have MeshFilter components attached for this to work.

public static void Combine(GameObject[] gos, GameObject staticBatchRoot);

具体的使用方法上面的注释已经说的很清楚了,这里就不再讨论了。使用这种方法我们可以避免最终打包的应用体积增大,但是由于在运行时通过CPU做模型的合并,会到来一次性的运行时内存和CPU开销。

Bone Matrix Palette Batching

对于Skinned Mesh Unity通常是无法合批渲染的。但是我们可以借用Skinned Mesh的一些原理来实现对于动态GameObject的另外一种合批渲染技术。对于动态物体可能每一帧每个物体的Transform都不一样。我们可以模仿“Hardware Skinning”的渲染方式,但是让每个物体仅受一根骨骼影响,创建一个Transform matrix palette,其中存放的是我们打包的变换矩阵。我们将矩阵数据打包到两个float4中:

1.Axis / Angle

2.Translate / Uniform scale

在渲染时我们充分利用Shader的寄存器空间,一次将所有同批次物体的Transform传给Shader。渲染时顶点自动查找其引用的Transform进行顶点变换。

通过查询gl_MaxVertexUniformVectors可知OpenGL ES 3.0的各个实现至少应提供256个vertex uniform vector。我们假设实现只提供256个vertex uniform vector,每个物体的方位信息需要2个float4,那么我们最多可以一个Draw call合批渲染接近128个物体。

12年的时候我为公司的自研引擎开发物理引擎部分,当时有一个功能是Ragdoll。这个大家一定很熟悉,在很多追求真实物理效果的游戏中都会用到。在做Ragdoll的同时尝试了将身体各个部分肢解。是不是听起来有点残酷。。。

当我把身体各个部分肢解开,完全由身体绑定的刚体自由控制的时候,我突然想到每一个身体部分如果是一个原本就独立的物体,那么可以利用骨骼动画技术来做一些物体合批渲染。

这项技术比Static batching节约内存空间,又比Dynamic batching节约运算资源,在某些合适的场景里感兴趣的朋友可以试试。其实这种合批方式的本质跟我们下面介绍的Instancing渲染很像的。

GPU Instancing

这个特性是从OpenGL ES 3.0开始支持的,Unity5.4开始加入。具体的原理这里就不介绍了,如果介绍这个恐怕又得写一篇。。。 不了解的人可以去Google一下或者看一下Unity的文档。这里主要强调一下这个特性与Unity中的Static batching和Dynamic batching的关系:

1.Static batching的优先级要比Instancing的优先级高,如果一个GameObject被标记为static物体并且在Build阶段成功地执行了静态合批,那么如果这个物体还要使用Instancing Shader渲染的话,Instancing会失效。

2.Dynamic batching的优先级要低于Instancing。如果一个GameObject使用Instancing渲染的话,那么对于它的Dynamic batching会失效。

Unity Batch Debugging

很多时候我们需要Profile数据来查看我们渲染的场景的Draw call数量和Batch数量。Unity提供了Stats Pane、Profiler和Frame Debugger三个很方便的工具来帮助我们获得Batch相关数据。

Stats Pane

切换到Game视图,点击Stats会弹出Stats pane:

通过Stats pane我们可以获得两个数据:

1.Batches – 目前绘制场景可视区域的Batch数量。

2.Saved by batching – 通过合批渲染我们减少的Batch(Draw call)数量。

Profiler

通过Window -> Profiler或者Ctrl+7我们可以打开Unity Profiler工具。点击Rendering下面会出现我们需要的数据显示区域:

这里我们可以获得比Stats pane更加详细的数据。我们能够看到Static Batching和Dynamic Batching的具体执行情况,每种类型的合批技术处理的三角形及顶点的数量。

Frame Debugger

通过Window -> Frame Debugger我们可以打开Unity的Frame Debugger调试工具:

点击“Enable”按钮就可以开始调试场景的Frame渲染了。

通过Frame Debugger我们可以获得比Unity Profiler更加详细的批次渲染数据。我们可以查看每一个批次的渲染顺序,批次内合并了哪些内容,并且可以看出是什么因素导致了合批的失败而开启了一个新的批次。

图形渲染及优化—Unity合批技术实践相关推荐

  1. 从编码、网络传输、架构设计揭秘腾讯云高质量、高可用实时音视频技术实践...

    在互联网飞速发展的时代,视频应用无处不在,各行各业对视频技术的需求开始爆发.伴随视频分辨率的提高,音视频玩法的快速更新以及用户对观看体验需求的不断增加,越来越多的应用场景对音视频技术提出了更高的要求. ...

  2. [前端工坊]第二届京东技术节-前端技术实践专场

    本文首发于微信公众号:前端工坊 转载请联系前端工坊编辑授权; 欢迎关注有趣好玩的前端技术公众号:前端工坊 项目简介 [TechDay京东技术节]是面向京东内部全体研发人员的技术交流大会,由集团技术委员 ...

  3. 批量 材质 调整_游戏图形批量渲染及优化:Unity静态合批技术

    作者:枸杞忧天 (本文首发于公众号"偶尔学学Unity",文章仅为作者观点,不代表GWB立场) 最近在准备公司的技术分享,主题是入门批量渲染,想着反正也总结了,不如充几篇博客吧,也 ...

  4. Unity性能优化 :合批篇

    前言 本系列为一些性能优化的小知识,是日常游戏开发中与性能表现的一些点,本篇为该系列文章的第二篇,前篇链接: 第一篇: Unity性能优化:资源篇 在早期Unity中,对于合批的处理手段主要是下面三种 ...

  5. 解读最佳实践:倚天 710 ARM 芯片的 Python+AI 算力优化 | 龙蜥技术

    编者按:在刚刚结束的 PyCon China 2022 大会上,龙蜥社区开发者朱宏林分享了主题为<ARM 芯片的 Python+AI 算力优化>的技术演讲.本次演讲,作者将向大家介绍他们在 ...

  6. 么么直播的音视频技术实践和优化

    2018年3月31日,"ZEGO Meetup 视频直播+的技术实践之道"第三期在上海成功举办,现场吸引了满堂的直播行业从业者到场聆听.会上,如预期一样,么么直播前端团队负责人黄铭 ...

  7. Unity项目技术方案Dots架构方案简介

    DOTS全称是Data-Oriented Tech Stack,翻译过来就是多线程式数据导向型技术堆栈(DOTS),它由任务系统(Job System).实体组件系统(ECS).Burst Compi ...

  8. Unity合批方式浅析

    Unity合批方式浅析 Draw call的优化是Unity性能优化中老生常谈的一环,而合批是CPU端优化Draw Call的主要手段.本文使用RenderDoc对Unity中常用的静态合批/动态合批 ...

  9. 华为开发者大会HDC.Cloud技术探秘:云搜索服务技术实践

    搜索是一个古老的技术,从互联网发展的第一天开始,搜索技术就绽放出了惊人的社会和经济价值.随着信息社会快速发展,数据呈爆炸式增长,搜索技术通过数据收集与处理,满足信息共享与快速检索的需求.基于搜索技术, ...

最新文章

  1. LIO-SAM: 紧耦合的激光与惯导里程计方案
  2. 1071. 小赌怡情(15)
  3. 每日程序C语言10-欧几里德算法
  4. 语音交互编程语言了解一下?
  5. python自动下载邮件附件_Python批量下载电子邮件附件并汇总合并Excel文件
  6. ZOC for Mac(终端仿真器)
  7. [转][darkbaby]任天堂传——失落的泰坦王朝(上)
  8. BlazeDS知识积累
  9. 自适应滤波器设计及matlab实现,[转载]自适应滤波器设计与MATLAB
  10. 机器学习与数据挖掘之ROC与AUC
  11. 面试题单例模式的五种写法(枚举妙用)
  12. Unity笔记-29-ARPG游戏项目-05-简易的战斗系统
  13. html默认样式重置,我们真的需要CSS重置来清除默认样式吗?
  14. Error in invoking target 'agent nmhs' of makefile
  15. 女生转行做什么工作好?想要转行互联网可以选择哪些方向?
  16. input框如何设置框禁止输入
  17. 【转载】一个程序猿必须会玩的游戏
  18. MySQL中PreparedStatement sql中in的引号问题
  19. ctf中常见的编码和密码收集
  20. 邮箱POP3、SMTP和IMAP

热门文章

  1. SpringBoot + Vue实现在线考试系统
  2. python椭圆代码_Python实现霍夫圆和椭圆变换代码详解
  3. 魅族16th操作系统android,魅族16如何开启镜像功能?
  4. 黄章“杀手锏”来了:魅族16sPro28号正式发布,煤油疯狂打call
  5. GMSH 使用 Tutorial-1
  6. 高性能 Java 缓存库 — Caffeine
  7. 浏览器 User-Agent解析
  8. 暨南大学计算机复试线2019,2019年暨南大学考研复试分数线及复试安排
  9. 慎用安卓USB调试模式 谨防陷入安全危机
  10. poj 3525 tomo带鞘