Projeto Virtual Worlds

Computação Gráfica 3D em C

Arquivos Mensais: junho 2015

Organizando a “zona”…

Confesso que, do jeito que estão, todos os textos sobre manipulação de matrizes ficaram bem confusos. Tentei misturar dois conceitos completamente diversos num só lugar e é importante separá-los… No OpenGL as matrizes são column-major, ou seja, a sequência de valores é organizada por coluna, não por linha… Mas, na literatura, toda manipulação matricial é feita por linha, ou seja, o índice da linha vem antes do índice da coluna. Usarei a notação tradicional daqui por diante…

Um vetor \vec{v} deve ser escrito como:

\displaystyle \vec{v}=\begin{pmatrix}  v_x \\  v_y \\  v_z  \end{pmatrix}

Ou seja, temos uma coluna de valores x, y e z. Ao passo que, uma matriz pode ser entendida como sendo composta de 3 vetores:

\displaystyle M=\begin{bmatrix}  \vec{i} & \vec{j} & \vec{j}  \end{bmatrix} = \begin{bmatrix}  i_x & j_x & k_x \\  i_y & j_y & k_y \\  i_z & j_z & k_z  \end{bmatrix}

Anteriormente eu tinha dito que esses vetores eram “transpostos”… Agora, essa é a definição de uma matriz, de acordo com o método tradicional!

Neste caso, os vetores \vec{i}, \vec{j} e \vec{k} são chamados vetores diretores. Eles são usados naquilo que é conhecido como combinação linear. Isso quer dizer que numa multiplicação da matriz M por um vetor \vec{v}, obteremos:

\displaystyle \vec{w}=\begin{bmatrix}  \vec{i} & \vec{j} & \vec{k}  \end{bmatrix} \cdot  \begin{pmatrix}  v_x \\  v_y \\  v_z  \end{pmatrix} = \begin{bmatrix}  \vec{i}\cdot v_x & \vec{j}\cdot v_y & \vec{k}\cdot v_z  \end{bmatrix}

É como se cada componente do vetor \vec{v} aplicasse um “peso” a um vetor diretor contido na matriz. O vetor \vec{v} é transformado para o vetor \vec{w}, de acordo com os vetores diretores contidos na matriz M.

Quero dizer que, ao aplicar a matriz com os vetores diretores ortogonais (perpendiculares entre si) muda as coordenadas do vetor \vec{v} de acordo com o sisetma de coordenadas dado pelos vetores diretores. Se rotacionarmos o sistema de coordenadas em 45°, por exemplo, o vetor \vec{v} permanecerá na mesma posição no novo sistema de coordenadas, o que significa que que estamos transformando \vec{v}.

Outra maneira de encarar matrizes é usar os vetores diretores transpostos:

\displaystyle \vec{w}=\begin{bmatrix}  \vec{a^T}\\  \vec{b^T}\\  \vec{c^T}  \end{bmatrix}\cdot\vec{v}=\begin{pmatrix}  \vec{a^T}\cdot\vec{v}\\  \vec{b^T}\cdot\vec{v}\\  \vec{c^T}\cdot\vec{v}\end{pmatrix}

Isto é uma transformação de sistemas de coordenadas. O vetor \vec{v} permanece no mesmo lugar no sistema de coordenadas original, mas é alterado no novo sistema. Ao multiplicar pela matriz de vetores diretores transposta estamos transformando o sistema de coordenadas, não o vetor \vec{v}.

Técnicamente, a matriz não deve ser uma transposta os vetores diretores originais, mas sua inversa. Mas, como estamos trabalhando com sistemas de coordenadas ortogonais, a inversa da matriz e sua transposta são a mesma coisa…

Uma explicação para o uso de matrizes com vetores diretores “diretos” e “transpostos”…

Para ilustrar a diferença, suponha que tenhamos um ponto \vec{p} na posição (2,1) no sistema de coordenadas tradicional (em azul). Em vermelho temos um sistema de coordenadas diferente, dados pelos vetores diretores \vec{i}=(\frac{\sqrt{2}}{2},\frac{\sqrt{2}}{2}) e \vec{j}=(\frac{\sqrt{2}}{2},\frac{-\sqrt{2}}{2}). Ambos são unitários e perpendiculares entre si:

Sistemas de coordenadas diferentes.

Sistemas de coordenadas diferentes.

Isso nos dá uma matriz com os vetores diretores (para o sistema de coordenadas vermelho) regido pela seguinte equação:

\displaystyle M=\begin{bmatrix} \vec{i} & \vec{j}\end{bmatrix}=  \begin{bmatrix}  \begin{pmatrix} \frac{\sqrt{2}}{2} \\ \frac{\sqrt{2}}{2} \end{pmatrix} &  \begin{pmatrix} -\frac{\sqrt{2}}{2} \\ \frac{\sqrt{2}}{2} \end{pmatrix} \end{bmatrix}=  \frac{1}{2}\cdot\begin{bmatrix} \sqrt{2} & -\sqrt{2} \\ \sqrt{2} & \sqrt{2} \end{bmatrix}

Ao fazermos a multiplicação desta matriz pelo ponto \vec{p}, obteremos:

\displaystyle \vec{{p}'} = M \cdot \vec{p} =  \frac{1}{2}\cdot\begin{bmatrix} \sqrt{2} & -\sqrt{2} \\ \sqrt{2} & \sqrt{2} \end{bmatrix} \cdot \begin{pmatrix} 2 \\ 1 \end{pmatrix} \approx \begin{pmatrix} 0.7 \\ 2.12\end{pmatrix}

Se você plotar o novo ponto \vec{{p}'} verá que ele está na posição (2,1) no sistema de coordenadas vermelho!

O que acontece quando usamos M^{-1}? Obteremos o ponto \vec{{p}'}=(2.12, 0.7) e se plotarmos o novo ponto \vec{{p}'} no sistema de coordenadas vermelha, veremos que ele fica no mesmo lugar, no sistema de coordenadas azul. Estamos então mapeando o ponto \vec{p} no sistema de coordenadas vermelho, mas mantendo a mesma posição em que ele estava, no azul!

A diferença é sutil: No primeiro caso o ponto \vec{{p}'} mantém a posição relativa em ambos os sistemas de coordenadas, sendo transformado de uma para outra. No segundo caso ele mantém a posição relativa ao sistema de coordenadas original, sendo mapeado ao ponto correspondente no sistema de coordenadas modificado.

É por isso que, quando queremos mudar o sistema de coordenadas model-view para o sistema de coordenadas da câmera, via função gluLookAt, usamos a matriz transposta dos vetores diretores do novo sistema de coordenadas (o da câmera): Queremos que os pontos permanecam na mesma posição em relação ao sistema model-view, mas “aplicando” o sistema da câmera.

Sobre os cálculos matriciais…

Então, por favor, não confunda a ordenação dos itens de uma matriz, usada pelo OpenGL, com o modelo matemático… são coisas diferentes…

Internamente, a sequência de componentes de uma matriz corresponde a uma coluna, os componentes x,y e z de um vetor ficam numa coluna, na sequência em que aparecem. Assim, para multiplicar uma linha por um vetor teríamos que transpor a matriz. Porque OpenGL usa essa organização, me escapa completamente (DirectX usa row-major, só para citar!). Talvez seja porque fica mais fácil copiar vetores para uma matriz, mas certamente dificulta a manipulação da mesma (internamente).

Felizmente, com a depreciação de funções fixas do OpenGL e o uso intensivo de shaders, passar matrizes row major para o GLSL é muito simples, mesmo que o padrão seja column major.