金属实例渲染——几个问题
Metal instance rendering – a few questions
我目前正在学习 Apple 的 Metal API,我无法理解如何实现大量实例的渲染,因为每个实例都需要有自己的(部分) 缓冲区。
假设我想绘制大约 50.000 个模型实例(我承认这很荒谬。)每个实例都将其定位和骨骼动画数据存储在缓冲区中,假设每个实例 50 个关节。现在考虑最多可以同时渲染三帧,我需要这些实例缓冲区乘以帧数。
这将导致相当荒谬的缓冲区大小,仅用于绘制实例。另外,应该如何实现不同数量的实例,如果当前实例不能容纳要呈现的所有对象的数据,则分配更大的缓冲区?只需决定每帧的最大实例数?
我研究过的所有文章和教程似乎都表明:场景中存在最大数量的对象。
让我们计算一下。为了便于讨论,我们将忽略静态网格几何体的大小和其他并非每帧都更新的数据(如反向绑定矩阵,如果您正在执行矩阵调色板蒙皮)。每个皮肤实例数据缓冲区有多大?
Instance count * joint count * elements per skinning matrix * bytes per matrix element
50,000 * 50 * 12 * 4 = 120 MB
(我忽略了每帧可能不同的其他因素,因此如果有帮助,请将这些数字替换为真实数据的数量级。其余分析仍然适用。)
所以是的,相当数量的 space,但不一定不可行。我们可能会选择回到双缓冲而不是三缓冲所有内容,将所需的总 space 从 360 MB 减少到 240 MB。
我们还假设我们正在使用 3x4 矩阵进行矩阵调色板蒙皮。我们可以使用 dual quaternion skinning.
再次将所需的 space 减半
这里的一个问题是,如果我们 运行 在离散 GPU 上运行,将每帧 60 MB 的蒙皮数据传输到 GPU 将消耗大量带宽。因此,我们可能会考虑将我们的联合计算转移到 GPU。在 iOS 上,使用共享内存架构,这不是什么大问题。无论哪种方式,我们都希望了解如何计算变换层次结构,因为如果每个关节在每一帧都移动,我们将进行大量乘法运算。
至于不同的实例数,我们绝对不想做的一件事是每帧都重新分配新的缓冲区。相反,当我们注意到实例数量超过缓冲区的当前最大容量时,我们可以重新分配缓冲区,增加其大小 geometrically。
或者,我们可以采用 "paged" 方法并将实例拆分到多个缓冲区中,每个缓冲区都可以容纳大量实例,并通过将它们添加到可重用缓冲池中进行回收一旦 GPU 从它们完成渲染。这确实需要将渲染拆分为多个绘制调用(除非我们可能正在使用参数缓冲区),但是发出 10 个实例化绘制调用(每个具有 1/10 的实例)的开销与时间相比可以忽略不计需要实际渲染。
我们也可以考虑使用 MTLHeap
创建瞬时缓冲区比直接从我们的设备分配缓冲区更便宜,但这仍然需要选择一个合理的初始上限和 growing/reallocating 作为最大数量实例增加。
归根结底,基准胜过理论。自然地,您应该从分析您的数据开始,就像我们在这里尝试做的那样,但是构建一个测试应用程序并将这些想法付诸实践,注意实际出现问题的地方,是确定您需要多少优化的唯一真正方法运行 在您的目标平台上顺利完成。
我目前正在学习 Apple 的 Metal API,我无法理解如何实现大量实例的渲染,因为每个实例都需要有自己的(部分) 缓冲区。
假设我想绘制大约 50.000 个模型实例(我承认这很荒谬。)每个实例都将其定位和骨骼动画数据存储在缓冲区中,假设每个实例 50 个关节。现在考虑最多可以同时渲染三帧,我需要这些实例缓冲区乘以帧数。
这将导致相当荒谬的缓冲区大小,仅用于绘制实例。另外,应该如何实现不同数量的实例,如果当前实例不能容纳要呈现的所有对象的数据,则分配更大的缓冲区?只需决定每帧的最大实例数?
我研究过的所有文章和教程似乎都表明:场景中存在最大数量的对象。
让我们计算一下。为了便于讨论,我们将忽略静态网格几何体的大小和其他并非每帧都更新的数据(如反向绑定矩阵,如果您正在执行矩阵调色板蒙皮)。每个皮肤实例数据缓冲区有多大?
Instance count * joint count * elements per skinning matrix * bytes per matrix element
50,000 * 50 * 12 * 4 = 120 MB
(我忽略了每帧可能不同的其他因素,因此如果有帮助,请将这些数字替换为真实数据的数量级。其余分析仍然适用。)
所以是的,相当数量的 space,但不一定不可行。我们可能会选择回到双缓冲而不是三缓冲所有内容,将所需的总 space 从 360 MB 减少到 240 MB。
我们还假设我们正在使用 3x4 矩阵进行矩阵调色板蒙皮。我们可以使用 dual quaternion skinning.
再次将所需的 space 减半这里的一个问题是,如果我们 运行 在离散 GPU 上运行,将每帧 60 MB 的蒙皮数据传输到 GPU 将消耗大量带宽。因此,我们可能会考虑将我们的联合计算转移到 GPU。在 iOS 上,使用共享内存架构,这不是什么大问题。无论哪种方式,我们都希望了解如何计算变换层次结构,因为如果每个关节在每一帧都移动,我们将进行大量乘法运算。
至于不同的实例数,我们绝对不想做的一件事是每帧都重新分配新的缓冲区。相反,当我们注意到实例数量超过缓冲区的当前最大容量时,我们可以重新分配缓冲区,增加其大小 geometrically。
或者,我们可以采用 "paged" 方法并将实例拆分到多个缓冲区中,每个缓冲区都可以容纳大量实例,并通过将它们添加到可重用缓冲池中进行回收一旦 GPU 从它们完成渲染。这确实需要将渲染拆分为多个绘制调用(除非我们可能正在使用参数缓冲区),但是发出 10 个实例化绘制调用(每个具有 1/10 的实例)的开销与时间相比可以忽略不计需要实际渲染。
我们也可以考虑使用 MTLHeap
创建瞬时缓冲区比直接从我们的设备分配缓冲区更便宜,但这仍然需要选择一个合理的初始上限和 growing/reallocating 作为最大数量实例增加。
归根结底,基准胜过理论。自然地,您应该从分析您的数据开始,就像我们在这里尝试做的那样,但是构建一个测试应用程序并将这些想法付诸实践,注意实际出现问题的地方,是确定您需要多少优化的唯一真正方法运行 在您的目标平台上顺利完成。