【问题标题】:OpenGL quality difference between glDrawElements and immediate modeglDrawElements和立即模式之间的OpenGL质量差异
【发布时间】:2014-02-03 20:57:56
【问题描述】:

我第一次在 3D 项目上工作(实际上,我正在编写一个 Quartz Composer 插件中的 Bullet Physics 集成),当我尝试优化我的渲染方法时,我开始使用 glDrawElements而不是 glVertex3d 直接访问顶点...

我对结果感到非常惊讶。我没有检查它是否真的更快,但我尝试了下面这个非常简单的场景。而且,从我的角度来看,即时模式下的渲染效果确实更好。

“绘制元素”方法不断显示三角形的边缘和立方体上非常难看的阴影。

我非常感谢有关此差异的一些信息,并且可能是使用 glDrawElements 保持质量的一种方式。我知道这可能真的是我的错误......

立即模式

绘图元素

在这两种方法中,顶点、索引和法线的计算方式相同。这是2个代码。

立即模式

glBegin (GL_TRIANGLES);
    int si=36;
    for (int i=0;i<si;i+=3)
    {
        const btVector3& v1 = verticesArray[indicesArray[i]];;
        const btVector3& v2 = verticesArray[indicesArray[i+1]];
        const btVector3& v3 = verticesArray[indicesArray[i+2]];


        btVector3 normal = (v1-v3).cross(v1-v2);
        normal.normalize ();
        glNormal3f(-normal.getX(),-normal.getY(),-normal.getZ());
        glVertex3f (v1.x(), v1.y(), v1.z());
        glVertex3f (v2.x(), v2.y(), v2.z());
        glVertex3f (v3.x(), v3.y(), v3.z());

    }
    glEnd();

glDrawElements

glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, sizeof(btVector3), &(normalsArray[0].getX()));
    glVertexPointer(3, GL_FLOAT, sizeof(btVector3), &(verticesArray[0].getX()));
    glDrawElements(GL_TRIANGLES, indicesCount, GL_UNSIGNED_BYTE, indicesArray);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

谢谢。

编辑

这是顶点/索引/法线的代码

GLubyte indicesArray[] = {
        0,1,2,
        3,2,1,
        4,0,6,
        6,0,2,
        5,1,4,
        4,1,0,
        7,3,1,
        7,1,5,
        5,4,7,
        7,4,6,
        7,2,3,
        7,6,2 };

    btVector3 verticesArray[] = {
        btVector3(halfExtent[0], halfExtent[1], halfExtent[2]),
        btVector3(-halfExtent[0], halfExtent[1], halfExtent[2]),
        btVector3(halfExtent[0], -halfExtent[1], halfExtent[2]),
        btVector3(-halfExtent[0], -halfExtent[1], halfExtent[2]),
        btVector3(halfExtent[0], halfExtent[1], -halfExtent[2]),
        btVector3(-halfExtent[0], halfExtent[1], -halfExtent[2]),
        btVector3(halfExtent[0], -halfExtent[1], -halfExtent[2]),
        btVector3(-halfExtent[0], -halfExtent[1], -halfExtent[2])
    };

    indicesCount = sizeof(indicesArray);
    verticesCount = sizeof(verticesArray);


    btVector3 normalsArray[verticesCount];

    int j = 0;
    for (int i = 0; i < verticesCount * 3; i += 3)
    {
        const btVector3& v1 = verticesArray[indicesArray[i]];;
        const btVector3& v2 = verticesArray[indicesArray[i+1]];
        const btVector3& v3 = verticesArray[indicesArray[i+2]];

        btVector3 normal = (v1-v3).cross(v1-v2);
        normal.normalize ();

        normalsArray[j] = btVector3(-normal.getX(), -normal.getY(), -normal.getZ());

        j++;
    }

【问题讨论】:

  • 你所谓的“直接访问”实际上是slow轨道,与GPU分离。为了获得最大的性能,您希望将顶点数据放置在位于 GPU 自己的内存中的顶点数组中。 (顶点缓冲区对象)。即时模式也已从现代版本的 OpenGL 中删除。
  • 视觉质量与即时/可编程管道无关。两者最终都在 GPU 上
  • @datenwolf 作为 OpenGL 新手,我从这个论坛帖子中学到了很多东西。现在,我将顶点数据缓存在 C 数组中,并使用 glDrawElements 调用它们。我会尝试将 VBO 集成到我的程序中,但目前它似乎相当复杂。

