Como esse artigo tem como foco programação gráfica, não cobrirá boa parte da teoria por trás de matrizes. Apenas a teoria que é aplicada para gráficos computacionais será considerada e será explicada do ponto de vista de um programador. Se quiser aprender mais sobre esse tópico, esses videos da KhanAcademy são uma boa introdução sobre o assunto.
Uma matriz é um array retangular de expressões matemáticas, de forma análoga a um array de duas dimensões. Abaixo segue um exemplo de uma matriz exibida na sua forma mais comum:
$ a = \begin{bmatrix}
1 & 2 \\
3 & 4 \\
5 & 6
\end{bmatrix} $
Valores de matrizes são indexados por (i, j), onde i é a linha e j a coluna. Por isso a matriz exibida acima é chamada de matriz 3 por 2. Para fazer referência a um valor especifico da matriz, por exemplo o 5, a notação:
$ a_{31} $
é usada.
Operações básicas
Para ficar mais familiarizado com o conceito de um array de números, vamos dar uma olhada em algumas operações básicas.
Adição e subtração
Assim como números, os operadores de adição e subtração também são definidos para matrizes. O único requisito é que os dois operandos tenham exatamente as mesmas dimensões.
$ \begin{bmatrix}
3 & 2 \\
0 & 4
\end{bmatrix}
+
\begin{bmatrix}
4 & 2 \\
2 & 2
\end{bmatrix}
=
\begin{bmatrix}
3 + 4 & 2 + 2 \\
0 + 2 & 4 + 2
\end{bmatrix}
=
\begin{bmatrix}
7 & 4 \\
2 & 6
\end{bmatrix} $
$ \begin{bmatrix}
4 & 2 \\
2 & 7
\end{bmatrix}
–
\begin{bmatrix}
3 & 2 \\
0 & 4
\end{bmatrix}
=
\begin{bmatrix}
4 – 3 & 2 – 2 \\
2 – 0 & 7 – 4
\end{bmatrix}
=
\begin{bmatrix}
1 & 0 \\
2 & 3
\end{bmatrix} $
Os valores das matrizes são somados ou subtraídos individualmente um dos outros.
Produto escalar
O produto de um escalar e uma matriz é tão direto quanto a adição e a subtração.
$ 2 \cdot
\begin{bmatrix}
1 & 2 \\
3 & 4
\end{bmatrix}
=
\begin{bmatrix}
2 & 4 \\
6 & 8
\end{bmatrix} $
Cada valor da matriz é multiplicados pelo escala.
Produto Matriz-Vetor
O produto de uma matriz por outra matriz é um pouco mais complicado e frequentemente incompreendido, de forma que por questões de simplicidade mencionaremos apenas os casos específicos que se aplicam a programação gráfica. Para ver como matrizes são usadas de fato para transformar vetores, iremos ver agora o produto de uma matriz por um vetor.
$ \begin{bmatrix}
\color{red}a & \color{red}b & \color{red}c & \color{red}d \\
\color{blue}e & \color{blue}f & \color{blue}g & \color{blue}h \\
\color{green}i & \color{green}j & \color{green}k & \color{green}l \\
\color{purple}m & \color{purple}n & \color{purple}o & \color{purple}p
\end{bmatrix}
\begin{pmatrix}
x \\
y \\
z \\
1
\end{pmatrix}
=
\begin{pmatrix}
\color{red}a\cdot x + \color{red}b\cdot y + \color{red}c\cdot z + \color{red}d\cdot 1 \\
\color{blue}e\cdot x + \color{blue}f\cdot y + \color{blue}g\cdot z + \color{blue}h\cdot 1 \\
\color{green}i\cdot x + \color{green}j\cdot y + \color{green}k\cdot z + \color{green}l\cdot 1 \\
\color{purple}m\cdot x + \color{purple}n\cdot y + \color{purple}o\cdot z + \color{purple}p\cdot 1
\end{pmatrix} $
Para calcular o produto de uma matriz e um vetor, o vetor é escrito como uma matriz 4 por 1. As expressões à direita do sinal de igual mostram como os novos valores de x, y e z são calculador depois que vetor tenha sido transformado.
Nesse artigo, mencionaremos cada uma das transformações vetoriais comuns e como uma matriz que execute essas transformações pode ser formada. Para começar, vamos considerar uma transformação que faz absolutamente nada.
$ \begin{bmatrix}
\color{red}1 & \color{red}0 & \color{red}0 & \color{red}0 \\
\color{blue}0 & \color{blue}1 & \color{blue}0 & \color{blue}0 \\
\color{green}0 & \color{green}0 & \color{green}1 & \color{green}0 \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix}
\begin{pmatrix}
x \\
y \\
z \\
1
\end{pmatrix}
=
\begin{pmatrix}
\color{red}1\cdot x + \color{red}0\cdot y + \color{red}0\cdot z + \color{red}0\cdot 1 \\
\color{blue}0\cdot x + \color{blue}1\cdot y + \color{blue}0\cdot z + \color{blue}0\cdot 1 \\
\color{green}0\cdot x + \color{green}0\cdot y + \color{green}1\cdot z + \color{green}0\cdot 1 \\
\color{purple}0\cdot x + \color{purple}0\cdot y + \color{purple}0\cdot z + \color{purple}1\cdot 1
\end{pmatrix}
=
\begin{pmatrix}
\color{red}1\cdot x \\
\color{blue}1\cdot y \\
\color{green}1\cdot z \\
\color{purple}1\cdot 1
\end{pmatrix} $
Essa matriz é chamada de matriz identidade, porque se comporta como o número 1, e sempre retornará o valor pela qual foi originalmente multiplicado.
Vamos dar uma olhada nas transformações mais comuns e deduzir como uma matriz pode ser formada a partir delas.
Translação
Para ver o por quê de estarmos trabalhando como vetores 4 por 1 e subsequentemente como matrizes de transformação 4 por 4, vamos ver como uma matriz de transformação é formada. Uma translação move um vetor uma certa distância em uma certa direção.
Você consegue descobrir a partir do que vimos em relação à multiplicação como a matriz deve ser para transladar o vetor por (x, y, z)?
$ \begin{bmatrix}
\color{red}1 & \color{red}0 & \color{red}0 & \color{red}X \\
\color{blue}0 & \color{blue}1 & \color{blue}0 & \color{blue}Y \\
\color{green}0 & \color{green}0 & \color{green}1 & \color{green}Z \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix}
\begin{pmatrix}
x \\
y \\
z \\
1
\end{pmatrix}
=
\begin{pmatrix}
x+\color{red}X\cdot 1 \\
y+\color{blue}Y\cdot 1 \\
z+\color{green}Z\cdot 1 \\
1
\end{pmatrix} $
Sem a quarta coluna e o valor 1 na parte de baixo a translação não seria possível.
Escalonamento
Essa transformação escalona cada um dos componentes do vetor por um escalar diferente. É usada com frequência para esticar ou encolher um vetor como demonstrado abaixo.
Se você entende como a matriz anterior foi formada, não deve ter dificuldade para achar uma matriz que escale um dado vetor por (sx, sy, sz).
$ \begin{bmatrix}
\color{red}{SX} & \color{red}0 & \color{red}0 & \color{red}0 \\
\color{blue}0 & \color{blue}{SY} & \color{blue}0 & \color{blue}0 \\
\color{green}0 & \color{green}0 & \color{green}{SZ} & \color{green}0 \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix}
\begin{pmatrix}
x \\
y \\
z \\
1
\end{pmatrix}
=
\begin{pmatrix}
\color{red}{SX}\cdot x \\
\color{green}{SY}\cdot y \\
\color{blue}{SZ}\cdot z \\
1
\end{pmatrix} $
Se você pensar sobre isso por um momento, pode ver que o escalonamento também poderia ser possível para uma matriz 3 por 3.
Rotação
Essa transformação rotaciona um vetor em torno da origem (0, 0, 0) usando um eixo fornecido e um ângulo. Para entender como o eixo e o ângulo controlam a rotação, vamos fazer um pequeno experimento.
Ponha seu polegar virado na direção contrário de monitor e tente rotacionar sua mão em torno dele. O objeto, sua mão, está sendo rotacionada em torno de seu polegar: o eixo de rotação. Quanto mais você rotaciona sua mão em relação à posição original, maior o ângulo de rotação.
Desta forma o ângulo de rotação pode ser imaginado como uma seta por onde o objeto é rotacionado. Se você imaginar o seu monitor como uma superfície de 2 dimensões, o eixo de rotação (seu polegar) está apontando para a direção z.
Objetos podem ser rotacionados em torno de qualquer eixo, mas por enquanto apenas os eixos x, y e z são importantes. Você verá mais tarde que qualquer eixo de rotação pode ser estabelecido pela rotação em torno dos eixos X, Y e Z simultaneamente.
As matrizes para rotacionar em torno dos três eixos são especificadas aqui. O ângulo de rotação é indicado por theta (
[\theta]
).
Rotação em torno do eixo X:
$ \begin{bmatrix}
\color{red}1 & \color{red}0 & \color{red}0 & \color{red}0 \\
\color{blue}0 & \color{blue}{\cos\theta} & \color{blue}{-\sin\theta} & \color{blue}0 \\
\color{green}0 & \color{green}{\sin\theta} & \color{green}{\cos\theta} & \color{green}0 \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix}
\begin{pmatrix}
x \\
y \\
z \\
1
\end{pmatrix}
=
\begin{pmatrix}
x \\
\color{blue}{\cos\theta}\cdot y \color{blue}{-\sin\theta}\cdot z \\
\color{green}{\sin\theta}\cdot y + \color{green}{\cos\theta}\cdot z \\
1
\end{pmatrix} $
Rotação em torno do eixo Y:
$ \begin{bmatrix}
\color{red}{\cos\theta} & \color{red}0 & \color{red}{\sin\theta} & \color{red}0 \\
\color{blue}0 & \color{blue}1 & \color{blue}0 & \color{blue}0 \\
\color{green}{-\sin\theta} & \color{green}0 & \color{green}{\cos\theta} & \color{green}0 \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix}
\begin{pmatrix}
x \\
y \\
z \\
1
\end{pmatrix}
=
\begin{pmatrix}
\color{red}{\cos\theta}\cdot x + \color{red}{\sin\theta}\cdot z \\
y \\
\color{green}{-\sin\theta}\cdot x + \color{green}{\cos\theta}\cdot z \\
1
\end{pmatrix} $
Rotação em torno do eixo Z:
$ \begin{bmatrix}
\color{red}{\cos\theta} & \color{red}{-\sin\theta} & \color{red}0 & \color{red}0 \\
\color{blue}{\sin\theta} & \color{blue}{\cos\theta} & \color{blue}0 & \color{blue}0 \\
\color{green}0 & \color{green}0 & \color{green}1 & \color{green}0 \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix}
\begin{pmatrix}
x \\
y \\
z \\
1
\end{pmatrix}
=
\begin{pmatrix}
\color{red}{\cos\theta}\cdot x \color{red}{-\sin\theta}\cdot y \\
\color{blue}{\sin\theta}\cdot x + \color{blue}{\cos\theta}\cdot y \\
z \\
1
\end{pmatrix} $
Não se preocupe em entender a geometria por trás disso; essa explicação vai além do escopo desse artigo. O que importa é você ter uma idéia sólida de como uma rotação é descrita por um eixo de rotação e um ângulo e que voc~e tenha visto como uma matriz de rotação se parece.
Produto Matriz-Matriz
Na seção anterior vimos como matrizes de transformação podem ser usadas para aplicar transformações em vetores, mas isso por si só não é muito útil. Claramente é preciso menos esforço fazer uma translação e um escalonamento à mão sem todas essas matrizes!
Agora, e se eu lhe dizer que é possível combinar tantas transformações quanto você quiser em uma única matriz simplesmente multiplicando-as? Você seria capaz de aplicar mesmo a mais complexa das transformações a um vértice com uma simples multiplicação.
No mesmo estilo da seção anterior, esta é a forma como o produto de duas matrizes 4 por 4 é determinado:
$ \begin{bmatrix}
\color{red}a & \color{red}b & \color{red}c & \color{red}d \\
\color{blue}e & \color{blue}f & \color{blue}g & \color{blue}h \\
\color{green}i & \color{green}j & \color{green}k & \color{green}l \\
\color{purple}m & \color{purple}n & \color{purple}o & \color{purple}p
\end{bmatrix}
\begin{bmatrix}
\color{red}A & \color{blue}B & \color{green}C & \color{purple}D \\
\color{red}E & \color{blue}F & \color{green}G & \color{purple}H \\
\color{red}I & \color{blue}J & \color{green}K & \color{purple}L \\
\color{red}M & \color{blue}N & \color{green}O & \color{purple}P
\end{bmatrix}
= \\
\begin{bmatrix}
\color{red}{aA} + \color{red}{bE} + \color{red}{cI} + \color{red}{dM} &
\color{red}a\color{blue}B + \color{red}b\color{blue}F + \color{red}c\color{blue}J + \color{red}d\color{blue}N &
\color{red}a\color{green}C + \color{red}b\color{green}G + \color{red}c\color{green}K + \color{red}d\color{green}O &
\color{red}a\color{purple}D + \color{red}b\color{purple}H + \color{red}c\color{purple}L + \color{red}d\color{purple}P \\
\color{blue}e\color{red}A + \color{blue}f\color{red}E + \color{blue}g\color{red}I + \color{blue}h\color{red}M &
\color{blue}{eB} + \color{blue}{fF} + \color{blue}{gJ} + \color{blue}{hN} &
\color{blue}e\color{green}C + \color{blue}f\color{green}G + \color{blue}g\color{green}K + \color{blue}h\color{green}O &
\color{blue}e\color{purple}D + \color{blue}f\color{purple}H + \color{blue}g\color{purple}L + \color{blue}h\color{purple}P \\
\color{green}i\color{red}A + \color{green}j\color{red}E + \color{green}k\color{red}I + \color{green}l\color{red}M &
\color{green}i\color{blue}B + \color{green}j\color{blue}F + \color{green}k\color{blue}J + \color{green}l\color{blue}N &
\color{green}{iC} + \color{green}{jG} + \color{green}{kK} + \color{green}{lO} &
\color{green}i\color{purple}D + \color{green}j\color{purple}H + \color{green}k\color{purple}L + \color{green}l\color{purple}P \\
\color{purple}m\color{red}A + \color{purple}n\color{red}E + \color{purple}o\color{red}I + \color{purple}p\color{red}M &
\color{purple}m\color{blue}B + \color{purple}n\color{blue}F + \color{purple}o\color{blue}J + \color{purple}p\color{blue}N &
\color{purple}m\color{green}C + \color{purple}n\color{green}G + \color{purple}o\color{green}K + \color{purple}p\color{green}O &
\color{purple}{mD} + \color{purple}{nH} + \color{purple}{oL} + \color{purple}{pP}
\end{bmatrix} $
A sentença acima é conhecida entre os matemáticos como uma bagunça indecifrável. Para ter uma ideia melhor do que está acontecendo, vamos considerar duas matrizes 2 por 2.
$ \begin{bmatrix}
\color{red}1 & \color{red}2 \\
\color{blue}3 & \color{blue}4
\end{bmatrix}
\begin{bmatrix}
\color{green}a & \color{purple}b \\
\color{green}c & \color{purple}d
\end{bmatrix}
=
\begin{bmatrix}
\color{red}1\cdot \color{green}a + \color{red}2 \cdot \color{green}c & \color{red}1 \cdot \color{purple}b + \color{red}2 \cdot \color{purple}d \\
\color{blue}3\cdot \color{green}a + \color{blue}4 \cdot \color{green}c & \color{blue}3 \cdot \color{purple}b + \color{blue}4 \cdot \color{purple}d
\end{bmatrix} $
Tente visualizar o padrão aqui com a ajuda das cores. Os fatores do lado esquerdo (1, 2 e 3, 4) do sinal de multiplicação são os valores na linha da primeira matriz. Os fatores à direita são os valores na linha da segunda matriz repetidamente. Não é necessário lembrar exatamente como isso funciona, mas é bom ver com isso é feito uma vez.
Combinação de transformações
Para demonstrar a multiplicação de duas matrizes, vamos tentar escalonar um vetor por (2, 2, 2) e translada-lo por (1, 2, 3). Dada as matrizes de translação e escalonamento acima, o seguinte produto é calculado:
$ M_\text{translate}\cdot M_\text{scale} =
\begin{bmatrix}
\color{red}1 & \color{red}0 & \color{red}0 & \color{red}1 \\
\color{blue}0 & \color{blue}1 & \color{blue}0 & \color{blue}2 \\
\color{green}0 & \color{green}0 & \color{green}1 & \color{green}3 \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix}
\begin{bmatrix}
\color{red}{2} & \color{red}0 & \color{red}0 & \color{red}0 \\
\color{blue}0 & \color{blue}{2} & \color{blue}0 & \color{blue}0 \\
\color{green}0 & \color{green}0 & \color{green}{2} & \color{green}0 \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix}
=
\begin{bmatrix}
\color{red}{2} & \color{red}0 & \color{red}0 & \color{red}1 \\
\color{blue}0 & \color{blue}{2} & \color{blue}0 & \color{blue}2 \\
\color{green}0 & \color{green}0 & \color{green}{2} & \color{green}3 \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix} $
Observe que primeiro queremos escalonar o vetor, mas a matriz de transformação do escalonamento vem por último na multiplicação. Preste atenção a isso quanto for combinar transformações ou você irá obter o oposto do que queria.
Agora vamos tentar transformar um vetor e ver se funciona:
$ \begin{bmatrix}
\color{red}{2} & \color{red}0 & \color{red}0 & \color{red}1 \\
\color{blue}0 & \color{blue}{2} & \color{blue}0 & \color{blue}2 \\
\color{green}0 & \color{green}0 & \color{green}{2} & \color{green}3 \\
\color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1
\end{bmatrix}
\begin{pmatrix}
x \\
y \\
z \\
1
\end{pmatrix}
=
\begin{pmatrix}
\color{red}2 x + \color{red}1 \\
\color{blue}2y + \color{blue}2 \\
\color{green}2z + \color{green}3 \\
1
\end{pmatrix} $
Perfeito! O vetor é escalonado por 2 e depois tem sua posição alterada por (1, 2, 3).
Transformações em OpenGL
Nas seções anteriores, vimos como transformações básicas podem ser aplicadas a vetores para move-los pelo espaço. O trabalho de transformar pontos 3D em coordenadas 2D em sua tela é conseguido também através de transformações de matrizes. Assim com no caso fluxo gráfico, a transformações de um vetor é feito passo a passo. Apesar do OpenGL permitir que você tome decisões em relação a esses passos, todas as aplicações gráficas 3D usam uma variação do processo descrito a seguir.
Cadas transformação transforma um vetor para um novo sistema de coordenadas, movendo-se assim para o próximo passo. Essas transformações e sistemas de coordenadas serão discutidas abaixo em mais detalhes.
Matriz Model
A matrix Model transforma uma posição em um model para a posição no espaço. Esta posição é afetada pela posição, escalonamento e rotação do modelo que está sendo desenhado. É geralmente uma combinação das transformações simples que vimos acima. Se você já está especificando seus vértices em coordenadas do espaço (opção comum quando se está desenhando uma cena de teste simples), então essa matriz pode ser simplesmente a matriz identidade.
Matriz View
Na vida real, você move a câmera para alterar a visão de uma certa cena, no OpenGL é o contrário. A câmera no OpenGL não se move e é fixa na localização (0, 0, 0) e está virada para a direção negativa do eixo Z. Isso significa que ao invés de mover e rotacionar a câmera, o mundo é movido e rotacionado em torno da câmera para construir a visão apropriada.
Versões antigas do OpenGL forçavam você a usar transformações de ModelView e Projection. A matrix ModelView cobinava as transformações de Model e View em um só. Pessoalmente, acho que é mais fácil separar as duas, de forma que a transformação de View possa ser modificada independentemente da matriz Model.
Isso significa que para simular a transformação de câmera, você tem que transformar o mundo com o inverso da transformação desejada. Exemplo: se quiser mover a câmera para cima, tem que mover o mundo para baixo.
Matriz Projection
Depois que o mundo tiver sido alinhado com sua câmera usando a transformação de View, a transformação de Projection pode ser aplicada, resultando nas coordenadas de corte (clip). Se você estiver fazendo uma transformação de perspectiva, essas coordenadas ainda não estão prontas para serem usadas como coordenadas normalizadas do dispositivo.
Para transformas as coordenadas de corte em coordenadas normalizadas do dispositivo, uma divisão de perspectiva precisa ser executada. Uma coordenada de corte resultante de uma projeção de perspectiva tem um número diferente de 1 na quarta coluna, também conhecida como w. Esse número reflete diretamente no efeito dos objetos distantes serem menores do que aqueles mas próximos.
$ v_\text{normalized} =
\begin{pmatrix}
x_\text{clip} / w_\text{clip} \\
y_\text{clip} / w_\text{clip} \\
z_\text{clip} / w_\text{clip}
\end{pmatrix} $
As coordenadas x e y estarão na faixa familiar -1 e 1, que o OpenGL pode transformar em coordenadas da janela. A coordenada z é conhecida como profundidade e irá ter um papel importante em futuros artigos.
As coordenadas resultantes da transformação de Projection são chamadas de coordenadas de corte porque o valor de w é usado para determinar se um objeto está muito próximo ou atrás da câmera ou muito distante para ser desenhado. A matrix Projection é criada com esses limites, de forma que você pode especificar eles manualmente.
Colocando tudo junto
Somando tudo isso, a transformação final de um vértice é o produto das matrizes Model, View e Projection.
$ v’ = M_\text{proj} \cdot M_\text{view} \cdot M_\text{model} \cdot v $
$ v’ = M_\text{proj} \cdot M_\text{view} \cdot M_\text{model} \cdot v $
Essa operação é executada tipicamente no vertex shader e associada ao valor de retorno de gl_Position em coordenadas de corte. O OpenGL executará a divisão de perspectiva e a transformação em coordenadas da janela. É importante estar ciente desses passos, porque você terá que executar eles manualmente quando estiver trabalhando com técnicas como mapeamento de sombras.
Usando transformações em 3D
Agora que você conhece essas três transformações importantes, é hora de implementa-las em código para criar um cena 3D. Você pode usar qualquer um dos programas desenvolvidos nos artigos anteriores aqui.
Para introduzir matrizes no código, podemos usar a biblioteca GLM (OpenGL Math). Essa bibliotecas vem com classes para encapsular vetores e matrizes a lidará com a parte matemática sem problemas. É uma biblioteca onde você só precisa adicionar um arquivo de cabeçalho, sem a necessidade de lincar com nada.
Para usa-la, adicione o diretório raiz da GLM à seu caminho de arquivos de cabeçalho e inclua essas três linhas:
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
O segundo cabeçalho inclui funções para facilitar o cálculo com as matrizes View e Projection. O terceiro cabeçalho adiciona funcionalidade para converter um object matriz em um array de float para uso no OpenGL.
Uma transformação simples
Antes de partir direto para o 3D, vamos primeiro testar uma rotação 2D simples.
glm::mat4 trans;
trans = glm::rotate(trans, glm::radians(180.0f), glm::vec3(0.0f, 0.0f, 1.0f));
A primeira linha cria uma matriz 4 por 4, que por padrão será a matriz identidade. A função glm::rotate multiplica essa matriz por uma transformação de rotação de 180 graus em torno do eixo Z. Lembre que como a tela está no plano XY, o eixo Z é o eixo no qual você quer executar a rotação dos pontos.
Para ver se funciona, vamos tentar rotacionar um vetor com essa transformação:
glm::vec4 result = trans * glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
printf("%f, %f, %f\n", result.x, result.y, result.z);
Como esperado, a saída será (-1, 0, 0). Uma rotação anti-horário de 180 graus de um vetor que aponta para a direita resulta em um vetor que aponta para a esquerda. Note que a rotação seria no sentido horário se um eixo (0, 0, -1) fosse usado.
O próximo passo é executar essa transformação do vertex shader para rotacionar cada um dos vértices desenhados. O GLSL tem um tipo mat4 para armazenar matrizes e podem usar isso para carregar a transformação para a GPU como um uniform.
GLint uniTrans = glGetUniformLocation(shaderProgram, "trans");
glUniformMatrix4fv(uniTrans, 1, GL_FALSE, glm::value_ptr(trans));
O segundo parâmetro da função glUnformMatriz4fv especifica quantas matrizes serão carregadas, porque você pode ter arrays de matrizes no GLSL. O terceiro parâmetro especifica se a matriz especificada deve ser transposta antes de ser usada. Isso tem relação à maneira pela qual matrizes são armazenadas como arrays de float na memória; você não precisa se preocupar com isso. O último parâmetro especifica a matriz a ser carregada, ondea função glm::value_ptr converte a classe da matriz em um array de 16 floats.
Tudo que resta é atualizar o vertex shader para incluir esse uniform e usa-lo para transformar cada vértice:
#version 150
in vec2 position;
in vec3 color;
in vec2 texcoord;
out vec3 Color;
out vec2 Texcoord;
uniform mat4 trans;
void main() {
Color = color;
Texcoord = texcoord;
gl_Position = trans * vec4(position, 0.0, 1.0);
}
As primitivas em sua cena serão mostradas de cabeça para baixo agora:
Para incrementar um pouco as coisas, você pode alterar a rotação com o passar do tempo.
...
// Calculate transformation
glm::mat4 trans;
trans = glm::rotate(
trans,
(float)clock() / (float)CLOCKS_PER_SEC * glm::radians(180.0f),
glm::vec3(0.0f, 0.0f, 1.0f)
);
glUniformMatrix4fv(uniTrans, 1, GL_FALSE, glm::value_ptr(trans));
// Draw a rectangle from the 2 triangles using 6 indices
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
...
Isso resultará em algo como isso:
Você pode encontrar o código aqui se tiver algum problema.
Usando 3D
A rotação acima pode ser considerada a transformação do Model, porque transforma os vértices do espaço do objeto para o espaço do mundo usando a rotação do objeto.
glm::mat4 view = glm::lookAt(
glm::vec3(1.2f, 1.2f, 1.2f),
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 0.0f, 1.0f)
);
GLint uniView = glGetUniformLocation(shaderProgram, "view");
glUniformMatrix4fv(uniView, 1, GL_FALSE, glm::value_ptr(view));
Para criar a transformação do View, o GLM oferece a função glm::lookAt que simula a movimentação da câmera. O primeiro parâmetro especifica a posição da câmera, o segundo o ponto a ser centralizado na tela e o terceiro o eixo up. Aqui o up é definido como sendo o eixo Z, o que implica que o plano XY é o “chão”.
glm::mat4 proj = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 1.0f, 10.0f);
GLint uniProj = glGetUniformLocation(shaderProgram, "proj");
glUniformMatrix4fv(uniProj, 1, GL_FALSE, glm::value_ptr(proj));
De forma similar, o GLM vem com a função glm::perspective para criar uma matriz de projeção da perspectiva. O primeiro parâmetro é o campo de visão vertical, o segundo parâmetro o aspect ratio da tela e os últimos dois parâmetros são os planos near (próximo) e far (distante).
Campo de visão O campo de visão define o ângulo entre a parte superior e a parte inferior da superfície 2D em que o mundo será projetado. O zoom em games é frequentemente conseguido pela diminuição desse ângulo ao invés de mover a câmera para mais perto, porque o resultado é mais próximo do que seria na vida real.
Diminuindo esse ângulo, você pode imaginar que os “raios” da câmera se espalham menos e assim cobrem uma área menor da cena.
Os planos near e far são conhecidos como os planos de corte. Qualquer vértice mais próximo da câmera do que o plano de corte near e qualquer vértice mais distante da câmera do que o plano de corte far é cortada pois influenciam o valor de w. Pondo tudo para trabalhar junto, o vertex shader deve se parecer com isso:
#version 150
in vec2 position;
in vec3 color;
in vec2 texcoord;
out vec3 Color;
out vec2 Texcoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;
void main() {
Color = color;
Texcoord = texcoord;
gl_Position = proj * view * model * vec4(position, 0.0, 1.0);
}
Note que a matriz que foi chamada anteriormente de trans foi renomeada para model e ainda assim é atualizada a cada quadro.
Sucesso! Você pode encontrar o código completo aqui caso encontre alguma dificuldade.
Fonte: open.gl/transformations