【问题标题】:Cannot draw a triangle in OpenGL无法在OpenGL中绘制三角形
【发布时间】:2016-06-25 01:35:01
【问题描述】:

我最近开始学习 OpenGL,并编写了一些代码来在 SDL 窗口中绘制一个简单的白色三角形。我已经阅读并重新阅读了我的代码,并且我已经根据几个 opengl 教程中提出的内容对其进行了检查,但我无法使其工作。我知道 opengl 已正确绑定到 SDL,因为我可以使用 glClearColor 更改窗口背景颜色,但我无法显示那个该死的三角形。

我很乐意提供更多信息,但老实说我不知道​​问题出在哪里,所以这是我编写的代码:

int init()
{
    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        fprintf(stderr, "SDL error : %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

    gWindow = SDL_CreateWindow("Test OpenGL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINWIDTH, WINHEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
    if (gWindow == NULL)
    {
        fprintf(stderr, "Failed to create window. SDL error : %s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    gGLContext = SDL_GL_CreateContext(gWindow);
    if (gGLContext == 0)
    {
        fprintf(stderr, "Failed to create OpenGL context. SDL error : %s\n", SDL_GetError());
        SDL_DestroyWindow(gWindow);
        SDL_Quit();
        return -1;
    }

    GLenum glewInitResult = glewInit();
    if (glewInitResult != GLEW_OK)
    {
        fprintf(stderr, "Failed to initialize glew. Glew error : %s\n", glewGetErrorString(glewInitResult));
        SDL_GL_DeleteContext(gGLContext);
        SDL_DestroyWindow(gWindow);
        SDL_Quit();
        return -1;
    }
    glEnable(GL_DEPTH_TEST);

    return 0;
}

int loadShader(char *filename, GLint *shader)
{
    long sz;
    const GLchar *code;
    FILE *fp = fopen(filename, "rb");
    if( !fp ) perror(filename),exit(1);

    fseek( fp , 0L , SEEK_END);
    sz = ftell( fp );
    rewind( fp );

    code = calloc( 1, sz+1 );
    if( !code ) fclose(fp),fputs("memory alloc fails",stderr),exit(1);

    if( 1!=fread( code , sz, 1 , fp) )
      fclose(fp),free(code),fputs("entire read fails",stderr),exit(1);

    fclose(fp);
    char *dot = strrchr(filename, '.');
    GLenum type;
    if (dot)
        type = !strcmp(dot, ".vs") ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER;
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &code, NULL);
    glCompileShader(*shader);
    GLint status;
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);

    char buffer[512];
    glGetShaderInfoLog(*shader, sizeof(buffer), NULL, buffer);
    fprintf(stderr, buffer);
    if (status != GL_TRUE)
        return -1;
    return 0;
}

void printError(char *message)
{
#define TEST(ERR) case ERR: fprintf(stderr, "%s : " #ERR "\n", message); break;
    int error;
    if ((error = glGetError()))
        switch (error)
        {
        TEST(GL_INVALID_ENUM)
        TEST(GL_INVALID_VALUE)
        TEST(GL_INVALID_OPERATION)
        TEST(GL_STACK_OVERFLOW)
        TEST(GL_STACK_UNDERFLOW)
        TEST(GL_OUT_OF_MEMORY)
        TEST(GL_INVALID_FRAMEBUFFER_OPERATION)
        TEST(GL_TABLE_TOO_LARGE)
    default:
        fprintf(stderr, "Unknown error\n");
        }
#undef TEST
}

int main(int argc, char *argv[])
{
    if (init() != 0)
        return -1;

    float vertices[] = {0.0,  0.5,
                        0.5, -0.5,
                        -.5, -0.5};
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    GLuint vShader;
    GLuint fShader;
    if (loadShader("plain_2d.vs", &vShader) < 0)
        return -1;
    if (loadShader("plain_2d.fs", &fShader) < 0)
        return -1;
    GLint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vShader);
    glAttachShader(shaderProgram, fShader);
    glLinkProgram(shaderProgram);
    glUseProgram(shaderProgram);

    GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
    glEnableVertexAttribArray(posAttrib);
    glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);

    printError("Before entering the loop");

    SDL_Event e;
    int quit = 0;
    while (!quit)
    {
        printError("In the loop");
        while (SDL_PollEvent(&e))
        {
            switch (e.type)
            {
            case SDL_QUIT:
                quit = 1;
            }
        }

        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glDrawArrays(GL_TRIANGLES, 0, 3);

        SDL_GL_SwapWindow(gWindow);
    }
    return 0;
}

