跳到主要内容

WebGL 与 3D 可视化

WebGL 概述

WebGL(Web Graphics Library)是基于 OpenGL ES 的浏览器 3D 图形 API,通过 GPU 进行硬件加速渲染。

WebGL 渲染管线

阶段说明可编程
顶点着色器处理顶点位置、变换
图元装配将顶点组装为三角形
光栅化将三角形转为片元(像素)
片元着色器计算每个片元的颜色
输出合并深度测试、混合
面试速答版

WebGL 是什么?渲染管线长啥样? WebGL 基于 OpenGL ES,通过 GPU 渲染 3D 图形:

  • 顶点着色器:处理顶点位置、做 MVP 变换(模型→视图→投影)。
  • 图元装配 + 光栅化:把顶点装成三角形,再插值出每个像素片段。
  • 片元着色器:算每个片段的最终颜色(材质、光照、贴图)。
  • 输出合并:深度测试、Alpha 混合,写入帧缓冲。
  • 着色器都用 GLSL 写,通过 gl.createShader + 编译链接成 program。

前端怎么用?

  • 直接 WebGL API 太繁琐,业务里基本用 Three.js(最流行)或 Babylon.js(游戏感强)。
  • 大数据可视化用 Deck.gl(基于 WebGL 的图层化引擎)、regl(声明式 WebGL)。
  • 现代浏览器开始支持 WebGPU——下一代标准,更接近 Vulkan/Metal,性能更好、更易编程。

性能要点?

  • 批量绘制:减少 draw call,几何体合并成 BufferGeometry。
  • GPU Instancing:相同模型大量复用走 instancing,一个 draw call 画 N 个。
  • LOD:远处用低多边形模型;视椎剔除、遮挡剔除。

WebGL 基础

着色器(Shader)

着色器使用 GLSL(OpenGL Shading Language)编写:

// 顶点着色器:处理每个顶点的位置
const vertexShaderSource = `
attribute vec2 a_position; // 顶点坐标(从 JS 传入)
uniform vec2 u_resolution; // 画布分辨率

void main() {
// 将像素坐标转换为 [-1, 1] 的裁剪空间
vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}
`;

// 片元着色器:计算每个像素的颜色
const fragmentShaderSource = `
precision mediump float;
uniform vec4 u_color; // 颜色(从 JS 传入)

void main() {
gl_FragColor = u_color;
}
`;

初始化流程

function initWebGL(canvas: HTMLCanvasElement): WebGLRenderingContext | null {
const gl = canvas.getContext('webgl');
if (!gl) return null;

// 1. 编译着色器
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

// 2. 链接程序
const program = createProgram(gl, vertexShader!, fragmentShader!);
gl.useProgram(program);

// 3. 创建缓冲区并传入顶点数据
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
10, 20, 80, 20, 10, 80, // 三角形 1
10, 80, 80, 20, 80, 80, // 三角形 2
]), gl.STATIC_DRAW);

// 4. 设置属性指针
const posLoc = gl.getAttribLocation(program!, 'a_position');
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);

// 5. 绘制
gl.drawArrays(gl.TRIANGLES, 0, 6);

return gl;
}

function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | null {
const shader = gl.createShader(type);
if (!shader) return null;
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;
}

function createProgram(
gl: WebGLRenderingContext, vs: WebGLShader, fs: WebGLShader
): WebGLProgram | null {
const program = gl.createProgram();
if (!program) return null;
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
return null;
}
return program;
}

Three.js

Three.js 是最流行的 WebGL 封装库,大幅简化 3D 开发:

核心概念

import * as THREE from 'three';

// 1. 场景
const scene = new THREE.Scene();

// 2. 相机(透视相机)
const camera = new THREE.PerspectiveCamera(
75, // 视角(FOV)
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近平面
1000 // 远平面
);
camera.position.z = 5;

// 3. 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);

// 4. 创建物体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// 5. 添加灯光
scene.add(new THREE.AmbientLight(0x404040));
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);

// 6. 渲染循环
function animate(): void {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();

3D 数据可视化示例

// 3D 柱状图
function binderCreate3DBarChart(
scene: THREE.Scene,
data: Array<{ label: string; value: number }>,
): void {
const barWidth = 0.8;
const gap = 0.4;

data.forEach((item, i) => {
const height = item.value / 20; // 归一化高度
const geometry = new THREE.BoxGeometry(barWidth, height, barWidth);
const material = new THREE.MeshPhongMaterial({
color: new THREE.Color().setHSL(i / data.length, 0.7, 0.5),
});

const bar = new THREE.Mesh(geometry, material);
bar.position.x = i * (barWidth + gap);
bar.position.y = height / 2; // 底部对齐
scene.add(bar);
});
}

WebGL 2.0 与 WebGPU

特性WebGL 1.0WebGL 2.0WebGPU
基础OpenGL ES 2.0OpenGL ES 3.0新设计
3D 纹理
实例化渲染扩展
MSAA扩展
计算着色器
多线程
浏览器支持广泛广泛Chrome 113+

常见面试问题

Q1: WebGL 的渲染管线是什么?

答案

WebGL 渲染管线:顶点数据 → 顶点着色器(可编程,处理位置变换)→ 图元装配(组装三角形)→ 光栅化(转为像素片元)→ 片元着色器(可编程,计算颜色)→ 输出合并(深度测试、混合)→ 帧缓冲区。开发者主要编写的是顶点着色器和片元着色器。

Q2: Three.js 的核心概念有哪些?

答案

Scene(场景容器)、Camera(相机,定义观察视角)、Renderer(渲染器,输出画面)、Mesh(网格体 = Geometry 几何体 + Material 材质)、Light(灯光)。渲染流程:创建场景 → 添加物体和灯光 → 设置相机 → 渲染循环。

Q3: Canvas 2D、WebGL、Three.js 如何选择?

答案

  • Canvas 2D:2D 图表、简单动画、像素操作
  • WebGL:需要极致性能、自定义着色器效果
  • Three.js:3D 场景、快速开发 3D 可视化
  • Deck.gl/CesiumJS:地理信息 3D 可视化

Q4: WebGPU 和 WebGL 的区别?

答案

WebGPU 是下一代图形 API,支持计算着色器(通用 GPU 计算)、更低的 CPU 开销、更好的多线程支持。WebGL 基于 OpenGL ES,API 较旧且状态机模型效率低。WebGPU 在 Chrome 113+ 可用,但生态尚不成熟。

相关链接