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 z 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 2×2×2 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 (x=y=0), e o canto superior direito é a coordenada (w,h) onde w e h correspondem respectivamente à largura e altura da janela em pixels29. Assim, o seguinte mapeamento é feito internamente pelo pipeline:
- xndc∈[−1,1] em NDC torna-se xw∈[x,w] no espaço da janela.
- yndc∈[−1,1] em NDC torna-se yw∈[y,h] no espaço da janela.
- zndc∈[−1,1] em NDC torna-se zw∈[n,f] no espaço da janela.
O mapeamento pode ser representado pela seguinte matriz de viewport:
[xwywzw]=[w200x+w20h20y+h200f−n2f+n2][xndcyndczndc1].
Por padrão, n=0 e f=1 (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 zw.
A figura 8.5 mostra um exemplo no qual o espaço NDC contém 8 cubos posicionados em (±0.5,±0.5,±0.5), 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 z, como ocorreria numa projeção ortográfica sobre o plano z=−1 em NDC. A figura também mostra que a origem do espaço NDC (Ondc) é 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 (MviewMmodel), 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 z 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 −w e maiores que +w devem ser recortadas. Uma vez que w=1 para todos os pontos, são recortadas todas as primitivas que estiverem fora do cubo que vai de (−1,−1,−1) até (1,1,1). Após o recorte, as coordenadas são divididas por w para converter coordenadas do espaço de recorte para coordenadas normalizadas do dispositivo. Mas, como w=1, 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 z de cada ponto. Essa transformação pode ser feita por uma matriz de projeção ligeiramente diferente de uma matriz identidade:
Morth=[1000010000−100001].
Agora, no vertex shader, gl_Position
receberá os pontos transformados por MprojMviewMview, onde Mproj=Morth.
Com a matriz Morth 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 z negativo. Logo, não deveríamos ser capazes de enxergar algo que está com z 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 z<0, 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 n da câmera, medida ao longo de sua linha de visão (eixo z 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 f 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 l (left), r (right), b (bottom), t (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 (l,b,−n) e (r,t,−f) no espaço da câmera tornem-se respectivamente os pontos (−1,−1,−1) e (1,1,1) 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 z.
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 C=(cx,cy,cz) do volume de visão no espaço da câmera é
cx=r+l2,cy=t+b2,cz=−f+n2.
Logo, a matriz de translação que desloca o volume de visão para a origem é a matriz de translação por −C:
T=[100−r+l2010−t+b2001f+n20001].
Escala e reflexão
Os fatores de escala S=(sx,sy,sz) para redimensionar o volume de visão em um cubo com tamanho 2 em cada direção são:
sx=2r−l,sy=2t−b,sz=2f−n. Entretanto, precisamos refletir o cubo na direção z para a conversão da regra da mão direita para mão esquerda. Assim, precisamos inverter o sinal de sz:
sz=−2f−n. A matriz de escala ficará como a seguir:
S=[2r−l00002t−b0000−2f−n00001].
Matriz de projeção
Concatenando as transformações de translação, escala e reflexão, obtemos a nova matriz de projeção ortográfica:
Morth=ST=[2r−l00002t−b0000−2f−n00001][100−r+l2010−t+b2001f+n20001],Morth=[2r−l00−r+lr−l02t−b0−t+bt−b00−2f−n−f+nf−n0001].
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 l, r, b, t, n e f.
Na maioria das aplicações, trabalhamos com volumes simétricos nas direções x e y. Nesse caso, os pontos (0,0,z) em NDC são projetados no centro do viewport. Em um volume simétrico,
r=−l,t=−b.
Com isso, os termos da matriz anterior podem ser simplificados como segue:
r+l=0,r−l=2r,t+b=0,t−b=2t,
e a matriz assume o fomato
Morth=[1r00001t0000−2f−n−f+nf−n0001].
A configuração padrão é a configuração utilizada em todos os projetos da ABCg feitos até agora.↩︎