`gluOrtho2d`的现代等价方法

7

什么是OpenGL函数gluOrtho2d的现代等效版本?我收到了clang的弃用警告。我认为我需要编写某种顶点着色器?它应该长什么样子?

2个回答

15
我开始回答时认为“这并没有那么大的区别,你只需要...”。我开始编写一些代码来证明自己是正确的,但最终并没有真正做到。无论如何,以下是我努力的成果:一个注释示例,用于展示“现代”OpenGL。
在现代OpenGL开始像老式OpenGL一样工作之前,您需要编写相当多的代码。我不会讨论为什么你可能想要以新的方式进行操作(或者不这样做)——有无数其他的答案给出了相当好的概述。相反,我将发布一些最小的代码,如果您愿意,可以使您运行起来。
你应该最终得到这个惊人的艺术品:
基本渲染过程:
第一部分:顶点缓冲区。
void TestDraw(){
    // create a vertex buffer (This is a buffer in video memory)
    GLuint my_vertex_buffer;
    glGenBuffers(1 /*ask for one buffer*/, &my_vertex_buffer);

    const float a_2d_triangle[] =
    {
        200.0f, 10.0f,
        10.0f, 200.0f,
        400.0f, 200.0f
    };

    // GL_ARRAY_BUFFER indicates we're using this for 
    // vertex data (as opposed to things like feedback, index, or texture data)
    // so this call says use my_vertex_data as the vertex data source
    // this will become relevant as we make draw calls later 
    glBindBuffer(GL_ARRAY_BUFFER, my_vertex_buffer);


    // allocate some space for our buffer

    glBufferData(GL_ARRAY_BUFFER, 4096, NULL, GL_DYNAMIC_DRAW);

    // we've been a bit optimistic, asking for 4k of space even 
    // though there is only one triangle.
    // the NULL source indicates that we don't have any data 
    // to fill the buffer quite yet.
    // GL_DYNAMIC_DRAW indicates that we intend to change the buffer
    // data from frame-to-frame.
    // the idea is that we can place more than 3(!) vertices in the
    // buffer later as part of normal drawing activity

    // now we actually put the vertices into the buffer.
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(a_2d_triangle), a_2d_triangle);

第二部分:顶点数组对象:

我们需要定义 my_vertex_array 中包含的数据结构。这个状态是包含在一个顶点数组对象(VAO)中的。在现代OpenGL中,至少需要一个VAO。

    GLuint my_vao;
    glGenVertexArrays(1, &my_vao);

    //lets use the VAO we created
    glBindVertexArray(my_vao);

    // now we need to tell the VAO how the vertices in my_vertex_buffer
    // are structured
    // our vertices are really simple: each one has 2 floats of position data
    // they could have been more complicated (texture coordinates, color -- 
    // whatever you want)

    // enable the first attribute in our VAO
    glEnableVertexAttribArray(0);

    // describe what the data for this attribute is like
    glVertexAttribPointer(0, // the index we just enabled
        2, // the number of components (our two position floats) 
        GL_FLOAT, // the type of each component
        false, // should the GL normalize this for us?
        2 * sizeof(float), // number of bytes until the next component like this
        (void*)0); // the offset into our vertex buffer where this element starts

第三部分:着色器

好的,我们已经准备好了源数据,现在我们可以设置着色器,将其转换为像素。

    // first create some ids
    GLuint my_shader_program = glCreateProgram();
    GLuint my_fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    GLuint my_vertex_shader = glCreateShader(GL_VERTEX_SHADER);

    // we'll need to compile the vertex shader and fragment shader
    // and then link them into a full "shader program"
    // load one string from &my_fragment_source
    // the NULL indicates that the string is null-terminated
    const char* my_fragment_source = FragmentSourceFromSomewhere();
    glShaderSource(my_fragment_shader, 1, &my_fragment_source, NULL);
    // now compile it:
    glCompileShader(my_fragment_shader);

    // then check the result
    GLint compiled_ok;
    glGetShaderiv(my_fragment_shader, GL_COMPILE_STATUS, &compiled_ok);
    if (!compiled_ok){ printf("Oh Noes, fragment shader didn't compile!\n"); }
    else{
        glAttachShader(my_shader_program, my_fragment_shader);
    }

    // and again for the vertex shader
    const char* my_vertex_source = VertexSourceFromSomewhere();
    glShaderSource(my_vertex_shader, 1, &my_vertex_source, NULL);
    glCompileShader(my_vertex_shader);
    glGetShaderiv(my_vertex_shader, GL_COMPILE_STATUS, &compiled_ok);
    if (!compiled_ok){ printf("Oh Noes, vertex shader didn't compile!\n"); }
    else{
        glAttachShader(my_shader_program, my_vertex_shader);
    }

    //finally, link the program, and set it active
    glLinkProgram(my_shader_program);
    glUseProgram(my_shader_program);

