Skip to main content

threejs源码解析:从框架封装中学习设计

🎯 教程特色

本教程采用源码驱动学习法,每一节都包含:

  1. 源码精读:定位 Three.js 关键模块的核心代码,逐层剖析设计意图
  2. 原生 WebGL 对照:回顾底层 API,理解 Three.js 为什么要这样封装
  3. 设计决策分析:讨论 Three.js 在通用性、性能、易用性之间的取舍
  4. Mini 实现:每节末尾用 50-100 行代码实现一个"极简版"同等模块,加深理解
  5. OGL 对比:预览 OGL 对同一问题的极简解法,为后续课程铺路

📚 为什么需要读 Three.js 源码?

学完 WebGL 教程后你知道的读完 Three.js 源码后你还会知道的
gl.bindBuffer / gl.bindTextureThree.js 如何用状态缓存避免冗余绑定
手动编译 ShaderShaderChunk 拼接 + Program 缓存 + 宏定义条件编译
手动传 UniformWebGLUniforms 如何自动同步整个材质树
手动排序绘制不透明/透明两队列 + painterSort + reversePainterSort
手动管理 FBOWebGLRenderTarget 如何复用 FBO 和自动管理 viewport
知道性能瓶颈在哪Three.js 的 Frustum Culling、Instancing、LOD 如何实现

一句话:WebGL 教程让你"看见了骨骼",本课程让你"看见了肌肉是如何包裹骨骼的"。

📐 课程定位

数学基础 → Three.js 进阶 → GLSL → WebGL 底层

★ Three.js 源码解析 ★ ← 你在这里

OGL 源码解析 → TinyDrive 引擎

前置要求

  • 已完成 WebGL 教程(熟悉原生 API)
  • 已完成 GLSL 教程(熟悉 ShaderChunk 和 onBeforeCompile)

学习产出

  • 理解工业级 WebGL 框架的封装模式
  • 能独立阅读 Three.js 任意模块的源码
  • 为构建自研引擎积累设计经验

🗺️ 课程大纲

本教程共 12 节,从架构全景到设计启示,完整拆解 Three.js 的封装艺术。

第一阶段:架构全景(第 1-2 节)

第 1 节:Three.js 架构全景与源码阅读方法

  • 模块依赖图:Core / Math / Renderers / Materials / Geometries / Lights / Cameras 的关系
  • 源码目录结构src/ 下各子目录的职责划分
  • 阅读路径:从 renderer.render(scene, camera) 出发,跟踪一帧的完整调用链
  • 版本说明:基于 Three.js r160+ 源码,兼顾 WebGL1/2 差异
  • Mini 实现:搭建一个"空壳 Renderer",定义 render(scene, camera) 入口

第 2 节:render() 的完整旅程——一帧内部发生了什么

  • 入口分析WebGLRenderer.render() 的完整调用链
  • 场景准备projectObject() 递归遍历场景图 → 收集可渲染对象
  • 排序策略:不透明队列(前→后)vs 透明队列(后→前)的排序实现
  • 渲染循环renderObjects()renderObject()renderBufferDirect()
  • 清理收尾:状态复位、统计信息更新
  • Mini 实现:实现一个能遍历场景图并按顺序调用 gl.drawArrays 的极简渲染循环

第二阶段:核心封装三件套(第 3-5 节)

第 3 节:Geometry 系统——BufferGeometry 如何驯服 VBO

  • BufferAttribute:如何把 Float32Array 包装成带 itemSize / normalized / needsUpdate 的属性描述
  • BufferGeometry:attributes 字典 + index + groups 的数据结构设计
  • 上传机制WebGLAttributes.update() 如何决定 gl.bufferData vs gl.bufferSubData
  • Interleaved 优化InterleavedBuffer / InterleavedBufferAttribute 的 stride 共享
  • Dispose 机制geometry.dispose() 如何触发 GPU 资源释放
  • Mini 实现:实现一个带 setAttribute / setIndex / update 的极简 Geometry 类
  • OGL 对比:OGL 的 Geometry 类如何用更少的代码达到同样目的

第 4 节:Material 与 Program 系统——Shader 的编译、缓存与管理

  • Material 基类needsUpdateversiononBeforeCompile 的协作机制
  • ShaderChunk 拼接ShaderLib 如何把几十个 Chunk 拼成完整的 VS/FS
  • 宏定义系统#define 条件编译如何实现"一套 Shader 适配多种配置"
  • Program 缓存WebGLPrograms.getParameters() 如何生成唯一 key,避免重复编译
  • Uniform 同步WebGLUniforms 如何用 setValueXxx 系列方法自动传递数据
  • Mini 实现:实现一个支持 Shader 编译 + Uniform 管理 + 简易缓存的 Program 类
  • OGL 对比:OGL 的 Program 类——无 Chunk 拼接,直接传入 VS/FS 字符串

