Three.js 渲染架构深度解析
本文基于 Three.js
dev分支源码,系统梳理其渲染架构的设计思路与实现细节。
一、全局视角:两条渲染路径
Three.js 目前并行维护着两套完全独立的渲染体系,它们不共享渲染器层面的代码,面向不同的设计目标。

| 对比项 | 传统路径 | 新路径 |
|---|---|---|
| 入口类 | WebGLRenderer(src/renderers/WebGLRenderer.js) |
Renderer(src/renderers/common/Renderer.js) + 可插拔 Backend |
| Shader 来源 | 内置 GLSL(ShaderLib / ShaderChunk) |
节点图编译(WGSL 或 GLSL) |
| 材质类型 | 标准 Material 子类 |
NodeMaterial |
| 初始化方式 | 同步 | 异步,需 await renderer.init() |
| Compute Shader | 不支持 | 支持(仅 WebGPU 后端) |
| 标识符 | isWebGLRenderer |
isRenderer |
注:自
r163起,WebGL 1 已不再被支持。 three.js:61-62 three.js:55-113
降级措施实现
核心是注册回调→异步初始化失败→捕获错误切换后端路径
降级措施讲解


二、传统路径:WebGLRenderer 的内部世界
WebGLRenderer 是一个"大而全"的自包含渲染器,所有状态管理、Shader 编译、资源管理和绘制提交都由它在构造时创建的一批内部子系统负责。 three.js:422-537
2.1 子系统地图
这些子系统全部位于 src/renderers/webgl/ 目录下,在构造时一次性实例化:
渲染 Pass
WebGLRenderLists
渲染列表
WebGLShadowMap
阴影 Pass
WebGLBackground
背景渲染
Shader 系统
WebGLPrograms
Shader 编译与缓存
WebGLMaterials
Uniform 上传
GPU 资源
WebGLTextures
纹理上传
WebGLAttributes
顶点/索引缓冲
WebGLGeometries
几何体生命周期
WebGLObjects
场景对象更新
上下文 / 设备
WebGLExtensions
扩展检测
WebGLCapabilities
硬件能力查询
WebGLState
GL 状态缓存
WebGLInfo
绘制统计
WebGLRenderer
2.2 一帧的旅程:render(scene, camera) 执行流程
每一帧的渲染都从 render() 方法开始,按以下顺序执行: three.js:1595-1755
- 矩阵更新
updateMatrixWorld()
2. 渲染状态初始化
renderStates.get(scene)
3. 场景遍历 + 视锥剔除
projectObject()
4. 渲染列表排序
opaque 前到后 / transparent 后到前
5. 阴影 Pass
shadowMap.render()
6. 灯光设置
setupLights()
7. 背景渲染
background.render()
8. 不透明物体 Pass
9. 透射物体 Pass(折射)
10. 透明物体 Pass
渲染列表的排序策略是性能优化的关键:
- 不透明物体:从前到后排序,充分利用 GPU 的 Early-Z 深度剔除,避免无效像素着色。
- 透明物体:从后到前排序,保证 Alpha 混合结果正确。 three.js:1663-1667
三、Shader 编译系统:从材质到 GPU 程序
这是 Three.js 传统路径中最精妙的部分之一。
3.1 三层静态 GLSL 资源
ShaderChunk
~100+ GLSL 代码片段
如 lights_pars_fragment
UniformsLib
共享 Uniform 组定义
如 common, lights, fog
ShaderLib
15 个完整 Shader 模板
如 basic, lambert, physical
ShaderChunk:一个扁平对象,键名映射到 GLSL 字符串片段,通过#include <chunk_name>指令在 Shader 中引用。 three.js:1-120UniformsLib:定义了common、lights、fog等共享 Uniform 组,供ShaderLib合并使用。 three.js:7-232ShaderLib:将上述两者组合成完整的 Shader 定义,每个条目包含uniforms、vertexShader、fragmentShader。 three.js:9-105
材质类型到 ShaderLib 的映射:
| 材质类 | ShaderLib ID |
|---|---|
MeshBasicMaterial |
basic |
MeshLambertMaterial |
lambert |
MeshPhongMaterial |
phong |
MeshStandardMaterial |
physical |
MeshPhysicalMaterial |
physical |
3.2 程序编译与缓存:WebGLPrograms
每次绘制调用都需要一个已链接的 WebGL 程序。Three.js 通过**缓存键(Cache Key)**机制避免重复编译:
命中
未命中
render() 调用
材质 + 几何体 + 灯光 + 场景
getParameters()
收集所有影响 Shader 的状态
约 100 个字段
getProgramCacheKey()
序列化为字符串缓存键
programsMap.get(cacheKey)
缓存命中
直接复用 WebGLProgram
缓存未命中
创建新程序
resolveIncludes() → 展开 #include
replaceLightNums() → 替换灯光数量
unrollLoops() → 展开循环
gl.compileShader() + gl.linkProgram()
存入 programsMap
#include 展开 是编译流程的核心步骤,resolveIncludes 函数通过正则递归地将所有 #include <chunk_name> 替换为对应的 GLSL 字符串: three.js:243-276
四、新路径:通用 Renderer + 可插拔 Backend
新路径的核心设计理念是关注点分离 :Renderer 负责场景遍历和调度,具体的 GPU API 调用全部委托给注入的 Backend 实例。 three.js:1-21
4.1 Backend 抽象层
Backend(src/renderers/common/Backend.js)是定义 GPU 契约的抽象基类,两个具体实现分别是:
WebGPUBackend:使用 WebGPU API,坐标系为WebGPUCoordinateSystem(Y 轴朝下 NDC)。 three.js:51-88WebGLBackend:使用 WebGL 2 API,坐标系为WebGLCoordinateSystem(Y 轴朝上 NDC)。 three.js:24-57
Renderer 支持传入 getFallback 回调,当主 Backend(WebGPU)初始化失败时,自动切换到降级 Backend(WebGL 2): three.js:69-70
4.2 Renderer 内部模块
场景处理
RenderObjects
每次绘制的缓存对象
RenderLists
渲染列表
Background
背景节点渲染
管线系统
Pipelines
渲染/计算管线缓存
Bindings
Bind Group 管理
GPU 资源
Attributes
顶点/索引缓冲
Geometries
几何体映射
Textures
纹理上传
节点系统
NodeManager
节点编译生命周期
Animation
动画帧循环
Renderer
五、节点材质与 TSL:Shader 的新写法
新路径用**节点图(Node Graph)**替代了静态 GLSL 片段。材质通过连接 Node 实例来描述着色行为,NodeBuilder 子类遍历节点图并生成目标 Shader 代码。
5.1 核心类关系
持有节点图
遍历
继承
继承
输出
输出
NodeMaterial
节点材质基类
Node
节点基类
NodeBuilder
节点图遍历器
WGSLNodeBuilder
生成 WGSL
GLSLNodeBuilder
生成 GLSL
WebGPUBackend
WebGLBackend
5.2 TSL:用 JavaScript 写 Shader
TSL(Three.js Shading Language)是构建在节点系统之上的 JavaScript DSL,让开发者可以用函数式组合的方式编写 Shader 逻辑,并透明地编译为 WGSL 或 GLSL:
javascript
// TSL 示例:用 JavaScript 描述一个自定义颜色节点
import { Fn, vec3, uniform } from 'three/tsl';
const myColor = Fn(() => {
const baseColor = uniform(new THREE.Color(1, 0, 0)); // 红色
return vec3(baseColor);
});
material.colorNode = myColor();
```three.js:1-170
**TSL 的执行流程:**
```mermaid
graph TB
TSL_Code["TSL 脚本\nFn / attribute / uniform"]
TSL_Core["TSLCore.js / TSLBase.js\n构建节点图"]
NodeBuilder_Build["NodeBuilder.build()\n遍历节点图"]
WGSL_Gen["WGSLNodeBuilder\n生成 WGSL"]
GLSL_Gen["GLSLNodeBuilder\n生成 GLSL"]
TSL_Code --> TSL_Core --> NodeBuilder_Build
NodeBuilder_Build --> WGSL_Gen
NodeBuilder_Build --> GLSL_Gen
5.3 GPGPU:Compute Shader 支持
新路径通过 ComputeNode 支持 GPU 通用计算,可以直接在场景图中集成 GPGPU 任务,计算结果可被材质采样或用于更新几何体------这是传统 WebGLRenderer 路径完全不支持的能力。 three.js:102-102
六、两条路径的能力对比
| 特性 | WebGLRenderer(传统) | Renderer + WebGPUBackend(新) | Renderer + WebGLBackend(降级) |
|---|---|---|---|
| 节点材质 | 不支持 | 支持 | 支持 |
| TSL | 不支持 | 输出 WGSL | 输出 GLSL |
| Compute Shader | 不支持 | 原生支持 | 通过 Transform Feedback 支持 |
| Storage Buffer | 不支持 | 原生支持 | 不支持 |
| 初始化 | 同步 | 异步 | 异步 |
| 坐标系 | Y 轴朝上 NDC | Y 轴朝下 NDC | Y 轴朝上 NDC |
七、总结
Three.js 的渲染架构体现了一个成熟开源项目在向前演进时的典型策略:
-
传统路径(
WebGLRenderer) :经过多年打磨,稳定可靠,内部由 20+ 个专职子系统协作,通过ShaderChunk+ShaderLib的静态 GLSL 拼接方案实现高效的 Shader 复用与缓存。 -
新路径(
Renderer+Backend):通过后端抽象层实现了 WebGPU 与 WebGL 的统一调度,节点图 + TSL 的方案让 Shader 编写从"拼接字符串"升级为"组合函数",同时获得了 Compute Shader 等现代 GPU 能力。
两条路径的并存,既保证了现有项目的稳定性,也为未来全面迁移到 WebGPU 铺平了道路。对于新项目,推荐优先使用新路径,并通过 WebGLBackend 降级保证兼容性。 three.js:55-113