Comment utiliser glOrtho () dans OpenGL?

87

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

Est-il utilisé pour définir la plage de limites des coordonnées xy et z?

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

Cela signifie que la plage x, y et z va de -1 à 1?

ufk
la source
1
Cette vidéo m'a beaucoup aidé.
ViniciusArruda le

Réponses:

153

Jetez un œil à cette image: Projections graphiques entrez la description de l'image ici

La glOrthocommande produit une projection "Oblique" que vous voyez dans la rangée du bas. Quelle que soit la distance entre les sommets dans la direction z, ils ne reculeront pas dans la distance.

J'utilise glOrtho chaque fois que j'ai besoin de faire des graphiques 2D dans OpenGL (tels que les barres de santé, les menus, etc.) en utilisant le 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 dans les 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, j'obtiens plutôt un (0,0) plus conventionnel en partant du coin supérieur 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 tombe en dehors de cette plage. Sinon, s'il se trouve dans cette plage, il semblera n'avoir aucun effet sur la position, sauf pour les tests Z.

Mikepote
la source
90
oh mon dieu, je t'aime. Avez-vous une idée du temps qu'il faut pour trouver / comprendre cette seule ligne de code en ligne? Merci, je vais nommer mon premier enfant après vous pour cela
karpathy
2
Remarque: (sur Android) même si le modèle n'a que des valeurs z négatives, il semble nécessaire d'avoir une valeur positive pour le paramètre final (far). J'ai fait un simple test de triangle (avec sélection désactivée), avec des sommets à z= -2. Le triangle est invisible si je glOrtho(.., 0.0f, -4.0f);, ..-1.0f, -3.0f)ou ..-3.0f, -1.0f). Pour être visible, le paramètre far devait être POSITIF 2 ou supérieur; il ne semblait pas important quel était le paramètre proche. Chacune de ces travaillé: ..0.0f, 2.0f), ..-1.0f, 2.0f), ..-3.0f, 2.0f)ou ..0.0f, 1000.0f.
ToolmakerSteve
9
C'est ridicule la quantité de mauvais tutoriels sur OpenGl.
basickarl
1
@Kari, j'espère que ce lien pourrait vous aider. > learnopengl.com/#!In-Practice/2D-Game/Rendering-Sprites
huahsin68
1
@mgouin La plage z spécifie où se trouvent votre plan Z-proche et votre plan Z-far. Lorsque vous dessinez votre géométrie, ses valeurs Z doivent être à l'intérieur des deux plans Z. S'ils tombent en dehors des plans Z, votre géométrie ne sera pas rendue. De plus, votre moteur de rendu n'a qu'une certaine résolution pour la profondeur. Si votre plan éloigné est réglé à 1000 unités et que vous essayez de dessiner un minuscule modèle avec de petits visages à 0,1 unité l'un de l'autre, OpenGL ne pourra pas vous donner la résolution de profondeur dont vous avez besoin et vous obtiendrez des combats en Z (scintillement). entre les visages.
Mikepote
54

Exemple exécutable minimal

glOrtho: Les jeux 2D, les objets proches et éloignés ont la même taille:

entrez la description de l'image ici

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

entrez la description de l'image ici

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:

entrez la description de l'image ici

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

entrez la description de l'image ici

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 xon voit
  • right: maximum que xnous voyons
  • bottom: minimum yon voit
  • top: maximum que ynous voyons
  • -near: minimum que znous voyons. Oui , c'est le -1temps near. Donc, une entrée négative signifie positive z.
  • -far: maximum que znous voyons. Aussi négatif.

Schéma:

Source de l'image .

Comment ça marche sous le capot

Au final, OpenGL "utilise" toujours:

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

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

glOrthoet ne glFrustrumsont 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 de pyramide 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 de l' extérieur du cube (culling): il suffit de faire en sorte que x, yet zsont[-1, +1]
  • ignorer le zcomposant et ne prendre que xet y, qui peut maintenant être placé dans un écran 2D

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

Une des raisons que vous pourriez vouloir utiliser z != 0est de faire en sorte que les sprites masquent l'arrière-plan avec le tampon de profondeur.

Désapprobation

glOrthoest obsolète à partir d' OpenGL 4.5 : le profil de compatibilité 12.1. «TRANSFORMATIONS VERTEX À FONCTION FIXE» est en rouge.

Alors 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 modernes d'OpenGL 4 calculent la matrice de transformation (qui est petite) sur le CPU, puis donnent la matrice et tous les points à transformer en OpenGL, qui peut faire les milliers de multiplications de matrice pour différents points très rapidement en parallèle.

Les nuanceurs de vertex écrits manuellement effectuent ensuite la multiplication explicitement, généralement avec les types de données vectorielles pratiques du langage OpenGL Shading.

Puisque vous écrivez explicitement le shader, cela vous permet d'ajuster l'algorithme à vos besoins. Une telle flexibilité 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 explicite, GLfloat transform[]cela ressemblerait à quelque chose comme ceci:

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

Production:

entrez la description de l'image ici

La matrice pour glOrthoest vraiment simple, composée uniquement de mise à l'échelle et de translation:

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

comme mentionné dans la documentation OpenGL 2 .

La glFrustummatrice n'est pas trop difficile à calculer à la main non plus, mais commence à devenir ennuyeuse. Notez comment le frustum ne peut pas être composé uniquement avec une 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 populaire pour le calcul de telles matrices. http://glm.g-truc.net/0.9.2/api/a00245.html documente à la fois les opérations orthoet frustum.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
1
"que faut-il utiliser à la place?" - construisez vos propres matrices et attribuez-les directement.
Kromster
4

glOrtho décrit une transformation qui produit une projection parallèle . La matrice courante (voir glMatrixMode) est multipliée par cette matrice et le résultat remplace la matrice courante, 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étourage (gauche, droite, bas, haut, proche et éloigné).

La projection «normale» est une projection en perspective qui donne l'illusion de la 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, celui où la caméra se trouve à une distance infinie de l'objet et a une distance focale infinie, ou «zoom».

ChrisF
la source
Salut, merci pour l'info. Je ne pouvais pas tout à fait comprendre la différence entre la projection parallèle et la projection en perspective. J'ai cherché un peu sur Google
j'ai
6
Malheureusement, les informations que vous avez obtenues de answers.com sont sans valeur. Une vue isométrique, par exemple, est très 3D, mais c'est une projection parallèle sans perspective. Voir ici, et il y a aussi des liens vers de nombreux autres exemples de projections: en.wikipedia.org/wiki/Isometric_projection
Ben Voigt