13. Vistas 3D e projeções

A partir dessa aula vamos discutir como criar vistas (ou imagens) de cenas tridimensionais usando transformações perspectiva que podem ser consideradas como projeções da cena sobre um plano de imagem, de forma similar ao processo de aquisição de uma imagem por meio de uma câmera digital. Uma vista pode ser entendida como uma “foto” da cena 3D.

No WebGL e na maioria dos sistemas gráficos semelhantes, a geração de vistas envolve as seguintes 3 etapas, das quais a transformação perspectiva é apenas um componente. O processo assume que todos os objetos estão inicialmente representados em relação a um sistema de coordenadas 3D padrão, que chamaremos de coordenadas da cena ou do mundo.

  1. Transformação dos modelos usando a matrix modelview: mapeia os vértices de cada modelo (na cena) para um sistema de coordenadas centrado no observador (ou câmera). As coordenadas resultantes são chamadas de coordenadas da câmera, coordenadas de visualização ou ainda de coordenadas do olho.

  2. Projeção usando a matriz projection: etapa que projeta pontos em coordenadas do olho para pontos em um plano chamado plano da imagem. Esse processo de projeção consiste em três partes separadas:

    • a transformação de projeção (parte afim),
    • recorte (clipping) e
    • normalização perspectiva.

    Cada um desses processos será discutido mais adiante, nessa e em aulas futuras. As coordenadas de saída dessa etapa são chamadas de coordenadas normalizadas do dispositivo.

  3. Mapeamento para o Viewport: converte o ponto em coordenadas normalizadas para o viewport, que define uma região do canvas reservada para exibição do desenho. Essas coordenadas são chamadas de coordenadas da janela ou coordenadas do viewport.

Esse processo ainda ignora vários problemas, como iluminação e remoção de superfícies ocultas. Estes problemas serão considerados separadamente mais tarde. A Figura Fig. 13.1 ilustra as etapas desse processo. Como fizemos também com a geometria afim e euclidiana, vamos primeiro introduzir alguns fundamentos teóricos sobre a geometria projetiva para em seguida ilustrar como o WebGL aplica esses conceitos para desenhar em 3D.

Processo de geração de vistas no WebGL.

Fig. 13.1 O processo de geração de vistas no WebGL requer o posicionamento da câmera e definição da projeção.

13.1. Conversão para o sistema de coordenadas da câmera

Para simplificar a transformação perspectiva (que veremos mais adiante) vamos considerar que a origem do sistema de coordenadas da câmera é o centro de projeção. Tipicamente vamos considerar os vetores de base ortonormais, com \(x\) apontando para a direita, \(y\) para cima e, para criar um sistema seguindo a convenção da mão direita, o \(z\) aponta para trás, em sentido oposto a direção de observação da câmera. Vamos chamar essas coordenadas de coordenadas da câmera.

Lembre-se no entanto que, quando desenhamos, costumamos representar pontos relativos a um sistema de coordenadas que nos é mais conveniente, que vamos chamar de coordenadas do mundo. Isso sugere que, antes de realizar a transformação perspectiva, devemos realizar uma mudança de coordenadas para mapear pontos em coordenadas do mundo para coordenadas de câmera.

Vamos realizar essa mudança de coordenadas utilizando a função lookAt() da biblioteca MVnew.js que gera a matriz de transformação apropriada. Conceitualmente, essa mudança de coordenadas define a vista (view) da câmera, que deve ser realizada após a inserção dos objetos na cena, feitas por transformações da matriz model, daí o nome modelview, responsável por compor a cena e gerar a vista apropriada.

13.1.1. Parâmetros da função lookAt(eye, at, up)

