乐趣区

关于前端:WebGL-系列-13-简单的动画

  • 原文地址:Day 13. Simple animation
  • 原文作者:Andrei Lesnitsky

这是 WebGL 系列的第 13 天教程,每天都有新文章公布。

订阅或者退出邮件列表以便及时获取更新内容。

源代码在这里

以前的所有教程都是基于动态图像,当初让咱们增加动态效果!

咱们须要一个简略的顶点着色器

???? src/shaders/rotating-square.v.glsl

attribute vec2 position;
uniform vec2 resolution;

void main() {gl_Position = vec4(position / resolution * 2.0 - 1.0, 0, 1);
}

还有片段着色器

???? src/shaders/rotating-square.f.glsl

precision mediump float;

void main() {gl_FragColor = vec4(1, 0, 0, 1);
}

更新 JS 文件

???? index.html

    </head>
    <body>
      <canvas></canvas>
-     <script src="./dist/texture.js"></script>
+     <script src="./dist/rotating-square.js"></script>
    </body>
  </html>

???? src/rotating-square.js

import vShaderSource from './shaders/rotating-square.v.glsl';
import fShaderSource from './shaders/rotating-square.f.glsl';

???? webpack.config.js

      entry: {
          'week-1': './src/week-1.js',
          'texture': './src/texture.js',
+         'rotating-square': './src/rotating-square.js',
      },
  
      output: {

获取 WebGL 上下文

???? src/rotating-square.js

  import vShaderSource from './shaders/rotating-square.v.glsl';
  import fShaderSource from './shaders/rotating-square.f.glsl';
+ 
+ const canvas = document.querySelector('canvas');
+ const gl = canvas.getContext('webgl');
+ 

使画布填满页面

???? src/rotating-square.js

  const canvas = document.querySelector('canvas');
  const gl = canvas.getContext('webgl');
  
+ const width = document.body.offsetWidth;
+ const height = document.body.offsetHeight;
+ 
+ canvas.width = width * devicePixelRatio;
+ canvas.height = height * devicePixelRatio;
+ 
+ canvas.style.width = `${width}px`;
+ canvas.style.height = `${height}px`;

创立着色器

???? src/rotating-square.js

  import vShaderSource from './shaders/rotating-square.v.glsl';
  import fShaderSource from './shaders/rotating-square.f.glsl';
+ import {compileShader} from './gl-helpers';
  
  const canvas = document.querySelector('canvas');
  const gl = canvas.getContext('webgl');
  
  canvas.style.width = `${width}px`;
  canvas.style.height = `${height}px`;
+ 
+ const vShader = gl.createShader(gl.VERTEX_SHADER);
+ const fShader = gl.createShader(gl.FRAGMENT_SHADER);
+ 
+ compileShader(gl, vShader, vShaderSource);
+ compileShader(gl, fShader, fShaderSource);

创立程序 program

???? src/rotating-square.js

  
  compileShader(gl, vShader, vShaderSource);
  compileShader(gl, fShader, fShaderSource);
+ 
+ const program = gl.createProgram();
+ 
+ gl.attachShader(program, vShader);
+ gl.attachShader(program, fShader);
+ 
+ gl.linkProgram(program);
+ gl.useProgram(program);

获取属性 attributeuniform 坐标地位

???? src/rotating-square.js

  import vShaderSource from './shaders/rotating-square.v.glsl';
  import fShaderSource from './shaders/rotating-square.f.glsl';
- import {compileShader} from './gl-helpers';
+ import {setupShaderInput, compileShader} from './gl-helpers';
  
  const canvas = document.querySelector('canvas');
  const gl = canvas.getContext('webgl');
  
  gl.linkProgram(program);
  gl.useProgram(program);
+ 
+ const programInfo = setupShaderInput(gl, program, vShaderSource, fShaderSource);

创立顶点以便绘制正方形

???? src/rotating-square.js

  import vShaderSource from './shaders/rotating-square.v.glsl';
  import fShaderSource from './shaders/rotating-square.f.glsl';
  import {setupShaderInput, compileShader} from './gl-helpers';
+ import {createRect} from './shape-helpers';
+ import {GLBuffer} from './GLBuffer';
  
  const canvas = document.querySelector('canvas');
  const gl = canvas.getContext('webgl');
  gl.useProgram(program);
  
  const programInfo = setupShaderInput(gl, program, vShaderSource, fShaderSource);
+ 
+ const vertexPositionBuffer = new GLBuffer(gl, gl.ARRAY_BUFFER, new Float32Array([+     ...createRect(canvas.width / 2 - 100, canvas.height / 2 - 100, 200, 200),
+ ]), gl.STATIC_DRAW);

设置属性 attribute 指针

???? src/rotating-square.js

  const vertexPositionBuffer = new GLBuffer(gl, gl.ARRAY_BUFFER, new Float32Array([...createRect(canvas.width / 2 - 100, canvas.height / 2 - 100, 200, 200),
  ]), gl.STATIC_DRAW);
+ 
+ gl.vertexAttribPointer(programInfo.attributeLocations.position, 2, gl.FLOAT, false, 0, 0);

创立索引缓冲区

???? src/rotating-square.js

  ]), gl.STATIC_DRAW);
  
  gl.vertexAttribPointer(programInfo.attributeLocations.position, 2, gl.FLOAT, false, 0, 0);
