Three.js索引化缓冲几何体与实例化缓冲几何体的区别

6
我正在尝试学习关于THREE.js中高效几何体的知识,并且了解到索引BufferGeometry和InstancedBufferGeometry是两种最高效的几何类型。
到目前为止,我的理解是,在索引BufferGeometry中,重复使用的顶点只会被添加一次,并且每个给定重复顶点的实例都通过其在顶点数组中的索引位置进行引用。
我对InstancedBufferGeometry的理解是,该几何体允许创建一个对象的“蓝图”,将该对象的一个副本发送到着色器,然后使用自定义属性修改蓝图的每个副本的位置、旋转、缩放等。[source]
我想更好地了解:是否存在某些情况下,索引BufferGeometry比InstancedBufferGeometry更高效。
此外,在InstancedBufferGeometry中,是否有WebGL的最大参数(例如每个网格的最大顶点数)必须考虑,以避免使网格过大? InstancedBufferGeometry中的顶点如何计算?
如果有人能够帮助澄清何时应使用索引BufferGeometry和实例化BufferGeometry,以及实例化BufferGeometry的性能上限,我将非常感激。

1
可能会觉得这很有趣:https://stackoverflow.com/questions/48798175/how-are-vertices-transformed-in-webgl-in-indexed-and-non-indexed-geometries - pailhead
@pailhead 你是什么意思?new THREE.InstancedBufferGeometry(); 是一个有效的构造函数,至少在v92中是这样的... - duhaime
哦,我明白了,是的,确实没有新的THREE.IndexedBufferGeometry()构造函数。 - duhaime
1
我已经使用Three.js几年了,从未见过那个类。这一定是v92中的最新添加。抱歉进行编辑。 - pailhead
1
好的,我坚信歧义会伤害软件 : )。这个问题是有价值的,我只是觉得它需要改进一下。 - pailhead
显示剩余6条评论
1个回答

16
[...] IndexedBufferGeometry和InstancedBufferGeometry是两种最高效的几何类型。
是的,通常情况下BufferGeometries是处理几何数据最高效的方式,因为它们以与通过WebGL与GPU进行通信时使用的确切格式存储数据。任何普通的Geometry都会在渲染前被内部转换为BufferGeometry。
您对索引和实例化几何形状的描述也是正确的,但我想指出一个细节:在索引几何中,GPU装配三角形的指令与顶点数据分开,并将这些指令以特殊的索引属性的形式提供给GPU(而不是作为非索引数组的顶点的隐含部分)。
我想更好地理解:是否存在IndexedBufferGeometry比InstancedBufferGeometry更高效的情况。
它们在不同的级别上执行不同的操作,因此我认为很少有情况需要在它们之间进行选择。 实际上,您甚至可以基于是索引BufferGeometry的“蓝图”几何体创建实例几何体。
让我们深入了解细节以进行解释。实例化几何体允许您在单个绘制调用中呈现多个相同的“蓝图”几何体的副本。 其中的第一部分,创建蓝图,与呈现单个几何体完全相同。为此,需要将属性(位置、法线、UV坐标以及索引几何体的索引)传输到GPU。
实例化几何体的特殊之处在于一些额外的属性(在three.js中是InstancedBufferAttribute)。这些控制了几何体将被呈现多少次,并提供一些实例特定的值。一个典型的用例是为每个实例添加一个额外的vec3属性来表示实例位置和一个vec4属性来表示四元数。但它也可以是任何其他东西。
在顶点着色器中,这些特殊属性看起来就像任何其他属性一样,并且您需要手动对每个顶点应用实例特定的更新。因此,代替这个:
attribute vec3 position;
void main() {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

你会得到类似这样的东西:

attribute vec3 position;
attribute vec3 instanceOffset; // this is the InstancedBufferAttribute
void main() {
  gl_Position = 
    projectionMatrix 
    * modelViewMatrix 
    * vec4(position + instanceOffset, 1.0);
}

在实例化版本中看不到的是顶点着色器不仅会像常规渲染一样每个几何体顶点调用一次,而是每个顶点和实例都会调用一次。

因此,实例化几何体实际上并没有任何魔法,它们只是一种非常有效的表达整个几何体复制的方法。

同时,在InstancedBufferGeometry中是否有WebGL最大参数(例如网格每个顶点的最大数量),必须考虑以避免使网格过大?

我不确定这一点,但迄今为止我还没有遇到过这样的情况。如果您知道渲染1000个具有1000个顶点的对象的1000个实例将调用顶点着色器一百万次,那么应该能够帮助您判断性能影响。

如果有人可以帮助澄清何时应使用IndexedBufferGeometry和InstancedBufferGeometry以及InstancedBufferGeometry的性能上限,我会非常感激。

您可以(也许应该)对几乎任何类型的几何体使用索引几何。但它们并不是没有缺点:

  • 使用索引时,所有属性都将获得相同的处理。因此,例如,您不能在索引几何中使用每个面的颜色(请参见Access to faces in BufferGeometry)。
  • 对于点云或具有很少重复顶点的几何体,它们会带来更多的负面影响(由于需要额外的内存/带宽来存储索引)。

但大多数情况下它们将得到性能优势:

  • 所需的顶点数据内存/带宽更少
  • GPU可以缓存顶点着色器的结果,并将其重新用于重复的顶点(因此,在最佳情况下,您将得到每个存储的顶点一次VS调用,而不是每个索引一次)

对于实例化几何体

  • 如果您有许多相似的对象,其中差异只能用几个数字表示,请使用实例化几何体(简单情况:在不同位置渲染相同对象的副本, 复杂情况:通过使用实例属性基于树的几何体来呈现森林或通过改变单个人的姿势来呈现人员群体)
  • 我发现另一件事非常有启发性:使用实例化渲染粗线条:使用实例来渲染一堆线段,其中每个线段由6个三角形组成(请参见https://github.com/mrdoob/three.js/blob/dev/examples/js/lines/LineSegmentsGeometry.js by @WestLangley)

缺点:

  • 目前还没有内置支持将常规材质与实例化几何体一起使用。您必须自己编写着色器。(准确地说:有一种方法可以做到这一点,但它需要对three.js着色器的工作原理有一些熟

@MartinSchuhfuß非常感谢您的帮助!您知道在THREE.js中有关索引和实例化几何体的示例吗?如果有,我很想看看!我正在深入研究一个场景,基本上是绘制一堆四边形,每个四边形都有一个纹理,因此我想我不妨全力以赴。 - duhaime
2
@duhaime 大多数内置几何体默认都有索引。其中一个比较简单的是:https://github.com/mrdoob/three.js/blob/dev/src/geometries/PlaneGeometry.js#L74-L111,如果需要实例化,我建议您参考我之前写的这个 CodePen,里面有很多解释性的注释:https://codepen.io/usefulthink/pen/YNrvpY?editors=0010 - Martin Schuhfuß
很遗憾,你对缺点的看法是正确的。https://github.com/pailhead/three-instanced-mesh 这个项目的存在是为了尝试减轻这个问题。 - pailhead

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