Jak używać funkcji glOrtho () w OpenGL?

89

Nie mogę zrozumieć użycia glOrtho. Czy ktoś może wyjaśnić, do czego służy?

Czy służy do ustawiania zakresu limitu współrzędnych xy i z?

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

Oznacza to, że zakres x, yiz wynosi od -1 do 1?

ufk
źródło
1
Ten film bardzo mi pomógł.
ViniciusArruda

Odpowiedzi:

156

Spójrz na ten obrazek: Projekcje graficzne wprowadź opis obrazu tutaj

glOrthoKomenda wywołuje projekcję „ukryte”, które można zobaczyć w dolnym rzędzie. Bez względu na to, jak daleko znajdują się wierzchołki w kierunku z, nie cofną się one w dal.

Używam glOrtho za każdym razem, gdy potrzebuję zrobić grafikę 2D w OpenGL (takie jak paski zdrowia, menu itp.), Używając następującego kodu za każdym razem, gdy zmienia się rozmiar okna:

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

Spowoduje to przemapowanie współrzędnych OpenGL na równoważne wartości pikseli (X od 0 do windowWidth, a Y od 0 do windowHeight). Zwróć uwagę, że odwróciłem wartości Y, ponieważ współrzędne OpenGL zaczynają się od lewego dolnego rogu okna. Tak więc, odwracając, otrzymuję bardziej konwencjonalny (0,0), zaczynając raczej od lewego górnego rogu okna.

Zwróć uwagę, że wartości Z są obcinane od 0 do 1. Dlatego uważaj, kiedy określasz wartość Z dla położenia wierzchołka, zostanie on obcięty, jeśli wykracza poza ten zakres. W przeciwnym razie, jeśli znajduje się w tym zakresie, wydaje się, że nie ma wpływu na pozycję, z wyjątkiem testów Z.

Mikepote
źródło
91
o mój Boże, KOCHAM CIĘ. Czy masz pojęcie, ile czasu zajmuje znalezienie / wymyślenie tej pojedynczej linii kodu online? Dziękuję,
nadam
2
Uwaga: (na Androidzie) nawet jeśli model ma tylko ujemne wartości z, wydaje się konieczne mieć dodatnią wartość dla końcowego (dalekiego) parametru. Zrobiłem prosty test trójkąta (z wyłączonym eliminowaniem), z wierzchołkami na poziomie z= -2. Trójkąt był niewidoczny jeśli użyłem glOrtho(.., 0.0f, -4.0f);, ..-1.0f, -3.0f)albo ..-3.0f, -1.0f). Aby był widoczny, parametr far musiał być DODATNI 2 lub większy; wydawało się, że nie ma znaczenia, jaki był parametr w pobliżu. Każda z nich pracował: ..0.0f, 2.0f), ..-1.0f, 2.0f), ..-3.0f, 2.0f), lub ..0.0f, 1000.0f.
ToolmakerSteve
10
To absurdalne, ile jest złych tutoriali na temat OpenGl.
basickarl
1
@Kari, mam nadzieję, że ten link może pomóc. > learnopengl.com/#!In-Practice/2D-Game/Rendering-Sprites
huahsin68
1
@mgouin Zakres z określa, gdzie znajduje się Twoja płaszczyzna bliska Z i płaszczyzna daleka. Kiedy rysujesz geometrię, jej wartości Z muszą znajdować się wewnątrz dwóch płaszczyzn Z. Jeśli wypadną poza płaszczyzny Z, geometria nie zostanie renderowana. Ponadto twój renderer ma tylko określoną rozdzielczość dla głębi. Jeśli masz odległą płaszczyznę ustawioną na 1000 jednostek i spróbujesz narysować malutki model z małymi twarzami w odległości 0,1 jednostki od siebie, OpenGL nie będzie w stanie zapewnić wymaganej rozdzielczości głębi, a otrzymasz walkę Z (migotanie) między twarzami.
Mikepote
56

Minimalny działający przykład

glOrtho: Gry 2D, obiekty bliskie i dalekie mają ten sam rozmiar:

wprowadź opis obrazu tutaj

glFrustrum: bardziej realistyczne, jak 3D, identyczne obiekty dalej wydają się mniejsze:

wprowadź opis obrazu tutaj

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 .

