WebGL:

Parte II

Un Cuadrado se mueve

@xpktro - LimaJS

Programme

  • What
  • Why
  • How
  • Then?
  • Refs.
  • ?

What

Why

Hoy avanzaremos un paso más en nuestro trayecto como programadores de gráficos...

 

Haciendo un cuadrado.

 

Y aún más allá transformándolo.

How

Primero vamos a revisitar nuestro código y lo prepararemos para este y los siguientes talleres:

class WebGLGraph {
  constructor(canvas) { ... }
  createAndCompileShaders() { ... }
  createAndLinkProgram() { ... }
  setVAO() { ... }
  setArrayBuffers() { ... }
  draw() { ... }
}

How

constructor(canvas) {
  if(canvas) {
    this.canvas = canvas;
  } else {
    this.canvas = document.createElement('canvas');
    document.body.appendChild(this.canvas);
  }
    
  this.gl = this.canvas.getContext('webgl2');
  if(!this.gl) {
    alert('Webgl2 Unsupported');
    return;
  }
    
  this.createAndCompileShaders();
  this.createAndLinkProgram();
  this.setVAO();
  this.setArrayBuffers();
  this.draw();
}

How

createAndCompileShaders() {
  this.VERTEX_SHADER_SOURCE = `...`;
  this.FRAGMENT_SHADER_SOURCE = `...`;

  this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
  this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);

  this.gl.shaderSource(this.vertexShader, this.VERTEX_SHADER_SOURCE);
  this.gl.shaderSource(this.fragmentShader, this.FRAGMENT_SHADER_SOURCE);

  this.gl.compileShader(this.vertexShader);
  this.gl.compileShader(this.fragmentShader);
}

How

createAndLinkProgram() {
  this.program = this.gl.createProgram();
  this.gl.attachShader(this.program, this.vertexShader);
  this.gl.attachShader(this.program, this.fragmentShader);
  this.gl.linkProgram(this.program);
}

setVAO() {
  this.vao = this.gl.createVertexArray();
  this.gl.bindVertexArray(this.vao);
}

How

setArrayBuffer(attribute, size, contents) {
  const buffer = this.gl.createBuffer();
  this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);

  this.gl.bufferData(
    this.gl.ARRAY_BUFFER, 
    new Float32Array(contents), 
    this.gl.STATIC_DRAW
  );
  const attributeLocation = this.gl.getAttribLocation(this.program, attribute);
  this.gl.vertexAttribPointer(attributeLocation, size, this.gl.FLOAT, false, 0, 0);
  this.gl.enableVertexAttribArray(attributeLocation);
  return attributeLocation;
}

How

setArrayBuffers() {
  this.positionLocation = this.setArrayBuffer('position', 2, [
       0,  0.5,
     0.5, -0.5,
    -0.5, -0.5
  ]);

  this.colorLocation = this.setArrayBuffer('color', 3, [
    0, 0, 1,
    0, 1, 0,
    1, 0, 0
  ]);
}

How

draw() {
  const cssToRealPixels = window.devicePixelRatio || 1;
  this.canvas.width  = Math.floor(this.canvas.clientWidth  * cssToRealPixels);
  this.canvas.height = Math.floor(this.canvas.clientHeight * cssToRealPixels);

  this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
  this.gl.clearColor(0, 0, 0, 1.0);
  this.gl.clear(this.gl.COLOR_BUFFER_BIT);

  this.gl.useProgram(this.program);
  this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
}

const graph = new WebGLGraph();
const graph = new WebGLGraph();

How

setArrayBuffers() {
  this.positionLocation = this.setArrayBuffer('position', 2, [
   -0.5,  0.5,
    0.5,  0.5,
    0.5, -0.5,

   -0.5,  0.5,
   -0.5, -0.5,
    0.5, -0.5
  ]);

  this.colorLocation = this.setArrayBuffer('color', 3, [
    0, 0, 1,
    0, 1, 0,
    1, 0, 0,

    0, 0, 1,
    1, 1, 1,
    1, 0, 0
  ]);
}

