这是第30篇与游戏开发有关的文章。

| 写在最前

网上有多种通过GPU实现骨骼动画的实例化绘制方法,本文介绍的是其中的一种:将顶点信息逐帧写入纹理后,在顶点着色器中通过读取动画纹理,提取顶点位置并变换,最终实现角色动画的方法。

本文将简述其实现原理,并分享一个(完成了一半的)网格合并及实例化绘制工具。

| 如何提高绘制效率

当产生了“要将大量游戏对象呈现给玩家”的需求时,我们就会碰到这样一个问题:如何才能提高GPU的绘制效率。

批量绘制较多的骑兵

通常情况下CPU对GPU发起的绘制命令,才是性能的瓶颈所在。CPU为绘制准备数据、显存加载数据、为GPU设置渲染状态等行为所花费的时间,通常比GPU绘制所花费的时间要多。这也就是为什么我们经常会把DrawCall次数当成快速评判渲染效率的“KPI”。

反观Unity提供的Static batching(静态合批)Dynamic batching(动态合批),也都是从减少CPU到GPU的调用次数为出发点,尽量一次发送一个大的网格(一大堆顶点数据),以减少CPU和GPU的通信次数,提高彼此的工作效率。

但是无论静态还是动态合批,在大量游戏对象绘制的需求面前,都不太合适。

静态合批从名字上就知道不能用来绘制移动物体,而且其本身还会产生非常大的内存开销(它需要额外的内存空间来存储合并的网格);动态合批也有自己的问题,如顶点数量的限制、材质球限制、无法作用于蒙皮网格(SkinnedMeshRenderer)等,还会对CPU产生不小的压力(因为它要不停地去动态计算并合并网格)。

| 实例化绘制

实例化绘制技术的出现,就是为了在不提高CPU负担的基础之上,解决CPU到GPU调用开销大的问题。对于相同的物体(同一个网格),只需一次调用,GPU就会根据我们想要绘制的次数,啪啪啪一通画,非常的高效。

但是简单重复绘制一个物体多次(比如重复绘制1000次小兵),并没有任何意义。为了能够绘制出1000个不同的小兵,我们还需要提前为GPU准备一些额外的数据,比如1000个转换矩阵(画在不同的位置)、1000个混合色(呈现不同的颜色)等,最终在屏幕上呈现出千军万马的画面。

| 骨骼动画与实例化绘制

如果我们想要在游戏世界上呈现非常多相同的、静止不动的石头,那到此为止就可以了。我们使用Unity提供的手动实例化绘制接口Graphics.DrawMeshInstanced,通过传入同一个石头的网格和每一个石头的转换矩阵,就可以实现需求(其实Unity也会自动为添加了MeshRenderer组件的单位尝试使用实例化绘制以提高效率)。

但是对战场中的小兵做这种简单地操作就不太合适了,这个道理早在1994年上映的电影《精武英雄》中,就已经明确的告诉过我们了。

船越文夫在教导陈真 图源网络

这是因为小兵通常是采用骨骼动画来实现动作的,而骨骼动画对于蒙皮网格的驱动,是CPU即时计算出来的。每个小兵相同时刻的状态可能都不同,也就是说相同网格同一时刻的顶点位置会有很大差别,因此无法直接进行实例化绘制。

既然CPU上即时计算的骨骼动画无法进行实例化绘制,我们就不让CPU计算,而让这些计算发生在GPU上,便可将问题解决。

| 它的原理很简单

1、将骨骼动画每一帧对网格各个顶点的变化结果存在一张纹理中,其中二手手游账号交易平台纹理的横坐标是顶点索引,纵坐标是时间,而横纵相交对应的值,是这一时刻该顶点在本地空间下的坐标

2、有了这张“顶点动画纹理”,在顶点着色器中,我们就可以忽视传入顶点着色器的顶点位置信息;而以当前所处理的顶点索引U,以动画播放至此的时间刻度为V,从上一步的纹理坐标中采样。而采样到的结果,就是当前这个顶点此时的位置。

3、接下来的步骤便与传统绘制一样,与MVP矩阵相乘做空间变换,传入片段着色器中着色等...可以很容易的想象到,连续为网格上所有顶点设置不同时间下的空间位置,最终绘制到屏幕上时,就能呈现出动画效果了。

