如何使用正交投影在 OpenGL Core 3.3 中绘制一个圆?

Posted

技术标签:

【中文标题】如何使用正交投影在 OpenGL Core 3.3 中绘制一个圆?【英文标题】:How can I draw a circle in OpenGL Core 3.3 with orthographic projection? 【发布时间】:2019-12-22 00:04:57 【问题描述】:

我是 OpenGL 编程的完全初学者,我正在尝试遵循 learnopengl.com 的 Breakout 教程,但我想将球画成一个实际的圆圈,而不是像 Joey 建议的那样使用带纹理的四边形。然而,谷歌向我抛出的“draw circle opengl 3.3”或类似短语的每一个结果似乎都至少有几年的历史了,而且使用的 API 版本甚至比那个版本还要旧:-(

我找到的最接近的东西是this SO question,但当然,OP 只是不得不使用自定义 VertexFormat 对象来抽象一些细节,而不分享他/她执行之类的!只是我的运气! :P

还有 this YouTube tutorial 使用看似旧版本的 API,但逐字复制代码(除了最后几行代码看起来很旧)仍然让我无处可去。

教程中我的SpriteRenderer::initRenderData() 版本:

void SpriteRenderer::initRenderData() 
    GLuint vbo;

    auto attribSize = 0;
    GLfloat* vertices = nullptr;

    // Determine whether this sprite is a circle or
    // quad and setup the vertices array accordingly
    if (!this->isCircle) 
        attribSize = 4;
        vertices = new GLfloat[24] ...   // works for rendering quads
     else 
        // This code is adapted from the YouTube tutorial that I linked
        // above and is where things go pear-shaped for me...or at least
        // **not** circle-shaped :P
        attribSize = 3;

        GLfloat x = 0.0f;
        GLfloat y = 0.0f;
        GLfloat z = 0.0f;
        GLfloat r = 100.0f;

        GLint numSides = 6;
        GLint numVertices = numSides + 2;

        GLfloat* xCoords = new GLfloat[numVertices];
        GLfloat* yCoords = new GLfloat[numVertices];
        GLfloat* zCoords = new GLfloat[numVertices];

        xCoords[0] = x;
        yCoords[0] = y;
        zCoords[0] = z;

        for (auto i = 1; i < numVertices; i++) 
            xCoords[i] = x + (r * cos(i * (M_PI * 2.0f) / numSides));
            yCoords[i] = y + (r * sin(i * (M_PI * 2.0f) / numSides));
            zCoords[i] = z;
        

        vertices = new GLfloat[numVertices * 3];
        for (auto i = 0; i < numVertices; i++) 
            vertices[i * 3] = xCoords[i];
            vertices[i * 3 + 1] = yCoords[i];
            vertices[i * 3 + 2] = zCoords[i];
        
    

    // This is where I go back to the learnopengl.com code. Once
    // again, the following works for quads but not circles!
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, 24 * sizeof(
        GLfloat), vertices, GL_STATIC_DRAW);

    glBindVertexArray(vao);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(0, attribSize, GL_FLOAT, GL_FALSE,
        attribSize * sizeof(GLfloat), (GLvoid*)0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

这是SpriteRenderer::DrawSprite() 方法(与原始方法的唯一区别是第 24 - 28 行):

void SpriteRenderer::Draw(vec2 position, vec2 size, GLfloat rotation, vec3 colour) 
    // Prepare transformations
    shader.Use();

    auto model = mat4(1.0f);
    model = translate(model, vec3(position, 0.0f));

    model = translate(model, vec3(0.5f * size.x, 0.5f * size.y, 0.0f));   // Move origin of rotation to center
    model = rotate(model, rotation, vec3(0.0f, 0.0f, 1.0f));              // Rotate quad
    model = translate(model, vec3(-0.5f * size.x, -0.5f * size.y, 0.0f)); // Move origin back

    model = scale(model, vec3(size, 1.0f)); // Lastly, scale

    shader.SetMatrix4("model", model);

    // Render textured quad
    shader.SetVector3f("spriteColour", colour);

    glActiveTexture(GL_TEXTURE0);
    texture.Bind();

    glBindVertexArray(vao);

    if (!isCircular) 
        glDrawArrays(GL_TRIANGLES, 0, 6);
     else 
        glDrawArrays(GL_TRIANGLE_FAN, 0, 24);  // also tried "12" and "8" for the last param, to no avail
    

    glBindVertexArray(0);

最后,着色器(不同于用于四边形的着色器):

// Vertex shader
#version 330 core

layout (location = 0) in vec3 position;

uniform mat4 model;
uniform mat4 projection;

void main() 
    gl_Position = projection * model *
        vec4(position.xyz, 1.0f);


// Fragment shader
#version 330 core

out vec4 colour;

uniform vec3 spriteColour;

void main()     
    colour = vec4(spriteColour, 1.0);

附:我知道我可以只使用四边形,但我正在尝试学习如何在 OpenGL 中绘制 所有 图元,而不仅仅是四边形和三角形(无论如何感谢乔伊)!

P.P.S 我刚刚意识到 learnopengl.com 网站有一个专门用于调试 OpenGL 应用程序的部分,所以我进行了设置但无济于事:-( 我认为我的驱动程序不支持错误处理(英特尔UHD Graphics 620 最新驱动程序),因为按照说明操作后未设置 GL_CONTEXT_FLAG_DEBUG_BIT

在 GLFW 中请求调试上下文非常简单,因为我们所要做的就是向 GLFW 传递我们想要调试输出上下文的提示。在调用 glfwCreateWindow 之前,我们必须这样做:

glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);

一旦我们初始化 GLFW,如果我们使用 OpenGL 4.3 或更高版本,我们应该有一个调试上下文,否则我们必须抓住机会,希望系统仍然能够请求调试上下文。否则,我们必须使用其 OpenGL 扩展来请求调试输出。

要检查我们是否成功初始化了调试上下文,我们可以查询 OpenGL:

GLint 标志; glGetIntegerv(GL_CONTEXT_FLAGS, &flags);

如果(标志和 GL_CONTEXT_FLAG_DEBUG_BIT) // 初始化调试输出

永远不会输入if 语句!

【问题讨论】:

你得到了什么结果? glDrawArrays 的最后一个参数必须是顶点数。 “如何绘制所有基元” 是什么意思? OpenGL Primitives 是点、线和三角形。 你有没有设置统一的projection,在程序安装后(shader.Use();)? 感谢 cmets 窥视! :-) @ybungalobill 我的大部分场景都正确渲染了,除了那些根本没有出现的圆圈。 @Rabbid76 抱歉,我认为“原始”的意思是“基本的 2D 形状”。我想我需要阅读更多文档哈哈:P 是的,我在游戏的设置函数中设置了投影矩阵,如下所示:circleShader.Use().SetMatrix4("projection", ortho(0.0f, static_cast&lt;GLfloat&gt;(this-&gt;Width), static_cast&lt;GLfloat&gt;(this-&gt;Height), 0.0f, -1.0f, 1.0f)); Learning Modern 3D Graphics Programming 【参考方案1】:

感谢@Mykola 对this question 的回复,我已经完成了一半:

numVertices = 43;
vertices = new GLfloat[numVertices];

auto i = 2;
auto x = 0.0f,
     y = x,
     z = x,
     r = 0.3f;

auto numSides = 21;
auto TWO_PI = 2.0f * M_PI;
auto increment = TWO_PI / numSides;

for (auto angle = 0.0f; angle <= TWO_PI; angle += increment) 
    vertices[i++] = r * cos(angle) + x;
    vertices[i++] = r * sin(angle) + y;

这给了我。

我还有两个问题:

    为什么从中心到右侧有一条额外的线,我该如何解决? 根据@user1118321's comment 上的相关 SO 答案,我应该能够在 (0, 0) 处为数组添加另一个顶点并使用 GL_TRIANGLE_FAN 而不是 GL_LINE_LOOP 得到一个彩色圆圈。但这对我来说没有输出:-(为什么?

【讨论】:

以上是关于如何使用正交投影在 OpenGL Core 3.3 中绘制一个圆?的主要内容,如果未能解决你的问题,请参考以下文章

使用 OpenGL 进行正交投影以及如何实现相机或物体在空间中的移动

openGL - 正交投影矩阵

没有 OpenGL 的重复 OpenGL 正交投影行为

在 OpenGL 的正交投影中使用纹理

opengl中的正交投影和纹理坐标

在 iOS 上使用 OpenGL-ES 2.0 设置横向正交投影的正确方法是啥?