No artigo passado explicamos como desenhar um simples triângulo na tela. Entretanto, isso foi feito usando o sistema de coordenadas padrão, que mapeia a tela em intervalos de 1 até 1 em cada eixo. Esse sistema, embora simples, não é muito conveniente para o desenho: como a tela é mais larga do que alta, ele acaba não sendo proporcional. Além disso, o ideal seria que conseguíssemos fornecer individualmente as coordenadas de cada objeto e da câmera, em 3D. Tudo isso é possível graças as matrizes de transformação, que serão vistas neste texto. Sistemas de coordenadas Antes de falarmos nas transformações, é importante falarmos nos sistemas de coordenadas tradicionalmente encontrados quando criamos um mundo em 3D. - Coordenadas do modelo: É o sistema de coordenadas que o artista utilizou para desenhar o objeto na ferramenta de modelagem 3D. Nesse sistema, os eixos coordenados estão centralizados no ponto de rotação do modelo, normalmente seu centro. Esse sistema sempre estará alinhado com o modelo, não importanto sua posição no mundo. Ou seja, se x e z forem os eixos do chão, um ponto em +y sempre será estará acima do modelo, em +x sempre será a esquerda do modelo e em +z sempre estará para frente do modelo. - Coordenadas do mundo: Trata-se do sistema de coordenadas onde todos os objetos do mundo estão. É nesse sistema que geralmente pensamos ao posicionar objetos na cena. Nesse sistema de coordenadas nos preocupamos com detalhes como a escala dos objetos, a posição de um em relação a outro, etc. 1 / 11
sistema como - Coordenadas a o posição eixo está z. centralizado (0,0,0) da visão: na mundo. câmera. Trata-se A direção A do posição sistema para onde a coordenadas a camera câmera está olha do passa observador. a a encarada Esse máximo transformação que - trabalhamos estiver Coordenadas 1 fora até do desse no eixo 1. artigo de As intervalo z projeção: coordenadas portanto, passado). não São o será Nesse x cálculo as e desenhado. y coordenadas já sistema devem projeção os considerar eixos no já sistema deverá x, y o e impacto final, z ter variam sido da causado realizado. WebGL no intervalo pela (o Tudo mesmo o 2 / 11
Transformando coordenadas De nada adianta conhecer sobre os sistemas coordenados, se não podemos converter as imagens que estão num sistema em outro. Iremos fornecer nosso triângulo em coordenadas de modelo mas precisamos que, ao desenhá-lo, ele esteja em coordenadas de projeção. A ferramenta matemática que faz essa tranformação são as matrizes. Cada vértice da cena será transformado pelas matrizes informadas através da multiplicação de sua posição para matriz indicada. Se você não conhece bem como esse processo funciona, leia nossos artigos sobre matrizes e transformações. Em JavaScript, representaremos matrizes através de um Float32Array. Como estamos trabalhando com coordenadas 3D, e precisamos de transformações afins, a WebGL sempre considera matrizes como possuindo dimensão 4x4. Porém, trabalha com matrizes com uma única dimensão. Portanto, os índices 0 até 3 do array representam a primeira linha da matriz, os índices 4 até 7 a segunda linha, 8 até 11 a terceira e 12 até 15 a quarta linha. Para facilitar o trabalho com matrizes, iremos utilizar a biblioteca de matemática glmatrix, que possui diversas funções convenientes. Transformação Model > World A primeira transformação relevante é a transformação das coordenadas de modelo em coordenadas do mundo. A matriz responsável por essa transformação é geralmente chamada de matriz Model ou matriz World. É importante ressaltar que haverá uma matriz Model para cada objeto da cena. Portanto, podemos concluir que ela faz parte das propriedades da nossa malha. A transformação de modelo é subdividida em três transformações diferentes: - Translação: A transformação de translação afasta o sistema de coordenadas. É através dela que alteramos a posição em que o objeto será desenhado. - Rotação: Permite que giremos o eixo coordenado. Como consequencia, o objeto desenhado também girará. - Escala: Permite alterar a distância entre os pontos do eixo coordenado. Como resultado final, a escala do objeto dentro desse eixo também mudará. 3 / 11
É importante entender que as operações ocorrem sobre os eixos coordenados, não sobre os objetos. O objeto continuará sendo desenhado com os mesmos comandos, usando as mesmas coordenadas originais. Se isso ficou confuso, considere a seguinte as duas formas que você poderia fazer para desenhar um objeto rotacionado: 1. Você poderia desenha-lo já rotacionado. Isto é, adotar novas coordenadas para o objeto; 2. Você poderia desenhá-lo de pé, como sempre desenhou. Porém, antes de fazer o desenho, você rotaciona a folha de papel, ou seja, todo o seu sistema coordenado. Quando a folha for colocada de pé novamente, o objeto estará rotacionado. As matrizes trabalham é com essa segunda alternativa. É importante entender isso, pois as operações ocorrem sobre os eixos. Por exemplo, ao fazer uma translação, ela ocorrerá sobre o eixo da forma que está configurado. Isso quer dizer, que a ordem de uma translação e uma rotação é relevante, observe: 4 / 11
Não você nessa hora, coordenadas No uma vec3.fromvalues(0.5, A //Configura função caso pense translação der decore posição. linha do um nisso rotacionada. nosso a comando matriz regra Mesmo para mão como triângulo, prática do direita, 0, para um que 0)); modelo Leva-se problema, e desenhar você vamos mat4.rotatey(model, usado uma que var rotacione mesmo leve criar a pela model ordem mas um rotação no WebGL, um objeto sim, o = método para objeto tempo mat4.create(); como a é realizar model, direita torno a pai, draw para uma translação, do de toradians(45)); se matriz a as vantagem. nova eixo outro, acostumar transformações mat4.translate(model, chamada z: direita ele rotação Significa sempre com do Model, esse objeto no e será dizer escala. sistema que conceito. filho model, desenhado que conterá ficará Por mat4.create() da cria uma matriz 4x4 inicializada com a identidade. A matriz identidade representa a ausência de transformações. A função translate funciona da seguinte maneira: 1. Ela cria uma matriz de translação para o vetor indicado no terceiro parâmetro; 2. Essa matriz é multiplicada pela matriz indicada no segundo parâmetro (matriz de origem); 3. O resultado é armazenado na matriz indicada no primeiro parâmetro. A função mat4.rotatex() funciona de forma similar. O único detalhe adicional é que o terceiro parâmetro é o ângulo em radianos. Para fornecê-lo, criamos a função odegrees para a conversão de graus para radianos: t 5 / 11
function toradians(degrees) { return degrees * Math.PI / 180; } Transformação de World > View Precisamos agora posicionar nossa câmera em algum local do mundo. A matriz de transformação da câmera é formada a partir de três vetores: 1. A posição onde a camera está, denominado vetor eye; 2. O ponto para onde a câmera está olhando, chamado de vetor look; 3. A direção que representa para cima da câmera, conhecido como vetor up; No caso da nossa cena, é suficiente afastar a câmera um pouco para trás. Como a câmera olha para o z, significa que devemos afasta-la em +z. Ela continuará olhando para o ponto central da cena, ou seja, a coordenada (0,0,0). Além disso, a direção que representa para cima continua sendo +y, portanto, seu vetor de direção será (0,1,0). A função responsável por criar a matriz da câmera é a função mat4.lookat(). O primeiro parâmetro dessa função é a matriz que será multiplicada, nesse caso, usaremos a identidade. A função retorna a matriz criada: //Configura a matriz da camera var view = mat4.lookat(mat4.create(), vec3.fromvalues(0.0, 0.0, 5.0), //Onde está vec3.fromvalues(0.0, 0.0, 0.0), //Para onde olha vec3.fromvalues(0.0, 1.0, 0.0) //Onde é "para cima" ); Transformação View > Projection Por fim, precisamos definir qual será a projeção utilizada para transformar as coordenadas 3D em 2D. É nessa etapa que também definimos qual é a área que será visível para a câmera. Existem dois tipos de projeção: - Projeção ortogonal: Similar a projeção de games isométricos, onde objetos mais distante não parecem menores em relação a câmera. A área dessa projeção é um cubo, chamado de viewing cube. Um uso comum para esse tipo de projeção é desenhar o HUD do jogo (vidas, pontos, bússola, etc). Para isso, mapeamos uma projeção com largura e altura exatamente igual a da tela, fornecendo assim objetos com coordenadas de pixels. Projeções em ortogonais são criadas com a função mat4.ortho(), e as 6 coordenadas do cubo ( 6 / 11
left, right, bottom, top, near e far ) devem ser fornecidas: - Projeção em perspectiva: Faz o cálculo do quão menores os objetos parecerão em relação a câmera. A área dessa projeção é uma pirâmide seccionada, chamada frustum. Essa projeção é criada com a função mat4.perspective() e devem ser fornecidos o ângulo de visão em y ( fovy ), a taxa de aspecto (relação entre a altura e largura da cena), a distância mínima (plano near ) e máxima da visão (plano far ). 7 / 11
Um abertura portanto, uma e curto, visão teleobjetiva. imagem onde parâmetro cena muito menos o abaixo jogador da estranha, estreito Via câmera. objetos confuso compara de terá regra, dará parecem arrendadoda cabem a Quando para sensação a um bons impressão os na campo menores. o iniciantes valores cena, campo nos de de nunca de dando cantos visão para Usar de que é visão encostar o um mais a campo impressão um câmera é efeito largo, de na de está visão. com parede. conhecido mais que visão sendo um eles Ele objetos estão Com mais muito pode são vista como entre um estreito: cabem maiores. largo ser campo lente 45 encarado zoom, poderá e na 60 olho Um cena numa graus. visão ocasionar campo de como e, peixe Amaisa de 8 / 11
Esteja natural. No toradians(45), far //Configura caso ); apenas de nossa a atento matriz aplicação, //Abertura que de projeção a função vamos gl.width especifica var configurar projection / gl.height, a a abertura projeção = mat4.perspective(mat4.create(), //Aspecto em como: y, e não 0.1, em 100.0 x, como seria mais //Near e Aplicando as transformações As três matrizes já estão criadas, mas como aplicá-las em nossa figura? Quem tem o papel de multiplicar as matrizes pela coordenada dos vértices é o Vertex Shader. Como citamos no artigo anterior, essa é a principal tarefa desse shader. Para que ele possa cumpri-la, precisaremos modificar o shader do artigo passado para receber as três matrizes como parâmetro. Como as matrizes não mudam de vértice para vértice, faremos isso através da definição de variáveis uniformes. A linguagem GLSL suporta o tipo de dado mat4, representando uma matriz 4x4. O código do Vertex Shader em si então deve ser alterado para multiplicar a posição do vértice pelas matrizes em sequência: attribute vec3 avertexposition; uniform mat4 umodel; uniform mat4 uview; uniform mat4 9 / 11
uprojection; void main(void) { gl_position = uprojection * uview * umodel * vec4(avertexposition, 1.0); } Como a WebGL trabalha com o sistema de coordenadas da mão direita, observe que a multiplicação de matrizes teve de ser realizada na ordem contrária as transformações. Agora precisamos alterar o código da função initshaders() para colocar o endereço dessas três variáveis em nosso shader program. Para isso, acrescentamos essas três linhas ao final da função: shaderprogram.projection = gl.getuniformlocation(shaderprogram, "uprojection"); shaderprogram.view = gl.getuniformlocation(shaderprogram, "uview"); shaderprogram.model = gl.getuniformlocation(shaderprogram, "umodel"); O último passo é fornecer as matrizes model, view e projection para o shader. Fazermos isso na função draw, antes de copiar os dados do buffer para a placa: //Atualiza os valores do shader gl.uniformmatrix4fv(shaderprogram.projection, false, projection); gl.uniformmatrix4fv(shaderprogram.view, false, view); gl.uniformmatrix4fv(shaderprogram.model, false, model); Observe que aqui utilizarmos a função gl.uniformmatrix4fv. Essa função recebe três parâmetros: O índice da variável do shader que será modificada, um boolean indicando se as coordenadas serão transformadas. Em webgl, esse valor deve ser sempre false. E o último parâmetro refere-se ao array contendo a matriz sendo atribuída. O nome estranho dessa função segue a uma convenção, que data desde a OpenGL 1.0. O nome da função (uniformmatrix) é seguido da quantidade de parâmetros (4), seu tipo float (f) e o fato de ser um array (indicado pela letra v). Esse padrão é útil pois algumas funções vem em várias versões. Após essas modificações, o seguinte resultado será obtido: 10 / 11
Download Clique no ícone abaixo para fazer o download do código completo. Observe na pasta.js que o arquivo gl-matrix-min.js foi incluído no projeto. Trata-se da biblioteca gl-matrix. Esse arquivo também passou a ser referenciado no source do index.html. Concluindo Nesse artigo vimos como utilizar matrizes de transformação para posicionar objetos na cena, configurar nossa câmera. e nossa projeção. Vimos também como aplicar as transformações no Vertex Shader.No próximo artigo veremos como alterar nosso pixel shader para colorir o triângulo. Até lá! 11 / 11