Wie verwende ich glOrtho () in OpenGL?

85

Ich kann die Verwendung von nicht verstehen glOrtho. Kann jemand erklären, wofür es verwendet wird?

Wird es verwendet, um den Bereich der Grenzwerte für xy- und z-Koordinaten festzulegen?

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

Dies bedeutet, dass der x-, y- und z-Bereich von -1 bis 1?

ufk
quelle
1
Dieses Video hat mir sehr geholfen.
ViniciusArruda

Antworten:

150

Schauen Sie sich dieses Bild an: Grafische Projektionen Geben Sie hier die Bildbeschreibung ein

Der glOrthoBefehl erzeugt eine "schräge" Projektion, die Sie in der unteren Reihe sehen. Unabhängig davon, wie weit Scheitelpunkte in z-Richtung entfernt sind, treten sie nicht in die Ferne zurück.

Ich verwende glOrtho jedes Mal, wenn ich 2D-Grafiken in OpenGL erstellen muss (z. B. Integritätsleisten, Menüs usw.), und verwende bei jeder Größenänderung des Fensters den folgenden Code:

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

Dadurch werden die OpenGL-Koordinaten den entsprechenden Pixelwerten zugeordnet (X geht von 0 nach windowWidth und Y geht von 0 nach windowHeight). Beachten Sie, dass ich die Y-Werte umgedreht habe, da OpenGL-Koordinaten in der unteren linken Ecke des Fensters beginnen. Durch Umdrehen erhalte ich also eine konventionellere (0,0), die eher in der oberen linken Ecke des Fensters beginnt.

Beachten Sie, dass die Z-Werte von 0 bis 1 abgeschnitten werden. Wenn Sie also einen Z-Wert für die Position Ihres Scheitelpunkts angeben, wird dieser abgeschnitten, wenn er außerhalb dieses Bereichs liegt. Andernfalls scheint es, wenn es innerhalb dieses Bereichs liegt, keine Auswirkung auf die Position zu haben, außer bei Z-Tests.

Mikepote
quelle
89
Oh mein Gott, ich liebe dich. Haben Sie eine Idee, wie lange es dauert, diese einzelne Codezeile online zu finden / herauszufinden? Danke, ich werde mein erstgeborenes Kind dafür nach dir
benennen
2
Hinweis: (unter Android) Selbst wenn das Modell nur negative z-Werte aufweist, scheint es erforderlich zu sein, einen positiven Wert für den letzten (Fern-) Parameter zu haben. Ich habe einen einfachen Dreieckstest (mit deaktiviertem Keulen) mit Eckpunkten bei durchgeführt z= -2. Das Dreieck war unsichtbar , wenn ich verwende glOrtho(.., 0.0f, -4.0f);, ..-1.0f, -3.0f)oder ..-3.0f, -1.0f). Um sichtbar zu sein, musste der Parameter far POSITIVE 2 oder höher sein. Es schien nicht wichtig zu sein, was der Near-Parameter war. Jedes dieser Programme gearbeitet: ..0.0f, 2.0f), ..-1.0f, 2.0f), ..-3.0f, 2.0f), oder ..0.0f, 1000.0f.
ToolmakerSteve
9
Es ist lächerlich, wie viele schlechte Tutorials es auf OpenGl gibt.
Basickarl
1
@ Kari, hoffe dieser Link könnte helfen. > learnopengl.com/#!In-Practice/2D-Game/Rendering-Sprites
huahsin68
1
@mgouin Der Z-Bereich gibt an, wo sich Ihre Z-Nah-Ebene und Ihre Z-Fern-Ebene befinden. Wenn Sie Ihre Geometrie zeichnen, müssen sich die Z-Werte innerhalb der beiden Z-Ebenen befinden. Wenn sie außerhalb der Z-Ebenen liegen, wird Ihre Geometrie nicht gerendert. Außerdem hat Ihr Renderer nur eine bestimmte Auflösung für die Tiefe. Wenn Sie Ihre entfernte Ebene auf 1000 Einheiten eingestellt haben und versuchen, ein winziges Modell mit kleinen Gesichtern zu zeichnen, die 0,1 Einheiten voneinander entfernt sind, kann OpenGL Ihnen nicht die Tiefenauflösung geben, die Sie benötigen, und Sie werden Z-Fighting (Flackern) bekommen. zwischen den Gesichtern.
Mikepote
51

Minimal lauffähiges Beispiel

glOrtho: 2D-Spiele, Objekte in der Nähe und in der Ferne haben dieselbe Größe:

Geben Sie hier die Bildbeschreibung ein

glFrustrum: realistischer wie 3D erscheinen identische Objekte weiter entfernt kleiner:

Geben Sie hier die Bildbeschreibung ein

Haupt 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 stromaufwärts .

Kompilieren:

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

Laufen Sie mit glOrtho:

./main 1

Laufen Sie mit glFrustrum:

./main

Getestet unter Ubuntu 18.10.

Schema

Ortho: Kamera ist eine Ebene, sichtbares Volumen ein Rechteck:

Geben Sie hier die Bildbeschreibung ein

Frustrum: Kamera ist ein Punkt, sichtbares Volumen ein Stück einer Pyramide:

Geben Sie hier die Bildbeschreibung ein

Bildquelle .

Parameter

Wir schauen immer von + z nach -z mit + y nach oben:

glOrtho(left, right, bottom, top, near, far)
  • left: Minimum sehen xwir
  • right: Maximum sehen xwir
  • bottom: Minimum sehen ywir
  • top: Maximum sehen ywir
  • -near: Minimum sehen zwir. Ja , das ist -1mal near. Ein negativer Eingang bedeutet also positiv z.
  • -far: Maximum sehen zwir. Auch negativ.