标签: c++ opengl graphics 3d


【解决方案1】:

您可以(并且将会)使用即时模式和基于顶点数组的渲染获得完全相同的结果。您的图像表明您的法线错误。由于您没有包含用于创建数组的代码,我只能猜测可能出了什么问题。我可以想象的一件事:每个三角形使用一个法线,因此在法线数组中,您必须为每个顶点重复该法线。

您应该知道,GL 中的顶点不仅仅是位置(您在即时模式下通过glVertex 指定),而是一组 all 属性,如位置、法线、texcoords等等。因此,如果您有一个端点是不同三角形的一部分的网格,那么如果所有属性都是共享的,那么这只是一个顶点,而不仅仅是位置。在您的情况下,法线是每个三角形的,因此每个三角形需要不同的顶点(与其他一些顶点共享位置,但使用不同的法线)。

【讨论】:

  • 谢谢!我添加了用于“生成”我的顶点、索引和法线的代码。
  • @L'angeCarasuelo:这证实了我的猜测。您的 VA 代码不等同于立即模式代码。首先:什么是'j',为什么它从来没有增加?但主要问题正是我已经说过的:看看前两个三角形:(0,1,2) 和 (3,2,1)。 1 和 2 在这两个原语之间共享。但就 GL 而言,它们必须是不同的个顶点 - 每个图元都有一个顶点 1 和 2 的副本 - 因为它们使用不同的法线向量。
  • 我用 j 编辑了帖子。不知道它在哪里消失了:-/我不知道它很乱,我现在不太确定我的“j”......你能解释一下吗?我不明白如何组织我的顶点/索引......你的意思是我必须将每个具有不同法线的顶点加倍(事实上,我想我必须为每个三角形发送 3 个顶点,不是吗?是吗?)
  • @L'angeCarasuelo:是的,你必须“复制”这些数据。但是您不必最终每个三角形有 3 个不同的顶点。例如,查看立方体的侧面。一个立方体有 8 个角和 6 个边。每条边都有自己的法线,所以你只需要 6*4=24 个顶点。对于一侧,您需要两个三角形,而不是 6 个不同的顶点,而是只有 4 个,因为两个三角形可以共享每边 2 个顶点。当然,不共享这些并进行完整的“复制”(立方体 36 个顶点)也可以,但会浪费资源。
  • 再次感谢!我想我开始明白了。我真的不清楚 OpenGL 如何“链接”顶点、法线和索引......
【解决方案2】:

我开始使用 glDrawElements

很好!

而不是通过 glVertex3d 直接访问顶点...

即时模式没有任何“直接”意义。事实上,它离 GPU 尽可能远(在现代 GPU 架构上)。

我对结果感到非常惊讶。我没有检查它是否真的更快,但我尝试了下面这个非常简单的场景。而且,在我看来,直接访问方法的渲染效果确实更好。

实际上它慢了几个数量级。每个 glVertex 调用都会导致上下文切换的开销。此外,GPU 需要更大批量的数据才能高效工作,因此 glVertex 调用首先填充临时创建的缓冲区。

您的直接代码段必须实际理解如下

    glNormal3f(-normal.getX(),-normal.getY(),-normal.getZ());
    glVertex3f (v1.x(), v1.y(), v1.z());

    // implicit copy of the glNormal supplied above
    glVertex3f (v2.x(), v2.y(), v2.z());

    // implicit copy of the glNormal supplied above
    glVertex3f (v3.x(), v3.y(), v3.z());

这样做的原因是,顶点不仅仅是一个位置,而是它的属性的整体组合。并且在使用顶点数组时,您必须提供完整的属性向量以形成有效的顶点。

【讨论】:

  • +1 表示立即模式降额。我一直想知道是什么原因导致人们这些天仍在尝试使用它...
  • @MichaelIV:我将糟糕的教程和缺乏编程经验归咎于实际使用它的人;对于(还)不习惯在数据结构中思考的人来说,在编程“laudry list”时通常更容易思考。面对纯函数式编程时,命令式程序员(有经验的)同样会面临同样的心理障碍。
猜你喜欢
  • 2020-05-24
  • 1970-01-01
  • 2014-03-10
  • 2015-05-03
  • 1970-01-01
  • 2014-09-01
  • 2020-02-24
  • 2013-08-06
  • 2017-01-08
相关资源
最近更新 更多