让我们算一下。为了便于讨论,我们将忽略静态网格几何体的大小和其他并非每帧都更新的数据(例如反向绑定矩阵,如果您正在执行矩阵调色板蒙皮)。这些皮肤实例数据缓冲区中的每一个有多大?
Instance count * joint count * elements per skinning matrix * bytes per matrix element
50,000 * 50 * 12 * 4 = 120 MB
(我忽略了其他可能因帧而异的因素,因此如果有帮助,请将这些数字替换为您的真实数据的数量级。其余的分析仍然适用。)
所以是的,有相当多的空间,但不一定不可行。我们可能会选择退回到双缓冲而不是对所有内容进行三重缓冲,从而将所需的总空间从 360 MB 减少到 240 MB。
我们还假设我们正在使用 3x4 矩阵进行矩阵调色板蒙皮。我们可以使用dual quaternion skinning 再次将所需空间减半。
这里的一个问题是,如果我们在离散 GPU 上运行,将每帧 60 MB 的蒙皮数据传输到 GPU 将消耗大量带宽。因此,我们可能会考虑将我们的联合计算转移到 GPU。在具有共享内存架构的 iOS 上,这不是问题。无论哪种方式,我们都希望了解如何计算变换层次结构,因为如果每个关节每帧都移动,我们将进行大量乘法运算。
至于不同的实例计数,我们绝对不想做的一件事是每帧重新分配新的缓冲区。相反,我们可以重新分配缓冲区,增加其大小geometrically,当我们注意到实例数超过了我们缓冲区的当前最大容量时。
或者,我们可以采用“分页”方法,将实例拆分到多个缓冲区中,每个缓冲区都可以容纳相当数量的实例,并且在 GPU 启动后通过将它们添加到可重用的缓冲池中来回收这些实例。从他们那里完成渲染。这确实需要将渲染拆分为多个绘制调用(除非我们可能使用参数缓冲区),但是与时间相比,发出 10 个实例绘制调用(每个实例的 1/10)的开销可以忽略不计需要实际渲染。
我们还可以考虑使用MTLHeap 创建瞬态缓冲区,比直接从我们的设备分配缓冲区更便宜,但这仍然需要选择一个合理的初始上限,并随着最大实例数的增加而增长/重新分配。
归根结底,基准胜过理论。当然,您应该首先分析您的数据,就像我们在这里尝试做的那样,但是构建一个测试应用程序并将这些想法付诸实践,注意实际出现问题的地方,是确定您需要多少优化的唯一真正方法要在目标平台上顺利运行。