web-dev-qa-db-fra.com

Comment utiliser glOrtho () dans OpenGL?

Je ne comprends pas l'utilisation de glOrtho. Quelqu'un peut-il expliquer à quoi il sert?

Est-il utilisé pour définir la plage des coordonnées x y et z?

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

Cela signifie que les plages x, y et z vont de -1 à 1?

77
ufk

Regardez cette image: Projections Graphiquesenter image description here

La commande glOrtho produit une projection "oblique" que vous voyez dans la ligne du bas. Peu importe la distance qui sépare les vertex dans la direction z, ils ne reculeront pas dans la distance.

J'utilise glOrtho chaque fois que je dois créer des graphiques 2D sous OpenGL (barres de santé, menus, etc.) à l'aide du code suivant chaque fois que la fenêtre est redimensionnée:

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

Cela remappera les coordonnées OpenGL en valeurs de pixels équivalentes (X allant de 0 à windowWidth et Y allant de 0 à windowHeight). Notez que j'ai inversé les valeurs Y car les coordonnées OpenGL partent du coin inférieur gauche de la fenêtre. Donc, en retournant, je reçois un plus conventionnel (0,0) en partant du coin en haut à gauche de la fenêtre.

Notez que les valeurs Z sont coupées de 0 à 1. Soyez donc prudent lorsque vous spécifiez une valeur Z pour la position de votre sommet, elle sera coupée si elle se situe en dehors de cette plage. Sinon, si elle se situe à l'intérieur de cette plage, cela semblera n'avoir aucun effet sur la position, à l'exception des tests Z.

136
Mikepote

Exemple minimal exécutable

glOrtho: les jeux 2D, les objets proches et lointains apparaissent de la même taille:

enter image description here

glFrustrum: plus réel que la 3D, les objets identiques plus éloignés paraissent plus petits:

enter image description here

principal 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 en amont .

Compiler:

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

Courir avec glOrtho:

./main 1

Courir avec glFrustrum:

./main

Testé sur Ubuntu 18.10.

Schéma

Ortho: la caméra est un plan, le volume visible un rectangle:

enter image description here

Frustrum: la caméra est un point, le volume visible une tranche de pyramide:

enter image description here

Source de l'image .

Paramètres

Nous cherchons toujours de + z à -z avec + y vers le haut:

glOrtho(left, right, bottom, top, near, far)
  • left: minimum x on voit
  • right: maximum x on voit
  • bottom: minimum y on voit
  • top: maximum y on voit
  • -near: minimum z on voit. Oui, c'est -1 fois near. Donc, une entrée négative signifie positive z.
  • -far: maximum z on voit. Aussi négatif.

Schéma:

Source de l'image .

Comment ça marche sous le capot

En fin de compte, OpenGL "utilise" toujours:

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

Si nous n'utilisons ni glOrtho ni glFrustrum, c'est ce que nous obtenons.

glOrtho et glFrustrum ne sont que des transformations linéaires (multiplication de matrice AKA) telles que:

  • glOrtho: prend un rectangle 3D donné dans le cube par défaut
  • glFrustrum: prend une section pyramidale donnée dans le cube par défaut

Cette transformation est ensuite appliquée à tous les sommets. C'est ce que je veux dire en 2D:

Source de l'image .

La dernière étape après la transformation est simple:

  • supprimer tous les points en dehors du cube (culling): assurez-vous simplement que x, y et z sont dans [-1, +1]
  • ignorer le composant z et ne prendre que x et y, qui peuvent maintenant être placés dans un écran 2D

Avec glOrtho, z est ignoré, vous pouvez donc toujours utiliser 0.

Une des raisons pour lesquelles vous voudrez peut-être utiliser z != 0 est de faire en sorte que les sprites cachent l’arrière-plan avec le tampon de profondeur.

Déprécation

glOrtho est obsolète à compter de OpenGL 4.5 : le profil de compatibilité 12.1. "TRANSFORMATIONS VERTEX À FONCTION FIXE" est en rouge.

Donc, ne l'utilisez pas pour la production. Dans tous les cas, le comprendre est un bon moyen d’obtenir un aperçu d’OpenGL.

Les programmes OpenGL 4 modernes calculent la matrice de transformation (ce qui est petit) sur la CPU, puis attribuent la matrice et tous les points à transformer à OpenGL, ce qui permet de réaliser très rapidement, en parallèle, des milliers de multiplications de matrice pour différents points.

Ecrivez manuellement vertex shaders puis faites la multiplication de manière explicite, généralement avec les types de données vectoriels pratiques du langage de tramage OpenGL.

Puisque vous écrivez le shader de manière explicite, cela vous permet d’ajuster l’algorithme à vos besoins. Une telle souplesse est une caractéristique majeure des GPU plus modernes, qui, contrairement aux anciens qui utilisaient un algorithme fixe avec certains paramètres d'entrée, peuvent désormais effectuer des calculs arbitraires. Voir aussi: https://stackoverflow.com/a/36211337/895245

Avec un GLfloat transform[] cela ressemblerait à quelque chose comme ça:

#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 en amont .

Sortie:

enter image description here

La matrice pour glOrtho est très simple, composée uniquement de mise à l'échelle et de traduction:

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

comme mentionné dans le OpenGL 2 docs .

La glFrustum matrice n'est pas trop difficile à calculer à la main, mais commence à devenir ennuyeuse. Notez comment frustum ne peut pas être compensé par une simple mise à l'échelle et des traductions comme glOrtho, plus d'informations sur: https://gamedev.stackexchange.com/a/118848/25171

La bibliothèque mathématique GLM OpenGL C++ est un choix courant pour calculer de telles matrices. http://glm.g-truc.net/0.9.2/api/a00245.html documente à la fois une opération ortho et frustum.

glOrtho décrit une transformation qui produit une projection parallèle. La matrice actuelle (voir glMatrixMode) est multipliée par cette matrice et le résultat remplace la matrice actuelle, comme si glMultMatrix était appelée avec la matrice suivante comme argument:

documentation OpenGL (mon gras)

Les nombres définissent les emplacements des plans de découpage (gauche, droite, bas, haut, proche et éloigné).

La projection "normale" est une projection en perspective qui fournit l'illusion de profondeur. Wikipedia définit une projection parallèle comme:

Les projections parallèles ont des lignes de projection parallèles à la fois dans la réalité et dans le plan de projection.

La projection parallèle correspond à une projection en perspective avec un point de vue hypothétique, par exemple un point où la caméra se trouve à une distance infinie de l’objet et possède une distance focale infinie, ou "zoom".

4
ChrisF