• 原文地址: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 渲染世界中。

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