Y ahora: Cómo dibujar un cuadrado

+

=

How

El siguiente paso en nuestra aventura consiste en aprender el significado de geometría vs transformaciones:

Geometría: descripción de la forma del objeto a dibujar

Transformaciones: operaciones que alteran la geometría

How

La transformación más básica es la de la traslación.

this.VERTEX_SHADER_SOURCE = `#version 300 es
  in vec4 position;
  in vec4 color;
  out vec4 v_color;

  uniform vec4 translation;

  void main() {
    gl_Position = position + translation;
    v_color = color;
  }
`;

¿Qué es un uniform?

How

constructor() {
  ...
  this.transX = 0.0;
  this.transY = 0.0;

  this.draw();
}

set4fvUniform(attribute, contents) {
  const uniformLocation = this.gl.getUniformLocation(this.program, attribute);
  this.gl.uniform4fv(uniformLocation, contents);
  return uniformLocation;
}

setUniforms() {
  this.set4fvUniform('translation', [this.transX, this.transY, 0, 0]);
}

draw() {
  ...
  this.gl.useProgram(this.program);
  this.setUniforms();
  this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
}

How

gl.uniform1f (floatUniformLoc, v);                 // for float
gl.uniform1fv(floatUniformLoc, [v]);               // for float or float array
gl.uniform2f (vec2UniformLoc,  v0, v1);            // for vec2
gl.uniform2fv(vec2UniformLoc,  [v0, v1]);          // for vec2 or vec2 array
gl.uniform3f (vec3UniformLoc,  v0, v1, v2);        // for vec3
gl.uniform3fv(vec3UniformLoc,  [v0, v1, v2]);      // for vec3 or vec3 array
gl.uniform4f (vec4UniformLoc,  v0, v1, v2, v4);    // for vec4
gl.uniform4fv(vec4UniformLoc,  [v0, v1, v2, v4]);  // for vec4 or vec4 array
 
gl.uniformMatrix2fv(mat2UniformLoc, false, [  4x element array ])  // for mat2 or mat2 array
gl.uniformMatrix3fv(mat3UniformLoc, false, [  9x element array ])  // for mat3 or mat3 array
gl.uniformMatrix4fv(mat4UniformLoc, false, [ 16x element array ])  // for mat4 or mat4 array
 
gl.uniform1i (intUniformLoc,   v);                 // for int
gl.uniform1iv(intUniformLoc, [v]);                 // for int or int array
gl.uniform2i (ivec2UniformLoc, v0, v1);            // for ivec2
gl.uniform2iv(ivec2UniformLoc, [v0, v1]);          // for ivec2 or ivec2 array
gl.uniform3i (ivec3UniformLoc, v0, v1, v2);        // for ivec3
gl.uniform3iv(ivec3UniformLoc, [v0, v1, v2]);      // for ivec3 or ivec3 array
gl.uniform4i (ivec4UniformLoc, v0, v1, v2, v4);    // for ivec4
gl.uniform4iv(ivec4UniformLoc, [v0, v1, v2, v4]);  // for ivec4 or ivec4 array
 
gl.uniform1u (intUniformLoc,   v);                 // for uint
gl.uniform1uv(intUniformLoc, [v]);                 // for uint or uint array
gl.uniform2u (ivec2UniformLoc, v0, v1);            // for uvec2
gl.uniform2uv(ivec2UniformLoc, [v0, v1]);          // for uvec2 or uvec2 array
gl.uniform3u (ivec3UniformLoc, v0, v1, v2);        // for uvec3
gl.uniform3uv(ivec3UniformLoc, [v0, v1, v2]);      // for uvec3 or uvec3 array
gl.uniform4u (ivec4UniformLoc, v0, v1, v2, v4);    // for uvec4
gl.uniform4uv(ivec4UniformLoc, [v0, v1, v2, v4]);  // for uvec4 or uvec4 array
 