第 5 节:场景图与矩阵系统——Object3D 的树形世界

  • Object3D 设计position / rotation / quaternion / scale 的"双向同步"机制
  • 矩阵传播updateMatrix()updateMatrixWorld()updateWorldMatrix() 的递归链
  • 脏标记优化matrixWorldNeedsUpdate 如何避免每帧都重算所有矩阵
  • 遍历方法traverse() / traverseVisible() / traverseAncestors() 的实现
  • Layers 系统:位掩码实现的可见性分层(用于多相机 / 选择性渲染)
  • Mini 实现:实现一个带 parent/children + 矩阵自动传播的极简场景图
  • OGL 对比:OGL 的 Transform 类——相同的树结构,更精简的矩阵更新

第三阶段:GPU 资源管理(第 6-7 节)

第 6 节:纹理系统——从 Image 到 GPU 纹理对象

  • Texture 类设计wrapS/TmagFilter/minFilterformattype 的参数映射
  • WebGLTextures 模块uploadTexture() 如何处理 Image / Canvas / Video / TypedArray 等不同来源
  • Mipmap 策略:自动生成 vs 手动指定、needsPowerOfTwo 的兼容处理
  • 压缩纹理CompressedTexture 如何对接 ASTC / S3TC / ETC2 等 GPU 格式
  • 纹理单元管理WebGLTextures.allocateTextureUnit() 的分配与复用
  • 内存估算:Three.js 内部如何统计纹理内存使用量
  • Mini 实现:实现一个支持 2D 纹理上传 + 参数设置 + 纹理单元分配的极简类
  • OGL 对比:OGL 的 Texture 类——直接暴露 gl.texImage2D,零抽象开销

第 7 节:WebGL 状态管理——减少冗余 GL 调用的艺术

  • WebGLState 模块:Three.js 如何缓存 blendFunc / depthFunc / cullFace 等状态
  • 绑定缓存bindTexture() / bindBuffer() 如何跳过"已经绑定"的重复调用
  • 状态栈colorBuffer / depthBuffer / stencilBuffersetMask / setTest / setFunc 设计
  • Reset 机制reset() 在上下文丢失 / 外部库干扰时如何恢复
  • 性能影响:实测对比——有/无状态缓存时的 Draw Call 开销差异
  • Mini 实现:实现一个 GLState 类,缓存 5 种常见 WebGL 状态
  • OGL 对比:OGL 的状态管理——在 Renderer 内部用极简对象缓存

第四阶段:高级渲染系统(第 8-10 节)

第 8 节:Camera 与投影系统

  • Camera 基类matrixWorldInverse(View 矩阵)和 projectionMatrix 的更新时机
  • PerspectiveCameraupdateProjectionMatrix() 内部的透视矩阵推导
  • OrthographicCamera:正交矩阵的参数映射
  • Frustum CullingFrustum.intersectsObject() 如何用 6 个平面做快速剔除
  • Viewport 与 ScissorsetViewport() / setScissor() 对多视口渲染的支持
  • Mini 实现:实现一个带 View/Projection 矩阵 + 基础 Frustum Culling 的 Camera 类
  • OGL 对比:OGL 的 Camera 类——继承自 Transform,矩阵更新与场景图融为一体

第 9 节:光照与阴影系统

  • Light 体系AmbientLight / DirectionalLight / PointLight / SpotLight 的 Uniform 结构
  • 光照数据打包WebGLLights.setup() 如何把场景中所有光源打包成 Uniform 数组
  • 阴影渲染管线WebGLShadowMap.render() 的完整流程——从光源视角渲染深度图
  • Shadow Map 类型:Basic / PCF / PCF Soft / VSM 的实现差异
  • 级联阴影(CSM)CSMShadowMap 的分割策略与混合
  • Mini 实现:实现一个支持单个平行光 + 基础阴影贴图的光照系统
  • OGL 对比:OGL 不内置光照——理解"引擎不做光照"的设计取舍

第 10 节:RenderTarget 与后处理管线

  • WebGLRenderTarget:如何封装 gl.createFramebuffer + 颜色/深度附件
  • MRT(Multiple Render Targets)WebGLMultipleRenderTargets 的绑定策略
  • EffectComposer 内部readBuffer / writeBuffer 的 ping-pong 交换机制
  • Pass 系统RenderPassShaderPassOutputPass 的链式执行
  • 深度纹理depthTexture 的创建与在后处理中的使用
  • Mini 实现:实现一个支持离屏渲染 + 单个后处理 Pass 的极简管线
  • OGL 对比:OGL 的 RenderTarget——仅 30 行代码的 FBO 封装

第五阶段:性能与设计总结(第 11-12 节)

