在OpenGL ES中优化用于骨骼动画的顶点

5

我正在使用一个2D骨骼动画系统。

有X个骨头,每个骨头至少有一个部分(一个四边形,两个三角形)。平均而言,我可能有20个骨头和30个部分。大多数骨头依赖于父级,每帧骨头都会移动。每个动画总共多达1000帧,我使用了大约50个动画。任何时候都有约50000帧的内存负载。不同骨架实例之间的部件不同。

我采取的第一种方法是计算每个骨头的位置/旋转,并构建一个顶点数组,其中包括每个部分的以下内容:

[x1,y1,u1,v1],[x2,y2,u2,v2],[x3,y3,u3,v3],[x4,y4,u4,v4]

每帧都将其传递给glDrawElements。

这看起来很好,覆盖了我需要的所有场景,不占用太多内存,但是性能非常差。在iPod 4上,渲染10个这样的骨架可能只有15fps。

我发现大部分性能都被每帧复制的大量顶点数据所消耗。于是我决定采取另一个极端,“预先计算”动画,在每个角色开始时建立一个包含每个部位每一帧的xyuv坐标的顶点缓冲区。然后,我计算应该在特定时间使用哪个帧的索引,并计算一个增量值,通过传递到用于在当前和下一帧之间进行插值的着色器中。

每个帧的顶点如下所示:

[--------------------- Frame 1 ---------------------],[------- Frame 2 ------]
[x1,y1,u1,v1,boneIndex],[x2, ...],[x3, ...],[x4, ...],[x1, ...][x2, ...][....]

顶点着色器如下所示:
attribute vec4 a_position;
attribute vec4 a_nextPosition;
attribute vec2 a_texCoords;
attribute float a_boneIndex;

uniform mat4 u_projectionViewMatrix;
uniform float u_boneAlpha[255];

varying vec2 v_texCoords;

void main() {
    float alpha = u_boneAlpha[int(a_boneIndex)];
    vec4 position = mix(a_position, a_nextPosition, alpha);
    gl_Position = u_projectionViewMatrix * position;
    v_texCoords = a_texCoords;  
}

现在,性能很好,屏幕上有10个图形,帧率保持在50fps左右。但现在它使用了大量的内存。我通过缩减xyuv的精度(现在是ushorts)来优化它。
还有一个问题是骨骼依赖关系丢失了。如果有两个骨头,一个父骨和一个子骨,子骨在0秒和2秒有一个关键帧,父骨在0秒、0.5秒、1.5秒、2秒有关键帧,则子骨在0.5秒到1.5秒之间不会像应该的那样发生变化。
我想出了一个解决这个骨骼问题的办法——强制让子骨在与父骨相同的时间点拥有关键帧。但这将使用更多的内存,基本上破坏了骨骼层次结构的重点。
这就是我现在所处的状态。我正在尝试在性能和内存使用之间寻找平衡。我知道这里有很多冗余信息(对于特定部分的所有帧,UV坐标都是相同的,因此重复了约30次)。而且每组零件都必须创建一个新的缓冲区(因为不同的零件大小不同,所以位置会改变)。
现在我要尝试为每个字符设置一个顶点数组,其中包含所有零件的xyuv,并计算每个零件的矩阵,并在着色器中重新定位它们。我知道这会起作用,但我担心性能不会比我一开始上传XYUV的每个帧好。
有没有更好的方法来做到这一点,而不失去我所获得的性能?
还有什么疯狂的想法可以尝试吗?

每一帧所有的骨骼都是“自己移动”的吗?还是许多骨骼只是因为父级骨骼移动而被移动? - D-rk
除了一个骨骼之外,其他所有的骨骼都随着父级一起移动。有些骨骼几乎独立于父级而无法移动,而有些骨骼则比父级移动更多次。 - Richard Taylor
1个回答

1
更好的方法是在运行时转换您的30个部件,而不是制作成千上万个不同位置的部件副本。您的顶点缓冲区将包含一个顶点数据副本,节省大量内存。然后,每帧可以由一组变换表示,对于每个使用glDrawElements()调用绘制的骨骼,通过传递为uniform的一组变换到您的顶点着色器。每个依赖骨骼的变换都是相对于父骨骼构建的。然后,根据您想要的手工制作和程序生成之间的连续性,在您的变换集中可以占用更多或更少的空间和CPU计算时间。
Jason L. McKesson的免费书籍Learning Modern 3D Graphics Programming在第6章中对如何实现此目标进行了很好的解释。本章末尾的示例程序展示了如何使用矩阵堆栈实现分层模型。我有一个OpenGL ES 2.0 iOS端口的此程序可用

不幸的是,这正是我的原始版本的工作方式。而且它的性能远不如当前的实现。我知道用这种方式会使用大量令人讨厌的内存,并增加我们的加载时间,但至少在屏幕上有很多字符时,它快了至少50%。但是,感谢您的建议,已经点赞了。 - Richard Taylor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接