WebGL 入门 | 青训营笔记
这是我参与「第五届青训营」伴学笔记创作活动的第 16 天
0x1 初识 WebGL
-
WebGL
是一种运用 GPU 能力的渲染技术
-
Modern Graphics System
- 光栅(Raster):是指构成图像的像素阵列
- 像素(Pixel):通常保存图像上的某个具体位置的颜色等信息
- 帧缓存(Frame Buffer):是一块内存地址,用于存放像素信息
- CPU(Central Processing Unit):中央处理单元,负责逻辑计算
- GPU(Graphics Processing Unit):图形处理单元,负责图形计算
MGS 工作流程:
-
The Pipeline
0x2 WebGL 实战
-
WebGL Startup
- 创建 WebGL 上下文
- 创建 WebGL Program
- 将数据存入缓冲区
- 将缓冲区数据读取到 GPU
- GPU 执行 WebGL 程序并输出结果
举例:绘制三角形
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>1</title> <style> html,body{ padding: 0; margin: 0; } canvas{ width: 100vw; height: 100vw; max-width: 800px; max-height: 800px; } </style> </head> <body> <canvas width="800" height="800"></canvas> <script> // 创建上下文 const canvas = document.querySelector('canvas'); const gl = canvas.getContext('webgl'); // 定义着色器,添加运行在 GPU 中的 GLSL 代码 // 顶点着色器 const vertex = `attribute vec2 position; void main(){ gl_PointSize = 1.0; gl_Position = vec4(position, 1.0, 1.0); }`; // 片段着色器 const fragment = `precision highp float; void main(){ gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }`; // 创建项目 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, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); gl.useProgram(program); // 定义顶点,原点在画布正中央,右向 x 正半轴,上向 y 正半轴 const points = new Float32Array([ -1, -1, 0, 1, 1, -1 ]); // 创建缓冲区 const bufferId = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, bufferId); gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW); // 数据读取 const vPosition = gl.getAttribLocation(program, 'position'); gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(vPosition); // 清理缓存区并绘图 gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLES, 0, points.length/2); </script> </body> </html>
-
其他绘制方法
-
2D
const canvas = document.querySelector('canvas'); const 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
const { Renderer, Figure2D, Mesh2D } = meshjs; 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]);
-
-
绘制多边形:将多边形进行三角剖分
const vertices = [ [-0.7, 0.5], [-0.3, 0.4], [-0.2, 0.5], [-0.1, 0.9], [-0.3, 0.6], [-0.1, 0.1] ]; const points = vertices.flat(); cosnt triangles = earcut(points); const cells = new Uint16Array(triangles); const cellsBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cellsBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, cells, gl.STATIC_DRAW); gl.drawElements(gl.TRIANGLES, cells.length, gl.UNSIGNED_SHORT, 0);
-
3D Meshing:通过三角剖分绘制 3D 图形
-
图形移动(Transforms)
-
平移
-
旋转
-
缩放
-
平移的线性变换
-
旋转 + 缩放是线性变换,存在矩阵相乘的关系
-
假设平移为 ,此时
-
此时平移的线性变换表达式为
-
-
实现变换
attribute vec2 position; uniform mat3 modelMatrix; void main(){ gl_PointSize = 1.0; vec3 pos = modelMatrix * vec3(position, 1.0); gl_Position = vec4(pos, 1.0); }
let transform = gl.getUniformLocation(program, 'modelMatrix'); gl.uniformMatrix3fv(transform, false,[ 0.5, 0, 0, 0, 0.5, 0, 0, 0, 1 ]);
-
-
3D Matrix
3D 标准模型的四个齐次矩阵(mat4):
- 投影矩阵:Projection Matrix
- 模型矩阵:Model Matrix
- 视图矩阵:View Matrix
- 法向量矩阵:Normal Matrix
0x3 Shader
-
纯色
#version 300 es precision highp float; out vec4 fragColor; void main(){ fragColor = vec4(1.0, 0.0, 0.0, 1.0); }
- 通过修改
vec4(R, G, B, A)
中的参数(0~1)调整颜色
- 通过修改
-
渐变
#version 300 es precision highp float; uniform vec2 dd_resolution; out vec4 fragColor; void main(){ vec2 st = gl_FragCoord.xy / dd_resolution; fragColor = vec4(st, 0.0, 1.0); }
dd_resolution
:实时获取页面分辨率- 经计算后
st
的横纵坐标在 0~1 之间渐变,代入vec4()
中实现渐变效果
-
画圆
-
基础圆
#version 300 es precision highp float; uniform vec2 dd_resolution; out vec4 fragColor; void main(){ vec2 st = gl_FragCoord.xy / dd_resolution; vec2 center = vec2(0.5); float r = 0.2; fragColor.rgb = step(length(st - center), r) * vec3(1.0); fragColor.a = 1.0; }
-
平滑圆
float d = length(st - center); fragColor.rgb = smoothstep(d - 0.01, d, r) * vec3(1.0);
-
圆环
vec3 circle1 = smoothstep(d - 0.01, d, r) * vec3(1.0); vec3 circle2 = smoothstep(d, d + 0.01, r - 0.02) * vec3(1.0); fragColor.rgb = circle1 - circle2;
-
将画圆环的操作封装成函数
float stroke(float d, float d0, float w, float smth){ float th = 0.5 * w; smth = smth * w; float start = d0 - th; float end = d0 + th; return smoothstep(start, start + smth, d) - smoothstep(end - smth, end, d); } void main(){ vec2 st = gl_FragCoord.xy / dd_resolution; vec2 center = vec2(0.5); float r = 0.2; float d = length(st - center); float d1 = stroke(d, r, 0.02, 0.3); fragColor.rgb = d1 * vec3(1.0); fragColor.a = 1.0; }
-
-
-
斜线、曲线
#version 300 es precision highp float; uniform vec2 dd_resolution; out vec4 fragColor; float stroke(float d, float d0, float w, float smth){ float th = 0.5 * w; smth = smth * w; float start = d0 - th; float end = d0 + th; return smoothstep(start, start + smth, d) - smoothstep(end - smth, end, d); } void main(){ vec2 st = gl_FragCoord.xy / dd_resolution; // 斜线 float d1 = stroke(st.y, st.x, 0.02, 0.1); // 曲线 float d2 = stroke(st.y, 4.0 * (st.x - 0.5) * (st.x - 0.5), 0.02, 0.1); float d3 = stroke(st.y * 2.0, 1.0 - sin(30.0 * st.x), 0.02, 0.1); fragColor.rgb = d1 * vec3(1.0, 1.0, 0.0) + d2 * vec3(0.0, 1.0, 1.0) + d3 * vec3(0.5, 1.0, 0.5); fragColor.a = 1.0; } fragColor.rgb = d1 * vec3(1.0, 1.0, 0.0) + d2 * vec3(0.0, 1.0, 1.0); fragColor.a = 1.0; }