Canvas 与 WebGL 入门
概述
Canvas 和 WebGL 是现代 Web 图形技术的两大基石。Canvas 2D 提供了基于 CPU 的二维位图渲染能力,适合数据可视化、游戏 2D 图形、图像处理等场景;WebGL 则基于 OpenGL ES,通过 GPU 加速实现高性能 2D 和 3D 渲染,是浏览器端 3D 应用的核心技术。本文将系统介绍二者的核心概念、实践技巧以及生态工具,帮助开发者快速上手。
Canvas 2D 基础
Canvas 2D 是 HTML5 最强大的功能之一,通过 <canvas> 元素和 CanvasRenderingContext2D 接口提供丰富的 2D 绘图能力。
绘图 API
Canvas 2D 的绘图模型类似于"画布 + 画笔"模式。最基本的绘图操作包括:
基本图形绘制:
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')
// 矩形
ctx.fillStyle = '#ff6b6b'
ctx.fillRect(10, 10, 100, 60)
// 圆弧
ctx.beginPath()
ctx.arc(200, 100, 50, 0, Math.PI * 2)
ctx.fillStyle = '#4ecdc4'
ctx.fill()
// 文本
ctx.font = '24px "Segoe UI", sans-serif'
ctx.fillStyle = '#fff'
ctx.fillText('Hello Canvas', 50, 200)路径系统
路径(Path)是 Canvas 2D 的核心概念,通过 beginPath()、moveTo()、lineTo()、bezierCurveTo() 等方法构建复杂形状:
ctx.beginPath()
ctx.moveTo(50, 50)
ctx.lineTo(200, 50)
ctx.lineTo(125, 150)
ctx.closePath()
ctx.fillStyle = '#ffe66d'
ctx.fill()
ctx.strokeStyle = '#333'
ctx.lineWidth = 2
ctx.stroke()路径绘制支持二次贝塞尔曲线和三次贝塞尔曲线,适合绘制流畅的曲线图形:
ctx.beginPath()
ctx.moveTo(50, 200)
ctx.quadraticCurveTo(150, 50, 250, 200)
ctx.strokeStyle = '#ff6b6b'
ctx.lineWidth = 3
ctx.stroke()坐标变换
Canvas 2D 的变换系统基于变换矩阵(transformation matrix),支持平移、旋转、缩放和矩阵变换:
ctx.save() // 保存当前状态
ctx.translate(200, 150) // 平移
ctx.rotate(Math.PI / 4) // 旋转 45 度
ctx.scale(1.5, 1.5) // 缩放
ctx.fillStyle = '#ff6b6b'
ctx.fillRect(-25, -25, 50, 50)
ctx.restore() // 恢复状态使用 save() 和 restore() 管理变换状态栈,避免变换的叠加污染后续绘制。
像素操作
Canvas 2D 提供 getImageData() 和 putImageData() 方法,允许直接访问和修改像素数据:
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
const data = imageData.data
// 灰度滤镜:遍历每个像素
for (let i = 0; i < data.length; i += 4) {
const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]
data[i] = gray // R
data[i + 1] = gray // G
data[i + 2] = gray // B
}
ctx.putImageData(imageData, 0, 0)像素操作适合实现图像滤镜、颜色拾取、碰撞检测等场景。注意 getImageData 的性能开销较大,频繁调用可能导致帧率下降。
动画循环
Canvas 动画使用 requestAnimationFrame 驱动,它比 setInterval 更高效,能自动与显示刷新率同步:
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
// 更新逻辑
x += vx
y += vy
// 绘制
ctx.beginPath()
ctx.arc(x, y, radius, 0, Math.PI * 2)
ctx.fill()
requestAnimationFrame(animate)
}
requestAnimationFrame(animate)动画循环的标准结构包括:清空画布、更新状态、绘制内容、请求下一帧。
性能优化
Canvas 2D 性能优化策略:
- 离屏 Canvas:将静态内容预先绘制到离屏 Canvas 上,主 Canvas 直接绘制离屏 Canvas
- 分层 Canvas:将静态层和动态层分离为多个 Canvas,减少不必要的重绘
- 避免频繁状态变更:尽量减少
fillStyle、strokeStyle等状态的切换次数 - 批量绘制:将同类型绘制操作集中执行
- 裁剪区域:使用
clip()限制绘制范围 - 使用整数坐标:避免子像素渲染带来的性能损耗
- 限制重绘区域:使用
clearRect替代clearRect(0, 0, w, h),只清除需要更新的区域
Three.js 基础
Three.js 是目前最流行的 WebGL 封装库,它抽象了 WebGL 的复杂性,提供了一套面向对象的 3D 场景构建 API。
核心概念
Three.js 的渲染流程围绕四个核心对象展开:
Scene(场景):所有 3D 对象的容器,相当于一个虚拟世界。场景可以包含网格、灯光、相机等一切 3D 元素。
Renderer(渲染器):将场景渲染到屏幕上的引擎。最常用的是 WebGLRenderer:
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
document.body.appendChild(renderer.domElement)Camera(相机):定义观察的视角和方式。透视相机(PerspectiveCamera)模拟人眼效果,适合 3D 场景:
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
camera.position.set(0, 0, 5)Mesh(网格):3D 世界中可见的对象,由几何体(Geometry)和材质(Material)组合而成:
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshStandardMaterial({ color: 0x44aa88 })
const cube = new THREE.Mesh(geometry, material)
scene.add(cube)光照系统
Three.js 提供了多种光照类型:
- AmbientLight(环境光):均匀照亮所有面,无方向性
- DirectionalLight(平行光):模拟太阳光,有方向但无衰减
- PointLight(点光源):从一个点向四周发射,有距离衰减
- SpotLight(聚光灯):锥形光照,有方向和范围限制
- HemisphereLight(半球光):模拟天空和地面的环境光
const ambient = new THREE.AmbientLight(0x404060, 0.5)
scene.add(ambient)
const directional = new THREE.DirectionalLight(0xffffff, 1)
directional.position.set(5, 10, 7)
directional.castShadow = true
scene.add(directional)材质系统
Three.js 的材质决定了物体表面的外观:
- MeshBasicMaterial:不受光照影响,直接显示颜色或纹理
- MeshStandardMaterial:标准 PBR 材质,支持光照、粗糙度、金属度
- MeshPhongMaterial:Phong 光照模型材质,支持镜面高光
- MeshPhysicalMaterial:最逼真的材质,支持透明涂层、清漆效果
const material = new THREE.MeshStandardMaterial({
color: 0x44aa88,
roughness: 0.4,
metalness: 0.1,
map: textureLoader.load('texture.jpg'),
normalMap: textureLoader.load('normal.jpg')
})几何体
Three.js 内置了丰富的几何体类型:BoxGeometry(立方体)、SphereGeometry(球体)、CylinderGeometry(圆柱体)、PlaneGeometry(平面)、TorusGeometry(圆环)、TorusKnotGeometry(环面纽结)等。也可以通过 BufferGeometry 自定义顶点数据构建任意形状。
粒子系统
粒子系统是实现视觉效果(火焰、烟雾、星空、雨雪等)的核心技术。
Canvas 2D 粒子
在 Canvas 2D 中实现粒子系统的典型模式:
class Particle {
constructor() {
this.x = Math.random() * canvas.width
this.y = Math.random() * canvas.height
this.vx = (Math.random() - 0.5) * 2
this.vy = (Math.random() - 0.5) * 2
this.size = Math.random() * 3 + 1
this.life = 1
this.decay = 0.005 + Math.random() * 0.01
}
update() {
this.x += this.vx
this.y += this.vy
this.life -= this.decay
}
draw(ctx) {
ctx.globalAlpha = this.life
ctx.beginPath()
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
ctx.fill()
}
}粒子系统的性能关键点包括:对象池(复用粒子对象减少 GC)、批量绘制(同批粒子使用相同的样式)、碰撞检测的简化。
Three.js Points(粒子系统)
Three.js 使用 Points 对象实现高性能粒子系统,通过 BufferGeometry 和 PointsMaterial 控制:
const count = 10000
const geometry = new THREE.BufferGeometry()
const positions = new Float32Array(count * 3)
const colors = new Float32Array(count * 3)
for (let i = 0; i < count * 3; i++) {
positions[i] = (Math.random() - 0.5) * 100
colors[i] = Math.random()
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
const material = new THREE.PointsMaterial({
size: 0.2,
vertexColors: true,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending,
sizeAttenuation: true
})
const particles = new THREE.Points(geometry, material)
scene.add(particles)粒子动画与性能考量
- 使用 BufferAttribute:更新粒子位置直接操作 Float32Array 比重新创建 BufferGeometry 快得多
- 控制粒子数量:Canvas 2D 粒子建议控制在数千级别,WebGL/Three.js Points 可轻松处理数万到数十万粒子
- 使用精灵图(Sprite):粒子使用纹理贴图可以获得更丰富的视觉效果
- LOD 策略:远处粒子降低渲染精度
- GPU 计算:对于大规模粒子动画,使用 GPUComputationRenderer 在 GPU 端计算粒子位置
3D 场景构建
阴影
Three.js 中启用阴影需要三步:
// 1. 渲染器启用阴影
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
// 2. 光源投射阴影
directionalLight.castShadow = true
// 3. 物体投射和接收阴影
mesh.castShadow = true
ground.receiveShadow = true纹理贴图
纹理通过 TextureLoader 加载,支持多种纹理类型:
- map:颜色贴图,定义表面颜色
- normalMap:法线贴图,模拟表面凹凸细节
- roughnessMap:粗糙度贴图,定义表面粗糙度分布
- metalnessMap:金属度贴图
- alphaMap:透明度贴图
- envMap:环境贴图,用于反射/折射效果
const loader = new THREE.TextureLoader()
const material = new THREE.MeshStandardMaterial({
map: loader.load('diffuse.jpg'),
normalMap: loader.load('normal.jpg'),
roughnessMap: loader.load('roughness.jpg'),
metalnessMap: loader.load('metalness.jpg')
})模型加载
Three.js 通过不同的 Loader 支持多种 3D 模型格式:
- GLTFLoader:加载
.gltf/.glb格式(推荐,Web 标准) - FBXLoader:加载
.fbx格式 - OBJLoader:加载
.obj格式 - DRACOLoader:配合 Draco 压缩的 GLTF 模型
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
const loader = new GLTFLoader()
loader.load('model.glb', (gltf) => {
scene.add(gltf.scene)
})轨道控制
OrbitControls 提供鼠标/触控交互,支持旋转、缩放、平移:
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true // 启用惯性
controls.dampingFactor = 0.05
controls.autoRotate = true // 自动旋转
controls.autoRotateSpeed = 1.0
controls.update()Canvas vs WebGL vs WebGPU 对比
| 特性 | Canvas 2D | WebGL 2.0 | WebGPU |
|---|---|---|---|
| 渲染方式 | CPU 光栅化 | GPU 可编程管线 | GPU 现代计算管线 |
| 编程复杂度 | 简单,直接 | 中等,需要着色器 | 较高,新一代 API |
| 性能 | 中等,适合 2D | 高,适合 3D | 更高,低 CPU 开销 |
| 浏览器支持 | 所有浏览器 | 广泛支持 | Chrome/Edge/Firefox Nightly |
| 适用场景 | 数据可视化、2D 游戏 | 3D 游戏、可视化 | 高性能计算、大规模 3D |
| 着色器编程 | 不支持 | GLSL | WGSL |
| 计算能力 | 无 | 有限(通过变换反馈) | 通用计算(Compute Shader) |
| 学习曲线 | 低 | 中高 | 高 |
选型建议
- 数据图表/2D 游戏/图像处理:Canvas 2D 足够,开发效率最高
- 3D 可视化/产品展示/简单 3D 游戏:Three.js(基于 WebGL),平衡了性能和开发效率
- 高性能 3D 应用/大规模粒子/复杂着色器:原生 WebGL 或 WebGPU
- 未来项目:WebGPU 是 Web 图形技术的未来方向,但在主流浏览器完全支持前,WebGL 仍是实际选择
常见 3D 效果实现
旋转
在动画循环中更新物体的 rotation 属性即可实现旋转:
function animate() {
cube.rotation.x += 0.01
cube.rotation.y += 0.02
renderer.render(scene, camera)
requestAnimationFrame(animate)
}对于更复杂的旋转控制,可以使用四元数(Quaternion)避免万向锁:
const quaternion = new THREE.Quaternion()
quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), angle)
object.quaternion.copy(quaternion)缩放
通过 scale 属性控制物体的缩放:
object.scale.set(2, 2, 2) // 放大两倍
// 或渐变动画
object.scale.lerp(new THREE.Vector3(1.5, 1.5, 1.5), 0.05)拖拽
使用 OrbitControls 或 DragControls 实现拖拽交互:
import { DragControls } from 'three/addons/controls/DragControls.js'
const controls = new DragControls(objects, camera, renderer.domElement)
controls.addEventListener('dragstart', (event) => {
event.object.material.emissive.set(0x444444)
})
controls.addEventListener('dragend', (event) => {
event.object.material.emissive.set(0x000000)
})碰撞检测
Three.js 中使用 Raycaster 实现碰撞检测和拾取交互:
const raycaster = new THREE.Raycaster()
const pointer = new THREE.Vector2()
// 鼠标点击检测
window.addEventListener('click', (event) => {
pointer.x = (event.clientX / window.innerWidth) * 2 - 1
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1
raycaster.setFromCamera(pointer, camera)
const intersects = raycaster.intersectObjects(scene.children)
if (intersects.length > 0) {
// 第一个被击中的物体
console.log('Hit:', intersects[0].object.name)
}
})对于物体之间的碰撞检测,可以使用包围盒(Box3)或包围球(Sphere):
const box1 = new THREE.Box3().setFromObject(object1)
const box2 = new THREE.Box3().setFromObject(object2)
if (box1.intersectsBox(box2)) {
console.log('碰撞发生!')
}项目资源与学习路径
推荐库和工具
- Three.js:最流行的 WebGL 框架,生态完善
- Babylon.js:微软出品的强大 3D 引擎,适合大型项目
- GSAP:配合 Canvas 动画使用,提供专业补间动画
- PixiJS:高性能 Canvas 2D / WebGL 渲染引擎,适合 2D 游戏
- D3.js:数据驱动文档,结合 Canvas 实现高性能可视化
- dat.GUI / lil-gui:调试参数面板
- Blender:免费开源的 3D 建模工具
学习路径建议
- 入门阶段:掌握 Canvas 2D 基础 API,完成 2D 绘制、动画和交互练习
- 进阶阶段:学习 Three.js 核心概念(场景、相机、渲染器、几何体、材质、光照)
- 实践阶段:构建完整的 3D 场景,加入交互和动画,学习模型加载和纹理使用
- 深入阶段:学习 WebGL 着色器编程(GLSL),了解 GPU 渲染管线
- 前沿阶段:探索 WebGPU,学习计算着色器和现代图形编程技术
学习资源
- Three.js 官方文档:权威 API 参考
- Three.js Journey:Bruno Simon 的付费课程,质量极高
- WebGL Fundamentals:WebGL 底层教程
- WebGPU Fundamentals:WebGPU 入门教程
- The Book of Shaders:着色器编程入门
- MDN Canvas Tutorial:MDN 的 Canvas 教程
交互演示
以下 Demo 展示了 Canvas 2D 粒子系统和 Three.js 3D 场景的实际效果,点击按钮可在两者之间切换: