Agora que já entendemos que tipo de transformações existem, que tal darmos uma olhada em como elas são implementadas no OpenGL?
OpenGL e suas matrizes
Como já foi dito, as transformações ocorrem em matrizes, em especial, nas matrizes de projeção e modelview. O primeiro passo é dizer ao OpenGL qual matriz será usada. Fazemos isso através do comando void glMatrixMode(GLenum mode) passando como parâmetro o valor GL_MODELVIEW para a matriz de modelview ou GL_PROJECTION para a matriz de projeção. O valor GL_MODELVIEW já é padrão do OpenGL.
O OpenGL trabalha com matrizes quadradas, de 4×4. Por isso, todo vértice também é definido por quatro coordenadas x, y, z e w. A última coordenada é um fator de escala, geralmente definido em 1. Esse sistema é chamado de sistema de coordenadas homogêneo. Após a multiplicação de todas as matrizes, o valores de x, y e z finais serão divididos por w.
Podemos “zerar” qualquer matriz do OpenGL, usando o comando glLoadIdentity(). Isso fará com que a matriz selecionada seja substituída pela matriz identidade. Lembrando um pouco a matemática, qualquer matriz multiplicada pela identidade resulta na própria matriz. A matriz identidade 4×4 é a seguinte:

O OpenGL permite que multipliquemos o valor da matriz atual por um valor arbitrário. Sabendo as fórmulas corretas, é possível gerar efeitos de distorção de objetos, ou mesmo fazer qualquer uma das transformações descritas no artigo passado. O comando para multiplicar a matriz atual é o glMultMatrix. E vem em duas versões, uma para multiplicação de floats e outra de doubles. O comando aceita como parâmetro uma outra matriz, também 4×4, contendo os valores a serem multiplicados. Lembre-se que estamos falando aqui de multiplicação de matrizes. Talvez seja interessante você revisar em livros de matemática como essa multiplicação é feita, caso não se lembre – mas não se trata de simplesmente multiplicar o elemento atual com o mesmo elemento correspondente na outra matriz. Note que esse comando sobre a matriz identidade, trocaremos a matriz identidade pela matriz passada. Também é possivel substituir a matriz atual por uma arbitrária sem multiplica-la, usando o comando glLoadMatrix.
Uma peculiaridade estranha do OpenGL é que, diferentemente do que aprendemos na escola, as matrizes são baseadas em colunas, e não em linhas. O que significa que seus elementos estão dispostos da seguinte forma:

Essa notação também tem o inconveniente de não ser a que normalmente esperamos, mesmo do ponto de vista físico da organização dos dados na memória. Um artifício comum é criar uma classe para representar uma matriz e então fazer com que essa classe troque linhas por colunas apenas na hora de enviar para o OpenGL.
Embora multiplicar matrizes no braço possa ser realmente eficiente, deixa um código confuso e dificílimo de entender. Por isso, o OpenGL já fornece funções prontas para a manipualção (através de multiplicação) de suas matrizes. São elas:
-
Matrix ModelView: Escala, Translação, Rotação e Posicionamento do Olho;
-
Matrix de Projeção: Modo perspectiva, modo ortográfico;
Como vimos no artigo anterior, não há diferença entre deslocar o olho ou movimentar objetos, por isso todas as funções da matriz modelview podem ser usadas para as duas tarefas. Entretanto, intuitivamente, é mais fácil somente deslocar objetos com as três primeiras e utilizar uma função especial para posicionamento do olho.
Escala
Se os artistas forem caprichados e se os objetos do seu mundo não crescerem, o comando de escala nunca precisará ser usado. Agora, a vida de um programador de jogos caseiro envolve o download de modelos gratuitos, onde os artistas não sabiam o tamanho certo para exportar os seus modelos. Por isso, esse comando será usado para esticar o objeto.
A escala não precisa necessariamente ser proporcional nos três eixos. Você pode reduzir a escala somente no eixo y, por exemplo, e gerar uma figura parecida com os cogumelos após serem pisoteados pelo Super Mario. O comando para ajustar o tamanho de um objeto é o void glScalef(GLfloat x, GLfloat y, GLfloat z), onde x, y e z indica por quantas vezes o tamanho do modelo deve ser multiplicado em cada eixo. Esse comando multiplica a matriz ModelView pelo seguinte:

Há também uma versão do comando para o tipo double.
Translação
A translação permite que desloquemos um objeto em nosso mundo. O comando para realizar uma translação é o void glTranslatef(GLfloat x, GLfloaty, GLfloat z), onde x, y e z é a posição que o objeto deve ficar. O OpenGL não define escala portanto, pode-se usar quaisquer valores consistentes com o mundo sendo modelado.
Na verdade, o que o comando faz é multiplicar a matriz ModelView pelo seguinte valor:

Como todos os comandos tratam apenas de multiplicações sobre a matriz model view, devemos lembrar que eles são cumulativos. Como sempre, há uma versão desse comando para o tipo double.
Rotação
Para rotacionar um objeto na tela, usamos a função void glRotate[fd](angle, x, y, z), que, como a assinatura diz, vem em duas versões uma para valores float e outra para double. Essa função gira a matriz atual no ângulo especificado no primeiro parâmetro e em torno do vetor definido por x, y e z. Normalmente, uma maneira fácil de definir esse vetor é apenas usar o número 1 no eixo que queremos girar, e 0 nos demais eixos. Por exemplo, para uma rotação de 45º em Z, usaríamos o comando da seguinte forma:
glRotatef(45.0f, 0.0f, 0.0f, 1.0f);
Todos os objetos desenhados após o comando são também rotacionados. Rodar em x, y e z causa as seguintes multiplicações de matrizes.

Lembra-se que a ordem de uma rotação e de uma translação é relevante? No artigo passado, você viu uma explicação geométrica para o fenômeno. Estavamos lidando com rotações de eixos. Aqui está a explicação matemática: a multiplicação de matrizes não é comutativa. Isso é, multiplicar uma matriz A x B é diferente de multiplicar B x A. Por isso, usar o comando de rotação seguido do de translação também é diferente de usar a ordem inversa. O resultado de cada operação você já viu no artigo passado. Interessante, não?
Ajustando a posição do olho
Poderíamos ajustar a posição do olho usando os três comandos acima antes de qualquer comando. Se quiséssemos que o olho se deslocasse para frente, simplesmente aplicaríamos uma translação negativa, aproximando todos os modelos. Mas isso não seria nem prático e nem intuitivo.
Para isso, usamos a função da biblioteca auxiliar:
void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez,
GLdouble centerx, GLdouble centery, GLdouble centerz,
GLdouble upx, GLdouble upy, GLdouble upz)
Como parâmetros ela aceita a posição para onde a câmera está olhando (eye), a posição onde a câmera está no mundo (center) e a posição onde está o topo do mundo (top). A figura abaixo ajuda a entender essa analogia:

Matrizes de projeção
Os comandos acima são usados na matriz GL_MODELVIEW. Agora, quais são os comandos de projeção? E como aplica-los?
Como vimos no artigo sobre sistemas de coordenadas, podemos usar os comandos glOrtho, glPerspective e gluOrtho2D para alterar como o OpenGL projeta nossa imagem 3D numa tela de apenas 2 dimensões. Vejamos novamente o código de exemplo daquele artigo, que mostrava como usar a projeção 2D. Você notará que ele ficou muito mais claro:
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); //Resetamos a projeção atual
gluOrtho2D(0, GAME_WINDOW.getWidth(),
GAME_WINDOW.getHeight(), 0);
glMatrixMode(GL_MODELVIEW);
O que estamos fazendo? Basicamente, usamos o comando glMatrixMode para alterar para a matriz de projeção. Apagamos a projeção atual (provavelmente a de perspectiva) e damos um comando para usar a projeção ortográfica 2D. Agora que já estamos em um novo modo de projeção, voltamos para a matriz ModelView e poderemos posicionar os objetos do HUD.Veja que o comando glLoadIdentity() foi muito importante nesse contexto. Se o tivessemos esquecido, multiplicaríamos a matriz de perspectiva pela de projeção, resultando em algo bastante esquisito…Você deve estar curioso, então, eis as matrizes pela qual a projeção é multiplicada em cada comando:
Creio que agora você dormirá tranquilo… ou terá pesadelos com elas. Pelo menos, agora é possível ter uma clara noção do porque aqueles comandos foram criados.
Pilhas de matrizes
Seria muito trabalhoso resetar cada uma das matrizes com a identidade antes de colocar cada um dos objetos do mundo. Muitas vezes, é conveniente colocar um objeto em relação a outro – por exemplo, o chapéu, a espada e o escudo, podem estar em relação ao corpo do seu personagem. Por isso, o OpenGL não trabalha apenas com uma matriz, mas com uma pilha de matrizes de projeção e modelview (na verdade, o OpenGL tem mais dois tipos de matrizes empilhadas, as de textura e cores).
A grande vantagem de trabalhar com matrizes é que você torna o OpenGL capaz de guardar o estado da transformação atual antes de aplicar uma nova transformação. Então, uma vez terminados os desenhos nessa nova transformação, é possível retornar ao estado previamente salvo.
Os comando para empilhar uma matriz é o void glPushMatrix(). Para desempilhar use o void glPopMatrix(). Veja abaixo um exemplo simples de como usar os comandos para desenhar o boneco com chapéu e espada:
glPushMatrix(); //Guardamos o sistema atual
glTranslatef(pcPosX, pcPosY, pcPosZ);
drawPc();
glPushMatrix(); //Guardamos a posição do boneco
//Posicionamos o chapéu, em relação ao boneco
glTranslatef(hatPosX, hatPosY, hatPosZ);
drawHat(); //desenho do chapéu
//Voltamos as coordenadas do boneco
glPopMatrix();
glPushMatrix(); //Guardamos a posição do boneco
//Posicionamos a espada, em relação ao boneco
glTranslatef(swordPosX, swordPosY, swordPosZ);
drawSword(); //Desenho da espada
glPopMatrix();
glPopMatrix();
Nesse programa, drawPc(), drawHat() e drawSword() são funções que desenham o personagem, o chapéu e a espada. A implementação dessa função desenharia os objetos no centro de um sistema de coordenadas qualquer (mas todos proporcionais entre si, senão teríamos que usar também o glScale). Cada objeto é posicionado usando o glTranslate().Num primeiro momento, demos um glPushMatrix() e então mudamos o sistema de coodenadas para corresponder ao ponto no mundo onde queríamos esse personagem. Desenhamos então o personagem. Em seguida, com um novo glPushMatrix() guardamos essa posição e desenhamos o chapéu em relação a ela. Como não queríamos desenhar a espada em relação ao chapéu, desempilhamos a matriz com um glPopMatrix() , obtendo novamente as coordenadas do personagem, salvas previamente. Desenhamos então a espada em relação a ele, usando o mesmo processo.
Resumindo
- Nesse artigo você viu como alternar as matrizes de projeção e modelview;
- Também aprendeu a definir valores arbitrários as matrizes, multiplica-las e restaurar o valor da identidade;
- Aprendeu como rotacionar, transladar e redimensionar o modelview;
- Relembrou as funções para uso na matriz de projeção e viu suas fórmulas;
- Aprendeu a como posicionar as coodenadas do olho;
- Aprendeu sobre a pilha de matrizes.
Exercícios
1. Baixe os programas glTutors (1.24MB) do site do Nate Robins e brinque um pouco com eles (em especial com o lightposition, projection e transformation). Eles demonstrarão para você o uso das funções acima de maneira interativa.
2. Altere o seu programa do cubo para que possa “andar pelo cenário” usando as setas. Use para isso a função gluLookAt.
3. DESAFIO: Usando o cubo do exercício anterior, faça um pequeno “sistema solar de cubos”. Basta ter um sol, um planeta girando em torno dele e uma lua em torno do planeta. O planeta também deve girar em torno do seu próprio eixo, assim como a lua. Você deve usar apenas uma função para o desenho do cubo, e lidar apenas com as matrizes antes de chamar essa função. A lua deve ser menor que o planeta e o sol maior.
Você também pode usar a função glutWireSphere(1.0, 20, 16); no lugar dos planetas. Basta incluir o cabeçalho GL/glut.h. Talvez seja necessário também inserir a glut32 entre suas libs e baixar a biblioteca glut no site do Nate Robins. Use projeção em perspectiva.
Que saudosismo!!!Bem explicado Vinny!!!Me lembrou minhas aulas de CG(Computação Gráfica).
Pelo menos não massacrei ninguém com a dedução das matrizes de projeção. Elas por sí só já são bastante complicadas…
Legal saber q vc ainda visita o blog! Eu sei que quando comecei C++, muita gente se assustou!
Soh pra constar, vc eh um fanatico por jogos, openGL..
A empresa chamada Arcadia (de cassinos) em Floripa vai abrir vagas em breve =P
Os artigos estão ótimos eu to sem tempo de fazer os exercicios mas ainda to acompanhando.
Continue assim.
Ola Vinicius, vi que vc usa o code blocks, e gostaria de pedir sua ajuda, o caso é o seguinte, aprendi a programar em C em 1997/1998 na faculdade, de la pra ca so usei Delphi e PHP, nunca mais vi C, e agora tenho que aprender code blocks, mas nao sei nem por onde começar, sera que tem algum livro ou apostila que ensina a usar esta ferramenta? não da linguagem C que eu tenho muitos e ainda lembro bem como faz, mas usar a ferramenta code blocks mesmo, valeu.
Oi Marcelo. Não sei se existe. A ferramenta não é muito complicada e você pode aprender a configura-la vendo o meu artigo sobre criar um ambiente para desenvolver jogos.
Ali você verá como instalar e configurar a ferramenta para trabalhar com bibliotecas externas. Depois disso, é só usar o bom e velho C++ que você já conhece.
Vinícius,
Os seus artigos são muito bons. Parabéns.
Gostaria de saber, se você tem a representação em matriz do “glPerspective”.
Obrigado.
Valeu. É a mesma matriz o glFrustum. O que o glPerspective faz é calcular o tamanho das áreas, antes de aplicar a matriz.
Vlw Vinigodoy vc tem ajudado muto nos meus trabalhos.Obrigado mesmo.