Schema:

Bildquelle .

Wie es unter der Haube funktioniert

Am Ende "verwendet" OpenGL immer:

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

Wenn wir weder glOrthonoch verwenden glFrustrum, bekommen wir das.

glOrthound glFrustrumsind nur lineare Transformationen (AKA-Matrixmultiplikation), so dass:

  • glOrtho: Nimmt ein bestimmtes 3D-Rechteck in den Standardwürfel auf
  • glFrustrum: nimmt einen bestimmten Pyramidenabschnitt in den Standardwürfel auf

Diese Transformation wird dann auf alle Scheitelpunkte angewendet. Das meine ich in 2D:

Bildquelle .

Der letzte Schritt nach der Transformation ist einfach:

  • entfernen Sie alle Punkte außerhalb des Würfels (Culling): Stellen Sie sicher , dass nur x, yund zsind in[-1, +1]
  • ignoriere die zKomponente und nimm nur xund y, das jetzt in einen 2D-Bildschirm eingefügt werden kann

Mit glOrtho, zignoriert wird, so könnten Sie auch immer verwenden 0.

Ein Grund, den Sie möglicherweise verwenden möchten, z != 0besteht darin, dass Sprites den Hintergrund mit dem Tiefenpuffer ausblenden.

Missbilligung

glOrthoist ab OpenGL 4.5 veraltet : das Kompatibilitätsprofil 12.1. "VERTEX-TRANSFORMATIONEN MIT FESTFUNKTION" ist rot.

Verwenden Sie es also nicht für die Produktion. In jedem Fall ist das Verstehen ein guter Weg, um OpenGL-Einblicke zu erhalten.

Moderne OpenGL 4-Programme berechnen die Transformationsmatrix (die klein ist) auf der CPU und geben dann die Matrix und alle zu transformierenden Punkte in OpenGL an, wodurch die Tausenden von Matrixmultiplikationen für verschiedene Punkte sehr schnell parallel ausgeführt werden können.

Manuell geschriebene Vertex-Shader führen dann die Multiplikation explizit durch, normalerweise mit den praktischen Vektordatentypen der OpenGL Shading Language.

Da Sie den Shader explizit schreiben, können Sie den Algorithmus an Ihre Bedürfnisse anpassen. Diese Flexibilität ist ein Hauptmerkmal moderner GPUs, die im Gegensatz zu den alten, die einen festen Algorithmus mit einigen Eingabeparametern verwendet haben, jetzt beliebige Berechnungen durchführen können. Siehe auch: https://stackoverflow.com/a/36211337/895245

Mit einem expliziten GLfloat transform[]würde es ungefähr so ​​aussehen:

#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 stromaufwärts .

Ausgabe:

Geben Sie hier die Bildbeschreibung ein

Die Matrix für glOrthoist wirklich einfach und besteht nur aus Skalierung und Übersetzung:

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

wie in den OpenGL 2-Dokumenten erwähnt .

Die glFrustumMatrix ist auch nicht allzu schwer von Hand zu berechnen, wird aber nervig. Beachten Sie, dass Kegelstumpf nicht nur mit Skalierung und Übersetzungen wie möglich hergestellt werden glOrthokann. Weitere Informationen finden Sie unter: https://gamedev.stackexchange.com/a/118848/25171

Die GLM OpenGL C ++ - Mathematikbibliothek ist eine beliebte Wahl für die Berechnung solcher Matrizen. http://glm.g-truc.net/0.9.2/api/a00245.html dokumentiert sowohl eine orthoals auch frustumOperationen.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
1
"Was soll stattdessen verwendet werden?" - Erstellen Sie Ihre eigenen Matrizen und weisen Sie sie direkt zu.
Kromster
4

glOrtho beschreibt eine Transformation, die eine parallele Projektion erzeugt. Die aktuelle Matrix (siehe glMatrixMode) wird mit dieser Matrix multipliziert und das Ergebnis ersetzt die aktuelle Matrix, als ob glMultMatrix mit der folgenden Matrix als Argument aufgerufen würde:

OpenGL-Dokumentation (fett gedruckt)

Die Zahlen definieren die Positionen der Schnittebenen (links, rechts, unten, oben, nah und fern).

Die "normale" Projektion ist eine perspektivische Projektion, die die Illusion von Tiefe vermittelt. Wikipedia definiert eine Parallelprojektion als:

Parallele Projektionen haben Projektionslinien, die sowohl in der Realität als auch in der Projektionsebene parallel sind.

Die parallele Projektion entspricht einer perspektivischen Projektion mit einem hypothetischen Blickwinkel, z. B. einer, bei der die Kamera unendlich weit vom Objekt entfernt liegt und eine unendliche Brennweite oder einen "Zoom" aufweist.

ChrisF
quelle
Hallo danke für die Info. Ich konnte den Unterschied zwischen paralleler und perspektivischer Projektion nicht ganz verstehen. Ich googelte ein bisschen und fand die Antwort in wiki.answers.com/Q/…
ufk
6
Leider sind die Informationen, die Sie von answers.com erhalten haben, ziemlich wertlos. Eine isometrische Ansicht ist beispielsweise sehr dreidimensional, es handelt sich jedoch um eine parallele Projektion ohne Perspektive. Siehe hier, und es gibt auch Links zu vielen anderen Beispielen für Projektionen: en.wikipedia.org/wiki/Isometric_projection
Ben Voigt