// for sampler2D, sampler3D, samplerCube, samplerCubeShader, sampler2DShadow,
// sampler2DArray, sampler2DArrayShadow
gl.uniform1i (samplerUniformLoc,   v);
gl.uniform1iv(samplerUniformLoc, [v]);

How

La segunda transformación que veremos será la rotación.

this.VERTEX_SHADER_SOURCE = `#version 300 es
  in vec4 position;
  in vec4 color;
  out vec4 v_color;

  uniform vec4 translation;
  uniform float rotation;

  void main() {
    float rotation_rad = rotation * 3.141592 / 180.0;
    vec4 rotatedPosition = vec4(
      position.x *  cos(rotation_rad) + position.y * sin(rotation_rad),
      position.x * -sin(rotation_rad) + position.y * cos(rotation_rad),
      0, 
      1
    );
    gl_Position = rotatedPosition + translation;
    v_color = color;
  }
`;

@_@

How

constructor() {
  ...
  this.rotation = 0.0;

  this.draw();
}

set1fUniform(attribute, contents) {
  const uniformLocation = this.gl.getUniformLocation(this.program, attribute);
  this.gl.uniform1f(uniformLocation, contents);
  return uniformLocation;
}

setUniforms() {
  this.set4fvUniform('translation', [this.transX, this.transY, 0, 0]);
  this.set1fUniform('rotation', this.rotation);
}

How

El último tipo de transformación de hoy será el escalamiento.

this.VERTEX_SHADER_SOURCE = `#version 300 es
  in vec4 position;
  in vec4 color;
  out vec4 v_color;

  uniform vec4 translation;
  uniform float rotation;
  uniform vec4 scale;

  void main() {
    float rotation_rad = rotation * 3.141592 / 180.0;
    vec4 rotated_position = vec4(...);
    gl_Position = (rotated_position + translation) * scale;
    v_color = color;
  }
`;

How

constructor() {
  ...
  this.scaleX = 1.0;
  this.scaleY = 1.0;

  this.draw();
}

setUniforms() {
  this.set4fvUniform('translation', [this.transX, this.transY, 0, 0]);
  this.set1fUniform('rotation', this.rotation);
  this.set4fvUniform('scale', [this.scaleX, this.scaleY, 1, 1]);
}

How

El orden de las operaciones importa, y modificar los shaders antes de cada draw() es demasiado costoso.

How

Qué tal si te digo que no necesitamos tocar los shaders para efectuar transformaciones complejas

How

\begin{bmatrix}x & y & 1\end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ transX & transY & 1 \end{bmatrix}
[xy1]×[100010transXtransY1]\begin{bmatrix}x & y & 1\end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ transX & transY & 1 \end{bmatrix}
= \begin{bmatrix} x + transX & y + transY & 1 \end{bmatrix}
=[x+transXy+transY1]= \begin{bmatrix} x + transX & y + transY & 1 \end{bmatrix}

Traslación matricial

How

\begin{bmatrix}x & y & 1\end{bmatrix} \times \begin{bmatrix} cos(rotation) & -sin(rotation) & 0 \\ sin(rotation) & cos(rotation) & 0 \\ 0 & 0 & 1 \end{bmatrix}
[xy1]×[cos(rotation)sin(rotation)0sin(rotation)cos(rotation)0001]\begin{bmatrix}x & y & 1\end{bmatrix} \times \begin{bmatrix} cos(rotation) & -sin(rotation) & 0 \\ sin(rotation) & cos(rotation) & 0 \\ 0 & 0 & 1 \end{bmatrix}
= \begin{bmatrix} x \times cos(rotation) + y \times sin(rotation) \\ x \times -sin(rotation) + y \times cos(rotation) \\ 1 \end{bmatrix}_{(1 \times 3)}
=[x×cos(rotation)+y×sin(rotation)x×sin(rotation)+y×cos(rotation)1](1×3)= \begin{bmatrix} x \times cos(rotation) + y \times sin(rotation) \\ x \times -sin(rotation) + y \times cos(rotation) \\ 1 \end{bmatrix}_{(1 \times 3)}