Skompilować:

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

Uruchom z glOrtho:

./main 1

Uruchom z glFrustrum:

./main

Testowane na Ubuntu 18.10.

Schemat

Orto: kamera to płaszczyzna, widoczna objętość to prostokąt:

wprowadź opis obrazu tutaj

Frustrum: kamera to punkt, widoczna objętość kawałek piramidy:

wprowadź opis obrazu tutaj

Źródło obrazu .

Parametry

Zawsze patrzymy od + z do -z z + y w górę:

glOrtho(left, right, bottom, top, near, far)
  • left: minimum xwidzimy
  • right: maksimum, xktóre widzimy
  • bottom: minimum ywidzimy
  • top: maksimum, yktóre widzimy
  • -near: minimum zwidzimy. Tak , to -1czasy near. Zatem ujemne wejście oznacza pozytywne z.
  • -far: maksimum, zktóre widzimy. Również negatywne.

Schemat:

Źródło obrazu .

Jak to działa pod maską

Ostatecznie OpenGL zawsze „używa”:

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

Jeśli nie użyjemy ani glOrthonie glFrustrum, otrzymamy to.

glOrthoi glFrustrumsą po prostu przekształceniami liniowymi (mnożenie macierzy AKA), takimi, że:

  • glOrtho: pobiera dany prostokąt 3D do domyślnej kostki
  • glFrustrum: przenosi daną sekcję piramidy do domyślnej kostki

Ta transformacja jest następnie stosowana do wszystkich wierzchołków. To właśnie mam na myśli w 2D:

Źródło obrazu .

Ostatni krok po transformacji jest prosty:

  • usunięcia jakichkolwiek punktów poza sześcianu (uboju): po prostu upewnić się, że x, yi zsą w[-1, +1]
  • zignoruj zkomponent i weź tylko xi y, które teraz można umieścić na ekranie 2D

Z glOrtho, zjest ignorowane, więc równie dobrze możesz zawsze używać 0.

Jednym z powodów, dla których możesz chcieć użyć, z != 0jest sprawienie, by duszki ukrywały tło za pomocą bufora głębi.

Dezaprobata

glOrthojest przestarzałe od OpenGL 4.5 : profil zgodności 12.1. „TRANSFORMACJE VERTEX O STAŁEJ FUNKCJI” jest zaznaczone na czerwono.

Więc nie używaj go do produkcji. W każdym razie zrozumienie tego jest dobrym sposobem na uzyskanie wglądu w OpenGL.

Nowoczesne programy OpenGL 4 obliczają macierz transformacji (która jest mała) na CPU, a następnie przekazują macierz i wszystkie punkty do przekształcenia do OpenGL, który może wykonywać tysiące mnożenia macierzy dla różnych punktów naprawdę szybko równolegle.

Następnie ręcznie napisane moduły cieniujące wierzchołków wykonują mnożenie w sposób jawny, zwykle z wygodnymi typami danych wektorowych języka OpenGL Shading Language.

Ponieważ moduł cieniujący piszesz jawnie, pozwala to dostosować algorytm do własnych potrzeb. Taka elastyczność jest główną cechą nowocześniejszych procesorów graficznych, które w przeciwieństwie do starych, które stosowały stały algorytm z niektórymi parametrami wejściowymi, mogą teraz wykonywać dowolne obliczenia. Zobacz też: https://stackoverflow.com/a/36211337/895245

Z wyraźnym GLfloat transform[]wyglądem wyglądałoby to mniej więcej tak:

glfw_transform.c

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#define GLEW_STATIC
#include <GL/glew.h>

#include <GLFW/glfw3.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
};

