Primeiramente, esses artigos são sobre a WebGL2. Se você está interessado na WebGL 1.0 por favor, vá aqui. Observe que a WebGL2 é quase 100% compatível com a WebGL1. Dito isto, uma vez que você habilita a WebGL2, você também pode usá-la como ela deveria ser usada. Esses tutoriais seguem esse raciocínio.
Normalmente, a WebGL é vista como uma API 3D. As pessoas pensam: “eu irei usar a WebGL e como uma mágica, eu vou obter efeitos 3D super legais”. Na realidade não é nada disso, a WebGL é apenas um mecanismo de rasterização. Ela desenha pontos, linhas e triângulos com base no código que você fornece. Colocar a WebGL para fazer qualquer outra coisa depende de você fornecer o código de modo que o uso dos pontos, linhas e triângulos sejam capazes de realizar sua tarefa.
A WebGL é executada diretamente na GPU do seu computador. Como tal, você precisa indicar o código a ser executado na GPU. Você pode indicar o código na forma de pares de funções. Essas 2 funções são chamadas de vexter shader e fragment shader e cada uma delas está escrita em linguagem no estilo C/C++ estritamente tipada chamada GLSL. (GL Shader Language). Juntas, elas são chamadas de programa.
O trabalho da vertex shader é calcular as posições dos vértices. Com base nas posições que a função retornar, o WebGL pode então rasterizar vários tipos de primitivas, incluindo pontos, linhas ou triângulos. Ao rasterizar essas primitivas, ele chama uma segunda função fornecida pelo usuário chama fragment shader (sombreador de fragmento). O trabalho do fragment shader é calcula uma cor para cada pixel da primitiva sendo atualmente desenhada.
Quase toda a API da WebGL se resume em configurar o estado para esses pares de funções a serem executadas.
Para cada coisa que você deseja desenhar, configure um grupo de estados e execute um par de funções, chamando
gl.drawArrays
ou gl.drawElements
que executa seus shaders (sombreadores) na GPU.
Todos os dados que você deseja que essas funções tenham acesso devem ser fornecidas à GPU. Há quatro maneiras de como um shader é capaz de obter dados.
Atributos, Buffers, e Vertex Arrays
Buffers são arrays de dados binários que você carrega na GPU. Normalmente, os buffers contêm coisas como posições, normals, coordenadas de textura, cores de vértices, etc. embora você esteja livre para colocar tudo o que quiser neles.
Os atributos são usados para especificar como tirar dados dos seus buffers e fornecê-los para o seu vertex shader. Por exemplo, você pode colocar posições em um buffer como 3 floats de 32bits por posição. Você poderia dizer a um determinado atributo qual buffer irá extrair as posições, que tipo de ele deve retirar (3 componentes de números de pontos flutuantes de 32 bits), qual offset no buffer as posições se iniciam, e quantos bytes são necessários para obter de um a posição para o próximo.
Buffers não possuem acesso aleatórrio. Em vez disso, um vertex shaders é executado um número específico de vezes. Cada vez que é executado, o próximo valor de cada buffer especificado é puxado , e o seu valor é atribuído a um atributo.
O estado dos atributos, quais buffers usar para cada um e como extrair dados desses buffers, é coletado em um vertex array object (VAO).
Uniforms
Uniforms são, efetivamente, variáveis globais que você configurou antes de executar o seu shader.
Texturas
As texturas são matrizes de dados que você pode acessar aleatoriamente no seu programa de sombreamento. A coisa mais para se colocar em uma textura são dados de imagem, mas as apenas dados e podem facilmente conter algo diferente das cores.
Varyings
As Varyings são uma maneira de um vertex shader passar dados para um fragment shader. Dependendo do que está sendo renderizado, pontos, linhas, ou triângulos, os valores definidos em uma variável por um vertex shader serão interpolados enquanto o fragment shader é executado.
A WebGL se preocupa apenas com 2 coisas. Coordenadas do Clispace e cores. Seu trabalho como programador usando a WebGL é fornecer WebGL com essas 2 coisas. Você fornece seus 2 “shaders” para fazer isso. Um vexter shader que fornece fornece as coordenadas do Clispace e um fragment shader que fornece a cor.
As coordenadas do Clispace sempre vão de -1 a +1, independentemente do tamanho do seu canvas. Aqui está um simples exemplo da WebGL que a mostra em sua forma mais simples.
Vamos começar com um vertex shader
#version 300 es
// um atributo é um input (in) para um vertex shader.
// ele receberá dados de um buffer
in vec4 a_position;
// todos os shaders possuem uma função main
void main() {
// gl_Position é uma variável especial de um vertex shader
// é responsável pela configuração
gl_Position = a_position;
}
Quando executada, se toda a coisa fosse escrita em JavaScript em vez de GLSL você poderia imaginar que isso seria utilizado como o exemplo abaixo
// *** PSUEDO CÓDIGO!! ***
var positionBuffer = [
0, 0, 0, 0,
0, 0.5, 0, 0,
0.7, 0, 0, 0,
];
var attributes = {};
var gl_Position;
drawArrays(..., offset, count) {
var stride = 4;
var size = 4;
for (var i = 0; i < count; ++i) {
// copia os 4 próximos valores do positionBuffer para o atributo a_position
const start = offset + i * stride;
attributes.a_position = positionBuffer.slice(start, start + size);
runVertexShader();
...
doSomethingWith_gl_Position();
}
Na realidade, não é tão simples porque o positionBuffer
precisa ser convertido em dados
binários (veja abaixo) e, portanto, o cálculo real para obter os dados do buffer
seria um pouco diferente, mas espero que isso lhe dê uma ideia de como um vertex shader
será executado.
Em seguida, nós precisamos de um fragment shader
#version 300 es
// fragment shaders não tem uma precisão padrão, então nós precisamos
// escolher uma. highp é um bom valor padrão. Do Inglês "high precision", significa "precisão média"
precision highp float;
// precisamos declarar um output para o fragment shader
out vec4 outColor;
void main() {
// Simplesmente defina o output para um constante com uma cor avermelhada-roxa
outColor = vec4(1, 0, 0.5, 1);
}
Acima, nós declaramos outColor
como um output do nosso fragment shader. Estamos definindo outColor
com os valores 1, 0, 0.5, 1
sendo 1 para vermelho, 0 para verde, 0.5 para azul, 1 para alpha. As cores na WebGL vão de 0 a 1.
Agora que nós escrevemos as duas funções shaders, vamos iniciar com a WebGL
Primeiro precisaremos de um elemento canvas do HTML
<canvas id="c"></canvas>
Então, em JavaScript, podemos obtê-lo da seguinte forma
var canvas = document.querySelector("#c");
Agora podemos criar um WebGL2RenderingContext
var gl = canvas.getContext("webgl2");
if (!gl) {
// sem webgl2 pra você!
...
Agora precisamos compilar esses shaders para colocá-los na GPU, então primeiro precisaremos inseri-los em strings. Você criar suas strings GLSL da maneira que você normalmente cria strings em JavaScript. Por exemplo, concatenando, usando AJAX para obtê-las, colocando-as em tags de script non-javascript, ou neste caso, em literais de templates multilinha.
var vertexShaderSource = `#version 300 es
// um atributo é um input (in) para um vertex shader.
// ele receberá dados de um buffer
in vec4 a_position;
// todos os shaders possuem uma função main
void main() {
// gl_Position é uma variável especial de um vertex shader
// é responsável pela configuração
gl_Position = a_position;
}
`;
var fragmentShaderSource = `#version 300 es
// fragment shaders não tem uma precisão padrão, então nós precisamos
// escolher uma. highp é um bom valor padrão. Do Inglês "high precision", significa "precisão média"
precision highp float;
// precisamos declarar um output para o fragment shader
out vec4 outColor;
void main() {
// Simplesmente defina o output para um constante com uma cor avermelhada-roxa
outColor = vec4(1, 0, 0.5, 1);
}
`;
Na verdade, a maioria dos motores 3D geram shaders GLSL com case em vários tipos de templates, concatenação, etc. Para as amostras neste site, nenhuma delas é suficientemente complexa para precisar gerar GLSL em tempo de execução.
NOTE:
#version 300 es
DEVE SER A PRIMEIRA LINHA DO SEUS SHADER. Nenhum comentário ou linhas em branco são permitidas antes dele!#version 300 es
diz para a WebGL2 que você deseja usar a linguagem de shader da WebGL2, chamada GLSL ES 3.00. Se você não colocar isso como a primeira linha, a linguagem padrão do shader será definida para a da WebGL 1.0, a GLSL ES 1.00 que possui muitas diferenças e bem menos recursos.
Em seguida, precisamos de uma função que irá criar uma shader, faça o upload da fonte GLSL e compile o shader. Note que eu não escrevi nenhum comentários visto que através do nome das funções é fácil compreender o que está acontecendo.
function createShader(gl, type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
Agora podemos chamar essa função para criar os 2 shaders
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
Nós, então, precisamos linkar aqueles 2 shaders em um program
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
E chame isso
var program = createProgram(gl, vertexShader, fragmentShader);
Agora que criamos um programa GLSL na GPU, precisamos fornecer dados para ele.
A maioria da API da WebGL se trata de configurar o estado para fornecer dados aos nossos programas GLSL.
Nesse caso, nossa única entrada para o nosso programa GLSL é a_position
, que por sua vez, é um atributo.
A primeira coisa que devemos fazer é procurar a localização do atributo para o programa
que nós acabamos de criar
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
Procurar posições de atributos (e locais uniformes) é algo que você deve fazer durante a inicialização, e não no seu loop de renderização.
Atributos obtêm seus dados através de buffers, então, nós precisamos criar um buffer
var positionBuffer = gl.createBuffer();
A WebGL nos permite manipular muitos recursos da WebGL em pontos de consolidação global. Você pode pensar em pontos de ligação como variáveis internas globais dentro da WebGL. Primeiro, você vincula um recurso a um ponto de ligação. E então, todas as outras funções ao recurso através do ponto de ligação. Então, vamos vincular o buffer de posição.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
Agora podemos colocar dados nesse buffer, referenciando-o através do ponto de ligação
// três pontos 2d
var positions = [
0, 0,
0, 0.5,
0.7, 0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
Há muita coisa acontecendo aqui. A primeira coisa que temos é positions
, que é um
array em JavaScript. A WebGL, por outro lado, precisa de dados fortemente tipados, e então
a parte new Float32Array(position)
cria uma nova matriz de números de pontos flutuantes de 32 bits
e copia os valores de positions
. Então, gl.bufferData
copia esses dados para o positionBuffer
na GPU. O
buffer de posição está sendo usado porque nós o vinculamos ao ponto de ligação ARRAY_BUFFER
acima.
O último argumento, gl.STATIC_DRAW
é uma dica para a WebGL sobre como usaremos os dados.
A WebGL pode tentar usar essa sugestão para otimizar certas coisas. gl.STATIC_DRAW
diz para a WebGL
que não é provável que mudemos muito esses dados.
Agora que colocamos dados em um buffer, precisamos mostrar ao atributo como obter os dados dele. Primeiro, precisamos criar uma coleção do estado do atributo denominada Vertex Array Object.
var vao = gl.createVertexArray();
E precisamos fazer com que ele seja o vertex array atual para que todas as nossas configurações de atributos se apliquem a esse conjunto de estado de atributos
gl.bindVertexArray(vao);
Finalmente, nós configuramos os atributos no vertex array. Em primeiro lugar, precisamos ativar o atributo. Isso fala para a WebGL que queremos tirar dados de um buffer. Se não ativarmos o atributo, então, o atributo terá um valor constante.
gl.enableVertexAttribArray(positionAttributeLocation);
Então, nós precisamos especificar como obter os dados
var size = 2; // 2 componentes por iteração
var type = gl.FLOAT; // os dados são floats de 32bits
var normalize = false; // não normalize os dados
var stride = 0; // 0 = mover para frente size * sizeof(type) cada iteração para obter a próxima posição
var offset = 0; // comece no início do buffer
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset)
Uma parte oculta da gl.vertexAttribPointer
é que ela vincula o atual ARRAY_BUFFER
ao atributo. Em outras palavras, agora esse atributo é obrigado a vincular o positionBuffer
.
Isso significa que estamos livres para ligar outra coisa ao ponto de ligação ARRAY_BUFFER
;
O atributo continuará usando o positionBuffer
.
Observe que, do ponto de vista do nosso GLSL vertex shader, o atributo a_position
é um vec4
in vec4 a_position;
vec4
é um 4 float value. Em JavaScript, você poderia pensar em algo como
a_position = {x: 0, y: 0, z: 0, w: 0}
. Acima, nós definimos size = 2
. Atributos
padrão para 0, 0, 0, 1
então esse atributo receberá seus primeiros 2 valores (x e y)
do nosso buffer. O z e o w, será o padrão 0 e 1, respectivamente.
Antes de desenharmos, devemos redimensionar nosso canvas (ou nossa tela) para corresponder com o nosso tamanho de exibição. As telas, como as imagens, possuem 2 tamanhos. O número real de pixels em si e separadamente o tamanho que eles são exibidos. O CSS determina o tamanho que o canvas é exibido. Você sempre deve definir o tamanho que deseja uma tela com o CSS, pois é muito mais flexível do que qualquer outro método.
Para fazer com que o número de pixels na tela coincida com o tamanho exibido eu faço o uso de um helper, mais detalhes aqui.
Em quase todas essas amostras, o tamanho da tela é de 400x300 pixels se a anistra for executada em sua própria janela, mas ela se estende para preencher o espaço disponível se estiver dentro de um iframe como ele está nesta página. Ao permitir que o CSS determine o tamanho e, em seguida, ajuste seus tamanho para corresponder ao da tela, nós facilmente manipulamos ambos os casos.
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
Precisamos dizer ao WebGL como converter valores do clip space,
nós vamos configurar o gl_Position
de volta para pixels, muitas vezes chamado de screen space.
Para isso, chamamos gl.viewport
e passamos o tamanho atual da tela (canvas).
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
Isso diz para a WebGL que o clip space -1 +1 mapeia para o 0 <-> gl.canvas.width
para x e 0 <-> gl.canvas.height
para y.
Agora, nós limpamos nosso canvas. 0, 0, 0, 0
são vermelho, verde, azul, alpha, respectivamente, então, nesse caso, estamos definindo o canvas como transparente.
// Limpar o canvas
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
Em seguida, precisamos dizer ao WebGL qual shader é executado.
// Fala para usar nosso program (par de shaders)
gl.useProgram(program);
Então precisamos informar qual é o conjunto de buffers usar e como obter os dados desses buffers e então fornecer os dados aos atributos
// Vincule o atributo/buffer que desejamos.
gl.bindVertexArray(vao);
Depois de tudo o que fizeemos, finalmente, podemos pedir a WebGL que execute o nosso programa GLSL.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 3;
gl.drawArrays(primitiveType, offset, count);
Como a contagem é 3, isso executará o nosso vertex shader 3 vezes. A primeira vez a_position.x
e a_position.y
em nosso atributo vertex shader será configurado para os dois primeiros valores do positionBuffer.
A segunda vez a_position.xy
será configurado para os dois segundos valores. Na última vez, ele será
configurado para os últimos 2 valores.
Como definimos primitiveType
para gl.TRIANGLES
, cada vez que nosso vertex shader é executado 3 vezes,
a WebGL desenhará um triângulo com base nos 3 valores que definimos em gl_Position
. Não importa o tamanho da
nossa tela, esses valores estão nas coordenadas do nosso clip space que vão de -1 a 1 em cada direção.
Como o nosso vertex shader está simplesmente copiando os valores do nosso positionbuffer para a gl_Position
, o
o triângulo será desenhado nas coordenadas do clip space
0, 0,
0, 0.5,
0.7, 0,
Convertendo do clip space para o espaço da tela se o tamanho da tela passasse ser 400x300, nós teríamos algo assim
clip space screen space
0, 0 -> 200, 150
0, 0.5 -> 200, 225
0.7, 0 -> 340, 150
A WebGL agora renderizará esse triângulo. Para cada pixel que está prestes a desenhar, a WebGL chamará o nosso fragment shader.
Nosso fragment shader apenas define a outColor
para 1, 0, 0.5, 1
. Como o Canvas é um canvas de 8bits
por canal, significa que, a WebGL vai escrever os seguintes valores [255, 0, 127, 255]
na tela.
Aqui está um exemplo já pronto
No caso acima, você pode ver que o nosso vertex shader não está fazendo nada além de passar nossos dados de posição diretamente. Como os dados da posição já estão no clipspace, não há trabalho a fazer. Se você quer objetos 3D, só depende de você fornecer shaders que convertam de 3D para clipspace porque a WebGL é, apenas, uma API de rasterização.
Você pode estar se perguntando por que o triângulo começa no meio e vai para o canto superior direito.
O clip space em x
vai de -1 a +1. Isso significa que o 0 está no centro e os valores positivos
irão para à direita dele.
Quanto ao porquê está no topo, o clip space -1 está na parte inferior e +1 está no topo. Isso significa que, o 0 está no centro e os números positivos estarão acima do centro.
Para coisas 2D, você provavelmente iria preferir trabalhar em pixels do que com o clipspace então vamos mudar o shader para que possamos fornecer a posição em pixels e convertê-lo em um clipspace para nós. Aqui está o novo vertex shader
- in vec4 a_position;
+ in vec2 a_position;
+ uniform vec2 u_resolution;
void main() {
+ // converte a posição dos pixels de 0.0 para 1.0
+ vec2 zeroToOne = a_position / u_resolution;
+
+ // converte de 0->1 para 0->2
+ vec2 zeroToTwo = zeroToOne * 2.0;
+
+ // converte de 0->2 para -1->+1 (clipspace)
+ vec2 clipSpace = zeroToTwo - 1.0;
+
* gl_Position = vec4(clipSpace, 0, 1);
}
Algumas coisas que devemos notar sobre as mudanças. Nós mudamos a a_position
para um vec2
já que nós
estamos apenas usando x
e y
. Um vec2
é semelhante a um vec4
, porém, possui apenas x
e y
.
Em seguida, adicionamos um uniform
chamado u_resolution
. Para definir que precisamos procurar por sua localização.
var resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution");
O reste deve estar claro a partir dos comentários. Ao configurar u_resolution
para a resolução
do nosso canvas, o shader vai agora tomar as posições que colocamos no positionBuffer
fornecido
nas coordenadas dos pixels e convertê-los em clip space.
Agora podemos alterar os valores da nossa posição do clip space para pixels. Desta vez, vamos desenhar um retângulo feito de 2 triângulos, 3 pontos, cada.
var positions = [
* 10, 20,
* 80, 20,
* 10, 30,
* 10, 30,
* 80, 20,
* 80, 30,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
E depois de definirmos qual programa usar, podemos definir o valor do uniform que criamos.
gl.useProgram
é como gl.bindBuffer
acima, em que ele define o programa atual.
Depois de tudo, as funções gl.uniformXXX
definem os uniforms no programa atual.
gl.useProgram(program);
// Passa a resolução do canvas, assim nós podemos converter
// de pixels para clipspace no shader
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
E, claro, para desenhar dois triângulos, precisamos que a WebGL chame nosso vertex shader 6 vezes,
para isso, precisamos mudar o count
para 6
.
// desenha
var primitiveType = gl.TRIANGLES;
var offset = 0;
*var count = 6;
gl.drawArrays(primitiveType, offset, count);
E aqui está
Nota: Este exemplo e todos os exemplos a seguir usam webgl-utils.js
que contém funções para compilar e vincular os shaders. Não há nenhuma razão para desordenar os
exemplos com o boilerplate.
Novamente, você pode notar que o retângulo está perto do fundo dessa área. A WebGL considera que o canto inferior esquerdo é 0,0. Para que ele seja o tradicional canto superior esquerdo, usado nas APIS de gráficos 2D, nós podemos simplesmente virar a coordenada y do clipspace.
* gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
E agora o nosso retângulo está aonde esperavamos.
Vamos fazer o código que define um retângulo em uma função para podermos chamá-lo para retângulos de diferentes tamanhos. Enquanto estamos nisso, nós tornaremos a cor ajustável.
Primeiro fazemos o fragment shader pegar uma color uniform input.
#version 300 es
precision highp float;
+ uniform vec4 u_color;
out vec4 outColor;
void main() {
- outColor = vec4(1, 0, 0.5, 1);
* outColor = u_color;
}
E aqui está o novo código que desenha 50 retângulos com cores e locais aleatórios.
var colorLocation = gl.getUniformLocation(program, "u_color");
...
// desenha 50 retângulos com cores e locais aleatórios
for (var ii = 0; ii < 50; ++ii) {
// Define um retângulo aleatório
setRectangle(
gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));
// Define uma cor aleatória.
gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1);
// Desenha o retângulo.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
}
}
// Retorna um inteiro aleatório entre o intervalo de 0 e - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
// Preenche o buffer com os valores que definem um retângulo.
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
// NOTA: gl.bufferData(gl.ARRAY_BUFFER, ...) afetará
// qualquer buffer que esteja vinculado ao `ARRAY_BUFFER`,
// mas até agora temos apenas um buffer. Se tivessémos mais de um
// buffer, gostaríamos de vincular este buffer a `ARRAY_BUFFER` primeiro.
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
E aqui está os retângulos.
Espero que você veja que a WebGL é realmente uma API bastante simples. Tudo bem, simples pode ser a palavra errada. Mas o que ela faz é simples. Ela apenas executa 2 funções fornecidas pelo usuário, um vertex shader e um fragment shader e desenha triângulos, linhas ou pontos. Embora possa ser mais complicado fazer uma abordagem 3D, essa complicação é definida por você, o programador, sob a forma de shaders mais complexos. A própria API da WebGL é apenas um rasterizador e, conceitualmente, bastante simples.
Cobrimos um pequeno exemplo que mostrou como fornecer dados em um atributo e 2 uniforms. É comum ter múltiplos atributos e muitos uniforms. Perto do topo deste artigo também mencionamos varyings e texturas. Isso aparecerá em lições subsequentes.
Antes de avançarmos, quero mencionar que para a maioria das aplicações em atualização
os dados em um buffer, como fizemos em setRectangle
, não são comuns. Usei esso
exemplo porque pensei que era mais fácil de explicar porque mostra coordenadas de pixels
como entrada e demonstra como fazer uma pequena quantidade de cálculos na GLSL. Não é errado, há
muitos os casos em que isso é o certo a se fazer, mas você deve continuar lendo para descobrir
a maneira mais comum de posicionar, orientar e dimensionar coisas na WebGL.
Se você é 100% leigo na WebGL e não tem ideia do que é GLSL ou shaders ou o que a GPU faz, então, em seguida, verifique [os conceitos básicos de como a WebGL realmente funciona] (webgl-how-it-works.html).(webgl-how-it-works.html).
Você também deve, pelo menos, ler brevemente sobre o código boilerplate usado aqui que é usado na maioria dos exemplos. Você também deve, pelo menos ver como desenhar múltiplas coisas para ter uma ideia de como as aplicações em WebGL são estruturadas porque, infelizmente, apenas desenha algo e, portanto, não mostra essa estrutura.
Caso contrário, a partir daqui você pode ir em duas direções. Se você está interessado em processar imagens Vou lhes mostrar como fazer algum processamento de imagem 2D. Se você está interessado em aprender sobre translação, rotação e escala, então, comece aqui.