Moisés Gabriel Cachay Tello
Creator, destructor.
@xpktro - LimaJS
WebGL es un estándar que define un API cuyo propósito es rasterizar gráficos ejecutando código en la GPU con todas las ventajas que esto implica.
Esta API conforma casi por completo con OpenGL ES
Las GPU son extremadamente eficaces para realizar ciertos tipos de cálculos (colores, iluminación, perspectiva).
Aprovechar esto nos permite producir gráficos complejos a altas velocidades.
Y más 👀
Hoy empezaremos una serie de charlas cuyo objetivo será explicar el nivel más bajo de interacción con el WebGL API.
Existe una gran cantidad de librerías que abstraen este manejo de la API pero considero importante demostrar cómo funciona realmente para sacarle (potencialmente) mayor provecho.
VS
FS
Varyings
Vertex Shader (GLSL)
Fragment Shader (GLSL)
Compile
Link
GPU
Uniforms
Vertex Array Object
Attribute
<canvas>
Attribute
Attribute
...
Array Buffer
Buffer
Buffer
WebGL Program
Float32Array
WebGL Context
in
out
in
Why
<canvas>
WebGL Context
(canvas.getContext)
Un canvas es necesario para obtener el contexto de dibujo y usar las funciones del WebGL API
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
const gl = canvas.getContext('webgl2');
if(!gl) {
alert('Webgl2 Unsupported');
return;
}
Los shaders son pequeños programas que se ejecutan en la GPU y un programa de WebGL requiere 2 para funcionar.
El vertex shader se encarga de definir las posiciones de cada vértice en la pantalla y el fragment shader se encarga de dar el color de cada pixel a dibujar
VS
FS
Varyings
GPU
Uniforms
<canvas>
in
out
in
const VERTEX_SHADER_SOURCE = `#version 300 es
in vec4 position;
void main() {
gl_Position = position;
}
`;
const FRAGMENT_SHADER_SOURCE = `#version 300 es
precision mediump float;
out vec4 fragColor;
void main() {
fragColor = vec4(0, 0, 0.7, 1);
}
`;
Vertex Shader (GLSL)
Fragment Shader (GLSL)
1
1
-1
-1
R
G
B
A
(x, y, z, w)
Cada shader debe crearse y compilarse para ser usado en el programa
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE);
gl.compileShader(vertexShader);
Vertex Shader (GLSL)
Fragment Shader (GLSL)
Compile
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE);
gl.compileShader(fragmentShader);
VS
FS
Varyings
GPU
Uniforms
<canvas>
WebGL Program
WebGL Context
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
Vertex Array Object
Attribute
Attribute
Attribute
...
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
VS
in
La información que reciban los shaders (los atributos) se agrupa en un VAO
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
0, 0.5,
0.5, -0.5,
-0.5, -0.5,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
Array Buffer
Float32Array
Cada atributo va asociado a un array, este se define y llena de valores
const positionAttributeLocation = gl.getAttribLocation(program, 'position');
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionAttributeLocation);
Vertex Array Object
Attribute
Array Buffer
Para cada atributo del programa, hay que definir de qué manera se extraerá data del buffer asociado
const cssToRealPixels = window.devicePixelRatio || 1;
canvas.width = Math.floor(canvas.clientWidth * cssToRealPixels);
canvas.height = Math.floor(canvas.clientHeight * cssToRealPixels);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
Debemos darle el tamaño apropiado a nuestro canvas y definir qué parte de esta usará WebGL para renderizar
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.drawArrays(gl.TRIANGLES, 0, 3);
Definimos un color de relleno y limpiamos el framebuffer con este.
Finalmente declaramos el uso del programa y programamos el dibujo con este.
El fragment shader puede recibir valores desde el vertex shader a través de los varyings.
Estos valores se interpolarán debido a la naturaleza del fragment shader.
Podemos usar esto para crear efectos de degradado.
const VERTEX_SHADER_SOURCE = `#version 300 es
in vec4 position;
in vec4 color;
out vec4 v_color;
void main() {
gl_Position = position;
v_color = color;
}
`;
const FRAGMENT_SHADER_SOURCE = `#version 300 es
precision mediump float;
in vec4 v_color;
out vec4 fragColor;
void main() {
fragColor = v_color;
}
`;
< nuevo atributo
< varying irá al FS
< varying del VS
< los valores irán variando por cada pasada del FS
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
const colors = [
0, 0, 1,
0, 1, 0,
1, 0, 0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
const colorAttributeLocation = gl.getAttribLocation(program, 'color');
gl.vertexAttribPointer(colorAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(colorAttributeLocation);
Sólo hay que crear un nuevo array buffer y asignarlo al atributo respectivo. De este se sacarán uno a uno los valores de color junto con los vértices.
Quedan pocas cosas como manejo de texturas y cómo definir uniforms.
Lo que vimos hasta ahora es una base suficiente para aprender todo el resto rápidamente.
En la próxima sesión veremos más detalles interesantes como animaciones y 3d (TBD).
By Moisés Gabriel Cachay Tello