如何将模型矩阵包含到 VBO?

Posted

技术标签:

【中文标题】如何将模型矩阵包含到 VBO?【英文标题】:How to include model matrix to a VBO? 【发布时间】:2014-07-08 08:47:07 【问题描述】:

我想发送一个缓冲区列表(到 GPU/顶点着色器),其中包含有关顶点位置、世界位置、颜色、比例和旋转的信息。

如果我的每个 3D 对象在矩阵中都有与变换相关的信息,我如何通过 VBO 将这个矩阵数组(除了其他顶点数据)传递给 GPU?

更新 如有错别字请见谅:

// bind & set vertices.
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAtribPointer(a_Position, 3, gl.FLOAT, false, stride, 0);

// bind & set vertex normals.
gl.bindBuffer(gl.ARRAY_BUFFER,, vertexNormalsBuffer);
gl.vertexAttribPointer(a_Normal, 3, gl.FLOAT, false, stride, 0);

// becaue i cant pass in a model matrix via VBO, im tryng to pass in my world coordinates.
gl.bindBuffer(gl.ARRAY_BUFFER, worldPositionBuffer);

// not sure why i need to do this, but most tutorials i've read says to do this.
gl.bindBuffer(gl.ARRAY_BUFFER, null);

// bind & draw index buffer.
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
gl.drawElements(gl.TRIANGLES, vertexIndexCount, gl.UNSIGNED_SHORT, 0);

请注意,这些缓冲区(vertexBuffervertexNormalsBufferworldPostiionBuffervertexIndexBuffer)是我场景中所有相应 3D 对象的串联(我通过属性/制服一一渲染- 一种幼稚的方法,更简单、更容易掌握,但对于 1000 个对象来说速度非常慢)。

【问题讨论】:

通常会将模型矩阵设置为统一的并在模型之间进行更改 我有可以在平移、旋转和缩放方面变化的 3d 对象(全部包含在模型矩阵中)。所以我认为每个对象都有自己的模型矩阵。 但是每个模型都有很多三角形,所以只需为第一个模型设置模型矩阵统一,绘制第一个模型的三角形为第二个设置模型矩阵,绘制第二个的三角形,冲洗重复 我想我需要澄清一下,因为你解释的是我正在做的事情(但它很慢)。现在我正在尝试使用 VBO(s) 进行一次绘图调用。所以我所有的顶点信息都在一个顶点缓冲区中(例如,对于 1000 个三角形)。 您是否出于某种奇怪的未知原因试图为您绘制的每个三角形创建一个矩阵? 【参考方案1】:

对于在渲染帧时需要经常更改的任何值,将它们作为attribute 而不是uniform 传递到着色器会更有效。这也有一个优点,如果您愿意,您可以将值存储在 VBO 中。请注意,在 VBO 中存储属性不是必需,也可以使用 glVertexAttrib[1234]f()glVertexAttrib[1234]fv() 指定它们。

这适用于转换矩阵,就像传递给着色器的任何其他值一样。如果它变化非常频繁,您可能应该将其设为属性。在这种情况下,唯一的小问题是我们正在处理一个矩阵,并且属性必须是向量。但这很容易克服。通常作为mat4 传入的内容可以由vec4 类型的3 个值表示,其中这3 个向量是矩阵的列向量。表示一个完全通用的 4x4 矩阵当然是 4 个向量,但变换矩阵中的第 4 列不用于任何常见的变换类型(投影矩阵除外)。

如果您希望在 VBO 中进行转换,您可以再设置 3 个属性,就像您已经为位置和颜色所做的那样。你存储在VBO中的属性值就是对应变换矩阵的列向量。

然后在顶点着色器中,通过计算变换属性向量与输入位置的点积来应用变换。代码可能如下所示:

attribute vec4 InPosition;
attribute vec4 XTransform;
attribute vec4 YTransform;
attribute vec4 ZTransform;
main() 
    vec3 eyePosition = vec3(
        dot(XTransform, InPosition),
        dot(YTransform, InPosition),
        dot(ZTransform, InPosition));
    ...

在完整的 OpenGL 中还有其他方法可以解决这个问题,例如使用统一缓冲区对象。但是对于 WebGL 和 OpenGL ES 2.0,我认为这是最好的解决方案。

