Ponto V!

Home WebGL Transformações Model, View e Projection
Vinícius Godoy de Mendonça
Transformações Model, View e ProjectionImprimir
Escrito por Vinícius Godoy de Mendonça

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.

    Sistema de coordenadas exatamente no centro da vaquinha.

  • 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.

    Sistema de coordenadas do mundo, aparecendo no canto direito. Cena posicionada segundo esse sistema

  • Coordenadas da visão: Trata-se do sistema de coordenadas do observador. Esse sistema está centralizado na câmera. A posição onde a camera está passa a ser encarada como a posição (0,0,0) do mundo. A direção para onde a câmera olha passa a ser encarada como o eixo z.

Sistema de coordenadas da câmera. As coordenadas aparecerem exatamente no centro da cena.

  • Coordenadas de projeção: São as coordenadas no sistema final, da WebGL (o mesmo que trabalhamos no artigo passado). Nesse sistema os eixos x, y e z variam no intervalo máximo de –1 até 1. As coordenadas x e y já devem considerar o impacto causado pela transformação do eixo z – portanto, o cálculo de projeção já deverá ter sido realizado. Tudo o que estiver fora desse intervalo não será desenhado.

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á.

É 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:

Na ordem de rotação seguida de translação para a direita, a figura orbita em torno do ponto central. Na outra ordem, a figura vai para a direita e só depois gira.

Não pense nisso como um problema, mas sim, como uma vantagem. Significa dizer que se você der um comando para desenhar um objeto a direita de outro, ele sempre será desenhado nessa posição. Mesmo que você rotacione o objeto “pai”, a “nova direita” do objeto filho ficará nessa linha rotacionada. Leva-se mesmo um tempo para se acostumar com esse conceito. Por hora, decore a regra prática de que a ordem para realizar as transformações no sistema de coordenadas da mão direita, usado pela WebGL, é a de translação, rotação e escala.

No caso do nosso triângulo, vamos criar no método draw matriz chamada Model, que conterá uma translação para direita e uma leve rotação em torno do eixo z:

//Configura a matriz do modelo
var model = mat4.create();
mat4.translate(model, model, vec3.fromValues(0.5, 0, 0));
mat4.rotateY(model, model, toRadians(45));

A função 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 toDegrees para a conversão de graus para radianos:

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 (left, right, bottom, top, near e far) devem ser fornecidas:

    A projeção ortogonal tem a forma de um cubo
  • 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).

A projeção em perspectiva lembra uma pirâmide seccionada.

Um parâmetro confuso para os iniciantes é o campo de visão. Ele pode ser encarado como a “abertura da câmera”. Quando o campo de visão é largo, mais objetos cabem na cena e, portanto, os objetos parecem menores. Usar um campo de visão muito largo poderá ocasionar uma cena estranha, arrendadoda nos cantos – um efeito conhecido como “lente olho de peixe” e onde o jogador terá a sensação de nunca encostar na parede. Com um campo de visão mais curto, menos objetos cabem na cena, dando a impressão que eles são maiores. Um campo de visão muito estreito dará a impressão de que a câmera está sendo vista em zoom, numa teleobjetiva. Via de regra, bons valores para o campo de visão estão entre 45 e 60 graus. A imagem abaixo compara um campo de visão mais largo com um mais estreito:

Diferentes campos de visão: Quanto maior o ângulo, menor o zoom aparente dos objetos.

Esteja apenas atento que a função especifica a abertura em y, e não em x, como seria mais natural.

No caso de nossa aplicação, vamos configurar a projeção como:

//Configura a matriz de projeção
var projection = mat4.perspective(mat4.create(),
    toRadians(45),          //Abertura
    gl.width / gl.height,   //Aspecto
    0.1, 100.0              //Near e far
);

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 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:

Imagem final: Triângulo em perspectiva

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á!


Comentários (2)
  • Wagner Toscano  - Informação
    avatar

    Muito bom e didático seu tutorial, estou utilizando após ter lido "http://webglfundamentals.org/" .

    Por enquanto apenas uma observação: O link para download apesar de indicar exemplo3.zip, contém exemplo2.zip.

  • Ronaldow
    avatar

    O exemplo pra download é o mesmo do capítulo 2 :/

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter