Ponto V!

Home WebGL Colorindo o triângulo
Vinícius Godoy de Mendonça
Colorindo o triânguloImprimir
Escrito por Vinícius Godoy de Mendonça

No artigo passado, vimos que é possível posicionar o triângulo da forma que quisermos. Neste artigo, vamos explorar outro atributo do vértice: as cores. Porém, as cores tem uma característica diferente das posições: elas preenchem o interior do triângulo. Com isso, para que possamos fazê-las funcionar, precisaremos entender o funcionamento do fragment shader. E, então, mãos a obra?

A cor como uma onda


Antes de iniciarmos o estudo das cores na WebGL, vamos relembrar um pouco nossas aulas de ciências, e entender como o olho humano as enxerga. Assim, ficará mais fácil de entender a abstração desses conceitos criada pelos projetistas da WebGL e será mais fácil programar cores em nossos jogos.

A cor é simplesmente um comprimento de onda de luz que é visível ao olho humano. Em física, aprendemos que a luz é formada não só por uma onda, mas também por uma partícula, chamada fóton. A luz que você enxerga vinda de qualquer origem é, na verdade, formada pela combinações de vários tipos diferentes de fótons.

A distância de duas cristas da onda é chamada comprimento de onda. Os comprimentos de onda da luz visível variam de 390 nanômetros (um bilionésimo de metro) com coloração violeta até 720 nanômetros para a luz vermelha. Esse é o chamado espectro visível da luz. Luzes além desse espectro, chamadas ultra-violeta e o infra-vermelho, não são mais visíveis ao olho humano.

Onda

Quando a luz do sol bate em gotículas de água, vemos as cores desse espectro divididas no céu e em forma de um arco, que chamamos de arco-íris. Nós só conseguimos enxergar objetos coloridos porque a luz é formada de diversas cores, mas apenas algumas delas são refletidas pelo objeto.

A cor como uma partícula

Certo… mas e de onde vem o preto?!? E o branco?!? Afinal essas cores definitivamente não estão no arco-íris. Em primeiro lugar, precisamos entender que o preto não é uma cor… e nem o branco. Na verdade, o preto é a ausência total da luz, e o branco, a presença de todas as cores misturadas. Assim, objetos pretos absorvem toda a cor da luz imposta a eles, enquanto os objetos brancos a refletem totalmente.

Mas… e o marrom?!? Bom, o marrom é mesmo uma cor. Mas não uma cor simples, e sim, uma cor composta. Ele é criado pela combinação de várias cores puras do espectro. Para entender como esse conceito funciona, pense na luz como uma série de partículas. Qualquer objeto atingido pela luz é bombardeado por bilhões de fótons. O objeto, por sua vez, é formado de átomos. A reflexão dos fótons depende do tipo de átomos do objeto, sua ordenação no espaço (e de seus elétrons) e do tipo de átomos se combinando. Muitos arranjos absorvem alguns fótons, geralmente transformando-os em calor (é por isso que carros e roupas pretas esquentam tanto), enquanto outros são refletidos, nem sempre no exato sentido em que chegaram.

Reflexão dos Fótons

O olho – Um detector de fótons

Toda luz refletida que chega ao seu olho é interpretada como cor. Os bilhões de fótons que entram no olho são focados na sua retina, que age como uma espécie de filme fotográfico. Na retina, milhares de células em formato de cone são excitadas quando atingidas pelos fótons, e isso faz com que uma rede de neurônios dispare energia para o seu cérebro, que interpreta a informação como cor e luz. Quanto mais fótons atingem as os cones, mais excitadas ficam as células, e mais brilhante a luz parecerá.

Existem três tipos de células cone no olho. Todas elas respondem a presença de fótons, mas cada uma reage melhor a um determinado tipo de comprimento de onda. Um tipo dessas células as torna mais excitadas por fótons de cor avermelhada, outro por fótons de cor esverdeada e outros de cor azulada. Uma combinação de comprimentos de ondas diferentes, ao mesmo tempo, excitará grupos diferentes de células, criando misturas de cor. Quanto todas as células são excitadas juntas, percebemos a cor branca e, se nenhuma célula é excitada, veremos a cor preta.

É por isso que o padrão de cores usado no computador é chamado RGB, que significa Vermelho, Verde e Azul (Red, Green, Blue). Por exemplo, uma possível combinação de marrom poderia ser formada uma combinação de fótons 10% azul, 30% verdes e 60% vermelhos, como demonstrado abaixo:

A cor marrom

Como no final das contas quem interpreta as cores é o cérebro, a cor que vemos é também sujeita a falhas de interpretação. Isso porque nosso cérebro é capaz de fazer correções, de acordo com o contexto, para nos garantir um entendimento correto da imagem. Assim, estamos sujeitos também as famosas ilusões de ótica, como na figura abaixo, onde os quadrados A e B são exatamente da mesma cor! Se ainda duvida, cole essa imagem no seu paint, recorte um pedaço de um dos quadrados, arraste até o outro e confirme!

Ilusão de ótica - As casas A e B são do mesmo tom de cinza!

A ilusão desse tabuleiro é criada porque seu cérebro, por conhecer a imagem que está tentando enxergar entende o cinza debaixo como um “branco na sombra”, enquanto o de cima é uma casa “preta na luz”. O contraste das casas pretas na sombra e na luz, além do escurecimento gradual das casas adjacentes também ressalta esse conceito.

O computador como um gerador de fótons

Agora que você já é capaz de compreender como seu olho trabalha, faz sentido imaginar que para especificar as cores num computador, você usa intensidades separadas de vermelho, verde e azul. Por anos, os monitores de tubos de raios catódicos (CRTs) dominaram a indústria. Na parte de trás desses monitores existe um canhão de elétrons, que atingem um pequeno ponto com três cores de fósforos (adivinhem quais?), e cada cor brilha de acordo com a intensidade do raio que a atingiu. A tela é formada por milhares desses pequenos pontos.

Tubo de raios catódicos

Como visto o canhão é capaz de atingir apenas um único pixel por tiro. Mas ele faz isso rapidamente, sendo capaz de percorrer a tela inteira dezenas de vezes em apenas um único segundo. A velocidade com a qual o canhão faz esse percurso é chamada de refresh rate (taxa de atualização) e é medida em hertz. Quanto mais rápida for essa taxa, menos “piscante” será a imagem apresentada e mais confortável será a visualização da tela.

Hoje existem também muitos amantes da tecnologia de display de cristal liquido (LCD). As maiores razões disso são as dimensões dos equipamentos e uma taxa de atualização de tela mais rápida. O princípio da geração das cores no LCD não muda. Ele também combina as cores verde, vermelha e azul. Porém, nesse tipo de monitor, existe uma lâmpada branca continuamente acesa e na tela e milhares de polarizadores de cristal liquido, que são capazes de filtrar rapidamente essa luz, deixando passar só a cor desejada.

Cores na WebGL

Os engenheiros que criaram o computador precisavam de uma forma de representar em software uma cor. A solução encontrada foi dar ao programador a possibilidade de especificar a potência dos leds vermelho, verde e azul separadamente. O monitor leria uma área chamada de memória de vídeo que nada mais é do que uma matriz de inteiros contendo um valor para cada pixel da tela. Essa matriz, na WebGL é conhecida como color buffer.

As cores foram divididas em 256 graduações de potência. Esse número não é acidental: é a quantidade de valores que pode ser representada em um byte. Assim, com os 4 bytes de um inteiro, poderiam ser divididos como 1 byte para a cor vermelha, 1 byte para cor verde e um byte para a cor azul. Mais tarde, o byte restante passou a ser utilizado para representar como o hardware misturaria a cor já presente na tela com uma nova cor sendo pintada. Esse byte adicional ficou conhecido como canal alfa.

Tradicionalmente, na web, também representamos as cores no formato RGB, posicionando seus valores na forma hexadecimal, como #fa0b00. Ocasionalmente, também podemos usar a notação decimal, considerando números de 0 até 255. A mesma cor descrita anteriormente seria representada como 250, 11, 00. Na WebGL, a forma de representação tradicional de uma cor é um pouco diferente. Ao invés de passarmos o valor absoluto do byte, utilizamos o percentual do brilho de cada pixel. Portanto, a mesma cor seria representada por 98%, 04%, 0%, ou na forma de floats como 0.98, 0.04, 0.0. No caso da WebGL, também informamos o quarto byte, do canal alfa.

Criando os buffers de cor

As cores também são um atributo do vértice, e portanto, precisamos criá-las na forma de uma matriz e informá-las vértice-a-vértice.

var vertexColors = [
    1.00, 0.00, 0.00, 1.0,
    0.00, 1.00, 0.00, 1.0,
    0.00, 0.00, 1.00, 1.0
];

Criaremos uma variável global colors e atribuiremos a ela o buffer de cores, da mesma forma que fizemos com os vértices:

//Criação do buffer de cores
colors = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colors);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexColors), gl.STATIC_DRAW);
colors.itemSize = 4;
colors.numItems = 3;

Modificações nos shaders

Você deve ter notado que informamos uma cor por vértice. No entanto, o triângulo será inteiramente preenchido. Então, você pode estar se perguntando: como as cores do centro do triângulo serão definidas?

A WebGL mistura as cores do centro pixel-a-pixel de acordo com a proximidade do pixel ao vértice (a rigor, o a influência de vértice é calculada através do sistema de coordenadas baricêntrico). Assim, se um ponto estiver mais próximo do vértice vermelho, ficará mais avermelhado, conforme indica a figura abaixo:

Os três vértices exercem influência sobre o pixel central, mas por estar mais próximo do vermelho sua cor final ficou avermelhada.

Além das variáveis do tipo attribute e uniform os shaders disponibilizam um terceiro tipo, chamado de varying. Os dados apontados por essas variáveis irão sofrer essa mistura ao sair do vertex shader e entrar no fragment shader. Note que eles não precisam ser necessariamente cores. Também interpolaremos outras informações como coordenadas de texturas, normais de luz, etc.

Iniciamos modificando o vertex shader, para incluir nosso atributo aVertexColor e um varying chamado vColor:

attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
        
uniform mat4 uModel;
uniform mat4 uView;
uniform mat4 uProjection;

varying vec4 vColor;

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

Observe que a cor do vértice foi repassada diretamente para o varying. Vemos aqui o segundo papel do vertex shader: definir que informações deverão ser repassadas para o pixel shader de forma interpolada. Não se esqueça de modificar seu javascript para também inserir um ponteiro para o atributo aVertexColor na função initShaders():

shaderProgram.aVertexColor = gl.getAttribLocation(shaderProgram, "aVertexColor");        
gl.enableVertexAttribArray(shaderProgram.aVertexColor);

Falta agora modificar o fragment shader. Precisaremos fazer os seguintes ajustes:

  • Indicar com qual precisão de floats iremos trabalhar: existem 3 precisões possíveis lowp, mediump e highp. Quanto mais preciso for o cálculo, mais caro ele será para ser calculado, porém, menos distorções ele terá. Uma observação importante é que nem todos os hardwares permitem o uso de alta precisão. Se você estiver objetivando um código portável, para uso em celulares, evite o uso de highp. Com o aumento crescente da potência dos aparelhos, essa limitação tende a desaparecer em poucos anos.
  • Receber o varying e utilizá-lo como cor: A variável varying já chegará ao fragment shader interpolada. Nós não precisamos fazer qualquer tipo de cálculo. Portanto, basta a utilizarmos diretamente como cor do fragmento.

Assim, a versão final de nosso fragment shader será:

precision mediump float;

varying vec4 vColor;

void main(void) 
{
    gl_FragColor = vColor;
}

Finalmente, o último passo necessário para o desenho das cores é a atribuição desse buffer antes da pintura. Para isso, basta fazermos exatamente igual já fizemos para os vértices no método drawScene():

    gl.bindBuffer(gl.ARRAY_BUFFER, colors);
    gl.vertexAttribPointer(shaderProgram.aVertexColor, colors.itemSize, gl.FLOAT, false, 0, 0);    

Pronto! Agora é só executar o programa e ver o seguinte resultado aparecer:

Versão final do desenho

Download

Clique no ícone abaixo para fazer o download do código completo.

Resumindo

Neste artigo você aprendeu:

  • Como as cores funcionam na física da luz;
  • Que os monitores nada mais são do que emissores de luzes;
  • Que as cores são representadas como potências dos leds vermelho verde e azul;
  • Que a função secundária do vertex shader é definir que atributos serão interpolados no pixel shader.
  • Que as variáveis varying são usadas para realizar a interpolação.
  • Como juntar tudo isso e fazer seu triângulo ficar colorido.

No próximo artigo, desenharemos um quadrado no lugar do triângulo e veremos como lidar com vértices coincidentes. Até lá!


Comentários (0)
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