您如何使用GL_TRIANGLE_FAN在OpenGL中绘制圆?

时间:2019-12-24 11:49:40

标签: c++ opengl glfw glew

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

weirdest circle ever

代替漂亮的圆形。这是代码:

#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给任何可以帮助的人:-)

1 个答案:

答案 0 :(得分:2)

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

注释:

  • 除非您需要,否则通常不希望使用doubledouble占用两倍的内存带宽,并且double上的算术通常比float至少慢一点(是的,即使在x86 CPU上,因为浮点算术实际上不是使用如今已经可以使用x87 FPU)。特别是为float算术构建了GPU。尤其是在消费类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