WebGL 与动画实现 | 青训营笔记

82 阅读3分钟

WebGL 与动画实现 | 青训营笔记

这是我参与「第四届青训营 」笔记创作活动的的第 21 天

学习资料

充分理解WebGL - 十年踪迹的专栏 - 掘金

初识 WebGL

Modern Graphics System

现代图像系统

Pasted image 20220813102707.png

  • 光栅(Raster):几乎所有的现代图形系统都是基于光栅来绘制图形的,光栅就是指构成图像的像素阵列。
  • 像素(Pixel):一个像素对应图像上的一个点,它通常保存图像上的某个具体位置的颜色等信息。
  • 帧缓存(Frame Buffer):在绘图过程中,像素信息被存放于帧缓存中,帧缓存是一块内存地址。
  • CPU(Central Processing Unit):中央处理单元,负责逻辑计算。
  • GPU(Graphics Processing Unit):图形处理单元,负责图形计算。

Pasted image 20220813102930.png

  1. 轮廓提取 / meshing
  2. 光栅化
  3. 帧缓存
  4. 渲染

The Pipline

渲染管线

Pasted image 20220813103026.png

CPU vs GPU

CPU 的工作能力,与管道本身的处理速度(频率)合管道的数量(内核数)有关系,频率越高,那么运算处理单一任务的速度就越快,内核数越多,那么能同时并行处理的任务数就越多。只能串行处理。

GPU 由大量的小运算单元构成,每个运算单元只负责处理很简单的计算,每个运算单元彼此独立,因此所有计算可以并行处理。

WebGL & OpenGL

WebGL 是 OpenGL 的子集,是其在 Web 上的实现。

OpenGL, OpenGL ES, WebGL, GLSL, GLSL ES API Tables

WebGL Startup

启动 WebGL

  1. 创建 WebGL 上下文
  2. 创建 WebGL Program
  3. 将数据存入缓冲区
  4. 将缓冲区数据读取到 GPU
  5. GPU 执行 WebGL 程序,输出结果

Pasted image 20220813103427.png

Create WebGL Context

创建 WebGL 上下文

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
function create3DContext(canvas, options) {
  const names = ['webgl', 'experimental-webgl', 'webkit-3d', 'moz-webgl'];
  if (options.webgl2) names.unshift('webgl2');
  let context = null;
  for (let ii = 0; ii < names.length; ++ii) {
    try {
      context = canvas.getContext(names[ii], options);
    } catch (e) {
      // no-empty
    }
    if (context) {
      break;
    }
  }
  return context;
}

The Shaders

着色器

  1. Vertex Shader

顶点着色器

attribute vec2 position;

void main() {
  gl_PointSize = 1.0;
  gl_Position = vec4(position, 1.0, 1.0);
}
  1. Fragment Shader

片段着色器

percision mediump float;

void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

Create Program

const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(fragmentShader);

const program = gl.createProgram();
gl.attachShader(program, vertextShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

gl.useProgram(program);

Data to Frame Buffer

Axes

Pasted image 20220813110358.png

Typed Array

const points = new Float32Array([
  -1, -1,
  0, 1,
  1, -1,
]);
const bufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
gl.bufferData(gl.ARRAT_BUFFER, points, gl.STATIC_DRAW);

Frame Buffer to GPU

const vPosition = gl.getAttribLocation(program, 'position'); //获取顶点着色器中position变量的地址
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); //给变量设置长度和类型
gl.enableVertexAttribArray(vPosition); //激活这个变量

'position'

attribute vec2 position;

void main() {
  gl_PointSize = 1.0;
  gl_Position = vec4(position, 1.0, 1.0);
}

Output

demo

l.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);

Mesh.js

2D:

2D

const canvas = document.querySelector('canvas');
cosnt ctx = canvas.getContext('2d');

ctx.beginPath();
ctx.moveTo(250, 0);
ctx.lineTo(500, 500);
ctx.lineTo(0, 500);
ctx.fillStyle = 'red';
ctx.fill();

mesh.js:

mesh.js

const {Renderer, Figure2D, Mesh2D} = mesh.js;
  
const canvas = document.querySelector('canvas');
const renderer = new Renderer(canvas);
  
const figure = new Figure2D();
figure.beginPath();
figure.moveTo(250, 0);
figure.lineTo(500, 500);
figure.lineTo(0, 500);
  
const mesh = new Mesh2D(figure, canvas);
mesh.setFill({
  color: [1, 0, 0, 1],
});
  
renderer.drawMeshes([mesh]);

Polygons

多边形

Draw Polygon with 2D Triangulations

用三角剖分绘制多边形

使用 Earcut 进行三角剖分

const vertices = [
  [-0.7, 0.5],
  [-0.4, 0.3],
  [-0.25, 0.71],
  [-0.1, 0.56],
  [-0.1, 0.13],
  [0.4, 0.21],
  [0, -0.6],
  [-0.3, -0.3],
  [-0.6, -0.3],
  [-0.45, 0.0],
];

const points = vertices.flat();
const triangles = earcut(points);

3D Meshing

Transform

平移

{x=x0+x1y=y0+y1\begin{cases} x = x_{0} + x_{1} \\ y = y_{0} + y_{1} \\ \end{cases}

旋转

{x=x0cosθy0sinθy=x0sinθ+y0cosθ(xy)=(cosθsinθsinθcosθ)×(x0y0)\begin{aligned} &\begin{cases} x = x_{0}\cos \theta - y_{0}\sin \theta \\ y = x_{0}\sin \theta + y_{0}\cos \theta \end{cases} \\ \\ &\begin{pmatrix} x \\ y \end{pmatrix} = \begin{pmatrix} \cos \theta & -\sin \theta\\ \sin \theta & \cos \theta \end{pmatrix} \times \begin{pmatrix} x_{0}\\ y_{0} \end{pmatrix} \end{aligned}

缩放

{x=sxx0y=syy0(xy)=(sx00sy)\begin{aligned} &\begin{cases} x = s_{x}x_{0}\\ y = s_{y}y_{0} \end{cases} \\ \\ &\begin{pmatrix} x \\ y \end{pmatrix} = \begin{pmatrix} s_{x} & 0 \\ 0 & s_{y} \end{pmatrix} \end{aligned}

旋转 + 缩放是线性变换

P=M1×M2×...×Mn×P0=M×P0  (M=M1×M2×...×Mn)\begin{aligned} P &= M_{1}\times M_{2}\times ... \times M_{n}\times P_{0}\\ &= M\times P_{0} \ \ (M = M_{1}\times M_{2}\times ... \times M_{n}) \end{aligned}

线性变换 + 平移

P=M×P0+P1(P1)=(MP101)×(P01)\begin{aligned} &P = M\times P_{0} + P_{1} \\ \\ &\begin{pmatrix} P \\ 1 \end{pmatrix} = \begin{pmatrix} M & P_{1}\\ 0 & 1 \end{pmatrix} \times \begin{pmatrix} P_{0}\\ 1 \end{pmatrix} \end{aligned}

Apply Transforms:

Apply Transforms

3D Matrix

3D 标准模型的四个齐次矩阵(mat4)

  1. 投影矩阵 Projection Matrix
  2. 模型矩阵 Model Matrix
  3. 视图矩阵 View Matrix
  4. 法向量矩阵 Normal Matrix

阅读材料

  1. The book of shaders
  2. Mesh.js
  3. glsl-doodle
  4. SpriteJS
  5. ThreeJS
  6. ShaderToy