/* Build and compile shader program, return its ID. */
GLuint common_get_shader_program(
    const char *vertex_shader_source,
    const char *fragment_shader_source
) {
    GLchar *log = NULL;
    GLint log_length, success;
    GLuint fragment_shader, program, vertex_shader;

    /* Vertex shader */
    vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
    glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &log_length);
    log = malloc(log_length);
    if (log_length > 0) {
        glGetShaderInfoLog(vertex_shader, log_length, NULL, log);
        printf("vertex shader log:\n\n%s\n", log);
    }
    if (!success) {
        printf("vertex shader compile error\n");
        exit(EXIT_FAILURE);
    }

    /* Fragment shader */
    fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
    glCompileShader(fragment_shader);
    glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
    glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &log_length);
    if (log_length > 0) {
        log = realloc(log, log_length);
        glGetShaderInfoLog(fragment_shader, log_length, NULL, log);
        printf("fragment shader log:\n\n%s\n", log);
    }
    if (!success) {
        printf("fragment shader compile error\n");
        exit(EXIT_FAILURE);
    }

    /* Link shaders */
    program = glCreateProgram();
    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);
    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
    if (log_length > 0) {
        log = realloc(log, log_length);
        glGetProgramInfoLog(program, log_length, NULL, log);
        printf("shader link log:\n\n%s\n", log);
    }
    if (!success) {
        printf("shader link error");
        exit(EXIT_FAILURE);
    }

    /* Cleanup. */
    free(log);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    return program;
}

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 .

Skompiluj i uruchom:

gcc -ggdb3 -O0 -o glfw_transform.out -std=c99 -Wall -Wextra -pedantic glfw_transform.c -lGL -lGLU -lglut -lGLEW -lglfw -lm
./glfw_transform.out

Wynik:

wprowadź opis obrazu tutaj

Matryca dla glOrthojest naprawdę prosta, składa się tylko ze skalowania i tłumaczenia:

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

jak wspomniano w dokumentacji OpenGL 2 .

glFrustumMatryca nie jest zbyt trudne do obliczenia przez strony albo, ale zaczyna się denerwujące. Zwróć uwagę, że frustum nie można nadrobić tylko ze skalowania i tłumaczeń glOrtho, więcej informacji na: https://gamedev.stackexchange.com/a/118848/25171

Biblioteka matematyczna GLM OpenGL C ++ jest popularnym wyborem do obliczania takich macierzy. http://glm.g-truc.net/0.9.2/api/a00245.html dokumentuje zarówno an, jak orthoi frustumoperacje.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
1
„czego należy użyć zamiast tego?” - skonstruuj własne macierze i przypisz je bezpośrednio.
Kromster
Mam trudności ze skompilowaniem ostatniego przykładu kodu (przekształcanie trójkąta), sklonowałem repozytorium, ale po prostu common.h:19:23: error: ‘TIME_UTC’ undeclared (first use in this function) timespec_get(&ts, TIME_UTC);
pojawia się
1
@Ivanzinho Nie mogłem odtworzyć na Ubuntu 20.04, prawdopodobnie dzieje się tak, ponieważ jest to w C11, którego GCC jeszcze nie implementuje. Ale teraz zminimalizowałem przykład tej odpowiedzi bez wspólnego. H tak jak powinienem był zrobić wcześniej, więc powinno działać :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
4

glOrtho opisuje transformację, która tworzy równoległą projekcję. Bieżąca macierz (patrz glMatrixMode) jest mnożona przez tę macierz, a wynik zastępuje aktualną macierz, tak jakby glMultMatrix zostało wywołane z następującą macierzą jako argumentem:

Dokumentacja OpenGL (moja pogrubienie)

Liczby określają położenie płaszczyzn tnących (lewa, prawa, dół, góra, blisko i daleko).

„Normalna” projekcja to rzut perspektywiczny, który zapewnia iluzję głębi. Wikipedia definiuje równoległe odwzorowanie jako:

Rzuty równoległe mają linie rzutowania, które są równoległe zarówno w rzeczywistości, jak i na płaszczyźnie rzutowania.

Rzutowanie równoległe odpowiada rzutowi perspektywicznemu z hipotetycznym punktem widzenia - np. Takim, w którym kamera znajduje się w nieskończonej odległości od obiektu i ma nieskończoną ogniskową, czyli „zoom”.

ChrisF
źródło
cześć dzięki za informację. Nie mogłem do końca zrozumieć różnicy między rzutowaniem równoległym a perspektywicznym. trochę googlowałem
ufk
6
Niestety informacje, które otrzymałeś z responsów.com, są bezwartościowe. Na przykład widok izometryczny jest bardzo trójwymiarowy, ale jest to rzut równoległy bez perspektywy. Zobacz tutaj, a są tam również linki do wielu innych przykładów projekcji: en.wikipedia.org/wiki/Isometric_projection
Ben Voigt