Bem vindo ao tutorial número 3 nessa série de tutoriais WebGL. Neste momento vamos deixar o mundo do estático e irmos para o mundo dos movimentos. Por enquanto estaremos o mesmo triângulo e quadrado, planos, mas não se preocupe. No próximo tutorial iremos trabalhar com formas tridimensionais. O tutorial é baseado na lição 3 do LearningWebGL.
Veja o resultado obtido:
Um aviso: estas lições estão baseadas no conteúdo dado na disciplina de Introdução à Computação Gráfica do Instituto de Matemática e Estatística da USP. Mesmo assim, outras pessoas que não sejam alunos dessa disciplina podem aproveitar e compreender o conteúdo destes tutoriais. Se você não fez o tutorial 02 e o tutorial 03, recomendo fazê-lo antes de avançar para este tutorial. Se você se sente seguro em compreender o que se passa aqui, pode continuar. Se houver falhas ou achar que falta alguma coisa para melhorar o tutorial, não hesite em me avisar.
Para o OpenGL, existem diversas bibliotecas gráficas (como GLUT, GLFW, QT, SDL e WxWidgets) que promovem um gerenciamento de janelas e modos de desenhar a imagem nessas janelas. No WebGL, precisamos utilizar o JavaScript. Para fazer animação, precisamos a cada instante atualizar nossa tela de desenho. Então precisamos chamar a função desenharCena
constantemente.
Com que frequência? Você escolhe. Você pode esperar a cada segundo, para desenhar uma cena, pode desenhar 30 imagens dentro desse segundo, ou então desenhar logo que a imagem ficar pronta e disponível. O JavaScript contém funções como requestAnimFrame(func)
que recebe uma função que será chamada tão logo quanto possível. E se dentro da função func
estiver justamente o requestAnimFrame(func)
? Então a função func
será regularmente chamada, e podemos utilizar esse recurso para fazer nossa animação. Alguns navegadores preferem criar suas próprias funções. Por exemplo, a Mozilla (do Firefox) disponibiliza a função mozRequestAnimationFrame
. Para evitar que nosso ambiente seja incompatível com algum navegador, existe um código feito para resolver a incompatibilidade. Ele está disponível no WebGLSamples, um repositório de exemplos no Google Code. O arquivo é o webgl-utils.js.
31 32 33 34 |
|
Tarefa: Modifique a função iniciaWebGL
da forma como abaixo.
62 63 64 65 66 67 68 69 70 71 |
|
Removemos a função desenharCena (ela vai ser chamada dentro do tick()
) e inserimos a função tick()
. A função tick()
programará a sua próxima chamada atraveś do requestAnimFrame
.
E a função setInterval
do Javascript? A função setInterval
programa a chamada de uma função depois de um determinado tempo, e isso repetidamente. Ele também pode ser usado para animações. Porém ele tem um problema: mesmo que você não esteja vendo a animação (está em outra aba por exemplo acessando outras coisas), a função é executada, piorando o desempenho de sua navegação, especialmente quando você tem mais de uma aba com animações WebGL (a execução de uma animação pioraria a execução da outra). A função requestAnimFrame
só é chamada quando a aba estiver ativada.
Tarefa: Adicione a função tick()
no script em JavaScript:
72 73 74 75 76 77 |
|
Ele programa sua próxima execução, desenha a cena e atualiza informações para animação (por exemplo, incrementando o ângulo de rotação). Você pode, se desejar, trocar a ordem das funções (atualizar os dados antes de desenhar). Ah, e já que vamos rotacionar, vamos então guardar o ângulo de rotação em uma variável.
Tarefa: Adicione estas variáveis globais (fora de qualquer função):
50 51 |
|
Eles serão usados para rotacionar os objetos. A cada tick, iremos incrementar o ângulo. Variáveis globais não são uma boa prática quando for trabalhar com projetos mais complexos. Teremos tutoriais em que organizaremos melhor estas variáveis.
A próxima mudança se dá em desenharCena
. Logo após a translação, faremos a rotação dos objetos. Vamos fazer rotações diferentes para cada objeto. Então precisamos guardar a matriz antes da rotação do triângulo para recuperar a matriz original e rotacioná-lo para o quadrado. Se não houver essa operação guardar/recuperar, as rotações serão combinadas. Então o quadrado sofrerá a rotação do triângulo e dele (nessa ordem). Não queremos isso.
Tarefa: Modifique a função desenharCena
(a parte do triângulo):
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
|
Digamos que nós temos a matriz \(T_t\) que representa a translação do triângulo, e \(R_t\) a sua rotação. A matriz \(R_tT_t\) representa a translação e rotação do triângulo (a ordem é da direita para a esquerda, como uma composição de funções matemáticas). Para o quadrado, poderíamos aplicar a rotação inversa do triângulo para neutralizar sua rotação (\(R_t^{-1}R_tT_t\)) e depois a traslação do quadrado e sua rotação (\(R_qT_qR_t^{-1}R_tT_t\)). Mas veja que \(R_t^{-1}R_t = I\). E por isso a transformação do quadrado é \(R_qT_qT_t\). Ou seja, antes da translação do quadrado, só basta a translação do triângulo. Então guardamos a transformação \(T_t\), e depois de rotacionar o triângulo, substituímos a transformação atual \(R_tT_t\) pela matriz salva \(T_t\), utilizando uma estrutura de pilha. Dessa forma, não precisamos aplicar a rotação inversa.
Permita-me explicar essa função de rotação: os ângulos precisam ser convertidos para radianos (mostrarei a função degToRad
depois, ele é bem simples). Para o triângulo, utilizamos a nossa variável rTri
. Vamos rotacionar a partir do eixo \(y = [0,1,0]^T\). Para saber como ele será rotacionado, vou te dar uma dica: usando a mão direita (estamos utilizando a regra da mão direita como sistema de coordenadas), coloque o seu polegar oapontado para o eixo (nesse caso o y) e os outros dedos curvados darão o sentido da rotação. Olha a figura abaixo:
Tarefa: Modifique a função desenharCena
(agora para o quadrado):
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
|
Isso é tudo para a função desenharCena
. Vamos agora atualizar os ângulos para o próximo desenho.
Tarefa: Adicione a função animar()
.
268 269 270 271 272 273 274 275 276 277 278 279 |
|
Poderíamos atualizar o ângulo simplesmente com rTri += valor_a_incrementar
e rQuad += valor_a_incrementar
. Mas há um problema: máquinas mais rápidas rotacionarão o triângulo mais rápido do que máquinas mais lentas (pois elas incrementarão o ângulo mais rapidamente). E como sincronizá-los para ter a mesma animação independente da velocidade da máquina? Usamos a duração entre os frames para servir de peso para a rotação: se a máquina é lenta, então a duração entre os frames é maior, então rotacionaremos o proporcional a essa duração; se a máquina for rápida, a duração entre os frames é menor, então faremos pequenas rotações. No final, em um segundo, tanto a máquina rápida quanto a máquina lenta terão a mesma rotação.
Ah, adicionamos novas funções mPushMatrix
e mPopMatrix
. Eles simplesmente trabalham com uma pilha, retirando e colocando matrizes.
Tarefa: Adicione a pilha (variável global) e as funções mPushMatrix
e mPopMatrix
.
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
|
A função mPushMatrix
copia a matriz atual mMatrix
e guarda na pilha. A função mPopMatrix
devolve a matriz guardada no topo da pilha para a mMatrix
.
Lembra da função degToRad? Ela é apenas uma linha de código, em que \(rad = \frac{PI}{180}\times graus\).
Tarefa: Adicione a função degToRad
:
1 2 3 |
|
Pronto. A função tick
chamará o desenharCena
que rotacionará os objetos, e o animar
que atualizará os ângulos. Fim das modificações. Agora vamos dar um corpo tridimensional nesses polígonos no tutorial 05