How to use glOrtho() in OpenGL?

前端 未结 3 427
别那么骄傲
别那么骄傲 2020-11-28 01:32

I can\'t understand the usage of glOrtho. Can someone explain what it is used for?

Is it used to set the range of x y and z coordinates limit?



        
相关标签:
3条回答
  • 2020-11-28 02:01

    Minimal runnable example

    glOrtho: 2D games, objects close and far appear the same size:

    glFrustrum: more real-life like 3D, identical objects further away appear smaller:

    main.c

    #include <stdlib.h>
    
    #include <GL/gl.h>
    #include <GL/glu.h>
    #include <GL/glut.h>
    
    static int ortho = 0;
    
    static void display(void) {
        glClear(GL_COLOR_BUFFER_BIT);
        glLoadIdentity();
        if (ortho) {
        } else {
            /* This only rotates and translates the world around to look like the camera moved. */
            gluLookAt(0.0, 0.0, -3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
        }
        glColor3f(1.0f, 1.0f, 1.0f);
        glutWireCube(2);
        glFlush();
    }
    
    static void reshape(int w, int h) {
        glViewport(0, 0, w, h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        if (ortho) {
            glOrtho(-2.0, 2.0, -2.0, 2.0, -1.5, 1.5);
        } else {
            glFrustum(-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);
        }
        glMatrixMode(GL_MODELVIEW);
    }
    
    int main(int argc, char** argv) {
        glutInit(&argc, argv);
        if (argc > 1) {
            ortho = 1;
        }
        glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
        glutInitWindowSize(500, 500);
        glutInitWindowPosition(100, 100);
        glutCreateWindow(argv[0]);
        glClearColor(0.0, 0.0, 0.0, 0.0);
        glShadeModel(GL_FLAT);
        glutDisplayFunc(display);
        glutReshapeFunc(reshape);
        glutMainLoop();
        return EXIT_SUCCESS;
    }
    

    GitHub upstream.

    Compile:

    gcc -ggdb3 -O0 -o main -std=c99 -Wall -Wextra -pedantic main.c -lGL -lGLU -lglut
    

    Run with glOrtho:

    ./main 1
    

    Run with glFrustrum:

    ./main
    

    Tested on Ubuntu 18.10.

    Schema

    Ortho: camera is a plane, visible volume a rectangle:

    Frustrum: camera is a point,visible volume a slice of a pyramid:

    Image source.

    Parameters

    We are always looking from +z to -z with +y upwards:

    glOrtho(left, right, bottom, top, near, far)
    
    • left: minimum x we see
    • right: maximum x we see
    • bottom: minimum y we see
    • top: maximum y we see
    • -near: minimum z we see. Yes, this is -1 times near. So a negative input means positive z.
    • -far: maximum z we see. Also negative.

    Schema:

    Image source.

    How it works under the hood

    In the end, OpenGL always "uses":

    glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
    

    If we use neither glOrtho nor glFrustrum, that is what we get.

    glOrtho and glFrustrum are just linear transformations (AKA matrix multiplication) such that:

    • glOrtho: takes a given 3D rectangle into the default cube
    • glFrustrum: takes a given pyramid section into the default cube

    This transformation is then applied to all vertexes. This is what I mean in 2D:

    Image source.

    The final step after transformation is simple:

    • remove any points outside of the cube (culling): just ensure that x, y and z are in [-1, +1]
    • ignore the z component and take only x and y, which now can be put into a 2D screen

    With glOrtho, z is ignored, so you might as well always use 0.

    One reason you might want to use z != 0 is to make sprites hide the background with the depth buffer.

    Deprecation

    glOrtho is deprecated as of OpenGL 4.5: the compatibility profile 12.1. "FIXED-FUNCTION VERTEX TRANSFORMATIONS" is in red.

    So don't use it for production. In any case, understanding it is a good way to get some OpenGL insight.

    Modern OpenGL 4 programs calculate the transformation matrix (which is small) on the CPU, and then give the matrix and all points to be transformed to OpenGL, which can do the thousands of matrix multiplications for different points really fast in parallel.

    Manually written vertex shaders then do the multiplication explicitly, usually with the convenient vector data types of the OpenGL Shading Language.

    Since you write the shader explicitly, this allows you to tweak the algorithm to your needs. Such flexibility is a major feature of more modern GPUs, which unlike the old ones that did a fixed algorithm with some input parameters, can now do arbitrary computations. See also: https://stackoverflow.com/a/36211337/895245

    With an explicit GLfloat transform[] it would look something like this:

    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    #define GLEW_STATIC
    #include <GL/glew.h>
    
    #include <GLFW/glfw3.h>
    
    #include "common.h"
    
    static const GLuint WIDTH = 800;
    static const GLuint HEIGHT = 600;
    /* ourColor is passed on to the fragment shader. */
    static const GLchar* vertex_shader_source =
        "#version 330 core\n"
        "layout (location = 0) in vec3 position;\n"
        "layout (location = 1) in vec3 color;\n"
        "out vec3 ourColor;\n"
        "uniform mat4 transform;\n"
        "void main() {\n"
        "    gl_Position = transform * vec4(position, 1.0f);\n"
        "    ourColor = color;\n"
        "}\n";
    static const GLchar* fragment_shader_source =
        "#version 330 core\n"
        "in vec3 ourColor;\n"
        "out vec4 color;\n"
        "void main() {\n"
        "    color = vec4(ourColor, 1.0f);\n"
        "}\n";
    static GLfloat vertices[] = {
    /*   Positions          Colors */
         0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,
        -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
         0.0f,  0.5f, 0.0f, 0.0f, 0.0f, 1.0f
    };
    
    int main(void) {
        GLint shader_program;
        GLint transform_location;
        GLuint vbo;
        GLuint vao;
        GLFWwindow* window;
        double time;
    
        glfwInit();
        window = glfwCreateWindow(WIDTH, HEIGHT, __FILE__, NULL, NULL);
        glfwMakeContextCurrent(window);
        glewExperimental = GL_TRUE;
        glewInit();
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glViewport(0, 0, WIDTH, HEIGHT);
    
        shader_program = common_get_shader_program(vertex_shader_source, fragment_shader_source);
    
        glGenVertexArrays(1, &vao);
        glGenBuffers(1, &vbo);
        glBindVertexArray(vao);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        /* Position attribute */
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
        glEnableVertexAttribArray(0);
        /* Color attribute */
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
        glBindVertexArray(0);
    
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
            glClear(GL_COLOR_BUFFER_BIT);
    
            glUseProgram(shader_program);
            transform_location = glGetUniformLocation(shader_program, "transform");
            /* THIS is just a dummy transform. */
            GLfloat transform[] = {
                0.0f, 0.0f, 0.0f, 0.0f,
                0.0f, 0.0f, 0.0f, 0.0f,
                0.0f, 0.0f, 1.0f, 0.0f,
                0.0f, 0.0f, 0.0f, 1.0f,
            };
            time = glfwGetTime();
            transform[0] = 2.0f * sin(time);
            transform[5] = 2.0f * cos(time);
            glUniformMatrix4fv(transform_location, 1, GL_FALSE, transform);
    
            glBindVertexArray(vao);
            glDrawArrays(GL_TRIANGLES, 0, 3);
            glBindVertexArray(0);
            glfwSwapBuffers(window);
        }
        glDeleteVertexArrays(1, &vao);
        glDeleteBuffers(1, &vbo);
        glfwTerminate();
        return EXIT_SUCCESS;
    }
    

    GitHub upstream.

    Output:

    The matrix for glOrtho is really simple, composed only of scaling and translation:

    scalex, 0,      0,      translatex,
    0,      scaley, 0,      translatey,
    0,      0,      scalez, translatez,
    0,      0,      0,      1
    

    as mentioned in the OpenGL 2 docs.

    The glFrustum matrix is not too hard to calculate by hand either, but starts getting annoying. Note how frustum cannot be made up with only scaling and translations like glOrtho, more info at: https://gamedev.stackexchange.com/a/118848/25171

    The GLM OpenGL C++ math library is a popular choice for calculating such matrices. http://glm.g-truc.net/0.9.2/api/a00245.html documents both an ortho and frustum operations.

    0 讨论(0)
  • 2020-11-28 02:02

    glOrtho describes a transformation that produces a parallel projection. The current matrix (see glMatrixMode) is multiplied by this matrix and the result replaces the current matrix, as if glMultMatrix were called with the following matrix as its argument:

    OpenGL documentation (my bold)

    The numbers define the locations of the clipping planes (left, right, bottom, top, near and far).

    The "normal" projection is a perspective projection that provides the illusion of depth. Wikipedia defines a parallel projection as:

    Parallel projections have lines of projection that are parallel both in reality and in the projection plane.

    Parallel projection corresponds to a perspective projection with a hypothetical viewpoint—e.g., one where the camera lies an infinite distance away from the object and has an infinite focal length, or "zoom".

    0 讨论(0)
  • 2020-11-28 02:26

    Have a look at this picture: Graphical Projections

    The glOrtho command produces an "Oblique" projection that you see in the bottom row. No matter how far away vertexes are in the z direction, they will not recede into the distance.

    I use glOrtho every time I need to do 2D graphics in OpenGL (such as health bars, menus etc) using the following code every time the window is resized:

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0f, windowWidth, windowHeight, 0.0f, 0.0f, 1.0f);
    

    This will remap the OpenGL coordinates into the equivalent pixel values (X going from 0 to windowWidth and Y going from 0 to windowHeight). Note that I've flipped the Y values because OpenGL coordinates start from the bottom left corner of the window. So by flipping, I get a more conventional (0,0) starting at the top left corner of the window rather.

    Note that the Z values are clipped from 0 to 1. So be careful when you specify a Z value for your vertex's position, it will be clipped if it falls outside that range. Otherwise if it's inside that range, it will appear to have no effect on the position except for Z tests.

    0 讨论(0)
提交回复
热议问题