+ 
+ const indexBuffer = new GLBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint8Array([
+     0, 1, 2, 
+     1, 2, 3, 
+ ]), gl.STATIC_DRAW);

调制分辨率并设置视口 viewport

???? src/rotating-square.js

      0, 1, 2, 
      1, 2, 3, 
  ]), gl.STATIC_DRAW);
+ 
+ gl.uniform2fv(programInfo.uniformLocations.resolution, [canvas.width, canvas.height]);
+ 
+ gl.viewport(0, 0, canvas.width, canvas.height);

并启动绘制调用性能

???? src/rotating-square.js

  gl.uniform2fv(programInfo.uniformLocations.resolution, [canvas.width, canvas.height]);
  
  gl.viewport(0, 0, canvas.width, canvas.height);
+ gl.drawElements(gl.TRIANGLES, indexBuffer.data.length, gl.UNSIGNED_BYTE, 0);

当初让咱们思考如何旋转这个正方形

实际上,咱们能够拟合到圆中,并且能够应用 radiuscossin 来计算每个顶点的坐标,咱们须要做的是向每个顶点减少一些角度。

让咱们从角度动手重构咱们的 createRect 助手。

???? src/rotating-square.js

  const programInfo = setupShaderInput(gl, program, vShaderSource, fShaderSource);
  
  const vertexPositionBuffer = new GLBuffer(gl, gl.ARRAY_BUFFER, new Float32Array([-     ...createRect(canvas.width / 2 - 100, canvas.height / 2 - 100, 200, 200),
+     ...createRect(canvas.width / 2 - 100, canvas.height / 2 - 100, 200, 200, 0),
  ]), gl.STATIC_DRAW);
  
  gl.vertexAttribPointer(programInfo.attributeLocations.position, 2, gl.FLOAT, false, 0, 0);

???? src/shape-helpers.js

- export function createRect(top, left, width, height) {
+ const Pi_4 = Math.PI / 4;
+ 
+ export function createRect(top, left, width, height, angle = 0) {
+     const centerX = width / 2;
+     const centerY = height / 2;
+ 
+     const diagonalLength = Math.sqrt(centerX ** 2 + centerY ** 2);
+ 
+     const x1 = centerX + diagonalLength * Math.cos(angle + Pi_4);
+     const y1 = centerY + diagonalLength * Math.sin(angle + Pi_4);
+ 
+     const x2 = centerX + diagonalLength * Math.cos(angle + Pi_4 * 3);
+     const y2 = centerY + diagonalLength * Math.sin(angle + Pi_4 * 3);
+ 
+     const x3 = centerX + diagonalLength * Math.cos(angle - Pi_4);
+     const y3 = centerY + diagonalLength * Math.sin(angle - Pi_4);
+ 
+     const x4 = centerX + diagonalLength * Math.cos(angle - Pi_4 * 3);
+     const y4 = centerY + diagonalLength * Math.sin(angle - Pi_4 * 3);
+ 
      return [
-         left, top, // x1 y1
-         left + width, top, // x2 y2
-         left, top + height, // x3 y3
-         left + width, top + height, // x4 y4
+         x1 + left, y1 + top,
+         x2 + left, y2 + top,
+         x3 + left, y3 + top,
+         x4 + left, y4 + top,
      ];
  }
  

当初咱们须要定义初始角度

???? src/rotating-square.js

  gl.uniform2fv(programInfo.uniformLocations.resolution, [canvas.width, canvas.height]);
  
  gl.viewport(0, 0, canvas.width, canvas.height);
- gl.drawElements(gl.TRIANGLES, indexBuffer.data.length, gl.UNSIGNED_BYTE, 0);
+ 
+ let angle = 0;

以及每帧都会调用的函数

???? src/rotating-square.js

  gl.viewport(0, 0, canvas.width, canvas.height);
  
  let angle = 0;
+ 
+ function frame() {+     requestAnimationFrame(frame);
+ }
+ 
+ frame();

WebGL 会遍历顶点数据并进行渲染,咱们须要更新此数据使其出现不同成果。

???? src/rotating-square.js

  let angle = 0;
  
  function frame() {
+     vertexPositionBuffer.setData(
+         gl, 
+         new Float32Array(+             createRect(canvas.width / 2 - 100, canvas.height / 2 - 100, 200, 200, angle)
+         ), 
+         gl.STATIC_DRAW,
+     );
+ 
      requestAnimationFrame(frame);
  }
  

咱们还须要更新每帧的旋转角度

???? src/rotating-square.js

          gl.STATIC_DRAW,
      );
  