第 11 节:性能优化机制深度解析

  • Instanced RenderingInstancedMesh / InstancedBufferGeometry 的源码实现
  • Geometry MergingBufferGeometryUtils.mergeGeometries() 的合并策略
  • LOD 系统LOD.update() 如何根据距离切换几何体
  • Frustum Culling 优化BoundingSphere vs BoundingBox 的选择与自动计算
  • 资源释放dispose() 事件系统如何通知 WebGL 层释放 GPU 资源
  • 内存追踪WebGLInfo 如何统计 Draw Call、三角面数、纹理内存
  • Mini 实现:为前面的极简引擎添加 Instancing 支持
  • OGL 对比:OGL 中的 Instancing——直接在 Geometry 上设置 instanced 属性

第 12 节:设计启示——从 Three.js 到自研引擎

  • Three.js 的设计哲学:通用性优先、向后兼容、功能完备——以及由此带来的体积与性能代价
  • 值得借鉴的模式
    • 状态缓存模式(WebGLState)
    • Program 缓存与去重(WebGLPrograms)
    • 事件驱动的资源释放(dispose 模式)
    • Uniform 自动同步(WebGLUniforms)
  • 需要精简的部分
    • 过度抽象的 Material 层级(30+ 种材质类)
    • 全局单例依赖(如 _canvas_gl
    • 遗留兼容代码(WebGL1 降级路径)
  • 自研引擎设计清单:从 Three.js 源码中提炼的 10 条架构原则
  • OGL 全面对比:逐模块对比 Three.js(大而全)vs OGL(小而美)的代码量与设计取舍
  • 桥接下一步:如何把 Three.js 的工程经验带入 OGL 源码解析课程

📊 源码版本与阅读工具

推荐版本

阅读工具推荐

  • VS Code + Go to Definition:直接跳转源码定义
  • Chrome DevTools Source Panel:断点调试运行时调用栈
  • GitHub Code Search:快速全局搜索 WebGLRenderer 等关键词

核心源码文件索引

模块源码路径对应章节
渲染器入口src/renderers/WebGLRenderer.js第 1-2 节
Geometry 系统src/core/BufferGeometry.js第 3 节
属性上传src/renderers/webgl/WebGLAttributes.js第 3 节
Material 基类src/materials/Material.js第 4 节
Shader 拼接src/renderers/shaders/ShaderLib.js第 4 节
Program 缓存src/renderers/webgl/WebGLPrograms.js第 4 节
Uniform 管理src/renderers/webgl/WebGLUniforms.js第 4 节
场景图src/core/Object3D.js第 5 节
纹理管理src/renderers/webgl/WebGLTextures.js第 6 节
状态管理src/renderers/webgl/WebGLState.js第 7 节
相机src/cameras/PerspectiveCamera.js第 8 节
视锥体剔除src/math/Frustum.js第 8 节
光照src/renderers/webgl/WebGLLights.js第 9 节
阴影src/renderers/webgl/WebGLShadowMap.js第 9 节
渲染目标src/renderers/WebGLRenderTarget.js第 10 节
后处理examples/jsm/postprocessing/EffectComposer.js第 10 节
实例化渲染src/objects/InstancedMesh.js第 11 节

📝 学习方式建议

每节学习流程

  1. 先读导论:理解本节要解决的问题和 Three.js 的设计思路
  2. 跟读源码:对照教程,在 IDE 中打开对应源文件,逐函数阅读
  3. 断点调试:写一个最小 Three.js demo,在 Chrome DevTools 中断点跟踪调用链
  4. 完成 Mini 实现:用 50-100 行代码实现本节的核心封装
  5. 对比 OGL:翻阅 OGL 对应模块的源码,体会"极简设计"与"功能完备"的差异

调试技巧

// 在 Chrome DevTools 中断点调试 Three.js 源码
// 方法 1:在 renderer.render() 入口打断点
debugger; // 加在你的代码里
renderer.render(scene, camera); // 然后 Step Into

// 方法 2:直接在 Three.js 源码中搜索关键函数
// Sources → webpack:// → three → src/renderers/WebGLRenderer.js
// 找到 render() 函数,打断点

🎓 学习建议

  1. 不要试图读完所有源码:Three.js 有 10 万行代码,跟着教程读核心模块即可
  2. 带着问题读:每节开头都有"这一节要回答的问题",带着问题读效率更高
  3. 动手实现 Mini 版:只看不写等于没学,Mini 实现是最好的理解验证
  4. 善用对比:原生 WebGL / Three.js / OGL 三方对照,理解不同抽象层级的设计取舍
  5. 记录设计笔记:为后续构建自研引擎积累设计决策参考

🚀 开始学习

从第 1 节开始Three.js 架构全景与源码阅读方法

学习路径

  1. 按顺序学习第 1-11 节,每节完成 Mini 实现
  2. 第 12 节整合所有知识,提炼设计启示

预计学习时间

  • 每节:3-4 小时(包括源码阅读 + Mini 实现)
  • 完整课程:36-48 小时
  • 适合:已完成 WebGL 教程和 GLSL 教程的开发者

前置课程