Modern equivalent of `gluOrtho2d `
Asked Answered
A

2

7

What is the modern equivalent of the OpenGL function gluOrtho2d? clang is giving me deprecation warnings. I believe I need to write some kind of vertex shader? What should it look like?

Ameeameer answered 24/1, 2014 at 3:13 Comment(0)
F
15

I started off this answer thinking "It's not that different, you just have to...". I started writing some code to prove myself right, and ended up not really doing so. Anyway, here are the fruits of my efforts: a minimal annotated example of "modern" OpenGL.

There's a good bit of code you'll need before modern OpenGL will start to act like old-school OpenGL. I'm not going to get into the reasons why you might like to do it the new way (or not) -- there are countless other answers that give a pretty good rundown. Instead I'll post some minimal code that can get you running if you're so inclined.

You should end up with this stunning piece of art:

a stunningly awesome magenta triangle

Basic Render Process

Part 1: Vertex buffers

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);

Part 2: Vertex Array Object:

We need to define how the data contained in my_vertex_array is structured. This state is contained in a vertex array object (VAO). In modern OpenGL there needs to be at least one of these

    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

Part 3: Shaders

OK, we have our source data all set up, now we can set up the shader which will transform it into pixels

    // 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);

Part 4: Drawing things on the screen

    //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
}

And That's it!

... except for the actual

Shaders

I started to get a little tired at this point, so the comments are a bit lacking. Let me know if you'd like anything clarified.

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";
}

The Actual Question you asked: Orthographic Projection

As noted, the actual math is just directly from Wikipedia.

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);
}
Featherston answered 24/1, 2014 at 12:57 Comment(5)
Excellent work overall, but typically in OpenGL zNear is -1.0 and zFar is 1.0. There is no reason it cannot be any other set of non-zero values (for an orthographic projection), but typically zNear < zFar. It can mess up things like polygon winding direction to have one axis flipped in the projection matrix. Fortunately, you have also flipped the Y axis by swapping the bottom and top parameters, so this works itself out in the end and handedness is unaffected. This projection matrix will, however, produce the point (0,0) at the top-left corner instead of bottom-left.Cavour
gluOrtho2D sets up a two-dimensional orthographic viewing region. This is equivalent to calling glOrtho with near = -1 and far = 1.Cavour
@Andon Thanks, my brain reversed those z values on transcription -- Fixed. The choice of corner was deliberate, though (although totally arbitrary).Featherston
Wow, this is really above and beyond. Thank you!Ameeameer
This is a great answer, but I believe you should be calling glBindBuffer after glBindVertexArray and before any vertex attributes that are stored in that buffer.Ameeameer

© 2022 - 2024 — McMap. All rights reserved.