Para construir a matriz de transformação da câmera, a função lookAt() precisa receber 3 parâmetros:

  • O ponto eye: que define a posição da câmera (seu centro de projeção). É representado por um ponto 3D \(eye = (e_x, e_y, e_z)^T\).
  • O ponto at: que define a direção da câmera. É representada por qualquer ponto na direção que desejamos apontar a câmera, dado por \(at = (a_x, a_y, a_y)^T\). Esse ponto será usado para definir um vetor que equivale ao eixo óptico da câmera.
  • O vetor up: que fornece a direção “para cima” da câmera. Observe que os dois primeiros pontos definem a posição e a direção da câmera, mas sem o vetor up, a câmera ainda pode girar em torno desse eixo. Assim, o vetor \(up = (u_x, u_y, u_z)^T\) fixa esse último grau de liberdade. Em situações mais simples, você pode assumir que \(up = (0, 1, 0)^T\) (assumindo a convenção que \(y\) deve apontar para cima). Mas em alguns casos, como por exemplo em um simulador de vôo, esse vetor deve mudar para que a vista do avião se incline para um lado. Observe que esse vetor pode ser qualquer, apenas não deve ser paralelo a direção de visualização.

A função lookAt(eye, at, up) contrói um sistema de coordenadas centrado na câmera. Os eixos \(x\) e \(y\) são direcionados para direita e para cima, respectivamente, em relação ao observador. O eixo \(z\) é direcionado para trás, para seguir a convenção da mão direita. Embora possa parecer estranho, os sistemas de coordenadas destros são usados por padrão em toda a matemática. Se não seguirmos essa convenção, o cálculo das orientações ficaria confuso.

13.2. Construção do sistema de coordenadas da câmera

O processo de construção do sistema de coordenadas da câmera a partir dos parâmetros eye, at e up é um bom exercício de computação geométrica. Para resolver esse problema, precisamos construir um sistema ortonnormal com origem no ponto eye, com o vetor de base \(-z\), paralelo à direção de visualização (vamos chamar de vetor view) e tal que o vetor up se projete para cima.

Seja \(V = (V.\vec{v}_x,V.\vec{v}_y,V.\vec{v}_z,V.\mathcal{O})^T\) esse sistema de coordenadas, onde \((\vec{v}_x,\vec{v}_y,\vec{v}_z)^T\) são os três vetores unitários do sistema e \(\mathcal{O}\) é a origem. Claramente \(V.\mathcal{O}\) = eye. Como mencionado anteriormente, o vetor view é direcionado de eye para at.

\(\vec{view} = normalize(at - eye)\)

e

\(V.\vec{v}_z = -\vec{view}\).

Lembre-se de que a operação de normalização divide um vetor por seu comprimento, resultando em um vetor com a mesma direção e comprimento unitário.

Agora podemos definir o vetor base \(x\) para o sistema da nossa câmera. Deve ser ortogonal à direção de visualização view, deve ser ortogonal ao vetor up e deve ser direcionado para a direita da câmera. Lembre-se de que o produto vetorial produzirá um vetor ortogonal a qualquer par de vetores e é direcionado de acordo com a regra da mão direita. Além disso, queremos que esse vetor tenha comprimento unitário. Assim temos que:

\(V.\vec{v}_x = normalize(\vec{view} \times \vec{up})\).

O resultado do produto vetorial deve ser um vetor diferente de zero. É por isso que exigimos que view e up não sejam paralelos entre si.

Já temos dois dos três vetores de base para o nosso sistema. Podemos extrair o último fazendo um produto vetorial dos dois primeiros.

\(V.\vec{v}_y = (V.\vec{v}_z \times V.\vec{v}_x)\).

Não há necessidade de normalizar este vetor, pois ele é o produto vetorial de dois vetores ortogonais, cada um de comprimento unitário.

13.3. Matriz de transformação da câmera

Agora, é preciso construir a matriz de mudança de coordenadas do sistema de coordenadas W (do mundo) para o sistema V da nossa câmera. Não vamos nos deter nos detalhes da álgebra linear, mas a mudança de coordenadas pode ser calculada considerando a matriz M cujas colunas são os elementos de base de V em relação a W, e então invertendo essa matriz. A matriz antes da inversão é:

\(M = \begin{pmatrix} (V.\vec{v}_x)_{[W]} \;\; \mid \;\; (V.\vec{v}_y)_{[W]} \;\; \mid \;\; (V.\vec{v}_z))_{[W]} \;\; \mid \;\; (V.\mathcal{O})_{[W]} \end{pmatrix} = \begin{pmatrix}v_{xx} & v_{yx} & v_{zx} & \mathcal{O}_x \\ v_{xy} & v_{yy} & v_{zy} & \mathcal{O}_y \\ v_{xz} & v_{yz} & v_{zz} & \mathcal{O}_z \\ 0 & 0 & 0 & 1\end{pmatrix}\).

Como M é construído a partir de um sistema ortonormal, há uma maneira muito fácil de se calcular a inversa. Em particular, a porção \(3 \times 3\) superior da matriz pode ser invertida tomando sua transposta. Seja R a parte linear da matriz M, e seja T a negação da parte de translação:

\(R = \begin{pmatrix}v_{xx} & v_{yx} & v_{zx} & 0 \\ v_{xy} & v_{yy} & v_{zy} & 0 \\ v_{xz} & v_{yz} & v_{zz} & 0 \\ 0 & 0 & 0 & 1\end{pmatrix} \;\;\;\;\) e \(\;\;\;\;T = \begin{pmatrix}1 & 0 & 0 & -\mathcal{O}_x \\ 0 & 1 & 0 & -\mathcal{O}_y \\ 0 & 0 & 1 & -\mathcal{O}_z \\ 0 & 0 & 0 & 1\end{pmatrix}\).

Pode-se demonstrar que a matriz inversa é dada por

\(M^{-1} = R^T \cdot T\).

13.4. Projeção Perspectiva

Até aqui, nossos exemplos cobriram problemas gerais que incluem desenhos em 2D, ignorando de certa forma o problema de projeção que ocorre com a luz para sensibilizar o sensor de uma câmera ou a retina de um olho. As projeções se dividem em dois grupos básicos, as projeções paralelas, nas quais as linhas de projeção são paralelas umas às outras, e a projeção perspectiva, na qual as linhas de projeção convergem para um ponto.

Vamos recordar brevemente o modelo de câmera pinhole que introduzimos para descrever o processo de formação de imagem, reproduzida na Figura Fig. 13.2 abaixo. A luz do ambiente que entra na câmera pelo centro de projeção \(C\) é projetada sobre o plano de imagem, distante \(f\) (comprimento focal da câmera) de \(C\).

Modelo de câmera pinhole.

Fig. 13.2 Modelo de câmera pinhole.