printError 和 loadShader 是我编写的辅助函数,并且肯定可以完美运行。这是顶点和片段着色器的代码,它非常基本,我看不出那里会出现什么问题:

顶点着色器

#version 150

in vec2 position;

void main()
{
    gl_Position = vec4(position, 0.0, 1.0);
}

片段着色器

#version 150

out vec4 outColor;

void main()
{
    outColor = vec4(1.0, 1.0, 1.0, 1.0);
}

很抱歉,这含糊不清,但我做了研究,确实没有发现我的代码有任何问题,所以我希望更有经验的人能够找到问题。

提前致谢!

【问题讨论】:

  • 为什么您认为您的loadShader 功能可以正常工作?很可能,问题就出在这里。
  • 您应该定期检查您的 opengl 是否有错误。您也永远不会检查以确保着色器在没有警告的情况下编译(除非在 loadShader 中发生),并且您永远不会检查程序是否存在链接器错误。
  • 尝试在你的循环中调用glGetErrors()。如果 if 触发,请找出触发它的确切行。
  • 我会在加载着色器(这就是我说它正确加载的原因)和使用printError(char *message) 执行程序期间检查错误。上面的代码表示没有错误,这就是为什么我很难弄清楚它有什么问题。如果您想查看,我将这两个函数的代码添加到我的原始帖子中。

标签: c opengl


【解决方案1】:

您还没有生成和绑定一个顶点数组对象,只是一个顶点缓冲区。本质上,您已经创建了一个数据源(VBO),但数据无处可去,因此当您调用 glEnableVertexAttribArray(posAttrib); 时没有绑定数组对象

在生成和绑定缓冲区之前添加以下内容:

GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

编辑:在启用兼容性配置文件时,3.1 和更新版本提供默认 VAO - 核心配置文件需要您手动执行。

另外,请显示loadShader()的来源

【讨论】:

  • 我不确定这是否会有很大的不同,因为从一开始就有一个默认的 VAO 绑定。
  • @Taywee 我在本地对其进行了测试,尽管使用的是 glfw 而不是 sdl。 IIUC 它取决于 GL 版本 - 如果您处于兼容模式,默认 VAO 仅适用于 core>=3.1。
  • @HolyBlackCat 正确 afaik,可能来自 nvidia 的一些怪异(不符合规范?)
  • 感谢您的快速回复,我已经添加了您编写的代码,但我仍然得到黑色(或我选择的任何颜色)屏幕。我仍然对 VBO 和 VAO 感到困惑。如果我理解正确的话,VBO 是一个在显卡中存储顶点信息的低级数组,而 VAO 是一个“配置文件”,它存储有关要渲染什么以及如何渲染的信息 - 这是正确的,还是有点接近现实?至于loadShader(),我在帖子里加了代码。
  • @Toctave 这或多或少是正确的。 VAO 管理客户端(即不在 GPU 上)状态,例如顶点布局以及在您想要绘制数组时使用哪个 VBO。学习 OpenGL 是相当困难的,因为你基本上是在追赶多年的发展。我看到 Taywee 解决了你剩下的问题;我错过了你没有启用核心配置文件。
【解决方案2】:

我对您的代码进行了一些检查。一些错误的地方:

  • 您不是在请求核心配置文件
  • 你没有检查你的链接
  • 你在滥用逗号操作符
  • 您没有启用 glewExperimental
  • 您没有清除深度位

这是您与上下文的差异(来自修改后的版本,因为您没有包含标题或定义):

