OpenGL:批处理渲染器:变换应该在CPU或GPU上进行?

4

我正在开发一个支持未来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(纹理索引)和模型变换矩阵的统一支持就会被忽略。这完全取决于您进行多少批处理。而这是个人选择,而不是神圣的教诲。 - Nicol Bolas
不是通过顶点缓冲区传递。你可以有统一变量数组,像我下面的答案中的SSBOs数组,或者通过纹理传递数据。在极端情况下,你甚至可以在VAO中不使用任何数据进行渲染。 - Yakov Galka
在99%的情况下,拥有一个4x4的模型视图矩阵是过度的。通常你只需要投影矩阵来进行透视变换。因此,通过使用3x4矩阵,你可以将浮点数从16个减少到12个。你还可以进一步限制自己只使用等距变换,由四元数+向量编码,这只需要7个浮点数,并覆盖了80%的情况。 - Yakov Galka
我会选择使用SSBOs。感谢你们所有人的答案。 - Christopher Barrios Agosto
2个回答

3
应该将变换应用在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.

编辑:您可以通过实例化和多次绘制来实现类似的效果。但可能会更慢。


你能详细说明一下什么是模型吗?我正在尝试实现它,但屏幕上只有一个精灵,我认为这是因为我没有发送正确的信息。 - Christopher Barrios Agosto
@ChristopherBarriosAgosto MV 数组中模型矩阵的索引:0、1、2 等等... - Yakov Galka
1
由于您正在呈现单独转换的四边形,如果使用glDrawElements进行呈现,则为0,0,0,0,1,1,1,1,2,2,2,2,... - Yakov Galka
1
我解决了!现在一切都正常了!原来我传递和解释索引是正确的,但是当它期望一个整数时,我将索引作为浮点数传递给GLSL。我让它接收一个浮点数,并在GLSL中将其转换为整数,现在它正确地解释所有索引。我必须这样做,因为顶点缓冲区是浮点类型的。感谢您的帮助! - Christopher Barrios Agosto
@ChristopherBarriosAgosto:恭喜你找到了答案。“顶点缓冲区是浮点类型”——顶点缓冲区是字节序列。你可以在其中存储一些带有int字段和一些float字段的结构体。 - Yakov Galka

0

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

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

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

但是,如果您想要在呈现函数以外找到特定组的所有顶点的世界坐标,可能需要在 CPU 上执行,或者您将需要使用一些并行编程技术,例如 OpenCL 在 GPU 上完成工作。

最重要的是,为什么您特别想要在绘图过程之外获得世界坐标信息? 如果您只是想要查找模型的世界坐标,则可以为场景中的每个模型设置一个中心坐标,并仅跟踪单个坐标而不是整个网格组。

顶点信息应始终处于模型坐标中,并存储在顶点缓冲区中,除非您想对其进行修改。


我正在将多个图形批处理在一起。因此,我不能使用uniforms来传递图形的变换,因为我无法在绘制调用之间更改变换的uniform。然而,我相信使用SSBO是一个非常好的替代方案。 - Christopher Barrios Agosto

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