Um objeto com altura \(y\) distante \(d\) da câmera é projetada para um ponto \(y'\). Essa relação é dada por:

\(y' = y * f / d\).

De forma semelhante para o eixo \(x\) temos:

\(x' = x * f / d\).

Observe na figura que, uma forma de transformar os raios de projeção em raios paralelos é “esticar” o comprimento focal \(f\) até o infinito. Na prática, câmeras com comprimento focal muito longo são usadas para capturar imagens de objetos muito distantes. Como nessas condições as dimensões dos objetos são muito pequenas em relação a \(d\), o tamanho da imagem do objeto pode ser considerado invariante a pequenas alterações na distância do objeto à câmera, ou seja, como se os raios fossem paralelos.

Apesar de algumas semelhanças, as projeções paralelas e perspectiva se comportam de maneira bastante diferente em relação à geometria. As projeções paralelas são transformações afins, enquanto as projeções perspectiva não são. Em particular, as projeções perspectiva não preservam paralelismo, como é evidenciado por uma vista perspectiva de um par de trilhos de trem que parecem convergir no horizonte. Esse é um dos efeitos da projeção perspectiva que nos permite perceber imagens 2D como reproduções de objetos 3D.

As transformações perspectiva são estudadas pela geometria projetiva. Vamos assumir um espaço tridimensional com objetos que (por meio de uma transformação perspectiva) podem ser representados em coordenadas da câmera.

Transformações projetivas mapeiam linhas para linhas. No entanto, as transformações projetivas não são afins, pois (exceto no caso especial de projeção paralela) não preservam combinações afins e não preservam paralelismo. Por exemplo, considere a projeção perspectiva \(T\) mostrada na Figura Fig. 13.3. Seja \(R\) o ponto médio do segmento \(PQ\). Como visto na figura, \(T (R)\) não é necessariamente o ponto médio de \(T (P)\) e \(T (Q)\).

Perspectiva não conserva transformações afim.

Fig. 13.3 As transformações perspectiva não necessariamente preservam combinações afins, pois o ponto médio de \(PQ\) não é mapeado para o ponto médio do segmento projetado \(T(P)T(Q)\).

13.5. Geometria Projetiva

Para melhor entender as transformações projetivas vamos começar com uma breve introdução à geometria projetiva. A geometria projetiva foi desenvolvida no século XVII por matemáticos interessados no fenômeno da perspectiva. Intuitivamente, a ideia básica que dá origem à geometria projetiva é bastante simples, mas suas consequências são um tanto surpreendentes.

Na geometria euclidiana sabemos que duas retas distintas se cruzam exatamente em um ponto, a menos que as duas retas sejam paralelas uma à outra. Este caso especial parece uma coisa indesejável e, para eliminá-la, vamos estender o conjunto de pontos regulares no plano (aqueles com coordenadas finitas) com um conjunto de pontos ideais (ou pontos no infinito).

Agora podemos eliminar o caso especial e dizer que cada par de linhas distintas se cruzam em um único ponto. Se as linhas são paralelas, então elas se cruzam em um ponto ideal. Mas parece haver dois desses pontos ideais (um em cada extremidade das linhas paralelas). Como não queremos linhas se cruzando mais de uma vez, vamos imaginar que o plano projetivo se contorna de modo que dois pontos ideais nas extremidades opostas de uma linha sejam iguais entre si. Isso é muito elegante, pois todas as linhas se comportam como curvas fechadas, algo como um círculo de raio infinito.

Por exemplo, na Figura fig:geoProjetiva`a, considere que o ponto :math:`P seja um ponto no infinito. Apesar de \(P\) estar infinitamente distante, ele tem uma posição (no sentido de espaço afim), e que pode ser especificado também apontando para ele, ou seja, por uma direção (como se fosse um vetor). Todas as linhas paralelas entre si ao longo desta direção se cruzam em \(P\). No plano, a união de todos os pontos no infinito forma uma linha, chamada de linha no infinito. Em 3D, a entidade correspondente é chamada de plano no infinito. Observe que todas as outras linhas interceptam a linha no infinito exatamente uma vez. O plano afim regular junto com os pontos e a linha no infinito definem o plano projetivo. É fácil generalizar isso também para dimensões arbitrárias.

Embora os pontos no infinito pareçam ser especiais, um princípio importante da geometria projetiva é que eles não são essencialmente diferentes dos pontos regulares. Em particular, ao aplicar transformações projetivas veremos que pontos regulares podem ser mapeados para pontos no infinito e vice-versa.

13.6. Orientabilidade e o espaço projetivo

A geometria projetiva parece generalizar e simplificar a geometria afim, então por que simplesmente não dispensamos a geometria afim e usamos apenas a geometria projetiva? A razão é que, apesar de muitas coisas boas, algumas consequências são bastante estranhas.

Considere por exemplo a forma como o plano projetivo se envolve ao redor dos pontos ideais. Lembre-se que uma característica interessante dos planos afim é que cada linha divide o plano em duas metades, uma acima e outra abaixo (ou esquerda e direita, se a linha for vertical). Isso não é verdade para o plano projetivo, pois cada ponto ideal está simultaneamente acima e abaixo da linha dada (ou a esquerda e a direita).

Como outro exemplo das coisas estranhas que ocorrem na geometria projetiva, considere a Figura fig:geoProjetiva`b. Considere dois pontos ideais :math:`P e \(Q\) no plano projetivo. Imagine que você se encontre no ponto central da figura onde as linhas que contém esses pontos ideais se cruzam. Digamos que você está caminhando para a direita na direção de \(Q\) e que você está carregando um relógio com o ponteiro maior apontando para frente e o ponteiro menor apontando para cima na direção \(QP\). No instante que você passa por \(Q\) (no infinito), como em uma fita de Möbius, você desaparece à direita e reaparece no lado esquerdo da figura. Como você continua andando para frente, o ponteiro dos minutos continua apontando ao longo da mesma linha e o ponteiro das horas na direção \(QP\), mas agora para baixo? Seu relógio passa a girar no sentido oposto. Isso implica que não há uma maneira consistente de definir conceitos como sentido horário e anti-horário na geometria projetiva pois o plano projetivo não é orientável. Ele se comporta muito como a fita de Möbius. À medida que você contorna o infinito, há uma torção, que inverte as orientações.

Plano projetivo não orientável.

Fig. 13.4 O plano projetivo não é orientável. Ele se comporta como a fita de Möbius. Quando você passa pelo infinito, o plano se torce, invertendo as orientações.

Em termos topológicos, dizemos que o plano projetivo é um manifold não orientável, ao contrário do plano euclidiano e da superfície de uma esfera que são superfícies orientáveis.

Por essas razões, optamos por não usar o espaço projetivo como um domínio para fazer a maioria de nossos cálculos geométricos. Em vez disso, faremos quase todos os nossos cálculos geométricos no plano afim, sempre que possível. Entraremos brevemente no domínio da geometria projetiva, quando necessário, para fazer nossas transformações projetivas. Teremos que tomar cuidado quando o objeto mapear pontos para o infinito já que não podemos mapear esses pontos de volta para o espaço euclidiano.

13.7. Novas coordenadas homogêneas

Como representamos pontos no espaço projetivo?

Vamos usar coordenadas homogêneas, mas com algumas diferenças em relação às coordenadas homogêneas que introduzimos com a geometria afim. Primeiramente, não vamos lidar com vetores livres no espaço projetivo, apenas com pontos. Considere um ponto regular \(P\) no plano, com coordenadas padrão (não homogêneas) \((x, y)^T\). Não há uma representação única para este ponto no espaço projetivo. Esse ponto pode ser representado por qualquer vetor de coordenadas da forma:

\(\begin{pmatrix}w \cdot x \\ w \cdot y \\ w\end{pmatrix}\), para \(w \ne 0\).

Assim, se \(P = (4, 3)^T\) são as coordenadas cartesianas padrão de \(P\), as coordenadas homogêneas \((4, 3, 1)^T\), \((8, 6, 2)^T\) e \((-12,-9,-3)^T\) são todas representações legais de \(P\) no plano projetivo. Por causa de sua familiaridade, usaremos o caso \(w = 1\) com mais frequência.

Dadas as coordenadas homogêneas de um ponto regular \(P = (x,y,w)^T\), a normalização projetiva de \(P\) é o vetor coordenado \((x/w,y/w,1)^T\). Esse termo pode ser confuso, porque é bem diferente do processo de normalização de comprimento, que mapeia um vetor para um de comprimento unitário. Em computação gráfica, essa operação também é chamada de divisão perspectiva ou normalização perspectiva.

Como representamos pontos ideais?

Considere uma linha que passa pela origem com inclinação de 2. Considere a seguinte lista de coordenadas homogêneas de alguns pontos situados nesta linha:

\(\begin{pmatrix}1 \\ 2 \\ 1\end{pmatrix}\), \(\begin{pmatrix}2 \\ 4 \\ 1\end{pmatrix}\), \(\begin{pmatrix}3 \\ 6 \\ 1\end{pmatrix}\), \(\begin{pmatrix}4 \\ 8 \\ 1\end{pmatrix}\), \(\;\; \ldots \;\;\), \(\begin{pmatrix}x \\ 2x \\ 1\end{pmatrix}\),

Observe que essas coordenadas podem também ser representadas da seguinte forma:

\(\begin{pmatrix}1 \\ 2 \\ 1\end{pmatrix}\), \(\begin{pmatrix}1 \\ 2 \\ 1/2\end{pmatrix}\), \(\begin{pmatrix}1 \\ 2 \\ 1/3\end{pmatrix}\), \(\begin{pmatrix}1 \\ 2 \\ 1/4\end{pmatrix}\), \(\;\; \ldots \;\;\), \(\begin{pmatrix}1 \\ 2 \\ 1/x\end{pmatrix}\),

Isto é ilustrado na Figura Fig. 13.5. Podemos ver que quando \(x\) tende ao infinito, o ponto limite tem as coordenadas homogêneas \((1, 2, 0)^T\). Então, quando \(w = 0\), o ponto \((x, y, w)^T\) corresponde ao ponto no infinito, que é apontado pelo vetor \((x, y)^T\) (e também por \((-x, -y)^T\)).

Coordenadas homogêneas de pontos ideais.

Fig. 13.5 Coordenadas homogêneas para pontos ideais.

Nota importante

Apesar da semelhança dos nomes, coordenadas homogêneas em geometria projetiva e coordenadas homogêneas em espaços afim são conceitos totalmente diferentes, e não devem ser misturados. Isso ocorre porque os dois sistemas geométricos são totalmente diferentes.

13.8. Onde estamos e para onde vamos?

Vimos que a geração de imagens que se parecem tridimensionais parte de uma transformação das coordenadas do mundo (onde estão os objetos 3D que compõem a cena) para um sistema de coordenadas centro na câmera. Vimos como construir essa transformação a partir dos pontos eye e at e da direção up, usando a função lookAt().

Essa mudança de coordenadas é o primeiro passo para construir a vista que desejamos da cena. A sensação de tridimensionalidade no entanto não é completamente obtida sem o efeito da projeção perspectiva, onde objetos mais distantes do observador são desenhados em menor escala. Observe que, na geometria euclidiana, os objetos tem sempre o mesmo tamanho.

Nessa aula começamos introduzir alguns fundamentos da geometria projetiva para entender a representação que vamos usar para representar transformações perspectivas. Essas matrizes de projeção perspectiva serão introduzidas na próxima aula.

13.9. Exercícios

  1. Perguntas de resposta imediata (ou quase):
  • Considere os pontos P=(2,3,0.5) e Q=(-6,8,-1.5) no plano projetivo. Esses pontos correspondem ao mesmo ponto afim?
  • Considere os pontos P=(4,8,12) e Q=(-1,-2,-3) no plano projetivo. Esses pontos correspondem ao mesmo ponto afim?
  • Considere a hipérbole \(y^2 - x^2 = 1\) no plano projetivo. Observe que você pode considerar as curvas acima e abaixo do eixo \(x\). Considere as 4 extensões da hipérbole para o infinito. Quais são as coordenadas homogêneas desses pontos? DICA: lembre-se que uma hipérbole equilátera dada por \(x^2 - y^2 = a^2\) corresponde à figura abaixo.
exercício de reposta rápida: hipérbole equilátera
  1. Baseado no Problema 4 do Midterm Exam do Prof. Mount.

Suponha que você tem uma função drawWing(), que desenha uma asa como mostrado na figura abaixo à esquerda. A função desenha no plano \(z=0\), que vamos considerar horizontal (imagine o desenho em um papel sobre uma mesa). Nesse caso na figura, o eixo \(z\) aponta para cima, saindo do papel.

    1. Use a função drawWind() e outras funções de transformação afins básicas, como translação, rotação e escala, para escrever a função drawBird(), que desenha duas asas como ilustrado na figura abaixo, ao centro.
    1. Explique como modificar sua solução da parte a) para escrever a função drawBird2(), que tem asas de mesmo tamanho e formato da parte a), mas com os dois triângulos (asas) rodados para cima em relação ao eixo central, simulando o batimento das asas. O ângulo de rotação é 60 graus.
exercício de desenho usando transformações afins.

13.10. Para saber mais