Nessa aula veremos como é possível definir uma matriz de transformação que aplique uma “distorção” perspectiva na construção do desenho 3D. Assim como fizemos com transformações afins, essa matriz tem dimensão
Vamos supor, por enquanto, que os pontos a serem transformados estão todos estritamente à frente da câmera. Veremos que os objetos atrás da câmera devem eventualmente ser cortados, mas consideraremos isso mais tarde.
Agora considere a seguinte situação de visualização ilustrada na Figura Fig. 14.1, onde o centro de projeção corresponde à origem do sistema de coordenadas e que, normalmente, corresponde ao sistema de coordenadas da câmera que vai gerar a foto (desenho) da cena 3D. Vimos na aula passada como construir esse sistema usando a função lookAt(eye, at, up)
. Nessa situação a câmera aponta na direção
Fig. 14.1 Projeção perspectiva. No corte à direita, imagine que o eixo
Suponha que estamos projetando pontos em um plano de projeção ortogonal ao eixo
Considere um ponto
.
E portanto o ponto projetado tem coordenada
.
Infelizmente, não existe uma matriz
Uma alternativa é usar uma matriz
.
As coordenadas desse vetor são todas funções lineares de
.
Uma vez que tenhamos calculado as coordenadas de um ponto transformado (afim) usando
.
Observe que caso
Você deve se lembrar que para desenhar em 2D no WebGL, devemos limitar nosso desenho ao quadrado de lado 2 definido pelos cantos (-1, -1) a (1,1). Vértices desenhados fora desse espaço normalizado são automaticamente recortados. Em 3D temos uma limitação semelhante, os objetos visíveis devem estar contidos no cubo de lado 2 definido pelos cantos (-1,-1,-1) a (1,1,1). Esse cubo define o volume de visualização canônico.
Você pode imaginar que seja natural limitar em
A projeção perspectiva juntamente com o problema de determinação da profundidade de cada objeto com relação ao observador e ainda eliminação de pontos fora do volume canônico pode ser feita de forma bastante elegante e eficiente, mas para isso precisamos considerar mais alguns elementos geométricos que definem um volume de visualização genérico na forma de um frustum (tronco de pirâmide) de base retangular, como ilustrado na Figura Fig. 14.2. A definição desse frustum nos ajuda a delimitar (recortar ou clip) a região da cena que deve ser mapeada para o volume de visualização canônico.
Fig. 14.2 Volume de visualização da projeção perspectiva.¶
O frustum é limitado pelos planos near
e far
. Isso significa que o observador (câmera) só consegue ver objetos além do plano near
e até o plano far
. Observe que, como temos um número limitado de bits para representar a profundidade, quanto maior a distância entre esses planos, menor será a precisão disponível para discriminar essas profundidades.
Objetos (ou partes de objetos) fora desse intervalo são “cortados” para fora do volume normalizado (e portanto do desenho). O ápice da pirâmide corresponde ao centro de projeção da câmera (ou olho do observador).
Vamos assumir também que o centro da imagem (ou sensor da câmera) está alinhado com o eixo fovy
(vertical field-of-view), um ângulo que define a altura (ou abertura) vertical da pirâmide, como mostrado na Figura Fig. 14.2.
Observe que a partir dos valores do aspecto
e do fovy
é possível derivar o campo de visão horizontal (ou fovx
).
Assim como fizemos com a função lookAt(eye, at, up)
na aula anterior, vamos ver como construir uma matriz de transformação perspectiva a partir desses parâmetros.
Nessa seção vamos derivar a função perspective(fovy, aspect, near, far)
, que recebe os parâmetros que definem o frustum de visualização com descrito na seção anterior e retorna uma matriz de transformação perspectiva com profundidade que podemos usar para definir a matriz de projeção (ou ao menos parte dela). Se as características de projeção da câmera não se alterarem, essa matriz pode ser calculada uma única vez durante a inicialização do seu programa.
Vamos começar tentando entender o que significa perspectiva com profundidade.
Recode que a transformação perspectiva que descrevemos anteriormente mapeia o ponto
Como vimos, ao invés de mapear todos os pontos para o plano da imagem em
Para isso vamos definir uma matriz que transforma um ponto de coordenadas
Vamos começar de forma simples, considerando, como de costume, que o centro de projeção da câmera corresponde à origem do sistema de coordenadas e que a câmera está voltada para baixo, na mesma direção do eixo
.
Quando
.
Observe que as coordenadas
.
Dependendo dos valores que escolhemos para
De fato, escolhendo
Ao aplicar a transformação perspectiva, todos os pontos no espaço projetivo são transformados. Isso inclui pontos que não estão dentro do frustum de visualização (por exemplo, pontos atrás do observador). Uma das tarefas importantes a serem executadas pelo sistema, antes da divisão de perspectiva (quando todas as coisas ruins podem acontecer) é recortar partes da cena que não estão dentro do frustum.
Podemos ajudar a transformação perspectiva com profundidade de forma elegante (escolhendo valores adequados para
Desejamos portanto que os planos near
e far
sejam mapeados para as faces
Para escolher valores adequados para n
e f
denotam as distâncias para os planos de recorte near
e far
. Com isso temos o seguinte sistema de equações:
e .
Resolvendo esse sistema temos:
e .
Para montar a matriz de transformação perspectiva completa, que você pode utilizar em seus desenhos 3D, considere a razão de aspecto denominada por near
e far
, respectivamente, considerando todos esses valores positivos. A matriz calculada pela função perspective()
pode ser definida como:
.
Assim, essa transformação mapeia um ponto
.
Ficou curioso para saber de onde surgiu esse mapeamento estranho?
Observe que, além dos fatores de escala, isso é muito semelhante à matriz de perspectiva com profundidade fornecida anteriormente (já com os nossos valores de
Para ver que isso funciona, mostraremos que os cantos do frustum genérico são mapeados para os cantos do volume de visualização canônico (e vamos confiar que tudo entre eles se comporta bem). Na Figura Fig. 14.3 mostramos uma vista lateral que mostra apenas o plano
Fig. 14.3 Perspectiva com profundidade e o volume de visualização canônico.¶
Considere um ponto que fica no lado superior da pirâmide (ponto verde na figura acima). Temos near
, teremos near
(ponto verde) são
.
Note que esse ponto corresponde ao canto superior direito do volume de visualização canônico no plano near
(
Da mesma forma, considere um ponto que se encontra na parte inferior do frustum (ponto vermelho na figura). Temos far
, então temos far
(ponto vermelho) são
.
Que corresponde ao canto inferior esquerdo do volume de visualização canônico no plano far
(
Vimos que a projeção perspectiva na verdade é uma transformação não linear que não possui uma forma matricial direta para ser calculada. Mas aplicando os fundamentos de geometria projetiva introduzidos na última aula vimos que é possível construir uma matriz para aplicar a projeção perspectiva e que, após a divisão perspectiva, obtemos as coordenadas projetadas sobre o plano da imagem. Essa transformação no entanto elimina a informação de profundidade dos vértices, dificultando o desenho das partes visíveis e/ou remoção das partes ocultas por oclusão dos objetos que estão mais próximos à câmera.
Para resolver isso, modificamos a matriz de transformação perspectiva para incluir também informação de profundidade que possa ser utilizada para ordenar os objetos segundo sua profundidade com relação à câmera. Para isso, utilizamos um frustum definido por quatro parâmetros: fovy
, a
(razão de aspecto),e os planos near
e far
. Na prática, podemos utilizar a função perspective(fovy, a, near, far)
para calcular a matriz de projeção perspectiva com profundidade.
Na próxima aula, vamos ver alguns exemplos de aplicação dessa matriz no WebGL.