利用GPU实现大规模动画角色的渲染

作者:陈嘉栋
2017-08-17
20 23 6

编者按

本文已于作者 @陈嘉栋 授权转载,原载于知乎,如需转载请务必联系原作者。

0x00 前言

我想很多开发游戏的小伙伴都希望自己的场景内能渲染越多物体越好,甚至是能同时渲染成千上万个有自己动作的游戏角色就更好了。

但不幸的是,渲染和管理大量的游戏对象是以牺牲 CPU 和 GPU 性能为代价的,因为有太多 Draw Call 的问题,如果游戏对象有动画的话还会涉及到cpu的蒙皮开销,最后我们必须找到其他的解决方案。那么本文就来聊聊利用 GPU 实现角色的动画效果,减少 CPU 端的蒙皮开销;同时将渲染10,000个带动画的模型的 Draw Call 从10,000+减少到22个。(模型来自:RTS Mini Legion Footman Handpainted

gif1

0x01 Animator和SkinnedMeshRender 的问题

正常情况下,大家都会使用 Animator 来管理角色的动画,而角色也必须使用 SkinnedMeshRender 来进行渲染。
gif2
例如在我的测试场景中,默认情况下渲染10,000个带动作的士兵模型,可以看到此时的各个性能指标十分糟糕:CPU 320+ms,DrawCall:8700+。

3

因此,可以发现如果要渲染的动画角色数量很大时主要会有以下两个巨大的开销:

  • CPU在处理动画时的开销。
  • 每个角色一个 Draw Call 造成的开销。

CPU 的这两大开销限制了我们使用传统方式渲染大规模角色的可能性。因此一些替代方案——例如广告牌技术——被应用在这种情况下。但是实事求是的说,在这种情境下广告牌技术的实现效果并不好。

那么有没有可能让我们使用很少的开销就渲染出大规模的动画角色呢?

其实我们只需要回过头看看造成开销很大的原因,解决方案已经藏在问题之中了。

首先,主要瓶颈之一是角色动画的处理都集中在 CPU 端。因此一个简单的想法就是我们能否将这部分的开销转移到 GPU 上呢?因为 GPU 的运算能力可是它的强项。

其次,瓶颈之二是 CPU 和 GPU 之间的 Draw Call 问题,如果利用批处理(包括 Static Batching 和 Dynamic Batching )或是从 Unity5.4 之后引入的 GPU Instancing 就可以解决这个问题。但是,不幸的是这两种技术都不支持动画角色的 SkinnedMeshRender。

那么解决方案就呼之欲出了,那就是将动画相关的内容从CPU转移到GPU,同时由于CPU不需要再处理动画的逻辑了,因此 CPU 不仅省去了这部分的开销而且SkinnedMeshRender 也可以替换成一般的 Mesh Render,我们就可以很开心的使用 GPU Instancing 来减少 Draw Call 了。

0x02 Vertex Shader 和 AnimMap

写过 shader 的小伙伴可能很清楚,我们可以很方便的在 vs 中改变网格的顶点坐标。因此,一些简单的动画效果往往可以在 vs 中实现。例如飘扬的旗帜或者是波浪等等。

v2-560b1bf6db389e410f305708cb2cbba0_b

来源于 bing 搜索

那么我们能否利用 vs 设置顶点坐标的方式来展现我们的角色动画呢?

gif5

答案当然是可行。只不过和飘扬的旗帜那种简单的效果不同,这次我们不仅仅利用几个简单的 vs 的属性来实现动画效果,而是将角色的动画信息烘焙成一张贴图供 vs 使用。

简单来说,我们按照固定的频率对角色动画取样并记录取样点时刻角色网格上各个顶点的位置信息,并利用贴图的纹素的颜色属性(Color(float r, float g, float b, float a))保存对应顶点的位置(Vector3(float x, float y, float z))。当然利用颜色属性保存顶点的位置信息时需要考虑到一个小问题,在下文我会再说。

这样该贴图就记录了整个动画时间内角色网格顶点在各个取样点时刻的位置,这个贴图我把它称为 AnimMap。

一个 AnimMap 的结构就是下图这样的:

6

在实际工程中,AnimMap 是这个样子的。水平方向记录网格各个顶点的位置,垂直方向是时间信息。

7

gif8

上图是将角色的 Animator 或 Animation 去掉,将 SkinnedMeshRender 更换为一般的 Mesh Render,只使用 AnimMap 利用 vs 来随时间修改顶点坐标实现的动画效果。

到这里我们就完成了将动画效果的实现从 CPU 转移到 GPU 运算的目的,可以看到在 CPU 的开销统计中已经没有了动画相关的内容。但是在渲染的统计中,Draw Call 并没有减少,此时渲染8个角色的场景内仍然有10个 Draw Call 的开销。因此下一步我们就来利用 GPU Instancing 技术减少 Draw Call。

0x03 效果不错的GPU Instancing

除了使用批处理,提高图形性能的另一个好办法是使用 GPU Instancing(批处理可以合并不同的mesh,而 GPU Instancing 主要是针对同一个 mesh 来的)。

GPU Instancing 的最大优势是可以减少内存使用和 CPU 开销。当使用 GPU Instancing 时,不需要打开批处理,GPU Instancing 的目的是一个网格可以与一系列附加参数一起被推送到 GPU。要利用 GPU Instancing,则必须使用相同的材质,并传递额外的参数到着色器,如颜色,浮点数等。

不过 GPU Instancing 是不支持 SkinnedMeshRender 的,也就是正常情况下我们带动画的角色是无法使用 GPU Instancing 来减少 Draw Call 的,所以我们必须先完成上一小节的目标,将动画逻辑从 CPU 转移到 GPU 后就可以只使用 Mesh Render 而放弃 SkinnedMeshRender 了。

9

很多 build-in 的 shader 默认是有开启 GPU Instancing 的选项的,但是我们利用AnimMap实现角色动画效果的 shader 显然不是 build-in,因此需要我们自己开启 GPU Instancing 的功能。

#pragma multi_compile_instancing//告诉Unity生成一个开启instancing功能的shader variant

...

struct appdata

{

    float2 uv : TEXCOORD0;

    UNITY_VERTEX_INPUT_INSTANCE_ID//用来给该顶点定义一个instance ID

}

v2f vert(appdata v, uint vid : SV_VertexID)

{

    UNITY_SETUP_INSTANCE_ID(v);//让shader的方法可以访问到该instance ID

    ...

}

使用 GPU Instancing 之后,我们渲染10,000个士兵的 Draw Call 就从10,000左右降低到20上下了。

10

当然,关于 GPU Instancing 的更多内容各位可以在文末的参考链接中找到。

0x04 颜色精度和顶点坐标

还记得之前我说过在利用贴图的纹素的颜色属性保存对应顶点的位置时需要考虑到的一个小问题吗?

是的,那就是颜色的精度问题。

由于现在 rgb 分别代表了坐标的x、y、z,因此rgb的精度就要好好考虑了。例如 rgba32,每个通道只有8位,也就是某一个方向上的位置只有256种可能性,这对位置来说是一个不好的限制。

那么有没有解决方案呢?

当然还是有的。既然这是一个和颜色的精度相关的问题,那么最简单的方案就是增加精度。例如在写本文的时我的 Demo 就是采用的这种方式,我使用了 RGBAHalf 这种纹理格式,而它的精度是每个通道 16bit。当然,移动平台上渲染大量角色的需求往往对动画的精确程度的要求没有那么高,因此 8bit 的精度问题应该也不大。

完整的项目可以到这里到这里下载:

chenjd/Render-Crowd-Of-Animated-Characters

相关资料

近期点赞的会员

 分享这篇文章

陈嘉栋 

慕容小匹夫 微软MVP《Unity 3D脚本编程 》作者 公众号chenjd01 

您可能还会对这些文章感兴趣

参与此文章的讨论

  1. OTAKU牧师 2017-08-17

    一直就很好奇全战他们是怎么做的,文章太棒了。

  2. tanyyt 2017-08-17

    很棒的文章,先收藏

  3. Refrain 2017-08-17

    刺客信条大革命也用过gpu管理渲染的技术,gdcvault上面有ppt吧
    然而水平太低并看不懂那个玩法

    animmap这个玩法很新颖的,我觉得挺有意思。不过vertices一多,图片尺寸也挺麻烦的感觉。这个问题有没有什么解决方案?

    说起来。。突然意识到这算不算是3D版的帧动画?

    最近由 Refrain 修改于:2017-08-17 16:23:13
  4. William Chang 2017-08-17

    看到这篇文章第一反应非常激动。

  5. Wizcas.陈小一 2017-09-22

    很棒的做大规模群体的思路!收益匪浅!

  6. lixd 2019-09-16 微信会员

    呼叫 大神 品测一下,公司新项目手游,类似亿万僵尸,在考虑满屏僵尸的问题~
    这个方案,
    1. 普适性高吗,也就是 对美术素材生产的影响大吗?
    2. 手游的话 即便利用GPU,提升大吗,提升空间大嘛
    比如 unity开发, iphone6的机子,正常开发,大概 会全屏100僵尸(还没测过) ,
    应用此方案,美术更改代价不大(要么还可以考虑通用工具,将原格式素材改成新模式)的假设下,
    能将 僵尸 数量提升到500/1000的程度,或者至少150+,也就是 1.5倍 以上或者 达到 一个最低下限的数据,而不是 在有限 机子性能下,也仅仅是从 100 -》102/110 的地步。。。

您需要登录或者注册后才能发表评论

登录/注册