Skip to main content

第 11 节:综合实战

🎯 学习目标

  • 综合运用所学知识
  • 创建一个完整的 3D 场景
  • 对比原生 WebGL 和 Three.js 的实现
  • 理解实际项目开发流程

📖 项目要求

创建一个完整的 3D 场景,包含以下功能:

  1. 场景搭建:多个 3D 物体、灯光、相机
  2. 交互控制:相机控制、物体选择
  3. 视觉效果:光照、阴影、后处理
  4. 性能优化:合理的 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();
}
}

🔍 对比分析

代码量对比

功能原生 WebGLThree.js
场景管理~200 行~50 行
相机控制~100 行~10 行
光照系统~150 行~20 行
后处理~200 行~30 行
总计~650 行~110 行

性能对比

指标原生 WebGLThree.js
Draw Call完全控制自动优化
内存使用手动管理自动管理
渲染速度可能更快通常足够快
开发效率

适用场景

原生 WebGL

  • 需要完全控制
  • 性能要求极高
  • 特殊需求(如自定义渲染管线)

Three.js

  • 快速开发
  • 复杂场景管理
  • 团队协作
  • 大多数商业项目

📝 编程作业

综合项目(⭐⭐⭐)

任务:创建一个完整的 3D 场景

要求

  1. 场景搭建

    • 至少 5 个不同的 3D 物体
    • 多种材质和纹理
    • 合理的场景布局
  2. 交互控制

    • 相机控制(旋转、缩放、平移)
    • 物体选择和高亮
    • 参数调节面板
  3. 视觉效果

    • 完整的光照系统
    • 至少 2 种后处理效果
    • 平滑的动画
  4. 性能优化

    • 合理的 Draw Call
    • 使用 LOD 或批处理
    • 性能监控面板
  5. 代码质量

    • 清晰的代码结构
    • 完整的注释
    • 错误处理

检查清单

  • 场景可以正常运行
  • 所有功能都实现
  • 性能达到要求(60 FPS)
  • 代码结构清晰
  • 有完整的文档

🎓 项目总结

关键技术点

  1. 场景管理:对象组织、渲染顺序
  2. 相机控制:多种控制模式
  3. 光照系统:多光源支持
  4. 后处理:效果链管理
  5. 性能优化:批处理、LOD

开发流程

  1. 需求分析:明确功能需求
  2. 架构设计:设计代码结构
  3. 功能实现:逐步实现功能
  4. 性能优化:测试和优化
  5. 代码重构:优化代码结构

最佳实践

  1. 模块化设计:将功能拆分为模块
  2. 错误处理:完善的错误处理机制
  3. 性能监控:实时监控性能指标
  4. 代码注释:清晰的代码注释
  5. 文档完善:完整的使用文档

🎉 课程总结

恭喜你完成了 WebGL 系统化教程!

你学会了什么

  1. WebGL 基础:渲染管线、坐标系统、矩阵变换
  2. Shader 编程:顶点着色器、片段着色器、Uniform 变量
  3. 纹理和光照:纹理映射、光照模型、材质系统
  4. 高级特性:帧缓冲、后处理、性能优化
  5. 实战经验:完整的项目开发流程

下一步学习建议

  1. 深入学习

    • PBR 渲染
    • 阴影映射
    • 延迟渲染
    • 体积渲染
  2. 实践项目

    • 3D 游戏
    • 数据可视化
    • 创意作品
    • 商业项目
  3. 社区参与

    • 参与开源项目
    • 分享学习经验
    • 帮助其他学习者

推荐资源

祝你学习愉快,在 WebGL 的道路上越走越远! 🚀


📚 参考资源