OpenGL中如何使用GL_TRIANGLE_FAN画圆?

How to use GL_TRIANGLE_FAN to draw a circle in OpenGL?

四季问候大家!我修改了 this tutorial 中的代码以支持使用 VAO/VBO,但现在我明白了:

而不是漂亮的圆圈。这是代码:

#define GLEW_STATIC

#include <GLEW/glew.h>
#include <GLFW/glfw3.h>

#include <corecrt_math_defines.h>
#include <cmath>
#include <vector>

#define SCREEN_WIDTH 3000
#define SCREEN_HEIGHT 1900

void drawPolygon(GLuint& vao, GLuint& vbo, GLfloat x,
    GLfloat y, GLdouble radius, GLint numberOfSides);

int main(void) {
    if (!glfwInit()) {
        return -1;
    }

    GLFWwindow* window = glfwCreateWindow(SCREEN_WIDTH,
        SCREEN_HEIGHT, "Hello World", NULL, NULL);
    if (!window) {
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);

    glewExperimental = GL_TRUE;
    glewInit();
    glGetError();

    glViewport(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT);

    GLuint vao, vbo;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, SCREEN_WIDTH, 0, SCREEN_HEIGHT, -1, 1);

    while (!glfwWindowShouldClose(window)) {
        glClear(GL_COLOR_BUFFER_BIT);
        drawPolygon(vao, vbo, SCREEN_WIDTH / 2,
            SCREEN_HEIGHT / 2, 250.0f, 50);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

void drawPolygon(GLuint& vao, GLuint& vbo, GLfloat x,
    GLfloat y, GLdouble radius, GLint numberOfSides) {
    int numVertices = numberOfSides + 2;

    GLdouble twicePi = 2.0f * M_PI;

    vector<GLdouble> circleVerticesX;
    vector<GLdouble> circleVerticesY;

    circleVerticesX.push_back(x);
    circleVerticesY.push_back(y);

    for (int i = 1; i < numVertices; i++) {
        circleVerticesX.push_back(x + (radius *
            cos(i * twicePi / numberOfSides)));
        circleVerticesY.push_back(y + (radius *
            sin(i * twicePi / numberOfSides)));
    }

    vector<GLdouble> vertices;
    for (int i = 0; i < numVertices; i++) {
        vertices.push_back(circleVerticesX[i]);
        vertices.push_back(circleVerticesY[i]);
    }

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(
        GLdouble), vertices.data(), GL_STATIC_DRAW);
    glBindVertexArray(vao);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_DOUBLE, GL_FALSE,
        2 * sizeof(GLdouble), (void*)0);

    glDrawArrays(GL_TRIANGLE_FAN, 0, 27);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

我到底做错了什么?!使用 the original code 有效,所以我对这个荒谬的结果感到非常困惑! MTIA 致任何可以提供帮助的人:-)

您的 VBO 的大小是 numVertices * sizeof(GLdouble),它是顶点数据实际大小的一半(每个顶点有一个 x 一个 y 分量)。因此,您最终绘制的顶点数量是您的 VBO 实际拥有的顶点数据的两倍。读取你的 VBO 的边界似乎会导致你的 OpenGL 实现中只有零,这就是为什么你的圆的下半部分的所有顶点都只是左下角(除非你明确启用健壮的缓冲区访问,否则不能保证这一点,只是你的驱动程序和 GPU 似乎在做什么)…

一些注意事项:

  • 您通常不想使用 double 除非您需要它。 double 占用内存带宽的两倍,double 上的算术通常至少比 float 慢一点(是的,即使在 x86 CPU 上也是如此,因为浮点算术不是现在真的不再使用 x87 FPU 了)。 GPU 特别适用于 float 算法。特别是在消费类 GPU 上,double 算法比 float.
  • 显着 (一个数量级)慢
  • 为什么不简单地将顶点数据直接推入 vertices,而不是先推入 circleVerticesXcircleVerticesY,然后从那里将其复制到 vertices
  • 您确切地知道将要生成多少个顶点。因此,无需在生成坐标的循环中动态增加顶点容器(除其他事项外,.push_back() 几乎肯定会阻止循环的矢量化)。在进入循环之前,我建议至少 .reserve() 相应数量的元素(假设这是一个 std::vector)。就个人而言,我会通过

    分配一个适当的固定大小的数组
    auto vertex_data = std::unique_ptr<GLfloat[]> { new GLfloat[numVertices * 2] };
    

    在这种情况下。

  • 您实际上不需要中心点。由于圆是凸形,您可以简单地使用圆上的一个点作为扇形的中心顶点。
  • 这不一定是画圆的最有效方法(很多长而细的三角形;更多 here
  • 您可能不希望每一帧都一次又一次地生成和上传顶点数据,除非它发生某些变化。
  • 除此之外,您可能希望 glDrawArrays 调用绘制实际的顶点数,而不是总是 27