第四部分:在屏幕上绘制图形

    //get the screen size
    float my_viewport[4];
    glGetFloatv(GL_VIEWPORT, my_viewport);

    //now create a projection matrix
    float my_proj_matrix[16];
    MyOrtho2D(my_proj_matrix, 0.0f, my_viewport[2], my_viewport[3], 0.0f);

    //"uProjectionMatrix" refers directly to the variable of that name in 
    // shader source
    GLuint my_projection_ref = 
        glGetUniformLocation(my_shader_program, "uProjectionMatrix");

    // send our projection matrix to the shader
    glUniformMatrix4fv(my_projection_ref, 1, GL_FALSE, my_proj_matrix );


    //clear the background
    glClearColor(0.3, 0.4, 0.4, 1.0);
    glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);

    // *now* after that tiny setup, we're ready to draw the best 24 bytes of
    // vertex data ever.

    // draw the 3 vertices starting at index 0, interpreting them as triangles
    glDrawArrays(GL_TRIANGLES, 0, 3);

    // now just swap buffers however your window manager lets you
}

就是这样了!

... 除了实际的操作外

着色器(Shaders)

我到这个点有些疲倦了,因此注释有点少。如果您需要澄清任何事情,请告诉我。

const char* VertexSourceFromSomewhere()
{
    return
        "#version 330\n"
        "layout(location = 0) in vec2 inCoord;\n"
        "uniform mat4 uProjectionMatrix;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = uProjectionMatrix*(vec4(inCoord, 0, 1.0));\n"
        "}\n";
}

const char* FragmentSourceFromSomewhere()
{
    return
        "#version 330 \n"
        "out vec4 outFragColor;\n"
        "vec4 DebugMagenta(){ return vec4(1.0, 0.0, 1.0, 1.0); }\n"
        "void main() \n"
        "{\n"
        "   outFragColor = DebugMagenta();\n"
        "}\n";
}

你实际提出的问题:正交投影

如注所述,实际的数学内容直接来自维基百科。

void MyOrtho2D(float* mat, float left, float right, float bottom, float top)
{
    // this is basically from
    // http://en.wikipedia.org/wiki/Orthographic_projection_(geometry)
    const float zNear = -1.0f;
    const float zFar = 1.0f;
    const float inv_z = 1.0f / (zFar - zNear);
    const float inv_y = 1.0f / (top - bottom);
    const float inv_x = 1.0f / (right - left);

    //first column
    *mat++ = (2.0f*inv_x);
    *mat++ = (0.0f);
    *mat++ = (0.0f);
    *mat++ = (0.0f);

    //second
    *mat++ = (0.0f);
    *mat++ = (2.0*inv_y);
    *mat++ = (0.0f);
    *mat++ = (0.0f);

    //third
    *mat++ = (0.0f);
    *mat++ = (0.0f);
    *mat++ = (-2.0f*inv_z);
    *mat++ = (0.0f);

    //fourth
    *mat++ = (-(right + left)*inv_x);
    *mat++ = (-(top + bottom)*inv_y);
    *mat++ = (-(zFar + zNear)*inv_z);
    *mat++ = (1.0f);
}

整体表现优秀,但在OpenGL中,通常zNear的值为**-1.0,而zFar的值为1.0**。理论上说,对于正交投影,可以使用任何一组非零值,但通常 zNear < zFar。如果在投影矩阵中翻转了一个轴线,可能会导致多边形绕序方向出现问题。幸运的是,在这里您还通过交换bottomtop参数来翻转了Y轴,因此最终不会受到影响,并且左右手坐标系也不会改变。然而,这个投影矩阵将使原点(0,0)位于左上角,而不是左下角。 - Andon M. Coleman
gluOrtho2D 设置了一个二维正交视图区域。这相当于使用 near = -1 和 far = 1 调用 glOrtho。 - Andon M. Coleman
@Andon 谢谢,我的大脑在转录时颠倒了这些 z 值 -- 已修复。选择角落是故意的,尽管完全是任意的。 - Profram Files
哇,这真的超出预期了。谢谢! - Neil G
这是一个很好的答案,但我认为在任何存储在该缓冲区中的顶点属性之前,您应该在调用glBindVertexArray之后调用glBindBuffer - Neil G


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接