| 一些相对重要的细节

1、用实例化ID来获取差异实例单位的属性

由于我们的最终目标是绘制多个不同动画状态的单位,因此从动画纹理中,用于采样信息的时间刻度值,是根据实例化ID,从保存实例化属性的数据块中获取到的,这样就可以实现每个实例化单位的动画播放进度的差异。

2、合并多个不同的网格

手动调用实例化绘制接口时,只能传入一个网格。而我们平时使用的游戏对象,通常是由若干个蒙皮网格和若干个普通网格组成。比如一个骑兵模型:士兵和马匹分别是两个蒙皮网格;而士兵手持的武器通常是一个普通网格,以方便后期做武器替换。

一个游戏对象可能会由两种、多个网格组合而成

因此我们会在编辑器模式下,将整个对象包含的网格合并成一个网格,并将这个网格保存成资源,以便后面调用绘制命令时作为实参传入。

合并成为一个网格

3、多贴图时处理UV

此外,有些模型上不同的网格还对应了不同的贴图,比如网格Mesh_0,使用了贴图Texture_0,网格Mesh_1使用了贴图Texture_1,由于网格进行了合并,如果针对合并后的网格使用同一张贴图,便会出现错误。

胯下战马错误的颜色采样

针对这种情况我们要在合并时做特殊处理,一种处理方式是合并多张贴图,如将Texture_0与Texture_1合并,然后偏移原本Mesh_1的uv坐标,但是这要求两张贴图都不能太大,否则无法合并到一张贴图中;另一种方法是仍然保留两张贴图Texture_0和Texture_1,但是对Mesh_0和Mesh_1的uv2做特殊处理,如使用uv2x保存两张贴图的Lerp值。​这样片段着色器中对两张贴图的采样结果做二次计算后,就可以得到正确的颜色了。

为战士和战马分别替换贴图​

4、动画的混合

通过纹理实现的动画也可以实现简单的混合效果,它是通过在顶点着色器中对多个动画纹理进行采样,然后根据一个混合比例,对多个位置信息进行计算以实现的。

根据速度一维向量进行的Locomotion状态混合

5、脱离了Renderer的渲染

由于是直接调用了Graphics.DrawMeshInstanced进行的绘制,因此并没有GameObject被创建出来,减少了对象的创建数量,一定程度上也减少了内存及CPU的开销;但是需要自己在loop中组织数据的更新及渲染的更新。

脱离了GameObject+Renderer的绘制

| 使用动画纹理的优点

1、易于理解、易于实现;

2、CPU的计算(合并网格、记录动画信息)发生在编辑器阶段,游戏运行时CPU没有额外的开销;

3、可以实现实例化绘制,充分发挥GPU的绘制效率。

| 使用动画纹理的缺点

1、记录顶点动画的纹理大小,一方面取决于模型的顶点数量,另一方面取决于动画的长度,如果顶点数量过多,或动画过长,生成的纹理就会很大,对显存的占用量也会上升;

2、实现动画混合,需要从多个动画纹理中采样并进行计算,采样次数多;

3、无法使用动画状态机控制动作;

4、动作信息在存储时会受保存格式的精度影响,因此读取出来的动画可能不够精确;

5、无法实现骨骼动画中的IK(反向动力学)等。

虽然有不少缺点,但是如果你的目的是大批量绘制环境装饰(树、草、石头)或细节要求不高的杂鱼小兵、路人,它都是你实现目的优秀手段,值得你去使用它。

| 写在最后

最后,分享一个没有写完的网格合并及实例化绘制工具,可以实现上述简单的功能。

通过工具生成动画资源文件

简单的动画播放

大批携带动画角色的实例化绘制

