[CG] 顶点动画贴图 (Vertex Animation Texture, VAT)

什么是顶点动画?

简单来说,通过改变网格顶点的位置,使网格变形从而做成的动画。顶点动画的灵活度要远远高于骨骼动画。骨骼动画是靠骨骼(一堆有层级结构的节点,数量应该是远远小于网格顶点的数量的)的变化来驱动网格的形变,从自由度上来说,顶点动画每个网格的顶点都可以发生变化,因此自由度要远高于骨骼动画,可以做出骨骼动画达不到的一些效果,例如像流体这种拓扑发生变化的物体。

如何在游戏引擎中使用顶点动画?

在动画的每一帧,网格的顶点的位置、数量、拓扑都可以发生变化,因此对于这种动画,我们只能把每一帧的数据都给保存下来(当然也有网格序列的压缩方法,例如利用类似 PCA 的方法等),因此数据量会很大。在游戏引擎中有两种常见的做法

  • 一种是在 CPU 侧,将数据存储为网格序列,然后按照一定的规则去播放这个网格序列,像 Unity 就支持 .abc (alembic)文件的格式的导入。
  • 一种是在 GPU 侧,将数据写入到一张或几张贴图中,然后在 shader 中贴图进行采样、还原,然后在 vertex shader 中根据贴图中的数据来改变顶点的位置,这就是本文要介绍的 “顶点动画贴图” (VAT)技术。

VAT 烘焙的流程

VAT 技术很灵活,因为核心思想只是在贴图中存了一些数据,至于这个数据怎么存?存什么?怎么解析?完全都是可以自己控制的,因此也没有什么规范之类的。在 Houdini 中是支持导出 VAT 的,也有配套的 Unity/UE 中解析的 shader,Blender 似乎是没有内置的导出 VAT 的能力,一般都是靠自己写或者相关插件之类。其实也不一定要从 DCC 软件中导出 VAT,只要这些模型文件(例如 .usd, .abc, .fbx, …)中保存了网格序列必要的信息,我们可以直接去解析这些文件,来制作 VAT。除了保存数据的贴图外,还需要一个基础的网格来承载基础的顶点数据,因为必须要有网格数据,才能用 shader 来进行绘制,每次渲染都是绘制的这个网格,只不过我们在 shader 中利用 VAT 改变了这个网格的顶点数据而已。

我们可以简单的分一分几种动画类型:

  • 拓扑改变的:例如流体
  • 拓扑不变的:例如布料

每种类型的处理会稍稍有些区别:

拓扑改变的

  1. 首先需要遍历一下网格序列,找一下哪一帧的网格的面最多。原因是绘制的三角面的数量,如果我们随便找一帧作为基础网格,例如基础网格有 100 个三角面(face),而其他帧也许有 200 个三角面,那么无论如何也没法绘制 200 个三角面了,所以需要找一下最大的那个。对于某一帧的绘制,也许当前需要绘制的面的数量小于基础网格面的数量,这个也不要紧,可以将初始网格的所有顶点的位置都设置为 (0,0,0),所以多余的面也不会画出来。
  2. 计算贴图的尺寸,因为贴图的尺寸关系到我们如何进行采样。可以简单的计算一下,我们可以假设 y 方向代表的是不用的帧,而 x 方向代表的是同一帧内不同的顶点。例如一帧我们有 1000 个顶点(顶点可能会重复,例如 2 个面有 6 个顶点,不会共用顶点),那么我们可以选择 width = 512 的贴图,那么要存 1 帧的 1000 个顶点,需要 2 行,如果我们有 100 帧,那最终 VAT 的尺寸为 (512, 200)。
  3. 计算 顶点 uv 坐标,在计算完 VAT 的尺寸后,我们需要给基础网格的顶点计算 uv 坐标,来代表这个顶点在 VAT 对应的位置。如下:
# + 0.5 是为了采样的时候在像素的中心位置
# 1.0 - 是因为 uv 坐标一般左下是 (0,0),而图像一般左上是 (0,0)
for i in range(1000):
    u = ((i % 512) + 0.5) / 512 
    v = 1.0 - ((i // 512) + 0.5 / 200)
  1. 计算完成 uv 以后,我们就可以将 uv 数据写入网格顶点数据来,网格的顶点数据可以是 POS + TEXCOORD0 + NORMAL。
  2. 写入贴图,可以遍历每一帧,然后在每一帧按照面的顺序遍历每一个顶点,在贴图中,依次每 3 个顶点代表一个面。同理,法线数据(normal)也是类似顶点的方式,写入另一张贴图里。这里需要记得一件事情,如果是 RGB8,那么 RGB 和对应 xyz,但是 xyz 需要先归一化一下,即用 minmax 归一化的方法,因为 RGB 的只能存储 0-255。
  3. 保存元信息,这里的元信息可以保存帧数,所有顶点中的最大最小值,bbox 等自己可以用的到的信息。

这样我们最终得到了 VAT_pos, VAT_nor, mesh, meta 四个文件,分别对应顶点位置信息、法线信息、网格信息和元信息,有了这些数据就可以拿到游戏引擎中去解析了。

拓扑不变的

拓扑也可以按照拓扑改变的方法可以进行制作,不过可以省略一些步骤,例如遍历网格序列寻找最大的面数,因为拓扑不变,所以可以用第一帧的网格作为基础网格,其他的做法基本都一样

有了 VAT 如何使用?

有了 VAT 以后,需要有可以解析的 shader 进行支持,即在 shader 里将 VAT 里的数据读出来,这个 shader 根据自己的烘焙的规则,然后自己进行解析。github上有一些开源的 shader,或者是 Houdini 配套的 shader 可以使用。本文先介绍一下 VAT 的概念以后烘焙的方法,下一篇来介绍一下在 shader 中解析的方法。