3D渲染中享元模式需分离内在状态(几何拓扑、材质参数等)与外在状态(位置、旋转等),后者交由GPU实例化统一处理;静态方法仅作轻量工厂入口,核心依赖MeshPool缓存、InstancedMesh底层优化及渲染管线协同。

直接用 class 静态方法管理享元对象,在 3D 渲染中不是最优解——它容易绕过生命周期控制、破坏状态隔离,还难以对接 GPU 实例化(Instancing)等底层优化机制。真正高效复用千万级网格实例,核心在于“分离可共享状态 + 统一调度 + 原生渲染支持”,静态方法仅适合做轻量级工厂入口,不能承担主干逻辑。
享元对象必须只存内在状态
内在状态是几何拓扑、材质参数、着色器定义等不随实例位置/旋转/缩放变化的数据。比如一个标准螺栓模型的顶点缓冲、法线、UV、基础 PBR 参数(albedo、roughness、metallic)都属于内在状态,应由享元对象唯一持有。
- 不要在享元类里存 position、rotation、scale、color 等每实例独有数据
- 避免把 transform 或 uniform 数据塞进享元实例,否则会失去共享意义,内存反而膨胀
- Three.js 中对应的是 Geometry + Material 的组合(且 Material 必须是 shared = true)
外在状态交给 GPU 实例化层统一传递
位置、缩放、颜色、ID 等动态数据,不应由 JavaScript 对象维护,而应批量写入 GPU Buffer(如 InstancedBufferAttribute),由顶点着色器读取。这才是千万级复用的物理基础。
- 用
InstancedMesh替代Mesh,底层自动启用 OpenGL/Vulkan 的 glDrawArraysInstanced - 将百万个变换矩阵拆成 3 个
InstancedBufferAttribute(position、rotation、scale),避免单 buffer 过大导致上传卡顿 - 若需逐实例着色,用
InstancedBufferAttribute存 color 或 index,片段着色器查表或插值
静态方法只做享元注册与检索入口
可以定义一个 MeshPool 类,用 static 方法封装资源获取逻辑,但内部必须依赖 Map + 弱引用 + 生命周期钩子:
-
static get(id: string): Mesh—— 检查缓存,命中则返回;未命中则创建并缓存 -
static release(id: string)—— 主动清理,或配合onBeforeRender自动回收闲置超过阈值的享元 - 缓存 key 应基于 geometry hash + material hash(而非字符串拼接),防止重复加载相同资源
- 对 GLTF 模型,优先复用
GLTFLoader.parse()后的scene.children中已解析的 geometry/material,而非重新 parse
与渲染管线深度协同才能真正“极致”
享元不是孤立模式,必须嵌入完整渲染链路:
- 视锥剔除(Frustum Culling)前,先按 LOD 分组,不同精度网格走不同享元池
- 实例化绘制前,用
computeBoundingBox批量计算可见性,剔除不可见 instance ID - 结合 WebGPU 或 WebGL2 的
transform feedback,让 GPU 自行更新运动轨迹,减少 CPU-GPU 数据拷贝 - 对数字孪生场景中的传感器点,用
PointsMaterial+ instancedBufferGeometry,比 Mesh 节省 90% 内存

