@@ -22,6 +22,7 @@

     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
     SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
+    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
     SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
@@ -43,6 +44,7 @@
         return -1;
     }

+    glewExperimental = GL_TRUE;
     GLenum glewInitResult = glewInit();
     if (glewInitResult != GLEW_OK)
     {
@@ -69,10 +71,10 @@
     rewind( fp );

     code = calloc( 1, sz+1 );
-    if( !code ) fclose(fp),fputs("memory alloc fails",stderr),exit(1);
+    if( !code ) {fclose(fp);fputs("memory alloc fails",stderr);exit(1);}

     if( 1!=fread( code , sz, 1 , fp) )
-      fclose(fp),free(code),fputs("entire read fails",stderr),exit(1);
+        {fclose(fp);free(code);fputs("entire read fails",stderr);exit(1);}

     fclose(fp);
     char *dot = strrchr(filename, '.');
@@ -122,6 +124,10 @@
     float vertices[] = {0.0,  0.5,
                         0.5, -0.5,
                         -.5, -0.5};
+    GLuint vao;
+    glGenVertexArrays(1, &vao);
+    glBindVertexArray(vao);
+
     GLuint vbo;
     glGenBuffers(1, &vbo);
     glBindBuffer(GL_ARRAY_BUFFER, vbo);
@@ -160,7 +166,7 @@
         }

         glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-        glClear(GL_COLOR_BUFFER_BIT);
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

         glDrawArrays(GL_TRIANGLES, 0, 3);

结果:

【讨论】:

  • 为了可读性,我将在多个 cmets 中回答。核心配置文件允许后 OpenGL 3.2 功能。许多驱动程序允许您忽略这一点,有些则不允许。不过,启用它总是一个好主意,因为它会禁用您不应该使用的已弃用功能(而且您不在这里)。
  • glewExperimental 是一种允许尝试加载不在驱动程序的实验字符串中的扩展的 hack。无论如何, glew 本身已经过时了,如果可以的话,我总是建议使用glLoadGen。这将为您的标准的特定版本创建加载器,因此您可以更轻松地仅针对特定版本的 OpenGL。
  • “检查您的链接”意味着检查以确保您的着色器程序正确链接。检查链接状态几乎就像检查着色器编译状态一样:opengl.org/wiki/Shader_Compilation#Error_handling。我将这些部分排除在我的修改之外,因为它很臃肿,并且实际上并不需要在这里让代码正常运行。不过,它应该始终在着色器链接中完成。
  • 逗号运算符有一个特定的语义用途,就是对左操作数求值,并返回右操作数。它不应该被用作允许多个语句代替块的技巧,除非不能使用块,例如在 for 循环的初始化中。这完全取决于个人风格,但通常最好只在需要的地方使用逗号运算符,或者在它为您带来更多灵活性的地方使用逗号运算符,特别是当函数时难以在一行中阅读多个语句时带有逗号的调用混合在一起,或者在您需要的地方添加语句。
  • 要清楚,唯一阻止绘图的地方是清除深度缓冲区位(因为深度缓冲区会阻止您在同一深度绘制两次),启用 glewExperimental(因为 GLEW 不是找到它需要的扩展),并创建和绑定一个 VAO。其余的只是“好主意”领域,而不是让它在每种情况下都能正常工作。如果您需要更多说明,请随时询问。
【解决方案3】:

通常,当您创建着色器时,您必须指定着色器类型:

GLuint vertexShader, fragmentShader;

vertexShader = glCreateShader(GL_VERTEX_SHADER);
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

我在您的代码中没有看到任何内容。 loadShader 是否根据扩展进行此检测?如果不是,那将是最有可能出现的问题。

【讨论】:

  • 感谢您的回复。 loadShader() 正是这样做的,如果您想查看,我将代码添加到我的原始帖子中。
猜你喜欢
  • 2013-07-10
  • 2020-12-08
  • 2015-05-26
  • 1970-01-01
  • 1970-01-01
  • 2018-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多