三维查看器
查看器要的是 轨道相机 + 一份世界矩阵树 + 一个 GLTF 网格投影,不是自研完整 PBR。场景图(节点 id、local 变换、网格引用)留在 CPU;GPU 是 VBO / IBO / 纹理和 view × proj。只有一个模型要转一转:用引擎。散点/后处理那种自研栈不要扩成渲染器。HUD 用 DOM 或第二张 canvas,同一元素不能再要 2d。
1. 场景怎么切
CPU:node 树、轨道球状态、动画时钟
↓ 每帧 world 矩阵
GPU:mesh 资源 × viewProj × 灯光 uniform
拾取:CPU AABB 或 1px ID FBO
HUD:DOM overlay,不进场景 FBO
坐标系:世界单位(GLTF meter 惯例)+ lookAt 视图 + perspective。不要把 2D Camera2D.scale 套到透视上,见 相机。
export type OrbitCamera = {
targetX: number;
targetY: number;
targetZ: number;
distance: number;
yaw: number;
pitch: number;
};
export function orbitEye(c: OrbitCamera): { x: number; y: number; z: number } {
const cp = Math.cos(c.pitch);
return {
x: c.targetX + c.distance * cp * Math.sin(c.yaw),
y: c.targetY + c.distance * Math.sin(c.pitch),
z: c.targetZ + c.distance * cp * Math.cos(c.yaw),
};
}
export function zoomOrbit(c: OrbitCamera, factor: number): OrbitCamera {
return {
...c,
distance: Math.min(1e5, Math.max(0.05, c.distance * factor)),
};
}
export function clampPitch(c: OrbitCamera, delta: number): OrbitCamera {
return {
...c,
pitch: Math.min(Math.PI / 2 - 0.04, Math.max(-Math.PI / 2 + 0.04, c.pitch + delta)),
};
}
指针增量用前后两帧 NDC 差,不要 movementX,见 指针到 NDC。setPointerCapture 打在 canvas 上。模型加载失败、无网格、无皮肤,是文档错误,不是「shader 没写对」。
适用:看 GLTF/GLB、简单拆件、剖面。
不适用:为看一个模型写金属度工作流;把查看器当游戏引擎。选型边界见 自研 vs Three。
2. 哪层必须 CPU,哪层进 GPU;何时上 Three
| 层 | 归属 |
|---|---|
| 节点树、local TRS、显隐、选中 id | CPU 文档 |
| 蒙皮绑定、动画 clip 时钟 | CPU(引擎则它内部仍是这份职责) |
| 几何、材质纹理、shadow map | GPU 投影 |
| 零件名、属性面板 | DOM |
有 GLTF / PBR / 蒙皮 / 动画剪辑:Three 或 Babylon 当壳,不要在本体系其余章绑它们的 API。引擎不替你做产品文档,也不替你做撤销:mesh.position 不能当唯一数据源。无障碍文本不要画进 mesh。
自研仅当:已有 CPU 场景、只要无光照或简单 Phong、网格自己产。生产默认 WebGL2(Three 默认也是这条)。WebGPU 后端是引擎/探测后的事,adapter null 必须能看模型。
拾取:零件少于几千用 CPU AABB;互交三角再用 ID 缓冲 且只读 1px。热路径禁止全屏读深度。跨源纹理 taint 后不能 readPixels / toBlob。preserveDrawingBuffer 默认 false:截图在本帧提交前读 FBO。
3. 失败形态
| 症状 | 原因 |
|---|---|
| 滚轮乱飞 | 2D scale 当了透视相机 |
| 为查看器写了半年着色器 | 该用引擎还在自研 PBR |
| 撤销/协作对不上 | 把引擎 mesh 当文档 |
| 拾取 Y 反 | drawing buffer 左下没翻 |
| 白模丢失后不回来 | 上下文恢复没重建 program |
权威资料
核对日期:2026-08-26