现代OpenGL:VBO,GLM和矩阵堆栈

8

在搜索和阅读有关现代OpenGL的内容以升级我的现有项目后,我有点困惑,因为我的3D框架基于OpenGL 2.1。

所以,就我所学到的...

  • We need to generate our Vertex-Buffer-Objects from vertices, indices, normals, colors, uvs, etc.

  • then we can use GLM for matrix transformation, and we only use VBO to create or manipulate meshes, finally we pass everything into GLSL vertex shader like this...

    glm::mat4 MVP = projection * view * model;
    glUniformMatrix4fv(glGetUniformLocation(shaderProgramID, "MVP"), 1, GL_FALSE, glm::value_ptr(MVP)); //or &MVP[0][0]
    
    // uniform mat4 MVP;
    // in vec3 Vertex;
    // void main(void)
    // {
    //    gl_Position = MVP * vec4(Vertex, 1.0); //instead of ftransform();
    // }
    

问题:如何在不使用pushMatrix/popMatrix的情况下进行分层变换?(或者我们可以通过使用VBO来进行分层变换,这是可能的吗?)

如果不可能,那么如何通过GLM和C++<stack>库实现与pushMatrix/popMatrix相同的结果?

假设我需要像这样的东西:

> Set identity
> Translate to X, Y, Z
> Draw Mesh 1
> Rotate 0.5 by X axis
> Draw Mesh 2
> Scale down to 0.1
> Draw Mesh 3

1
@BЈовић: OpenGL Mathematics (GLM),是一个仅包含头文件的C++库,用于矩阵/向量数学计算。 - genpfault
4个回答

6

如果你的渲染已经使用了类似函数递归的层次结构,那么你已经有了矩阵堆栈!

void renderMesh(Matrix transform, Mesh mesh)
{
    // here call glDrawElements/glDrawArrays and send transform matrix to MVP uniform
    mesh->draw(transform);

    // now render all the sub-meshes, then will be transformed relative to current mesh
    for (int i=0; i<mesh->subMeshCount(); i++)
    {
        Matrix subMeshTransform = mesh->getSubMeshTransform(i);
        Mesh subMesh = mesh->getSubMesh();

        renderMesh(subMeshTransform * transform, subMesh);
    }
}

// somwhere in main function
...
Matrix projection = Matrix::perspective(...);
Matrix view = camera->getViewMatrix();

Matrix transform = view * projectIon;
renderMesh(transform, rootMesh);

6
我们需要从顶点、索引、法线、颜色、纹理坐标等数据生成顶点缓冲对象。
客户端顶点数组也可以工作,不过强烈建议使用VBO,因为这会使驱动程序更容易处理数据,并且从长远来看,对于需要操纵数据的人来说也更容易。代码开销可以忽略不计(与生成和上传纹理数据大致相同),性能仅会提高。
然后我们可以使用GLM进行矩阵变换,只需使用VBO创建或操作网格,最后将所有内容传递到GLSL顶点着色器中,如下所示...
你并不限于GLM。任何矩阵数学库都可以。如果您正在寻找可以在C99中使用的库,请查看我的(尚未完全完成的)linmath.h https://github.com/datenwolf/linmath.h,它只是一个带有静态内联函数的头文件。我还没有对代码重复性能是否产生负面影响进行基准测试(代码大小会增加L1缓存压力)。 问题:如何在不使用pushMatrix/popMatrix的情况下进行层次变换?(或者也许我们可以通过使用VBO来进行层次变换,这是可能的吗?)
VBO与此无关。最令老式OpenGL用户困扰的是那些矩阵堆栈函数,它们使OpenGL看起来有点像场景图,但实际上不是。
如果你忘记了旧版OpenGL的矩阵堆栈,就会很明显如何进行分层变换:在层次结构中的每个分支处复制变换矩阵并对其进行操作。你得到一个变换的分层树,在每个节点存储相应的矩阵。然后将这些矩阵作为uniform传递给顶点着色器;或者如果你正在绘制只有一个变换的刚体对象,则只需要一个矩阵。通常只需要多个矩阵来处理可变形物体,比如这个角色的骨骼动画。
worldtransform -> 
    pelvis ->
        left upper leg -> left lower leg -> left foot
        right upper leg -> right lower leg -> right foot
    torso ->
        neck -> head ->
             left eye
             right eye
             facial deformation // this is a whole chapter of it's own
        left upper arm -> left lower arm -> left hand
        right upper arm -> right lower arm -> right hand

每当在这样的层次结构中遇到->,您都会复制矩阵并继续在该矩阵上工作。当回退到树的较高级别时,您将再次从该矩阵开始工作。

3
"facial deformation"意为"面部畸形",其中包括"下颌骨脱臼"。 - mlvljr

4
如果您需要层次转换,那么您就必须自己处理。然而,如果您只需要一个栈,那就很简单了。只需取出栈中的上一条目,并将其乘以您想要应用的下一个矩阵,然后将结果推送到栈中即可。
[在您编辑的问题中]:对于此操作,您根本不需要使用栈,也没有层次转换。只需有一个单一的矩阵,应用您的平移、绘制、乘以旋转矩阵,再绘制,最后乘以缩放矩阵并绘制即可。

1
如果您有初始矩阵M,以及平移T、旋转R和缩放S,则第一个矩阵是M=T,第二个矩阵是M*=R=(TR==先平移再旋转),第三个矩阵是M=S=(TRS==所有矩阵)。对于机器人手臂,您可以从顶部开始,并在向下遍历树时发出绘制调用。无论“堆栈”是std::stack还是您递归场景遍历例程的一部分都没有关系。 - Anteru
如果您不执行M*=S,而只是使用MS(复制!),则原始矩阵不会更改。您的堆栈将如下所示:[M],然后您推入T(导致堆栈[M,MT]),然后您弹出([M]),然后再次推入R([M,M*R])。我不明白您的问题在哪里? - Anteru
停止编辑您的注释含义。我说过,您可以通过堆栈来实现它,或者您可以将矩阵复制到递归绘制函数内部(这会隐式创建一个堆栈)。 [M,..] _是一个堆栈_,如果您想要使用它,只需使用即可。但是您也可以很容易地不使用任何std :: stack来完成。 - Anteru
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/6915/discussion-between-anteru-and-ndv - Anteru

0
关于VAO和VBO的性能问题,我不同意VBO更快的说法,建议查看此链接。

http://www.openglsuperbible.com/2013/12/09/vertex-array-performance/

从上面的结果可以看出,至少对于我们的小样本集,VAO在所有实现中都更快。这是有道理的——调用glBindVertexArray时需要验证的参数比glBindBuffer或glVertexAttribPointer要少。即使只有一个顶点属性,使用VAO切换比显式更新全局VAO时OpenGL调用减少了一半。除了明显的“API调用越少执行速度越快”的关系外,VAO还是OpenGL驱动程序可以存储编程底层GPU所需信息的地方。发送到GPU的状态更改总量是相同的。


2
这与问题有什么关系? - Reto Koradi

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