Como usar glOrtho () em OpenGL?

87

Não consigo entender o uso de glOrtho. Alguém pode explicar para que é usado?

É usado para definir o intervalo do limite das coordenadas xy e z?

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

Isso significa que o intervalo x, y e z é de -1 a 1?

ufk
fonte
1
Esse vídeo me ajudou muito.
ViniciusArruda de

Respostas:

153

Dê uma olhada nesta imagem: Projeções Gráficas insira a descrição da imagem aqui

O glOrthocomando produz uma projeção "Oblíqua" que você vê na linha inferior. Não importa a distância que os vértices estejam na direção z, eles não se distanciarão.

Eu uso glOrtho sempre que preciso fazer gráficos 2D em OpenGL (como barras de saúde, menus etc.) usando o seguinte código sempre que a janela é redimensionada:

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

Isso irá remapear as coordenadas OpenGL nos valores de pixel equivalentes (X indo de 0 para windowWidth e Y indo de 0 para windowHeight). Observe que inverti os valores Y porque as coordenadas OpenGL começam no canto inferior esquerdo da janela. Assim, ao virar, obtenho um (0,0) mais convencional começando no canto superior esquerdo da janela.

Observe que os valores Z são recortados de 0 a 1. Portanto, tome cuidado ao especificar um valor Z para a posição do seu vértice, ele será recortado se ficar fora desse intervalo. Caso contrário, se estiver dentro desse intervalo, parecerá não ter efeito sobre a posição, exceto para testes Z.

Mikepote
fonte
90
oh meu deus EU TE AMO. Você tem ideia de quanto tempo leva para encontrar / descobrir essa única linha de código online? Obrigado, devo nomear meu primeiro filho com seu nome
karpathy
2
Nota: (no Android) mesmo que o modelo tenha apenas valores z negativos, parece ser necessário ter um valor positivo para o parâmetro final (distante). Fiz um teste de triângulo simples (com seleção desativada), com vértices em z= -2. O triângulo era invisível se eu usei glOrtho(.., 0.0f, -4.0f);, ..-1.0f, -3.0f)ou ..-3.0f, -1.0f). Para ser visível, o parâmetro distante precisava ser POSITIVO 2 ou maior; não parecia importar qual era o parâmetro próximo. Qualquer um destes trabalhou: ..0.0f, 2.0f), ..-1.0f, 2.0f), ..-3.0f, 2.0f), ou ..0.0f, 1000.0f.
Toolmaker Steve
9
É ridículo a quantidade de tutoriais ruins sobre OpenGl que existem.
basickarl
1
@Kari, espero que este link possa ajudar. > learnopengl.com/#!In-Practice/2D-Game/Rendering-Sprites
huahsin68
1
@mgouin O intervalo z especifica onde o seu plano Z-próximo e o seu plano Z-distante estão. Quando você desenha sua geometria, seus valores Z devem estar dentro dos dois planos Z. Se eles ficarem fora dos planos Z, sua geometria não será renderizada. Além disso, seu renderizador só tem uma determinada resolução de profundidade. Se você tiver seu plano distante definido para 1000 unidades de distância e tentar desenhar um modelo minúsculo com rostos pequenos a 0,1 unidades de distância um do outro, o OpenGL não será capaz de fornecer a resolução de profundidade que você precisa e você obterá Z-fighting (piscando) entre os rostos.
Mikepote
54

Exemplo de execução mínima

glOrtho: Jogos 2D, objetos próximos e distantes parecem do mesmo tamanho:

insira a descrição da imagem aqui

glFrustrum: mais real como 3D, objetos idênticos mais distantes parecem menores:

insira a descrição da imagem aqui

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 .

Compilar:

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

Executar com glOrtho:

./main 1

Executar com glFrustrum:

./main

Testado em Ubuntu 18.10.

Esquema

Ortho: a câmera é um plano, o volume visível um retângulo:

insira a descrição da imagem aqui

Frustrum: a câmera é um ponto, volume visível uma fatia de uma pirâmide:

insira a descrição da imagem aqui

Fonte da imagem .

Parâmetros

Estamos sempre olhando de + z a -z com + y para cima:

glOrtho(left, right, bottom, top, near, far)
  • left: mínimo xque vemos
  • right: máximo xque vemos
  • bottom: mínimo yque vemos
  • top: máximo yque vemos
  • -near: mínimo zque vemos. Sim , esta é a -1hora near. Portanto, uma entrada negativa significa positiva z.
  • -far: máximo zque vemos. Também negativo.