入门顶点动画纹理的实例化绘制相关推荐

  1. Unity shader入门精要学习笔记-代码篇6(序列帧动画/滚动背景/流动河流/广告牌/顶点动画的阴影)

    一.序列帧动画 建立一个四边形对着摄像机. 我们需要一张序列帧图像,这里用到8x8的爆炸图. 给四边形上材质和shader,代码如下: Shader "Custom/NewSurfaceSh ...

  2. UnityShader入门精要——顶点动画

    流动的河流 Shader "Unity Shaders Book/Chapter 11/Water" {Properties {_MainTex ("Main Tex&q ...

  3. OpenGL从入门到精通--纹理

    纹理 github源码仓库 opengl环境准备 opengl编程从入门到精通-hello,window OpenGL从入门到精通–你好三角形 OpenGL从入门到精通–着色器的使用 我们可以为每个顶 ...

  4. Unity Shader 顶点动画 广告牌

    另一种常见的顶点动画就是广告牌技术(Billboarding).广告牌技术会根据视角方向来旋转一个被纹理着色的多边形(通常就是简单的四边形,这个多边形就是广告牌),使得多边形看起来好像总是面对这摄影机 ...

  5. 3dmax导出fbx时如何带贴图_houdini | 第一章 第三节 贴图与顶点动画

    大家好,上篇文章介绍了houdini的建模思路与常用节点.本篇文章将介绍houdini材质贴图与顶点动画的设置,其中会用到game_dev模块的节点.若未安装,请查看第一节基础入门与实践中的介绍进行安 ...

  6. ui unity 图片高亮_程序化生成UI模型与顶点动画

    顶点动画一般指通过顶点着色器对模型每个顶点独立运动的方法.在游戏中遇到碎片爆破或者聚合一类的.涉及到大量同类物体在有序和无序之间切换的特殊效果,就非常适合将所有物体顶点每帧位移和形变的迭代交付给GPU ...

  7. OPenGL实例化绘制、普通绘制说明

    OpenGL 实例化(Instancing)是一种只调用一次渲染函数就能绘制出不少物体的技术,能够实现将数据一次性而不是多次发送给 GPU ,告诉 OpenGL使用一个绘制函数,将这些数据绘制成多个物 ...

  8. UnityShader23:顶点动画

    前置:UnityShader22:序列帧动画 一.顶点动画 如果说序列帧动画(uv 动画)是通过改变 uv 坐标来实现的,那么顶点动画就是通过改变 Mesh 每个顶点的位置,直接让这个物体" ...

  9. 带顶点动画的护盾效果——UnityShader学习笔记

    文章目录 自言自语 一.效果 二.C# 三.Shader 总结 自言自语 最近又是很久没有更新笔记了.原因有二. 一.最近一直再啃一个看起来酷炫的护盾效果 啃了好久啊.直至效果满意 也理解了. 这个是 ...

最新文章

  1. .NET连接SAP系统专题:C#调用RFC代码(三)
  2. centos7 网卡配置vlan_Centos7安装后的一些基础配置
  3. mysql主从配置原理_MySQL主从复制原理
  4. 如何在 IDEA 使用Debug 图文教程
  5. Jstorm+Spring+mybatis整合
  6. Java多线程并发编程
  7. 单片机为什么一直用C语言,不用其他编程语言?只有学过的知道!
  8. sonar工具使用常见问题解决
  9. mysql调换数据_mysql互换表中两列数据方法
  10. [UVa 122] Trees On the Level
  11. 21复变函数的积分(七)
  12. IDDD 实现领域驱动设计-理解限界上下文
  13. docker镜像命令
  14. 数据库分库分表之后如何查询统计?
  15. 易宝支付[钱麦](附代码)
  16. 智慧多功能综合杆案例分享:上海市多杆合一、综合杆道路智慧路灯项目解决方案解析
  17. matlab四叶玫瑰线,多种语言画玫瑰,总有一款适合你表白
  18. 如果格局决定人生,那到底什么决定了格局
  19. 关于如何安装linux系统
  20. 安卓自定义下拉列表样式_自定义下拉列表样式

热门文章

  1. 我读研时通过实习和比赛收入五十万
  2. 35所大学获批新增「人工智能」本科专业,工学学位、四年制
  3. python的django框架与springboot_Python系统教学|为什么Django框架在Python开发很重要?...
  4. foreach判断最后一个_PHP 内核:foreach 是如何工作的(二)
  5. 深度学习-Tensorflow2.2-RNN循环神经网络{11}-评论分类-25
  6. linux ftp站点名称,Linux ftp命令的使用方法有哪些
  7. 安规电容能用什么代替_电容系列之安规电容
  8. PIE SDK剔除栅格块算法
  9. python中使用kazoo连接zookeeper(一)
  10. Java面试总结系列之Collections.sort()