openGL 中的对象有限制吗?

Is there a limit of object in openGL?

我想在 Visual C++ 中使用 OpenGL 绘制 2000 个球体。 以下代码绘制了 1000 个球体,结果看起来不错。

但是当我增加 2000 个球体的数量时(请参阅下面的部分代码并用 ** 突出显示),它失败了。 出现以下错误消息。

“freeglut:fgInitGL2:fghGenBuffers 为 NULL”

你能帮我解决这个问题吗?

    for (int j = 0; j < 10; j++) {
        for (int k = 0; k < 10; k++) {
            **for (int l = 0; l < 20; l++) { \ for (int l = 0; l < 10; l++)**
                glPushMatrix();
                glTranslatef(j, k, l);
                gluSphere(myQuad, 0.5, 100, 100);
                glPopMatrix();
            }
        }
    }

这是完整的测试代码。

#include <GL/glew.h>
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <fstream>
#include <string>
#include <cstring>
#include <cmath>
#include <iostream>

int selectedObject = 1;
bool drawThatAxis = 0;
bool lightEffect = 1;

float fovy = 60.0, aspect = 1.0, zNear = 1.0, zFar = 100.0;

float depth = 8;
float phi = 0, theta = 0;
float downX, downY;
bool leftButton = false, middleButton = false;

GLfloat white[3] = { 1.0, 1.0, 1.0 };

void displayCallback(void);

GLdouble width, height;
int wd;

int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH);
    glutInitWindowSize(800,600);

    wd = glutCreateWindow("3D Molecules");

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);
    glClearColor(0.0, 0.0, 0.0, 0.0);

    GLuint id;
    id = glGenLists(1);

    GLUquadric* myQuad;
    myQuad = gluNewQuadric();

    glNewList(id, GL_COMPILE);
    
    for (int j = 0; j < 10; j++) {
        for (int k = 0; k < 10; k++) {
            for (int l = 0; l < 10; l++) {
                glPushMatrix();
                glTranslatef(j, k, l);
                gluSphere(myQuad, 0.5, 100, 100);
                glPopMatrix();
            }
        }
    }
    
    glEndList();

    glutDisplayFunc(displayCallback);
    glutMainLoop();
    return 0;
}
void displayCallback(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(fovy, aspect, zNear, zFar);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(0, 0, 40, 0, 0, 0, 0, 1, 0);

    glTranslatef(0.0, 0.0, -depth);
    glRotatef(-theta, 1.0, 0.0, 0.0);
    glRotatef(phi, 0.0, 1.0, 0.0);

    if (lightEffect) {
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
    }
    else
    {
        glDisable(GL_LIGHTING);
        glDisable(GL_LIGHT0);
    }

    switch (selectedObject)
    {
    case (1):
        glCallList(1);
        break;
    default:
        break;
    }

    glFlush();
}

当然,缓冲区分配可能会失败 — 计算机中的一切都是有限的 — 但您的错误消息与您的问题无关。

您收到错误:

freeglut : fgInitGL2 : fghGenBuffers is NULL

这是 freeglut 的错误,它不是 OpenGL 的一部分。所以查找 the implementation of freeglut's fgInitGL2.

如果 fghGenBuffers 失败,则意味着以下行失败:

CHECK("fghGenBuffers", fghGenBuffers = (FGH_PFNGLGENBUFFERSPROC)glutGetProcAddress("glGenBuffers"));

即GLUT 无法获取 glGenBuffers 函数的地址。它没有请求缓冲区但未能获得它们,它询问了它应该请求缓冲区的函数的地址,但甚至没有得到。

然而,这只是一个 fgWarning,即警告,而不是错误。我敢猜测,从程序启动的那一刻起,您就会在终端上看到该消息,而不管它随后是否失败。这是 GLUT 希望您知道的事情,但它与您的失败并不接近。

至于您的实际问题:几乎可以肯定这与试图使显示列表过满有关。在上下文中,您的最佳解决方案是仅将一个球体放入显示列表中,并发出 2000 次调用来绘制它,修改每个球体之间的模型视图矩阵。

顺便说一句:显示列表被证明是一个坏主意,它没有提供太多的优化空间,而且几乎没有被 OpenGL 1.5 使用。它们在 2008 年的 OpenGL 3.0 中被弃用,整个固定功能管道也是如此——包括 glPushMatrixglTranslateglPopMatrix.