Esquema:

Fonte da imagem .

Como funciona nos bastidores

No final das contas, OpenGL sempre "usa":

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

Se não usarmos glOrthonem glFrustrum, é isso que obtemos.

glOrthoe glFrustrumsão apenas transformações lineares (também conhecida como multiplicação de matriz), de modo que:

  • glOrtho: leva um determinado retângulo 3D para o cubo padrão
  • glFrustrum: leva uma determinada seção da pirâmide para o cubo padrão

Essa transformação é então aplicada a todos os vértices. Isso é o que quero dizer em 2D:

Fonte da imagem .

A etapa final após a transformação é simples:

  • remova quaisquer pontos fora do cubo (abate): apenas certifique-se de que x, ye zestão dentro[-1, +1]
  • ignore o zcomponente e leve apenas xe y, que agora pode ser colocado em uma tela 2D

Com glOrtho, zé ignorado, então você pode sempre usar 0.

Uma razão que você pode querer usar z != 0é fazer com que os sprites ocultem o fundo com o buffer de profundidade.

Suspensão de uso

glOrthoestá obsoleto a partir do OpenGL 4.5 : o perfil de compatibilidade 12.1. "TRANSFORMAÇÕES DE VERTEX COM FUNÇÕES FIXAS" está a vermelho.

Portanto, não o use para produção. Em qualquer caso, entendê-lo é uma boa maneira de obter alguns insights do OpenGL.

Os programas OpenGL 4 modernos calculam a matriz de transformação (que é pequena) na CPU e, em seguida, fornecem a matriz e todos os pontos a serem transformados em OpenGL, que pode fazer milhares de multiplicações de matrizes para diferentes pontos em paralelo muito rápido.

Os sombreadores de vértice escritos manualmente fazem a multiplicação explicitamente, geralmente com os tipos de dados vetoriais convenientes da OpenGL Shading Language.

Como você escreve o sombreador explicitamente, isso permite ajustar o algoritmo de acordo com suas necessidades. Essa flexibilidade é uma característica importante das GPUs mais modernas, que ao contrário das antigas que faziam um algoritmo fixo com alguns parâmetros de entrada, agora podem fazer cálculos arbitrários. Veja também: https://stackoverflow.com/a/36211337/895245

Com um explícito GLfloat transform[], seria algo assim:

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

Resultado:

insira a descrição da imagem aqui

A matriz para glOrthoé muito simples, composta apenas de escalonamento e tradução:

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

conforme mencionado nos documentos do OpenGL 2 .

A glFrustummatriz não é muito difícil de calcular manualmente, mas começa a ficar chata. Observe como o frustum não pode ser feito apenas com escalas e traduções como glOrtho, mais informações em: https://gamedev.stackexchange.com/a/118848/25171

A biblioteca matemática GLM OpenGL C ++ é uma escolha popular para calcular tais matrizes. http://glm.g-truc.net/0.9.2/api/a00245.html documenta as operações orthoe frustum.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
1
"o que deve ser usado em vez disso?" - construa suas próprias matrizes e atribua-as diretamente.
Kromster
4

glOrtho descreve uma transformação que produz uma projeção paralela . A matriz atual (consulte glMatrixMode) é multiplicada por esta matriz e o resultado substitui a matriz atual, como se glMultMatrix fosse chamado com a seguinte matriz como seu argumento:

Documentação OpenGL (meu negrito)

Os números definem as localizações dos planos de recorte (esquerda, direita, inferior, superior, próximo e distante).

A projeção "normal" é uma projeção em perspectiva que fornece a ilusão de profundidade. A Wikipedia define uma projeção paralela como:

As projeções paralelas têm linhas de projeção que são paralelas tanto na realidade quanto no plano de projeção.

A projeção paralela corresponde a uma projeção em perspectiva com um ponto de vista hipotético - por exemplo, aquele em que a câmera fica a uma distância infinita do objeto e tem um comprimento focal infinito, ou "zoom".

ChrisF
fonte
Oi, obrigado pela informação. Eu não conseguia entender a diferença entre projeção paralela e em perspectiva. Pesquisei um pouco no Google e encontrei a resposta em wiki.answers.com/Q/…
ufk
6
Infelizmente, as informações que você obteve em answers.com são muito inúteis. Uma vista isométrica, por exemplo, é muito 3-D, mas é uma projeção paralela sem perspectiva. Veja aqui, e também há links para muitos outros exemplos de projeções: en.wikipedia.org/wiki/Isometric_projection
Ben Voigt