8.1 Projeção ortográfica
Por padrão, o pipeline do OpenGL produz uma projeção ortográfica das primitivas contidas no volume de visão, como se as coordenadas de todos os pontos dentro do volume de visão fossem descartadas para formar figuras no plano de imagem.
Lembre-se que o volume de visão é um cubo centralizado na origem no espaço normalizado do dispositivo (NDC). Logo antes da rasterização, o pipeline converte automaticamente as coordenadas do NDC para o espaço da janela, em pixels.
Na configuração padrão de glViewport(x, y, w, h)
e glDepthRange(n, f)
, o canto inferior esquerdo da janela é a origem do espaço da janela (), e o canto superior direito é a coordenada onde e correspondem respectivamente à largura e altura da janela em pixels29. Assim, o seguinte mapeamento é feito internamente pelo pipeline:
- em NDC torna-se no espaço da janela.
- em NDC torna-se no espaço da janela.
- em NDC torna-se no espaço da janela.
O mapeamento pode ser representado pela seguinte matriz de viewport:
Por padrão, e (configuração padrão de glDepthRange
).
Após o mapeamento para o espaço da janela, as primitivas são rasterizadas. Se mais de um fragmento for mapeado para o mesmo pixel no framebuffer, o teste de profundidade pode ser utilizado para manter apenas o fragmento de menor valor .
A figura 8.5 mostra um exemplo no qual o espaço NDC contém 8 cubos posicionados em , alinhados aos eixos principais. Após a rasterização com o teste de profundidade habilitado na configuração padrão, o conteúdo rasterizado no espaço da janela exibirá apenas a face da frente dos 4 cubos de menor valor , como ocorreria numa projeção ortográfica sobre o plano em NDC. A figura também mostra que a origem do espaço NDC () é mapeada para o centro da janela.
Figura 8.5: Objetos em NDC e conteúdo correspondente no espaço da janela.
Uma vez que o pipeline do OpenGL usa a projeção ortográfica como padrão, podemos supor inicialmente que nossa matriz de projeção é a matriz identidade, isto é, nenhuma transformação adicional precisa ser feita para produzir a projeção ortográfica. Assim, no vertex shader, as coordenadas serão modificadas apenas pelas matrizes de modelo e visão (), gerando pontos no espaço da câmera. Relembre que, no espaço da câmera, a posição da câmera é o ponto de referência do frame, e a direção de visão é a direção do eixo negativo.
A matriz de projeção é responsável por converter coordenadas do espaço da câmera para o espaço de recorte. Entretanto, como estamos supondo que a matriz de projeção é a matriz identidade, o espaço de recorte é, neste caso, idêntico ao espaço da câmera. Então, os pontos no espaço da câmera podem ser enviados diretamente à variável embutida gl_Position
.
Internamente, o pipeline supõe que, no espaço de recorte, todas as primitivas com coordenadas menores que e maiores que devem ser recortadas. Uma vez que para todos os pontos, são recortadas todas as primitivas que estiverem fora do cubo que vai de até . Após o recorte, as coordenadas são divididas por para converter coordenadas do espaço de recorte para coordenadas normalizadas do dispositivo. Mas, como , as coordenadas continuam com o mesmo valor. Então, neste caso, o espaço NDC é idêntico ao espaço de recorte projetado, que por sua vez é idêntico ao espaço da câmera.
Há um problema em usar a matriz identidade como matriz de projeção: consideramos até agora que os modelos geométricos são representados em um sistema que segue a regra da mão direita. Entretanto, as coordenadas normalizadas do dispositivo seguem a regra da mão esquerda. Isso faz com que orientação dos triângulos fique invertida (CW vira CCW e vice-versa). Felizmente, é fácil construir uma transformação que converte as coordenadas para a regra da mão direita: basta negarmos a coordenada de cada ponto. Essa transformação pode ser feita por uma matriz de projeção ligeiramente diferente de uma matriz identidade:
Agora, no vertex shader, gl_Position
receberá os pontos transformados por , onde .
Com a matriz conseguimos consertar a inversão de orientação das primitivas. No entanto, há ainda outro problema: como a câmera está na origem em NDC, só conseguimos enxergar na tela a geometria que estiver contida no cubo de tamanho 2 em torno da câmera (isto é, a geometria após o recorte). Esse tamanho é muito limitante para a maioria das cenas. Além disso, a posição da câmera no centro do cubo não parece ser algo muito intuitivo. Na câmera LookAt, a direção de visão é a direção de negativo. Logo, não deveríamos ser capazes de enxergar algo que está com positivo (isto é, atrás da câmera, ainda que dentro do cubo de tamanho de 2). Para resolver isso, vamos criar uma nova matriz de projeção que supõe que o volume de visão está sempre situado em algum lugar do espaço da câmera com , como ilustra a figura 8.6.
Figura 8.6: Volume de visão genérico para projeção ortográfica.
Nessa figura, o lado mais perto do volume de visão está a uma distância da câmera, medida ao longo de sua linha de visão (eixo negativo). Esse lado mais próximo em relação à posição da câmera é chamado de plano de recorte próximo ou near clipping plane. O lado mais distante do volume de visão está a uma distância da câmera, e é chamado de plano de recorte distante ou far clipping plane.
Na definição desse novo volume de visão, usaremos parâmetros (left), (right), (bottom), (top) para especificar a posição dos lados esquerdo e direito, de baixo e de cima do volume. Desse modo, o volume não precisará ser mais um cubo de tamanho 2 em cada direção. Podemos obter essa configuração através da modificação da matriz de projeção.
É interessante notar que a matriz de projeção ortográfica é simplesmente uma matriz que transforma o volume de visão, do espaço da câmera, para o volume de visão em NDC, como mostra a figura 8.7.
Figura 8.7: A matriz de projeção ortográfica representa a transformação do volume de visão do espaço da câmera para o volume de visão em NDC.
Esse mapeamento da transformação de projeção consiste em fazer com que os pontos e no espaço da câmera tornem-se respectivamente os pontos e em NDC. Tal processo é chamado de normalização do volume de visão. Podemos fazer a normalização em três etapas:
- Translação do volume de visão de modo a centralizá-lo na origem.
- Escala do volume de visão de modo a deixá-lo com tamanho 2 em cada direção.
- Reflexão para inverter a coordenada .
Como a reflexão é uma escala com inversão de sinal, as etapas 2 e 3 podem ser feitas em conjunto, como veremos a seguir.
Translação
O centroide do volume de visão no espaço da câmera é
Logo, a matriz de translação que desloca o volume de visão para a origem é a matriz de translação por :
Escala e reflexão
Os fatores de escala para redimensionar o volume de visão em um cubo com tamanho 2 em cada direção são:
Entretanto, precisamos refletir o cubo na direção para a conversão da regra da mão direita para mão esquerda. Assim, precisamos inverter o sinal de :
A matriz de escala ficará como a seguir:
Matriz de projeção
Concatenando as transformações de translação, escala e reflexão, obtemos a nova matriz de projeção ortográfica:
Na biblioteca GLM, tal matriz pode ser criada com a função glm::ortho
, definida em glm/gtc/matrix_transform.hpp
:
float left, float right, float bottom, float top, float zNear, float zFar);
glm::mat4 glm::ortho(double left, double right, double bottom, double top, double zNear, double zFar); glm::dmat4 glm::ortho(
onde left
, right
, bottom
, top
, zNear
e zFar
correspondem respectivamente aos valores , , , , e .
Na maioria das aplicações, trabalhamos com volumes simétricos nas direções e . Nesse caso, os pontos em NDC são projetados no centro do viewport. Em um volume simétrico,
Com isso, os termos da matriz anterior podem ser simplificados como segue:
e a matriz assume o fomato
A configuração padrão é a configuração utilizada em todos os projetos da ABCg feitos até agora.↩︎