原生 WebGL
面试中如果简历写了 WebGL,面试官常会让你脱离 Three.js,讲清楚「原生 WebGL 画一个三角形要哪几步」。这是检验是否真正理解底层的试金石。
WebGL 是什么
- WebGL 是基于 OpenGL ES 2.0(WebGL2 基于 ES 3.0)的浏览器 3D 绘图 API,运行在
<canvas>上,通过 GPU 硬件加速。 - 它是一个状态机:大量操作是「绑定当前对象 → 设置状态 → 绘制」。
- 它本身只懂点、线、三角形三种图元,复杂模型都是三角形拼出来的。
渲染管线回顾
顶点数据(Buffer)
→ 顶点着色器 (逐顶点,坐标变换,输出 gl_Position)
→ 图元装配 (连成三角形)
→ 光栅化 (三角形 → 像素片段,varying 插值)
→ 片元着色器 (逐像素,计算 gl_FragColor)
→ 逐片元操作 (深度测试 / 模板测试 / 混合 Blending)
→ 帧缓冲 (屏幕)
其中顶点着色器和片元着色器是可编程的,其余阶段是 GPU 固定功能。
完整流程:画一个三角形
经典面试手写题。核心七步:写着色器源码 → 编译 → 链接成 program → 准备顶点数据进 buffer → 关联 attribute → 设置 uniform → drawArrays。
const gl = canvas.getContext("webgl");
// 1. 着色器源码(GLSL)
const vsSource = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
const fsSource = `
precision mediump float; // 片元着色器必须声明浮点精度
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
// 2. 编译着色器
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vs = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fs = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
// 3. 链接成 program
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
gl.useProgram(program);
// 4. 顶点数据送入缓冲区(显存)
const positions = new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5]);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
// 5. 关联 attribute:告诉 GPU 如何从 buffer 读取顶点
const aPosition = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(aPosition);
// 每个顶点取 2 个 float,无归一化,步长 0,偏移 0
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
// 6. 设置 uniform
const uColor = gl.getUniformLocation(program, "u_color");
gl.uniform4f(uColor, 1, 0, 0, 1); // 红色
// 7. 绘制
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3); // 从第 0 个顶点开始画 3 个顶点
关键概念
- VBO(顶点缓冲对象):
gl.createBuffer()创建、bufferData上传顶点数据到显存。 - EBO/IBO(索引缓冲):用
ELEMENT_ARRAY_BUFFER+drawElements通过索引复用顶点,省内存(如矩形 4 个顶点画 2 个三角形)。 - VAO(顶点数组对象,WebGL2):记录一组 attribute 配置,切换几何体时一次绑定即可。
- uniform vs attribute:见 Shader 篇 的限定符表。
- 深度测试:
gl.enable(gl.DEPTH_TEST),用 z 值决定遮挡关系。 - 混合(Blending):
gl.enable(gl.BLEND)+gl.blendFunc(...)处理半透明。
drawArrays vs drawElements
drawArrays(mode, first, count):按顺序读取顶点绘制。drawElements(mode, count, type, offset):按索引读取顶点,适合顶点复用多的网格。
WebGL vs WebGPU(2026 热点)
面试题:WebGPU 和 WebGL 有什么区别?
| - | WebGL | WebGPU |
|---|---|---|
| 底层 | OpenGL ES | 现代图形 API(Vulkan/Metal/D3D12)的抽象 |
| 着色语言 | GLSL | WGSL |
| 计算能力 | 无原生 Compute(需 GPGPU 黑科技) | 原生支持 Compute Shader |
| API 风格 | 全局状态机 | 显式的管线/资源绑定,多线程友好 |
| 性能 | 受 Draw Call 与状态切换限制 | 更低 CPU 开销、更高并行 |
| 现状 | 兼容性最好、生态成熟 | 主流浏览器已支持,逐步成为新项目方向 |
核心差异详解:GPGPU vs Compute Shader
面试追问:在处理海量粒子运算或流体模拟时,WebGL 和 WebGPU 的做法有什么不同?
1. WebGL 时代的无奈之举:GPGPU (通用图形处理)
- 原理:WebGL 的渲染管线是焊死的(只能输出颜色到屏幕)。为了让 GPU 帮我们做通用计算(比如算 100 万个粒子的下一帧位置),我们必须“骗”它。我们把粒子的
(x, y, z)坐标伪装成一张图片的(R, G, B)像素颜色,传给片段着色器(Fragment Shader)。着色器计算出新位置后,再输出成一张新的“颜色图片”(离屏渲染,FBO)。 - 痛点:这种把数据伪装成像素的过程极其扭曲,调试困难,且无法跨线程共享内存。
2. WebGPU 的杀手锏:Compute Shader (计算着色器)
- 原理:WebGPU 原生提供了完全独立于渲染管线的计算管线。你可以直接向 GPU 丢一个纯粹处理数据的计算着色器(Compute Shader),并直接分配线程组(Workgroups)。
- 优势:
- 直接读写 Buffer:不需要再把数据伪装成图片,直接把 Float32Array 传给 GPU 算,算完直接拿给 Vertex Shader 用。
- Shared Memory (共享内存):同一个线程组内的 GPU 线程可以极速共享数据,这在实现矩阵乘法、图像模糊、流体力学时性能直接起飞。
- 应用场景:不仅是图形学,像网页端的 AI 大模型推理(如 WebLLM)、复杂的物理引擎模拟、音视频硬解,都可以完美利用 Compute Shader。
Three.js 已提供 WebGPURenderer(TSL 着色语言),新项目对极致性能/计算有要求时可考虑 WebGPU。