这不是要长篇大论,但请注意,您的代码的形成方式依赖于挥之不去的弃用功能。它可能包含没有人费心更新的硬限制,或者以任何其他方式看到非常有限的维护。

虽然这是最简单的开始方式,而且您可能与上千个 CAD 或其他科学程序为伍,所以现在最好的建议就是不要试图将所有一个显示列表中的球体。

Is there a limit of object in openGL?

OpenGL 没有定义像“最大对象数”这样的限制。 应用程序能够绘制尽可能多的对象,因为它们适合 CPU 内存,但通常在应用程序达到内存限制之前,绘制会变得非常慢。即使所有纹理和顶点数据都不适合 GPU 内存,OpenGL 仍然不会失败并通过不断上传 CPU->GPU 内存来继续绘制每一帧。

因此,如果我们回到 OpenGL 限制的问题上 - 实际上,存在内存限制,正如您从另一个 similar question. Your code doesn't actually check for any OpenGL errors using glGetError(), hence your conclusion about fghGenBuffers() being a root cause is misleading. I would expected GL_OUT_OF_MEMORY error to appear in your case. Modern OpenGL also defines a more sophisticated mechanism for reporting errors - ARB_debug_output.

中看到的那样

显示列表 是 OpenGL 世界中一种非常古老的机制,旨在通过将一系列 OpenGL 命令“记住”到某些内部驱动程序管理中来优化大量数据的绘制缓存。在广泛采用 Vertex Buffer Objects, which have been added to OpenGL 1.5 as a more straightforward and efficient way to control vertex data memory, and before Vulkan and GL_NV_command_list 重新发明的 命令缓冲区 作为缓存一系列 GPU 命令的更可靠接口之前,这种机制很普遍。

显示列表机制的一个大设计问题是不可预测的内存管理和不同供应商之间极其不同的实现(从非常差到非常优化)。现代图形驱动程序在编译显示列表时尝试将顶点数据隐式上传到 GPU 内存,但它们实际所做的仍然是隐藏的。

古老的 GLU 库是代码中的另一个谜团,因为很难估计 gluSphere() 使用的内存。悲观计算显示:

size_t aNbSpheres = 10 * 10 * 20;
size_t aNbSlices, aNbStacks = 100;
size_t aNbTriangles = aNbSlices * aNbSlices * 2;
size_t aNbNodes = aNbSpheres * aNbTriangles * 3; // non-indexed array
size_t aNodeSize = (3 * sizeof(GLfloat)) * 2; // position + normal
size_t aMemSize = aNbNodes * aNodeSize;
size_t aMemSizeMiB = aMemSize / 1024 / 1024;

仅仅 2000 个球体的顶点数据就可能使用大约 2.746 GiB 的内存! 如果您的应用程序是在 32 位模式下构建的,那么它会达到 32 位地址 space 内存限制也就不足为奇了。但即使在 64 位应用程序的情况下,OpenGL 驱动程序实现也可能会遇到一些内部限制,这将由相同的 GL_OUT_OF_MEMORY 错误报告。

无论内存限制如何,您的代码都试图绘制大约 40M 的三角形。这对于快速的现代 GPU 硬件来说并非不可能,但在低端嵌入式图形上可能真的很慢。

那么接下来可以做什么?

  • 学习 OpenGL 调试实践 - 使用 glGetError() and/or ARB_debug_output 定位此问题和其他问题的位置和根本原因。
  • 减少 gluSphere() 曲面细分参数。
  • 生成单个球体的显示列表并绘制多次。 实例化 显着减少了内存消耗。然而,这可能是一次绘制所有球体的较慢替代方法(但 2000 次绘制调用对于现代 CPU 来说并不大)。
  • 用直接生成的顶点数据替换过时的 GLU 库 - 球面细分并不难实现,网络上有很多示例。
  • 了解 Vertex Buffer Objects 并使用它们代替过时的显示列表。
  • 学习 GLSL 和现代 OpenGL,以便您可以最有效地实现 hardware instancing 绘制球体。

从另一方面看,fghGenBuffers 错误看起来很奇怪,因为 glGenBuffers() 应该出现在每个现代 OpenGL 实现中。通过 glGetString(GL_VENDOR)/glGetString(GL_RENDERER)/glGetString(GL_VERSION) 打印驱动程序信息以查看您的系统是否安装了正确的 GPU 驱动程序并且没有使用 OpenGL 1.1 的过时 Microsoft 软件实现。