Ponto V!

Home WebGL Uma malha de quadrados
Vinícius Godoy de Mendonça
Uma malha de quadradosImprimir
Escrito por Vinícius Godoy de Mendonça

No artigo entitulado “Um quadrado com Index Buffer” propusemos um desafio: o de construir uma malha quadriculada para o desenho de um chão. A função de construção da malha deveria receber como parâmetro o número de vértices na profundidade e largura, além de gerar a geometria centralizada no ponto (0,0). Nesse artigo, iremos resolver esse desafio.

Entendendo a geometria

Antes de iniciarmos, vamos ver um wireframe esquemático da malha que queremos construir:

Uma malha de 10x5 vértices

Na figura acima, temos uma malha contendo 10 vértices de largura por 5 de profundidade. Observe que o número de quadrados será sempre um a menos, tanto na largura quanto na profundidade. Assim, podemos tirar algumas relações:

  1. Para um conjunto de WxD vértices, teremos (W-1)x(D-1) quadrados
  2. Como a distância entre os pontos é 1, as dimensões da malha também será (W-1)x(D-1). Portanto, para centraliza-la, teremos que subtrair metade dessa valor das coordenadas de x e z;

Criando o array de vértices

Vamos iniciar com a função createData que fará a criação dos vértices. Nela, iremos criar um objeto chamado data, que conterá dentro dele o array de vértices e índices, respectivamente. Como podemos criar vértices lado-a-lado e um abaixo do outro, podemos simplesmente encadear dois for:

function createData(width, depth) {
    var data = {}
    data.vertices = [];    
    
    //Centralização da malha
    var hw = (width-1) / 2;
    var hd = (depth-1) / 2;
   
    for (var z = 0; z < depth; z++) {
        for (var x = 0; x < width; x++) {        
            data.vertices.push(x - hw);
            data.vertices.push(0);
            data.vertices.push(z - hd);
        }
    }

    return data;
}

Observe que, se quiséssemos criar a malha iniciando no ponto (0,0) os valores das coordenadas x e z seriam exatamente iguais ao das variáveis x e z. Porém, para que a malha fosse centralizada aplicamos o que foi descrito na observação 2.

Criando o array de índices

Se você tentou fazer esse desafio, provavelmente este é o local onde você quebrou a cabeça. Quais são os índices corretos?

Criaremos os índices quadrado-a-quadrado. Como podemos observar na figura abaixo, cada quadrado é formado por 2 triângulos. Esses dois triângulos, compõe 6 índices sendo dois deles compartilhados.

Por exemplo o quadrado abaixo:

Exemplo de quadrado

É formado pelos triângulos descritos pelos índices 0, 3, 1 e 0, 2, 3. Para esse quadrado, estão duplicados os índices 0 e 3.

Lembre-se que devemos fornecer os índices no sentido anti-horário.

Observe que se considerarmos o ponto 0 como a vértice de coordenadas (x,z), as coordenadas desse quadrado seriam:

  1. (x, z)
  2. (x+1, z)
  3. (x,z+1)
  4. (x+1, z+1)

Portanto, os dois triângulos que descrevem esse quadrado seriam dados por:

0. (x, z)
3. (x+1, z+1)
1. (x+1, z)
0. (x, z)
2. (x,z+1)
3. (x+1, z+1)

Pronto! Temos a lista de vértices para usar em nosso for. Porém, essa lista tem apenas um pequeno problema. O índice dos vértices é linear, e não bidimensional. Assim, se o for estiver no vértice (5, 3) qual será o índice dele no array de vértices?

Vamos pensar um pouco em como ficam organizados os índices de um array de 4 vértices de largura por 3 de altura:

0 1 2 3
4 5 6 7
8 9 10 11

Observe que em cada linha, temos uma quantidade de vértices igual ao número de colunas (largura). Portanto, convertemos facilmente um índices bidimensional num unidimensional com a fórmula:

coluna + linha * largura

Vamos colocar isso numa tabela?

Conversão dos índices segundo a fórmula

De posse dessas informações, agora basta gerar os fors que preenche os índices de cada quadrado:

for (z = 0; z < depth - 1; z++) {
    for (x = 0; x < width - 1; x++) {
        var zero = x + z * width;
        var one = (x + 1) + z * width;
        var two = x + (z + 1) * width;
        var three = (x + 1) + (z + 1) * width;

        data.indices.push(zero);
        data.indices.push(three);
        data.indices.push(one);

        data.indices.push(zero);
        data.indices.push(two);
        data.indices.push(three);
    }
}    

Desenhando a malha

Vamos agora alterar nosso programa principal para desenhar a malha criada. Vamos alterar o programa de modo a reforçar o conceito de malha poligonal. Entretanto, que conceito é esse?

