|
Agora que já entendemos que tipo de transformações existem, que tal darmos uma olhada em como elas são implementadas na OpenGL? Para esse artigo, é fortemente aconselhável que você conheça como a matemática das transformações funcionam, se não conhece, você pode descobrir aqui
.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 à 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 da OpenGL.
A 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 da 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:

A 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 da 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 a OpenGL.
Embora multiplicar matrizes no braço possa ser realmente eficiente, deixa um código confuso e dificílimo de entender. Por isso, a 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. A 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 onde a câmera está posicionada (eye), a posição para onde a câmera está olhando (center) e a posição onde está o topo do mundo (top) para câmera. A figura abaixo ajuda a entender essa analogia:

Note que o conceito de "topo do mundo" refere-se a um vetor que aponta da câmera para o que ela entende como topo, e não necessariamente para o céu. Por exemplo, um personagem que tenha caído de lado, poderia ter como vetor de topo o valor (1,0,0), considerando que z representasse a altura do mundo, e x e y as coordenadas do chão. Da mesma forma, se você negar o vetor de topo, a imagem da câmera ficará de cabeça para baixo.
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 aplicá-los?
Como vimos no artigo sobre sistemas de coordenadas, podemos usar os comandos glOrtho, glPerspective e gluOrtho2D para alterar como a 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, a OpenGL não trabalha apenas com uma matriz, mas com uma pilha de matrizes de projeção e modelview (na verdade, a OpenGL tem mais dois tipos de matrizes empilhadas, as de textura e cores).
A grande vantagem de trabalhar com matrizes é que você torna a 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 coordenadas 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
- 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.
- Altere o seu programa do cubo para que possa “andar pelo cenário” usando as setas. Use para isso a função gluLookAt.
- 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.
-
16/09/2010 19:34:49 | Vinícius Godoy de Mendonça

Obrigado. Mais exercícios são colocados à medida que o conteúdo avança.
-
22/09/2010 08:27:10 |201.88.80.xxx| Zero - gluLookAt()

Uma pequena correção:
você informou que o top representaria o topo do mundo para a função gluLookAt(), mas isso não é correto pois o observador (câmera) é que é afetado ao definir top, por exemplo, apontar para o eixo x (-1,0,0 como parâmetros de up), o quê poderia representar um personagem que caiu de lado. Isto se considerarmos o plano do solo (horizontal) sendo o xz e o eixo y definido como a altura do ambiente (vertical).
-
22/09/2010 09:01:24 | Vinícius Godoy de Mendonça

Sim, para alguém de lado, o topo do mundo agora está na lateral. Por topo do mundo não me referia ao céu.
Isso depende muito da interpretação e o termo "topo do mundo" é usado pelo próprio Red Book.
Mas concordo com você, vou melhorar a explicação ali no artigo.
-
22/09/2010 08:29:47 |201.88.80.xxx| Zero - re: gluLookAt()

errata:
Zero Escreveu:pois o observador (câmera) é que é afetado ao definir top, por exemplo, apontar para o eixo xpois o observador (câmera) é que é afetado ao definir top, por exemplo, apontado para o eixo x
-
25/11/2011 12:46:49 |177.10.148.xxx| lucas - rotação em torno do proprio centro

gostaria de saber como fazer pra rotacionar em torno do seu proprio centro....eu tenho uma imagem 2d e com o glRotatef(angulo, 0, 0, 1); só consigo rotacionar em torno de um dos eixos....
obrigado...mt boa suas explicações
-
25/11/2011 13:35:06 | Vinícius Godoy de Mendonça

Pode exemplificar o que você quer? Pois só existem 3 eixos possíveis, x, y e z. Um deles deve representar a rotação que você deseja.
Só lembre de aplicar essa rotação depois da translação da imagem, caso contrário, a imagem sairá deslocada. Lembre-se da sigla TRS (Translate-Rotation-Scale), pois é a ordem que essas operações são normalmente realizadas (em DirectX seria ao contrário, SRT).
-
05/01/2012 00:56:25 |200.220.245.xxx| Tales - Depth test simplesmente não funciona.

Eu tentei de tudo. Até olhei o redbook, mas não consegui fazer o zbuffer funcionar. Eu já tinha visto isso em outros exercícios, mas achei que era alguma config. da máquina de estados que estava errada. Porém fazendo exercício dos planetas percebi que a coisa simplesmente não funciona.
Não sei se é alguma configuração que fiz errado ou se é problema no meu driver/implementação do sistema. Eu botei o código (a definição da classe e os métodos setup e draw) em http://pastebin.com/VMaRktch. Se puder dar uma olhada seria bom... Senão puder, vc pode me indicar um fórum onde eu possa tirar esta dúvida?
-
05/01/2012 08:31:05 | Vinícius Godoy de Mendonça

Muito estranho, aparentemente seu código está certo. Tentou roda-lo em outra máquina?
Pode colocar um PrintScreen do que ocorre?
Vou testar à noite, quando chegar em casa.
-
06/01/2012 00:05:22 |189.96.226.xxx| Tales

Os printscreens:
[IMG]http://i118.photobucket.com/albums/o97/Invisivel3c/print0002.png[ /IMG]
[IMG]http://i118.photobucket.com/albums/o97/Invisivel3c/print0001.png[ /IMG]
[IMG]http://i118.photobucket.com/albums/o97/Invisivel3c/print0000.png[ /IMG]
A implementação completa: http://pastebin.com/tYneP8jv< /p>Ah fiz o código em um Core 2 duo com uma placa Geforce 210 512MB. Seguindo a sugestão, testei hoje também no pc do meu trabalho e lá deu o mesmo erro. O pc é um Dell com pentium dual core e vídeo onboard, configuração totalmente diferente do meu pc, o que indica que provavelmente eu fiz alguma besteira imensa no código e não consigui perceber.
-
06/01/2012 16:32:55 | Vinícius Godoy de Mendonça

Pode enviar um zip com os fontes do seu projeto para vinigodoy@pontov.com.br ?
-
16/01/2012 15:49:54 |200.239.65.xxx| Tales - Resolvido

Depois de dias debugando achei o maldito problema. O bug mesmo acontece na minha função "Main":
Código:
int main(int argc,char* argv[])
{
GAMEWINDOW.setup("Cube", 800, 600, 0, false );
GAMEWINDOW.show(new MainGameSteps);
return 0;
}
Eu passo a profundidade como zero. A SDL entende esse 0 como "use o bpp nativo" e cria a janela normalmente. Entretanto a classe GameWindow faz o seguinte:Código:
int GameWindow::setupOpenGL()
{
//Atributos do opengl
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, bpp);
SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE, 0);
SDL_GL_SetAttribute( SDL_GL_ACCUM_RED_SIZE, 0);
SDL_GL_SetAttribute( SDL_GL_ACCUM_GREEN_SIZE, 0);
SDL_GL_SetAttribute( SDL_GL_ACCUM_BLUE_SIZE, 0);
SDL_GL_SetAttribute( SDL_GL_ACCUM_ALPHA_SIZE, 0);
return createFlags();
}Só que olhando a documentação do SDL, o atributo SDL_GL_DEPTH_SIZE define o tamanho do Depth buffer e não o bits per pixel. Isso por si só não daria problema porque quando passamos um valor como 16 ou 32 o opengl enxerga um valor válido, mas como eu usei 0, ele reservou 0 pixels pro Z-buffer e por isso não funcionava.
Corrigi isso colocando 16 em vez de bpp e tudo agora funciona perfeitamente!
-
16/01/2012 15:59:24 | Vinícius Godoy de Mendonça

Nossa, que detalhezinho macabro.
O GL_SDL_DEPTH_SIZE diz quantos bits você usará para a resolução do DEPTH_BUFFER. Geralmente, é igual ao que você usa para a janela, por isso no código os bpps são iguais.
Entretanto, você não pode passar 0 nesse parâmetro. Embora a SDL entenda que 0 é bpp nativo, a opengl não. Talvez você possa colocar um if aí para, se o valor for 0, você procurar na SDL que valor ela detectou.
-
10/01/2012 15:04:05 |201.24.231.xxx| mateus - transformações

Para fazer transformações com duas dimensões,
seria necessário apenas de uma matriz 2X2, certo?
Se for como seria a matriz de translação, rotação (eixo Z) e escala?
E para encontrar as coordenadas de um ponto bastaria
multimplicar a matriz transformação pela transposta do vetor de coordenadas, nessa ordem?
obrigado.
-
10/01/2012 15:15:01 | Vinícius Godoy de Mendonça

Uma matriz 2x2 é apenas capaz de fazer transformações lineares. Portanto, ela não faz translações. Você pode fazer:
- Rotação
- Reflexo
- Escala
- Shearing (itálico).Se você quiser fazer translação, rotação e escala, você deve usar uma matriz 3x3. Note que é uma matrix 3x3 que representa operações bidimensionais, pois ela trabalha no sistema de coordenadas homogêneo.
Para obter uma matriz que faz ao mesmo tempo rotação, translação e escala, basta multiplicar matrizes que fazem cada operação dessas individualmente. O resultado, é uma matriz 3 em 1, que faz todas as operações ao mesmo tempo.
Para obter as coordenadas de um ponto, você deve então multiplicar o vetor do ponto por essa matriz. A ordem exata da multiplicação depende se seu sistema de cordenada é da mão direita ou esquerda.
Se for o da mão direita a multiplicação é geralmente feita na ordem: translação, rotação, escala. Se for da mão esquerda, é ao contrário escala, rotação e translação.











Muito bom o resumo. seria bom se postassem mais exercícios.