【讨论】:

为什么这样会更有效率?现在您需要为网格中的每个顶点而不是单个统一更新变换向量。这似乎也是对内存的巨大浪费,对于详细的网格来说,这可能会很快加起来。 您所指的是所谓的(静态)批处理。只有当您的对象不移动时,您才能执行此操作。在这种情况下,您只需修改顶点位置以减少绘制调用(不需要矩阵/向量)。请注意,绘图调用本身并不昂贵。昂贵的是状态变化。状态更改会延迟到您进行绘图调用。如果您的对象不是静态的,那么我看不出您如何从上述方法中受益。 @Homar:提出的问题是如何在 VBO 中使用转换矩阵。我就是这么回答的。如果不实施至少 4 或 5 种不同的方法并在各种平台上进行比较,我认为不可能知道 OP 用例的最快解决方案是什么。但是这个网站的想法确实是为了回答具体问题。 @Homar,关于您的最后一条评论:虽然我没有使用过 WebGL,但我相信那里不支持实例化渲染,因为它基于 ES 2.0。 很多人说 VBO 可以使用子数据缓冲区或子映射(或其他任何名称)进行修改,从而允许部分 VBO 数据更新。【参考方案2】:

你的方法是正确的,在某种程度上是不可避免的。如果您有 1000 个不同非静态对象,那么您将需要(或最好)进行 1000 次绘制调用。但是,如果您的对象是静态的,那么只要它们使用相同的材​​质,您就可以将它们合并在一起。

合并静态对象很简单。您可以通过乘以模型矩阵来修改顶点位置,以便将顶点转换为世界空间。然后在一次绘制调用中渲染批处理。

如果您有许多相同对象的实例,但具有不同的模型矩阵(即不同的位置、方向或比例),那么您应该使用实例化渲染。这将允许您在一次绘制调用中渲染所有实例。

最后,请注意,绘图调用不一定很昂贵。发生的情况是,状态更改会延迟到您发出绘图调用。例如,考虑以下情况:

gl.drawElements(gl.TRIANGLES, vertexIndexCount, gl.UNSIGNED_SHORT, 0);
gl.drawElements(gl.TRIANGLES, vertexIndexCount, gl.UNSIGNED_SHORT, 0);

与第二次相比,第二次绘制调用对 CPU 的负担要小得多(自己尝试一下)。这是因为两个绘图调用之间没有状态变化。如果您只是在绘制调用之间更新模型矩阵统一变量,那么这不应该显着增加成本。可以(并且建议)通过按着色器程序和材质对对象进行排序来最小化状态变化。

【讨论】:

在某些平台上更新制服可能会非常昂贵。我当然希望它至少与其他典型的状态变化一样昂贵,可能更昂贵。它们被称为 uniform 是有充分理由的,因为它们不会被频繁修改。正如我在之前的评论中提到的,实例化渲染在 OP 使用的平台上不可用。 uniform 就一定意味着“不经常修改”吗?我认为这意味着缓冲区中所有顶点的统一(而不是基于每个顶点应用的属性)。 据我所知,在处理 1000 个和 1000 个对象时,绘制调用非常昂贵。当然,最小化状态变化也有帮助。 但是关于状态变化,当我对 N 个对象进行 N 次绘制调用时,我唯一设置的是属性(顶点位置和颜色)和制服(模型矩阵)的绑定和设置目的。在 N > 2500 个物体的情况下,我看到的速度约为 5 fps。 不要相信您在互联网上阅读的所有内容。我已经看到由于过于频繁地更新制服而导致主要游戏出现性能问题。我确信它在某些情况下在某些平台上可以很快,但这并不意味着它是普遍正确的。

以上是关于如何将模型矩阵包含到 VBO?的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL 使用单个 VBO 渲染多个对象,并使用另一个 VBO 更新对象的矩阵

OpenGL - 矩阵逻辑错误

opengl vbo建议[关闭]

使用 VBO 在 OpenGL 中渲染人群

如何将元素映射到 OpenGL VBO 中的颜色?

在 OpenGL ES 2 中实现 VBO 以渲染精灵