A definição clássica da computação gráfica para malha poligonal é “o conjunto de vértices e índices que compõe as arestas e faces de um poliedro”. Observe que essa definição não leva em consideração aspectos como iluminação, textura ou a posição exata da malha no mundo. Muitas ferramentas de modelagem e até engines de games incluem esse tipo de informação associada ao conceito de malha e, embora isso não seja 100% correto, não deixa de ser prático. É o que faremos em nossos exemplos também.

Vamos começar criando um objeto chamado mesh, na parte “global” do arquivo de exemplo. Em seguida, iremos criar a função initMesh, em substituição a função initBuffers. Essa função:

  1. Chamará a função createDate para criar a malha de 256x256;
  2. Copiará os dados para os vertex buffers e index buffers;
  3. Associará a malha a matriz model.

function initMesh() {
    var data = createData(256,256);

    mesh.vertexPosition = glc.createBuffer(gl, gl.ARRAY_BUFFER, 3, data.vertices);
    mesh.indices = glc.createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, 1, data.indices);
    
    //Configura a matriz model
    mesh.transform = mat4.create();    
}

Atualizamos a função update para levar em consideração a matriz model da malha:

function update(secs) {
    var speed = 72 * secs;
    if (Key.isDown(Key.SHIFT)) {
        speed *= 3;
    }
    
    if (Key.isDown(Key.LEFT)) {
        angle += speed;
    } else if (Key.isDown(Key.RIGHT)) {
        angle -= speed;
    }
    
    mat4.rotateY(mesh.transform, mat4.create(), glc.toRadians(angle));
  }

E, por último, atualizamos a função drawScene para utilizar os dados vindos da malha. Para que a cena apareça bem, precisamos também alterar a posição de nossa câmera.

Observe novas coordenadas nas matrizes model e projection.

function drawScene() {
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);        
    
    //Desenhamos apenas se o shader e a malha já estiverem prontos
    if (!shaderProgram || !mesh) {
        return;
    }

    //Configura a matriz de projeção    
    var projection = mat4.perspective(mat4.create(),
        glc.toRadians(45), 
        gl.width / gl.height,
        0.1, 1000.0);

    //Configura a matriz view    
    var view = mat4.lookAt(mat4.create(), 
        vec3.fromValues(0.0, 100.0, 400.0),  //Onde está
        vec3.fromValues(0.0, 80.0, 0.0),  //Para onde olha
        vec3.fromValues(0.0, 1.0, 0.0)); //Onde o céu está    

    //Atualiza os valores do shader
    gl.uniformMatrix4fv(shaderProgram.model, false, mesh.transform);
    gl.uniformMatrix4fv(shaderProgram.view, false, view);
    gl.uniformMatrix4fv(shaderProgram.projection, false, projection);    
    
    //Copia dados do buffer
    gl.bindBuffer(gl.ARRAY_BUFFER, mesh.vertexPosition);
    gl.vertexAttribPointer(shaderProgram.vertexPosition, mesh.vertexPosition.itemSize, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mesh.indices);
    
    //Comanda o desenho
    gl.drawElements(gl.TRIANGLES, mesh.indices.numItems, gl.UNSIGNED_SHORT, 0);
}

Por fim, como nosso shader não tem mais informação de cor, iremos criar shaders chamado white.vs e white.fs sem as cores. O fragmente shader simplesmente retornará a cor branca:

white.vs

attribute vec3 aVertexPosition;

uniform mat4 uModel;
uniform mat4 uView;
uniform mat4 uProjection;

void main(void)
{
    gl_Position =
        uProjection * 
        uView * 
        uModel * 
        vec4(aVertexPosition, 1.0);    
}

white.fs

precision mediump float;

void main(void)
{
    gl_FragColor = vec4(1,1,1,1);
}

Concluindo

Nesse artigo vimos como desenhar uma malha mais complexa de quadrados com diversos vértices e índices. Apesar de parecer “bobinha” essa malha é a base para DIVERSOS efeitos gráficos: ondas, terrenos, scanners 3D, etc.

No próximo artigo, veremos como usar essa malha e uma imagem em tons de cinza para carregar um terreno.

Como sempre, é possível ver uma demonstração funcional do exemplo clicando aqui. Ou baixar todos os fontes clicando na figura abaixo:

Até lá!


Comentários (3)
  • António Diego  - Uso do Inglês
    avatar

    Olá Vini.
    Eu te aconselho à usar menos o Inglês e mais Português brasileiro em seus tutorias, já que se ensino se dirige à alunos brasileiros.

  • Wendy Long
    avatar

    This challenge seems complicated and hard, but anyway thanks for sharing guide.
    Epic War 5

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