Rotación matricial

How

\begin{bmatrix}x & y & 1\end{bmatrix} \times \begin{bmatrix} scaleX & 0 & 0 \\ 0 & scaleY & 0 \\ 0 & 0 & 1 \end{bmatrix}
[xy1]×[scaleX000scaleY0001]\begin{bmatrix}x & y & 1\end{bmatrix} \times \begin{bmatrix} scaleX & 0 & 0 \\ 0 & scaleY & 0 \\ 0 & 0 & 1 \end{bmatrix}
= \begin{bmatrix}x \times scaleX & y \times scaleY & 1\end{bmatrix}
=[x×scaleXy×scaleY1]= \begin{bmatrix}x \times scaleX & y \times scaleY & 1\end{bmatrix}

Escalamiento matricial

How

¿Cómo esto es mejor que hacerlo linealmente?

\begin{bmatrix} cos(rotation) & -sin(rotation) & 0 \\ sin(rotation) & cos(rotation) & 0 \\ 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} scaleX & 0 & 0 \\ 0 & scaleY & 0 \\ 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ transX & transY & 1 \end{bmatrix}
[cos(rotation)sin(rotation)0sin(rotation)cos(rotation)0001]×[scaleX000scaleY0001]×[100010transXtransY1]\begin{bmatrix} cos(rotation) & -sin(rotation) & 0 \\ sin(rotation) & cos(rotation) & 0 \\ 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} scaleX & 0 & 0 \\ 0 & scaleY & 0 \\ 0 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ transX & transY & 1 \end{bmatrix}
transfmat = transmat \times rotmat \times scalemat
transfmat=transmat×rotmat×scalemattransfmat = transmat \times rotmat \times scalemat

gl_Position = \(\begin{bmatrix}x & y & 1\end{bmatrix} \times transfmat\)

How

this.VERTEX_SHADER_SOURCE = `#version 300 es
  in vec2 position;
  in vec4 color;
  out vec4 v_color;

  uniform mat3 matrix;

  void main() {
    gl_Position = vec4((matrix * vec3(position, 1)).xy, 0, 1);
    v_color = color;
  }
`;
draw() {
  ...
  this.setUniforms();
  this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
}

setUniforms() {
  this.setMatrix3Uniform('matrix', this.getTransformationMatrix());
}

getTransformationMatrix() {
  let translationMatrix = mat3.fromTranslation(mat3.create(), [this.transX, this.transY]);
  let rotationMatrix = mat3.fromRotation(mat3.create(), glMatrix.toRadian(this.rotation));
  let scaleMatrix = mat3.fromScaling(mat3.create(), [this.scaleX, this.scaleY]);

  let transformationMatrix = mat3.multiply(mat3.create(), translationMatrix, rotationMatrix);
  transformationMatrix = mat3.multiply(mat3.create(), transformationMatrix, scaleMatrix)
  return transformationMatrix;
}

How

Then?

He decidido detenerme a explicar un poco más de fundamentos para dar una dimensión más técnica a los fundamentos de la programación de gráficos.

 

Ejercicio propuesto: animar posición, rotación y escala automáticamente (lo solucionamos en la siguiente charla)

Refs.

Material de Estudio

Fun

  • ShaderToy - Sandbox de creación de fragment shaders muy completo.

  • Vertex Shader Art - Sandbox de creación de vertex shaders.

  • GLSL Shader - Sandbox de creación de fragment shaders básico.

?

WebGL Part II

By Moisés Gabriel Cachay Tello

WebGL Part II

  • 1,008