+     angle += Math.PI / 60;
+ 
      requestAnimationFrame(frame);
  }
  

还要调用绘制函数

???? src/rotating-square.js

  
      angle += Math.PI / 60;
  
+     gl.drawElements(gl.TRIANGLES, indexBuffer.data.length, gl.UNSIGNED_BYTE, 0);
      requestAnimationFrame(frame);
  }
  

酷!咱们当初有一个旋转的正方形!????

应用旋转矩阵能够简化咱们刚刚实现的操作

如果不太纯熟线性代数也不必放心,这里有一个非凡的程序工具包????

???? package.json

      "webpack-cli": "^3.3.5"
    },
    "dependencies": {
+     "gl-matrix": "^3.0.0",
      "glsl-extract-sync": "0.0.0"
    }
  }

咱们须要定义一个旋转矩阵

???? src/shaders/rotating-square.v.glsl

  attribute vec2 position;
  uniform vec2 resolution;
  
+ uniform mat2 rotationMatrix;
+ 
  void main() {gl_Position = vec4(position / resolution * 2.0 - 1.0, 0, 1);
  }

并乘以顶点坐标地位

???? src/shaders/rotating-square.v.glsl

  uniform mat2 rotationMatrix;
  
  void main() {-     gl_Position = vec4(position / resolution * 2.0 - 1.0, 0, 1);
+     gl_Position = vec4((position / resolution * 2.0 - 1.0) * rotationMatrix, 0, 1);
  }

当初咱们能够解脱顶点地位更新影响

???? src/rotating-square.js

  import {setupShaderInput, compileShader} from './gl-helpers';
  import {createRect} from './shape-helpers';
  import {GLBuffer} from './GLBuffer';
+ import {mat2} from 'gl-matrix';
  
  const canvas = document.querySelector('canvas');
  const gl = canvas.getContext('webgl');
  
  gl.viewport(0, 0, canvas.width, canvas.height);
  
- let angle = 0;
+ const rotationMatrix = mat2.create();
  
  function frame() {
-     vertexPositionBuffer.setData(
-         gl, 
-         new Float32Array(-             createRect(canvas.width / 2 - 100, canvas.height / 2 - 100, 200, 200, angle)
-         ), 
-         gl.STATIC_DRAW,
-     );
- 
-     angle += Math.PI / 60;
  
      gl.drawElements(gl.TRIANGLES, indexBuffer.data.length, gl.UNSIGNED_BYTE, 0);
      requestAnimationFrame(frame);

并改用旋转矩阵

???? src/rotating-square.js

  const rotationMatrix = mat2.create();
  
  function frame() {+     gl.uniformMatrix2fv(programInfo.uniformLocations.rotationMatrix, false, rotationMatrix);
+ 
+     mat2.rotate(rotationMatrix, rotationMatrix, -Math.PI / 60);
  
      gl.drawElements(gl.TRIANGLES, indexBuffer.data.length, gl.UNSIGNED_BYTE, 0);
      requestAnimationFrame(frame);

论断

事实证明,在咱们的形态助手重构中,看起来很简单的数学能够通过矩阵运算轻松实现。GPU 执行矩阵乘法的速度十分快(对于此类操作,它在硬件级别上有非凡的优化),因而能够应用变换矩阵进行很多变换。这是十分重要的概念,尤其是在 3d 渲染世界中。

明天就这样,今天见!????

退出移动版