第 11 节:综合实战
🎯 学习目标
- 综合运用所学知识
- 创建一个完整的 3D 场景
- 对比原生 WebGL 和 Three.js 的实现
- 理解实际项目开发流程
📖 项目要求
创建一个完整的 3D 场景,包含以下功能:
- 场景搭建:多个 3D 物体、灯光、相机
- 交互控制:相机控制、物体选择
- 视觉效果:光照、阴影、后处理
- 性能优化:合理的 Draw Call、LOD、批处理
💻 项目实现
项目结构
project/
├── index.html # 主页面
├── js/
│ ├── scene.js # 场景管理(原生 WebGL)
│ ├── renderer.js # 渲染器封装
│ ├── camera.js # 相机控制
│ └── utils.js # 工具函数
└── threejs/
└── scene.js # Three.js 版本
核心功能实现
1. 场景管理(原生 WebGL)
class Scene {
constructor(gl) {
this.gl = gl;
this.objects = [];
this.lights = [];
this.camera = new Camera();
}
add(object) {
this.objects.push(object);
}
render() {
const gl = this.gl;
// 清空画布
gl.clearColor(0.1, 0.1, 0.1, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 按材质分组渲染
const groups = this.groupByMaterial();
for (const group of groups) {
gl.useProgram(group.program);
gl.bindTexture(gl.TEXTURE_2D, group.texture);
for (const obj of group.objects) {
this.renderObject(obj);
}
}
}
groupByMaterial() {
// 按材质分组,减少状态切换
const groups = new Map();
for (const obj of this.objects) {
const key = `${obj.program}-${obj.texture}`;
if (!groups.has(key)) {
groups.set(key, { program: obj.program, texture: obj.texture, objects: [] });
}
groups.get(key).objects.push(obj);
}
return Array.from(groups.values());
}
renderObject(object) {
// 设置 MVP 矩阵
const mvp = this.camera.getMVPMatrix(object);
const mvpLoc = this.gl.getUniformLocation(object.program, "uModelViewProjection");
this.gl.uniformMatrix4fv(mvpLoc, false, mvp);
// 绘制
this.gl.drawElements(this.gl.TRIANGLES, object.indexCount, this.gl.UNSIGNED_SHORT, 0);
}
}
2. 相机控制
class Camera {
constructor() {
this.position = [0, 0, 5];
this.target = [0, 0, 0];
this.up = [0, 1, 0];
this.fov = Math.PI / 4;
this.aspect = 1;
this.near = 0.1;
this.far = 100;
}
getViewMatrix() {
return mat4.lookAt([], this.position, this.target, this.up);
}
getProjectionMatrix() {
return mat4.perspective([], this.fov, this.aspect, this.near, this.far);
}
getMVPMatrix(object) {
const view = this.getViewMatrix();
const proj = this.getProjectionMatrix();
const mvp = mat4.create();
mat4.multiply(mvp, proj, view);
mat4.multiply(mvp, mvp, object.modelMatrix);
return mvp;
}
orbit(angleX, angleY, radius) {
// 球坐标控制
const x = radius * Math.sin(angleY) * Math.cos(angleX);
const y = radius * Math.cos(angleY);
const z = radius * Math.sin(angleY) * Math.sin(angleX);
this.position = [x, y, z];
}
}
3. 光照系统
class LightManager {
constructor(gl, program) {
this.gl = gl;
this.program = program;
this.lights = [];
}
addLight(light) {
this.lights.push(light);
}
updateUniforms() {
// 更新 Shader 中的光照 Uniform
for (let i = 0; i < this.lights.length; i++) {
const light = this.lights[i];
const posLoc = this.gl.getUniformLocation(this.program, `uLights[${i}].position`);
const colorLoc = this.gl.getUniformLocation(this.program, `uLights[${i}].color`);
this.gl.uniform3f(posLoc, ...light.position);
this.gl.uniform3f(colorLoc, ...light.color);
}
}
}
🎨 Three.js 版本
场景搭建
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
class Scene3D {
constructor() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.setupScene();
this.setupLights();
this.setupObjects();
this.setupPostProcessing();
}
setupScene() {
this.renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(this.renderer.domElement);
this.camera.position.set(0, 0, 5);
}
setupLights() {
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
this.scene.add(directionalLight);
}
setupObjects() {
// 创建多个物体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
for (let i = 0; i < 10; i++) {
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10
);
this.scene.add(mesh);
}
}
setupPostProcessing() {
this.composer = new EffectComposer(this.renderer);
const renderPass = new RenderPass(this.scene, this.camera);
this.composer.addPass(renderPass);
}
animate() {
requestAnimationFrame(() => this.animate());
this.controls.update();
this.composer.render();
}
}
🔍 对比分析
代码量对比
| 功能 | 原生 WebGL | Three.js |
|---|---|---|
| 场景管理 | ~200 行 | ~50 行 |
| 相机控制 | ~100 行 | ~10 行 |
| 光照系统 | ~150 行 | ~20 行 |
| 后处理 | ~200 行 | ~30 行 |
| 总计 | ~650 行 | ~110 行 |
性能对比
| 指标 | 原生 WebGL | Three.js |
|---|---|---|
| Draw Call | 完全控制 | 自动优化 |
| 内存使用 | 手动管理 | 自动管理 |
| 渲染速度 | 可能更快 | 通常足够快 |
| 开发效率 | 低 | 高 |
适用场景
原生 WebGL:
- 需要完全控制
- 性能要求极高
- 特殊需求(如自定义渲染管线)
Three.js:
- 快速开发
- 复杂场景管理
- 团队协作
- 大多数商业项目
📝 编程作业
综合项目(⭐⭐⭐)
任务:创建一个完整的 3D 场景
要求:
场景搭建:
- 至少 5 个不同的 3D 物体
- 多种材质和纹理
- 合理的场景布局
交互控制:
- 相机控制(旋转、缩放、平移)
- 物体选择和高亮
- 参数调节面板
视觉效果:
- 完整的光照系统
- 至少 2 种后处理效果
- 平滑的动画
性能优化:
- 合理的 Draw Call
- 使用 LOD 或批处理
- 性能监控面板
代码质量:
- 清晰的代码结构
- 完整的注释
- 错误处理
检查清单:
- 场景可以正常运行
- 所有功能都实现
- 性能达到要求(60 FPS)
- 代码结构清晰
- 有完整的文档
🎓 项目总结
关键技术点
- 场景管理:对象组织、渲染顺序
- 相机控制:多种控制模式
- 光照系统:多光源支持
- 后处理:效果链管理
- 性能优化:批处理、LOD
开发流程
- 需求分析:明确功能需求
- 架构设计:设计代码结构
- 功能实现:逐步实现功能
- 性能优化:测试和优化
- 代码重构:优化代码结构
最佳实践
- 模块化设计:将功能拆分为模块
- 错误处理:完善的错误处理机制
- 性能监控:实时监控性能指标
- 代码注释:清晰的代码注释
- 文档完善:完整的使用文档
🎉 课程总结
恭喜你完成了 WebGL 系统化教程!
你学会了什么
- WebGL 基础:渲染管线、坐标系统、矩阵变换
- Shader 编程:顶点着色器、片段着色器、Uniform 变量
- 纹理和光照:纹理映射、光照模型、材质系统
- 高级特性:帧缓冲、后处理、性能优化
- 实战经验:完整的项目开发流程
下一步学习建议
深入学习:
- PBR 渲染
- 阴影映射
- 延迟渲染
- 体积渲染
实践项目:
- 3D 游戏
- 数据可视化
- 创意作品
- 商业项目
社区参与:
- 参与开源项目
- 分享学习经验
- 帮助其他学习者
推荐资源
祝你学习愉快,在 WebGL 的道路上越走越远! 🚀