Bem vindo ao meu segundo tutorial de WebGL! Esta lição é baseado na lição 2 do site LearningWebGL. Vamos agora adicionar cores na cena. Veja o resultado:
Este tutorial foi feito baseando-se no conteúdo das aulas de Computação Gráfica
No tutorial 02, falei que o boa parte do código entre os tutoriais são similares. E m resumo é:
- Criar a estrutura HTML com o canvas e associações a arquivos externos (código javascript, por exemplo);
- Definir os shaders de vértices e de fragmento, dentro da tag
<script>
com os tipos "x-shader/x-vertex" e "x-shader/x-fragment"; - Inicializar um contexto WebGL;
- Carregar os shaders em um programa WebGL usando as funções que criamos
getShader
einiciarShaders
; - Definir as matrizes Model-View-Projection (MVP) e enviá-los para a GPU usando a função
setMatrixUniforms
; - Carregar os buffers com os dados dos vértices (posição, cor, textura...), usando nossa função criada
iniciarBuffers
; - Por fim, definir a função iniciaWebGL que chamará as outras funções em sequência;
O que vamos mudar para esse tutorial são os shaders (onde a cor será referenciada), a função iniciarBuffers (onde ela será guardada) e desenharCena (para ativar o atributo cor
do vértice).
O diagrama ao lado mostra como os dados em JavaScript são transformados em pixels para serem desenhados no canvas WebGL. No mais alto nível, o processo trabalha assim: a cada chamada de uma função de desenho como drawArrays
, WebGL processa os dados que você enviou para os buffers como atributos (como as posições do tutorial 2) e uniforms (como as matrizes MVP) e passa para o shader de vértices.
O shader de vértice é executado para cada vértice paralelamente, enquanto tiver núcleo disponível na GPU. Cada vértice tem seus atributos diferentes entre eles, mas eles têm os mesmos uniforms. O shader aplica a transformação do modelo primeiramente, depois a transformação da câmera e por último a transformação da projeção. Na verdade, você que deve ditar como estas transformações serão realizadas no shader; e não estás limitado a essa sequência: você pode não usar a matriz de câmera, ou então mudar a ordem (mas os efeitos são diferentes).
O resultado do shader de vértice são as variáveis denominadas "varying". Elas não são passadas para a CPU, e sim para o shader de fragmento. Antes disso, os vértices precisam se transformar em fragmentos (pixels candidatos), inclusive seus intermediários, através de uma interpolação. Quem conecta os vértices é o processo de montagem, e quem usa essa conexão para criar pixels interpolados é o rasterizador.
Depois os fragmentos são passados para o shader de fragmento, onde serão coloridos, praticamente é onde você realiza os efeitos de iluminação para dar a cor final. Essa cor é jogada na variável varying chamada gl_FragColor.
Depois de finalizado o shader de fragmento, os pixels da tela são guardados no Frame Buffer, que será usado para desenhar na tela (podemos utilizar essa imagem não para ser desenhada na tela, mas para texturizar um outro objeto, isso em outro tutorial). Então o objetivo dos shaders, no final das contas, é definir a cor do pixel do fragmento.
Podemos enviar não apenas a posição do vértice, mas também outros atributos. Para cada atributo, criamos um buffer (na verdade podemos criar apenas um buffer com todos os atributos, mas por enquanto vamos dividir os problemas). Podemos enviar a cor no shader de vértice para o shader de fragmento. E ganhamos de presente a possibilidade de figuras com gradiente de cores.
Por quê? Devido à interpolação de vértices, eles interpolam não apenas a posição, mas qualquer varying que for enviada do shader de vértice para o shader de fragmento. O resultado: o gradiente de cores do triângulo do resultado deste tutorial, por exemplo.
Vamos então ao código. Trabalharemos com modificações a partir do código da lição 1.
Tarefa: Copie a pasta da lição 1 e renomeie para lição 2. Modifique o shader de vértice para ficar desta forma:
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Por que um vetor de 4 valores para a posição: As projeções trabalham com coordenadas homogêneas, cujo sistema de coordenadas \(\mathbb{P}^n\) é isomórfico ào sistema de coordenadas \(\mathbb{R}^{n+1}\). Precisamos da quarta coordenada para realizar o efeito de perspectiva e para diferenciar um ponto de um vetor (o vetor tem valor 0.0 na 4ª coordenada e não é afetada pela translação, pois ele não tem posição). Outra característica é que a translação não é uma operação linear. Todavia, com uma matrix homogênea, a translação passa a ser linear.
Agora nós temos 2 atributos (posição e cor). A cor é um vetor 4x1 pois estamos representando o espaço de cores RGBA (red-green-blue-alpha). Por enquanto não estamos usando transparência. Deixaremos para outro tutorial.
Na função principal do shader, só adicionamos a atribuição do varying cor (que será passada para o fragment shader) pelo atributo (que foi passado pela CPU). Após esse ponto, a interpolação será realizada e as cores serão também interpolados entre os vértices do mesmo polígono. O shader de fragmento fica assim:
21 22 23 24 25 26 27 28 29 |
|
Depois de definir a precisão do ponto flutuante (existem highp que demanda mais computação e lowp, mas não use highp pois não é bem suportado), estamos declarando o varying que o shader está recebendo do vertex shader. A saída gl_FragColor é a cor final do pixel. Estamos apenas passando diretamente para a saída (poderíamos trabalhar os valores antes de passar para a saída, e vamos fazer isso posteriormente).
Existem duas outras modificações (iniciarShaders e desenharCena). As mudanças estão comentadas:
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
|
Estamos querendo saber a referência para o atributo 'cor' no shader. Vamos usar ele posteriormente. É preciso habilitá-lo também.
A próxima mudança se dá na função desenharCena, mas antes precisamos declarar as variáveis dos buffers de cores no JavaScript. Onde tiver os buffers, adicione as linhas comentadas:
38 39 40 41 |
|
Vamos agora inicializar os buffers de cores. Modifique a função iniciarShaders
:
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
|
O que fizemos para o buffer de posição fizemos para o buffer de cores. Criamos um buffer, dizemos ao WebGL para utilizar esse buffer nas próximas operações. Criamos um vetor de dados e passamos os dados para o buffer e salvamos o tamanhos de cada atributo e o número deles (3 cores RGBA).
Para o quadrado, é o mesmo, porém vamos usar apenas uma cor. Então criamos um laço para repetir a cor para os 4 vértices.
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
|
Agora vamos mudar a função desenharCena
.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
|
Lembre-se de adicionar antes da função drawArrays
. A próxima modificação é: não há mais modificação. É só isso.