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

0x00 前言
我想很多开发游戏的小伙伴都希望自己的场景内能渲染越多物体越好,甚至是能同时渲染成千上万个有自己动作的游戏角色就更好了。
但不幸的是,渲染和管理大量的游戏对象是以牺牲CPU和GPU性能为代价的,因为有太多Draw Call的问题,如果游戏对象有动画的话还会涉及到cpu的蒙皮开销,最后我们必须找到其他的解决方案。那么本文就来聊聊利用GPU实现角色的动画效果,减少CPU端的蒙皮开销;同时将渲染10,000个带动画的模型的Draw Call从10,000+减少到22个。(模型来自:RTS Mini Legion Footman Handpainted)

0x01 Animator和SkinnedMeshRender的问题
正常情况下,大家都会使用Animator来管理角色的动画,而角色也必须使用SkinnedMeshRender来进行渲染。

例如在我的测试场景中,默认情况下渲染10,000个带动作的士兵模型,可以看到此时的各个性能指标十分糟糕:CPU 320+ms,DrawCall:8700+。

因此,可以发现如果要渲染的动画角色数量很大时主要会有以下两个巨大的开销:
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中实现。例如飘扬的旗帜或者是波浪等等。

(来源于bing搜索)
那么我们能否利用vs设置顶点坐标的方式来展现我们的角色动画呢?

答案当然是可行。只不过和飘扬的旗帜那种简单的效果不同,这次我们不仅仅利用几个简单的vs的属性来实现动画效果,而是将角色的动画信息烘焙成一张贴图供vs使用。
简单来说,我们按照固定的频率对角色动画取样并记录取样点时刻角色网格上各个顶点的位置信息,并利用贴图的纹素的颜色属性(Color(float r, float g, float b, float a))保存对应顶点的位置(Vector3(float x, float y, float z))。当然利用颜色属性保存顶点的位置信息时需要考虑到一个小问题,在下文我会再说。
这样该贴图就记录了整个动画时间内角色网格顶点在各个取样点时刻的位置,这个贴图我把它称为AnimMap。
一个AnimMap的结构就是下图这样的:

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


上图是将角色的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了。

很多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上下了。
当然,关于GPU Instancing的更多内容各位可以在文末的参考链接中找到。
0x04 颜色精度和顶点坐标
还记得之前我说过在利用贴图的纹素的颜色属性保存对应顶点的位置时需要考虑到的一个小问题吗?
是的,那就是颜色的精度问题。
由于现在rgb分别代表了坐标的x、y、z,因此rgb的精度就要好好考虑了。例如rgba32,每个通道只有8位,也就是某一个方向上的位置只有256种可能性,这对位置来说是一个不好的限制。
那么有没有解决方案呢?
当然还是有的。既然这是一个和颜色的精度相关的问题,那么最简单的方案就是增加精度。例如在写本文的时我的Demo就是采用的这种方式,我使用了RGBAHalf这种纹理格式,而它的精度是每个通道16bit。当然,移动平台上渲染大量角色的需求往往对动画的精确程度的要求没有那么高,因此8bit的精度问题应该也不大。
完整的项目可以到这里到这里下载:
Render-Crowd-Of-Animated-Characters
ref:
【1】GPUInstancing
【2】How to take advantage of textures in the vertex shader
【3】GPU Gems 3 Chapter 2. Animated Crowd Rendering
【4】题图来自《全面战争:阿提拉》
各位如果觉得有趣的话,欢迎点个赞。
-EOF-
最后打个广告,欢迎支持我的书《Unity 3D脚本编程》

欢迎大家关注我的公众号慕容的游戏编程:chenjd01

利用GPU实现大规模动画角色的渲染的更多相关文章
- 利用GPU实现大规模动画角色的渲染(转)
原文: https://www.cnblogs.com/murongxiaopifu/p/7250772.html 利用GPU实现大规模动画角色的渲染 0x00 前言 我想很多开发游戏的小伙伴都希望自 ...
- 利用GPU实现无尽草地的实时渲染
0x00 前言 在游戏中展现一个写实的田园场景时,草地的渲染是必不可少的,而一提到高效率的渲染草地,很多人都会想起GPU Gems第七章 <Chapter 7. Rendering Countl ...
- 利用GPGPU计算大规模群落仿真行为
0x00 前言 在今年6月的Unite Europe 2017大会上 Unity 的CTO Joachim Ante演示了未来Unity新的编程特性--C# Job系统,它提供了编写多线程代码的一种既 ...
- 利用GPU实现翻页效果(分享自知乎网)
https://zhuanlan.zhihu.com/p/28836892?utm_source=qq&utm_medium=social 首发于Runtime 写文章 利用GPU实现翻页效果 ...
- "Javascript高性能动画与页面渲染"笔记
前言:好久没翻阅我的gmail邮箱了,午休时就打开看了一下,看到InfoQ推荐的一篇名为“Javascript高性能动画与页面渲染”文章,粗略的看了一下,很赞!讲的很详细,对好些细节讲的都很好,很通俗 ...
- Android利用温度传感器实现带动画效果的电子温度计
概述 Android利用温度传感器实现带动画效果的电子温度计. 详细 代码下载:http://www.demodashi.com/demo/10631.html 一.准备工作 需要准备一部带有温度传感 ...
- 我的Android进阶之旅------>Android利用温度传感器实现带动画效果的电子温度计
要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight ...
- Mysql 5.7.18 利用 MySQL proxies_priv(模拟角色)实现类似用户组管理
利用 MySQL proxies_priv(模拟角色)实现类似用户组管理 角色(Role)可以用来批量管理用户,同一个角色下的用户,拥有相同的权限. MySQL5.7.X以后可以模拟角色(Role)的 ...
- js课程 5-14 js如何实现控制动画角色走动
js课程 5-14 js如何实现控制动画角色走动 一.总结 一句话总结:首先是onkeydown事件,然后是改变元素的left和top属性 1.常用键盘事件有哪些? • onkeydown和 onke ...
随机推荐
- 尝试向树莓派3B引入Drbian 9 arm64-PART 1
Stage 1:试图加入arm64软件包 笔者默认您清楚这些指令背后的意义以及其可能造成的后果,并默认您已备份重要资料 sudo dpkg --add-archietcture arm64 sudo ...
- 数据结构与算法1-2 C语言运行时间检测算法
#include <stdio.h> #include <math.h> #include <time.h> clock_t start,stop; #define ...
- javamail 邮件格式再优化(由详情——>改为统计)
前言:之前扩展的ant-jmeter支持邮件附件形式上传以及邮件内容的html文件格式. 如图: 由于邮件的内容格式是详情信息,也就是说直观的显示的是case,但由于case的增加,邮件内容越来越大! ...
- MySQL5.7绿色版(免装版)的初始化和修改密码
1.下载MySQL5.7.18绿色版 1.1下载链接 以下是MySQL5.7.18绿色版的链接(来源oracle官网),打开链接直接下载 https://dev.mysql.com/gt/Downlo ...
- v9 调用模型中新增的字段
在模型中新增字段的时候,可以选择“是否为主表”. 若选是,则前台调用可直接通过字段名调用. 若选否,在前台调用是应在{pc:content}中添加 moreinfo="1",表示允 ...
- vue中数据双向绑定注意点
最近一个vue和element的项目中遇到了一个问题: 动态生成的对象进行双向绑定是失败 直接贴代码: <el-form :model="addClass" :rules=& ...
- 映射语句之INSERT语句
1.一个 INSERT SQL 语句可以在<insert>元素在映射器 XML 配置文件中配置 例子: <insert id="insertStudentWithId&qu ...
- webpack前端工程化构建工具的使用
一.模块打包机 1.创建文件 在目标文件下建立一个src文件夹作为js代码区:作为例子,我创建了两个js文件,并利用commonJS规范require引入到index.js中: moduleA.js: ...
- 使用Ansible进行项目的自动部署(Tomcat、Weblogic)
原文:https://github.com/x113773/testall/issues/4 问题:Weblogic/Tomcat 通过JAVA直接远程调用或者调用本地Shell还是通过Ansible ...
- gulp 运用 的理解
ugulp.task('build', function() { runSequence('clean', 'copy', ['uglify', 'sass', 'htmlmin'], 'base64 ...