【问题标题】:OpenGL: Batch Renderer: Should Transformations Take place on the CPU or GPU?OpenGL:批处理渲染器:转换应该发生在 CPU 还是 GPU 上?
【发布时间】:2022-01-02 12:17:56
【问题描述】:

我正在开发一个未来将支持 3D 的 2D 游戏引擎。在当前的开发阶段,我正在开发批处理渲染器。你们中的一些人可能知道,当将图形批处理在一起时,对颜色 (RGBA)、纹理坐标、纹理 ID(纹理索引)和模型变换矩阵的统一支持超出了窗口,而是通过顶点缓冲区传递。现在,我已经实现了将模型的位置、颜色、纹理坐标和纹理 ID 传递到顶点缓冲区。我的顶点缓冲区格式现在看起来像这样:

float* v0 = {x, y, r, g, b, a, u, v, textureID};
float* v1 = {x, y, r, g, b, a, u, v, textureID};
float* v2 = {x, y, r, g, b, a, u, v, textureID};
float* v3 = {x, y, r, g, b, a, u, v, textureID};

我即将使用变换矩阵集成计算对象在世界空间中的位置。这导致我问这个问题:

转换矩阵应该乘以模型顶点在 CPU 或 GPU 上的位置吗?

要记住的是,如果我将它传递给顶点缓冲区,我将不得不为每个顶点上传一次变换矩阵(每个精灵 4 次),这对我来说似乎是浪费内存。另一方面,将模型顶点位置乘以 CPU 上的变换矩阵似乎比 GPU 的并发能力要慢。

如果我在 GPU 上计算变换,这就是我的顶点缓冲区格式的样子:

float* v0 = {x, y, r, g, b, a, u, v, textureID, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15};
float* v1 = {x, y, r, g, b, a, u, v, textureID, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15};
float* v2 = {x, y, r, g, b, a, u, v, textureID, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15};
float* v3 = {x, y, r, g, b, a, u, v, textureID, m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15};

这个问题主要是理论上驱动的。因此,我们将不胜感激理论上和技术上的答案。但作为参考,这里是代码。

【问题讨论】:

  • "将图形批处理在一起时,对颜色(RGBA)、纹理坐标、纹理ID(纹理索引)和模型变换矩阵的统一支持出窗"而是取决于你做了多少批处理。这是个人的选择,而不是神圣的法令。
  • “而是通过顶点缓冲区传递”——不。您可以在下面我的回答中拥有制服数组、SSBO 数组,或者通过纹理传递数据。在极端情况下,您可以在 VAO 中完全不使用任何数据进行渲染。
  • 在 99% 的情况下,还有一个 4x4 模型视图矩阵是一种过度杀伤力。除了投影矩阵,您通常不需要任何透视变换。因此,您可以使用 3x4 矩阵将浮点数从 16 降低到 12。您可以进一步将自己限制为仅由四元数 + 向量编码的等距,这仅需要 7 个浮点数并涵盖 80% 的情况。
  • 我将继续使用 SSBO。感谢您的所有回答。

标签: c++ opengl glsl glm-math


【解决方案1】:

应该在 CPU 还是 GPU 上进行转换?

这真的取决于手头的情况。如果您每帧都重新提交顶点,最好对最适合您的情况进行基准测试。如果您想在不重新提交所有顶点的情况下制作动画,您别无选择,只能将其应用到 GPU 上。

无论出于何种原因,如果您决定在 GPU 上应用变换,除了为每个顶点复制矩阵之外,还有更好的方法来做到这一点。我会将转换矩阵放在SSBO

layout(std430, binding=0) buffer Models {
    mat4 MV[]; // model-view matrices
};

并在 VAO 的每个顶点中存储一个索引:

struct Vert {
    float x, y, r, g, b, a, u, v;
    int textureID, model;
};

顶点着色器可以根据索引属性去获取完整的矩阵:

layout(location = 0) in vec4 in_pos;
layout(location = 1) in int in_model;
void main() {
    gl_Position = MV[in_model] * in_pos;
}

您甚至可以将它与其他每个对象的属性结合起来,例如 textureID

编辑:您可以通过实例化和多重绘制实现类似的效果。虽然它可能会更慢。

【讨论】:

  • 您能否详细说明具体的模型是什么?我正在尝试实现它,但屏幕上只有一个精灵,我认为这是因为我没有发送正确的信息。
  • @ChristopherBarriosAgosto MV 数组中模型矩阵的索引:0、1、2 等...
  • 由于您正在渲染单独转换的四边形,如果您使用 glDrawElements 渲染,那将是 0,0,0,0,1,1,1,1,2,2,2,2,...
  • 我想通了!现在一切正常!原来我正在正确地传递和解释索引,但是当它期望一个 int 时,我将索引作为浮点数传递给 GLSL。我让它接收一个浮点数,并在 GLSL 中将它转换为一个 int,而不是正确解释所有索引。我必须这样做,因为顶点缓冲区是浮点类型。谢谢你的一切!
  • @ChristopherBarriosAgosto:恭喜你想通了。 '顶点缓冲区是浮点类型'——顶点缓冲区是字节序列。您可以在其中存储structs,其中包含一些int 字段和一些float 字段。
【解决方案2】:

我不确定您的引擎代码实际上是什么样子,但我认为它看起来像任何其他 OpenGL 程序。

如果是这样,根据我的经验,通常应该将变换矩阵传递给顶点着色器,并在绘制场景时在 GPU 上应用给定的顶点信息。例如:

//MVP matrix
GLuint MatrixID = glGetUniformLocation(shaderProgID, "MVP");
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &mvp[0][0]);

但是如果你想找到一个特定组的所有顶点的世界坐标,在渲染功能之外,你可能需要在 CPU 上完成,或者你需要使用一些并行编程技术,如 OpenCL 来在 GPU 上完成工作。

最重要的是,为什么要在绘图程序之外专门获取世界坐标信息?如果您只是想找到模型的世界坐标,您可以简单地为场景中的每个模型设置一个中心坐标,并且只跟踪该单个坐标而不是整个网格组。

顶点信息应始终位于模型坐标中,并存储在顶点缓冲区中,无需触摸,除非您想对它们进行一些修改。

【讨论】:

  • 我正在批量处理多个图形。因此,我不能使用制服来传递图形的变换,因为我将无法在绘制调用之间为变换更改制服。不过,我相信使用 SSBO 是一个很好的选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-13
  • 1970-01-01
  • 2012-08-25
  • 2015-02-24
  • 2021-06-06
  • 1970-01-01
相关资源
最近更新 更多