咨询电话:400-810-1418服务与监督电话:400-810-1418转接2
  • 相关推荐
  • 游戏图形批量渲染及优化:Unity动态合批技术
  • 游戏图形批量渲染及优化:Unity静态合批技术
  • 香蕉也能玩游戏?揭秘“奇葩”游戏的制作过程
  • U3D实时渲染教程之角色头发各向异性表达(下)
  • 大气渲染技术解析!如何在游戏中模拟出千变万化的天空?
  • 如何利用Unity3D打造虚拟现实场景?
  • 游戏图形批量渲染及优化:Unity实例化渲染

    发布时间:2020-11-19 13:51:09

    上回  游戏图形批量渲染及优化:Unity动态合批技术 简单总结了一下动态合批,这次我们继续说说Unity实例化渲染。

    | 实例化渲染

    当我们想要呈现这样的场景:一片茂密的森林、广阔的草原或崎岖的山路时,会发现在这些场景中存在大量重复性元素:树木、草和岩石。

    1.webp.jpg

    仙境怕是也不过如此吧

    它们都使用了相同的模型,或者模型的种类很少,比如:树可能只有几种;但为了做出差异化,它们的颜色略有不同,高低参差不齐,当然位置也各不相同。

    使用静态合批来处理它们(假设它们都没有动画),是不合适的。因为数量太多(林子大了,多少树都有),所以合并后的网格体积可能非常大,这会引起内存的增加;而且,这个合并后的网格还是由大量重复网格组成的,不划算。

    使用动态合批来处理他们,虽然不会“合并”网格,但是仍然需要在渲染前遍历所有顶点,进行空间变换的操作;虽然单颗树、石头的顶点数量可能不多,但由于数量很多,所以也会在一定程度上增加CPU性能的开销,没必要。

    那么,对于场景中这些模型重复、数量多的渲染需求,有没有适合的批处理策略呢?有吧,实例化渲染就是为了解决这样的问题。

    | 简述工作原理

    实例化渲染,是通过调用“特殊”的渲染接口,由GPU完成的“批处理”。

    它与传统的渲染方式相比,最大的差别在于:调用渲染命令时需要告知GPU这次渲染的次数(绘制N个)。当GPU接到这个命令时,就会连续绘制N个物体到我们的屏幕上,其效率远高于连续调用N次传统渲染命令的和(一次绘制一个)。

    举个例子,假设希望在屏幕上绘制出两个颜色、位置均不同的箱子。如果使用传统的渲染,则需要调用两次渲染命令(DrawCall = 2),分别为:画一个红箱子 和 画一个绿箱子。

    2.webp.jpg

    两个颜色、位置各异的箱子

    如果使用实例化渲染,则只需要调用一次渲染命令(DrawCall = 1),并且附带一个参数2(表示绘制两个)即可。

    当然,如果只是这样,那GPU就会把两个箱子画在相同的位置上。所以我们还需要告诉GPU两个箱子各自的位置(其实是转换矩阵)以及颜色。

    这个位置和颜色我们会按照数组的方式传递给GPU,大概这个样子吧:

    3.webp.jpg

    分别传递保存位置和颜色的数组

    那接下来GPU在进行渲染时,就会在渲染每一个箱子的时候,根据当前箱子的索引(第几个),拿到正确的属性(位置、颜色)来进行绘制了。

    4.webp.jpg

    一个简单的实例化渲染流程

    | Unity是如何处理实例化的

    我们通过一个简单的场景,来看一下Unity为实例化渲染做了什么。

    5.webp.jpg

    实例化渲染两个彩色箱子

    6.webp.jpg

    颜色属性通过MaterialPropertyBlock传入

    通过GPA观察Unity做了什么。

    7.png

    GPA中的VertexBuffer和IndexBuffer中的信息

    注:Unity默认Cube网格,包含24个顶点和36个索引。

    顶点缓冲区Size = (Position(float3)

    + Normal(float3)

    + Tangent(float4)

    + TexCoord(float2)

    + TexCoord1(float2)) x 24 = 1344Byte

    索引缓冲区Size = Index(ushort) x 36 = 72Byte

    可见,顶点、索引缓冲区内,确实只有一个网格的数据。

    那么GPU如何判断每个Cube的绘制位置,及其颜色呢?

    结合引擎为Dx平台生成的shader(我的测试环境使用的是Pc),可以很容易找到对应的数据。

    8.webp.jpg

    转换矩阵及颜色被分别填入Constant Buffer中

    9.webp.jpg

    Constant Buffer中的矩阵(Dx为行向量)

    10.webp.jpg

    Constant Buffer中的属性(颜色)

    可见,渲染时GPU可以通过当前实例化单位的索引,从Buffer中获取到对应的属性,完成正确的绘制。

    | Unity中启用实例化渲染

    当然,相比于上述无用的知识点,如何在Unity中使用实例化渲染可能更为重要。

    在Unity中可以通过自动或手动的方式,启用实例化渲染。

    自动启用实例化渲染

    使用支持实例化渲染的Shader,并勾选材质球上的启用开关,Unity便会对满足条件的物体,自动开启实例化渲染。

    11.webp.jpg

    有这个选项即表示该Shader支持实例化渲染

    自定义Shader

    如果你希望自己的Shader也支持实例化渲染,应重点注意以下内容:

    #pragma multi_compile_instancing

    启用实例化渲染(材质球上将出现启用实例化的勾选框);

    UNITY_VERTEX_INPUT_INSTANCE_ID

    在a2v及v2f的结构中定义实例化索引下标(SV_InstanceID ),也就是当前渲染单位的索引,用于从Constant Buffer中提取正确的属性(做显示差异化用);

    UNITY_INSTANCING_BUFFER_START ~ END

    在这个起止区域内定义属性,才能在着色器中正确的根据索引提取出当前渲染单位所对应的属性;

    UNITY_SETUP_INSTANCE_ID

    定义在着色器的起始位置,使顶点着色器(或片段着色器)可以正确的访问到实例化单位的索引;

    UNITY_ACCESS_INSTANCED_PROP

    根据索引访问到这个单位对应的属性,如上面例子中每个箱子的颜色属性。

    这里只是简述一些相对重要的内容(凑些字数),官方文档中有更详细内容,建议优先了解。

    手动实例化渲染

    使用  Graphics.DrawMeshInstanced 和 Graphics.DrawMeshInstancedIndirect 来手动执行 GPU 实例化,详见官方文档中的解释,这里就不再赘述了。

    | 实例化渲染的使用要求

    并非所有设备都可以使用实例化渲染。

    在Unity官方文档中,列举了各平台支持实例化渲染的最低要求。

    12.webp.jpg

    官方文档中对支持实例化渲染的最低API要求

    当然,我们也可以通过引擎中SystemInfo.supportsInstancing属性来判断环境是否支持实例化渲染。

    那支持实例化渲染的机器占比大概是多少呢?由于国内大多数游戏公司都是以手游项目糊口。所以开发者可能会更多关注其在安卓平台上的情况。

    根据Android开发者的官方数据显示,截至2020年8月30日,约88%的活跃安卓设备,都已经支持实例化渲染,所以基本上可以放心使用。

    13.webp.jpg

    android开发者官网发布的活跃设备OpenGL ES版本占比信息

    | 与静、动态合批的差异

    静、动态合批实质上是将可以合批的对象真正的合并成一个大物体后,再通知GPU进行渲染,也就是其顶点索引缓冲区中必须包含全部参与合批对象的顶点信息;因此,可以认为是CPU完成的批处理。

    实例化渲染是对网格信息的重复利用,无论最终要渲染出几个单位,其顶点和索引缓冲区内都只有一份数据,可以认为是GPU完成的批处理。

    其实这么总结也有点问题,本质上讲:动、静态合批解决的是合批问题,也就是先有大量存在的单位,再通过一些手段合并成为批次;而实例化渲染其实是个复制的事儿,是从少量复制为大量,只是利用了它“可以通过传入属性实现差异化”的特点,在某些条件下达到了与合批相同的效果。

    | 简单总结静、动态合批及实例化渲染

    无论是静态合批、动态合批或实例化渲染,本质上并无孰优孰劣,它们都只是提高渲染效率的解决方案,也都有自己适合的场景或擅长解决的问题。

    个人以为:

    如果你的场景中存在多数静止的、使用了不同网格、相同材质的物体,特别是当你的相机通常只能照到一部分物体时(如第一视角),可以优先尝试下静态合批,通过牺牲一些内存来提升渲染效率;

    针对那些运动的、网格顶点数很少、材质相同的物体,比如飞行的各种箭矢、炮弹等,使用动态合批,通过增加一些CPU处理顶点的性能开销,来提升渲染效率,也许是不错的选择;

    如果有大量模型相同、材质相同、或尽管表现上有一些不同,但仍然可以通过属性来实现这些差异化的物体时,启用实例化渲染通常可以在很大程度上提升渲染效率。

    |  写在最后

    按计划下次更新的内容应该是“优化骨骼蒙皮动画,以及两种常用的批量渲染方式”,但觉得内容有点多,所以将其分为两个部分;因此,下次更新的内容变为“优化骨骼蒙皮动画”,而“两种常用的骨骼蒙皮动画单位的批量渲染方式”,将作为本系列的最后一次更新内容。

    下回见喽。

    作者:枸杞忧天

    来源:偶尔学学Unity公众号