Appearance
Viewer API
Viewer 是 u-space 的核心类,它将 WebGPU 渲染器、场景、相机、控制器以及各种管理器封装为一个易用的接口。
构造函数
typescript
new Viewer(options: ViewerOptions)ViewerOptions
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
el | HTMLElement | 是 | WebGPURenderer 的 canvas 将被注入到此 DOM 元素中。 |
rendererOptions | WebGPURendererParameters | 否 | 直接传递给底层 WebGPURenderer 的选项。默认使用 WebGPU 的高性能配置。 |
pixelRatio | number | 否 | Drawing buffer 像素比;默认 Math.min(window.devicePixelRatio, 1.5)。在 Retina 屏幕上使用 SSR、SSGI 或 TRAA 等重型全屏效果时,可设为 1 以降低 GPU 开销。 |
pixelRatio 应为有限正数,并会直接传给 Three.js。数值越高,画面越清晰,但完整 drawing buffer 以及依赖它的全屏后处理成本也会提高。
Viewer 默认启用 reversedDepthBuffer。RenderPipeline 内置 SSGI 和 SSR 均兼容该深度模式;只有业务明确需要标准深度时,才需要传入 rendererOptions: { reversedDepthBuffer: false }。
typescript
const viewer = new Viewer({
el: document.getElementById('app')!,
pixelRatio: 1,
});属性
Viewer 实例暴露了若干核心 Three.js 和 u-space 组件。
| 属性 | 类型 | 说明 |
|---|---|---|
el | HTMLElement | 容器元素。 |
renderer | WebGPURenderer | 底层 WebGPU 渲染器实例。 |
scene | Scene | 主 Three.js 场景,背景默认为 0x000000。 |
camera | PerspectiveCamera | OrthographicCamera | 当前激活的相机。 |
controls | CameraControls | 相机控制器(基于 camera-controls 库)。 |
renderPipeline | RenderPipeline | 管理后处理通道和最终渲染调用。 |
timer | Timer | Three.js Timer 实例,用于每帧精确的 delta time 跟踪。 |
roomEnvironment | RoomEnvironment | 为场景提供默认的类室内 IBL 环境贴图。 |
info | Info | 以叠加层的形式渲染渲染器诊断信息(绘制调用次数、三角面数量等)。 |
viewerHelper | ViewerHelper | 提供实用辅助工具(如坐标轴、网格显示),用于开发/调试。 |
interactionManager | InteractionManager | 管理对象的指针事件和射线检测。 |
objectManager | ObjectManager | 用于按 ID 或名称注册和检索对象的工具。 |
frameloop | 'always' | 'demand' | 设置渲染模式。默认为 'demand'(仅在需要时渲染)。设置为 'always' 可开启持续渲染。 |
cssRenderer | CSSRenderer | CSS 渲染器,支持在 3D 场景中叠加 HTML 元素(CSS2D / CSS2.5D / CSS3D)。 |
frameCount | number | 待渲染帧的内部计数器;同一刷新周期内的请求会合并,避免多个动画产生帧积压。 |
方法
init()
异步初始化渲染器。如果需要确保渲染器(如环境设置或插件)完全就绪,必须调用此方法。
typescript
await viewer.init();render(frame?: number)
请求渲染一帧。在 'demand' 模式下,每当场景发生视觉变化时必须调用此方法来更新画布。frame 指定渲染的帧数(默认 1)。返回一个在帧渲染完成后 resolve 的 Promise<void>。
typescript
viewer.render();
// 等待帧渲染完成
await viewer.render();invalidate(frame?: number)
请求渲染但不创建 Promise。动画循环、插件更新等不需要等待渲染完成的高频路径应优先使用此方法,避免每帧分配 Promise 和一次性事件监听器。frame 指定至少保留的待渲染帧数(默认 1);同一刷新周期内多次调用会合并,不会累加成渲染积压。
typescript
viewer.invalidate();setCamera(camera: PerspectiveCamera | OrthographicCamera)
为查看器设置自定义相机,并自动更新控制器和交互管理器以使用该相机。
typescript
const customCamera = new PerspectiveCamera(75, width / height, 0.1, 1000);
viewer.setCamera(customCamera);setCameraByType(type: 'perspective' | 'orthographic')
便捷方法,在保持查看器上下文的同时切换相机类型。
typescript
viewer.setCameraByType('orthographic');createScene()
创建并返回一个黑色背景的新 Scene。由构造函数内部调用,也可用于重置/替换场景。
typescript
viewer.scene = viewer.createScene();createPerspectiveCamera()
使用合理的默认值创建 PerspectiveCamera(视角 50°,近裁 0.1,远裁 1e5,位置在 (5, 5, 5))。
typescript
const camera = viewer.createPerspectiveCamera();
viewer.setCamera(camera);createOrthographicCamera()
创建与容器元素等大的 OrthographicCamera,近裁 0.1,远裁 1e5,位置在 (5, 5, 5)。
typescript
const camera = viewer.createOrthographicCamera();
viewer.setCamera(camera);resize(width?, height?)
手动触发容器尺寸更新。可选传入指定宽高。
typescript
// 手动触发 resize(如容器尺寸变化后)
viewer.resize();
// 指定新尺寸
viewer.resize(800, 600);screenshot(options?)
捕获当前渲染画面并返回 Data URL。
typescript
const dataUrl = await viewer.screenshot();
// 指定格式和分辨率
const dataUrl = await viewer.screenshot({
width: 1920,
height: 1080,
type: 'image/png',
quality: 1.0,
});ScreenshotOptions
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
width | number | 容器宽度 | 截图宽度。 |
height | number | 容器高度 | 截图高度。 |
type | 'image/png' | 'image/jpeg' | 'image/webp' | 'image/png' | 图片格式。 |
quality | number | 1.0 | 图片质量(0-1)。 |
setBackground(background)
设置场景背景颜色、纹理或清除背景。
typescript
viewer.setBackground(0x333333); // 颜色
viewer.setBackground('#87ceeb'); // CSS 颜色字符串
viewer.setBackground(hdrTexture); // 纹理
viewer.setBackground(null); // 清除背景setEnvironment(envMap)
设置场景环境贴图(用于反射/IBL)。
typescript
viewer.setEnvironment(hdrTexture);
viewer.setEnvironment(null); // 清除enableShadow() / disableShadow()
开启或关闭阴影渲染。
typescript
viewer.enableShadow();
viewer.disableShadow();enableFog(options?) / enableFogExp2(options?) / disableFog()
开启线性雾、指数雾或关闭雾效。
typescript
// 线性雾
viewer.enableFog({ color: 0xcccccc, near: 10, far: 100 });
// 指数雾
viewer.enableFogExp2({ color: 0xcccccc, density: 0.01 });
// 关闭雾效
viewer.disableFog();FogOptions
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
color | ColorRepresentation | 0xcccccc | 雾的颜色。 |
near | number | 10 | 雾的起始距离。 |
far | number | 100 | 雾的结束距离。 |
FogExp2Options
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
color | ColorRepresentation | 0xcccccc | 雾的颜色。 |
density | number | 0.01 | 雾的密度。 |
调试工具
Info
viewer.info 是一个轻量的渲染统计叠加层,显示当前帧的 GPU 诊断数据,适合开发调试阶段使用。
typescript
viewer.info.enable(); // 在画面左下角显示统计信息
viewer.info.disable(); // 隐藏统计信息启用后将在 viewer.el 左下角叠加以下数据:
| 指标 | 说明 |
|---|---|
draw calls | 当前帧的绘制调用次数 |
frame calls | 当前帧的帧调用次数 |
triangles | 当前帧渲染的三角面数量 |
points | 当前帧渲染的点数量 |
lines | 当前帧渲染的线段数量 |
timestamp | GPU 渲染耗时(ms,WebGPU 专属) |
timestamp指标仅在 WebGPU 后端可用,WebGL 回退模式下显示0。
ViewerHelper
viewer.viewerHelper 是一个方向指示器 gizmo(基于 Three.js ViewHelper),显示当前相机朝向的 XYZ 轴,渲染为画面角落的叠加层。
typescript
viewer.viewerHelper.enable(); // 显示方向 gizmo
viewer.viewerHelper.disable(); // 隐藏方向 gizmo属性:
| 属性 | 类型 | 说明 |
|---|---|---|
location | object | gizmo 在画面中的位置,支持 top、bottom、left、right 偏移(像素)。默认右下角。 |
typescript
// 调整位置到左下角
viewer.viewerHelper.location.left = 12;
viewer.viewerHelper.location.bottom = 12;
viewer.viewerHelper.location.right = null;dispose()
清理查看器,从 DOM 中移除 canvas,移除事件监听,并释放渲染器和环境贴图,以防止内存泄漏。
typescript
viewer.dispose();OffscreenCanvas Worker(实验性)
u-space/worker 提供一个独立入口,可将 Viewer、Three.js 场景、相机控制和 WebGPU 渲染全部放入 dedicated render Worker。主线程只负责持有可见 canvas、转发输入事件与尺寸,并接收状态或渲染统计。模型默认仍在 render Worker 内加载;大型静态 glTF/SBMX 场景还可以 opt-in 第二个 decode Worker,让网络读取、SBMX 字节还原、JSON/base64 解析和图片解码不阻塞相机与已有场景的渲染。
Worker runtime 入口仅提供 ESM,需使用 { type: 'module' } 创建 Worker。构建工具也必须保留 ES module 输出;Vite 项目需配置 worker: { format: 'es' },否则 IIFE Worker 无法编译 top-level await。
主线程:
typescript
import { OffscreenViewerHost } from 'u-space/worker';
const worker = new Worker(new URL('./scene.worker.ts', import.meta.url), {
type: 'module',
});
const host = new OffscreenViewerHost({
el: document.getElementById('app')!,
worker,
});
host.addEventListener('ready', (event) => {
console.log('scene ready', (event as CustomEvent).detail);
});
host.addEventListener('stats', (event) => {
console.log((event as CustomEvent).detail);
});
await host.init();await host.init() 只等待 Worker 创建 Viewer、完成 await viewer.init() 并发送 initialized,此时 WebGPU renderer 和内置 command 已可用,但 HDR、业务模型、editable batch、pipeline 预热和业务首帧可能仍在继续。initialized 事件与 host.init() resolve 表示同一个边界;ready 是业务层边界,只会在 Worker 代码显式调用一次 worker.ready(detail) 后触发。因此应在 host.init() 之前注册 ready / status / stats 等监听,业务 command 则通常在 ready 后调用。
Worker 超大贴图保护
createWorkerViewer() 会在创建 Viewer 前安装 Worker 图片加载兼容层,同时覆盖 Three.js ImageLoader 和 glTF 常用的 ImageBitmapLoader。JPEG、PNG、GIF、WebP 在解码前读取编码尺寸;任一边超过 WebGPU 默认可移植上限 8192 时,会在第一次 createImageBitmap() 解码时按原宽高比缩小。无法预读尺寸的格式会在首次解码后检查,超限时生成缩放后的 bitmap,并立即 close() 临时原图。
该处理保留 ImageBitmapLoader.setOptions()、Three.js Cache、request headers、credentials 和 Loader/LoadingManager abort 语义,也不会修改磁盘或服务端源贴图。它避免在默认 maxTextureDimension2D = 8192 的 WebGPU device 上触发 Texture size,以及随后连续出现的 Invalid TextureView、Invalid BindGroup 和 Invalid CommandBuffer。即使 adapter 支持 16384,runtime 也不会强制申请更高 limit,以保持不同 GPU 的可移植性并限制超大贴图的显存占用。
自动缩放只覆盖经 ImageLoader / ImageBitmapLoader 加载的普通图片;KTX2 等压缩纹理由各自 loader 处理,业务仍应在资产管线中确保其尺寸不超过目标设备 limit。
独立模型 Decode Worker
在拥有 Three.js/WebGPU 的 render Worker 中,把一个专用 Worker 交给 ModelLoaderManager:
typescript
import { ModelLoaderManager } from 'u-space';
import { createWorkerViewer } from 'u-space/worker/runtime';
const worker = await createWorkerViewer();
const modelDecodeWorker = new Worker(
new URL('./model.decode.worker.ts', import.meta.url),
{ type: 'module' },
);
const disposeModelDecodeWorker = ModelLoaderManager.setDecodeWorker(modelDecodeWorker);
worker.onDispose(disposeModelDecodeWorker);专用 decode Worker 入口不创建 Viewer,只安装解码协议:
typescript
import { installModelDecodeWorker } from 'u-space/worker/model-decoder';
installModelDecodeWorker();启用后,ModelLoaderManager.loadAsync() 会把受支持的静态 .gltf、.glb 和 .sbmx 请求排入 decode Worker。decode Worker 负责 fetch/Cache Storage、SBMX nibble swap、glTF JSON/base64/buffer 解析、普通图片解码和超大图片等比缩放;完成后以 transferable ArrayBuffer / ImageBitmap 把纯数据包发送给 render Worker。render Worker 才创建 BufferGeometry、MeshStandardMaterial、Texture 和 Object3D,因此 Three.js 对象和 GPU 资源仍只有一个 owner。
协议采用串行队列和 ACK backpressure:render Worker 重建完当前模型后才允许发送下一个大数据包,避免消息队列同时堆积多个模型副本。相同图片会按 SHA-256 内容身份复用已转移的 ImageBitmap,保持与 Three.js loader cache 一致的材质签名和 editable batching 数量;身份索引使用有界 LRU,不会随不同贴图持续增长。传输层直接使用浏览器 structured clone/transferable,没有额外二进制序列化依赖。
外部 buffer/贴图 URL 会回到 render Worker 经 LoadingManager.resolveURL() 处理,并完整触发 itemStart / itemEnd / itemError。模型请求的 headers 与 credentials 只会传给同源依赖;URL modifier 把依赖改写到其他 origin 时会自动去掉敏感 headers 并使用 credentials: 'omit',避免授权信息泄漏给资产文件中声明的第三方地址。
当前快速路径只接受无 animation、skin、morph target、camera、sparse accessor、glTF extension/压缩扩展且 primitive mode 为 triangles 的 glTF 2 静态模型;节点图还必须满足无重复 child、无多父节点、无环及安全深度限制。其他受支持但不适合快速路径的模型会自动回退现有 GLTFLoader / SBMXLoader,不会降级功能;decode Worker 致命错误也会摘除失效 client 并回退当前请求。ModelLoaderManager 拥有传入的 Worker;再次调用 setDecodeWorker() 或传入 null 会 dispose 并 terminate 旧 Worker。setDecodeWorker(worker) 返回与该实例绑定的 disposer,建议交给 render runtime 的 onDispose(),避免旧清理回调误终止后来替换的 Worker。AbortSignal 同时覆盖 decode Worker 快速路径、普通 loader 顶层 fetch 和 glTF/SBMX 外部资源加载。
这个能力也可以在主线程的普通 Viewer 中启用,但它的主要收益是在 Offscreen render Worker 中把模型解码进一步拆开,使场景已可交互时仍能继续流式装载。它不减少网络体积、最终 GPU 内存或 draw calls;这些仍由资源压缩、instancing、editable batching、LOD 和裁剪解决。
OffscreenViewerHost
构造选项:
| 选项 | 类型 | 必需 | 说明 |
|---|---|---|---|
el | HTMLElement | 是 | 主线程容器;默认创建的 canvas 会挂载到这里。 |
worker | Worker | 是 | 使用 { type: 'module' } 创建的 dedicated Worker。 |
canvas | HTMLCanvasElement | 否 | 自定义可见 canvas;省略时自动创建。 |
pixelRatio | number | 否 | 发送给 Worker renderer 的 drawing-buffer pixel ratio;省略时使用 Math.min(window.devicePixelRatio, 1.5)。 |
terminateWorkerOnDispose | boolean | 否 | dispose() 完成后是否终止 Worker,默认 true。 |
公开方法:
| 方法 | 说明 |
|---|---|
OffscreenViewerHost.isSupported() | 检查当前环境是否具备 Worker、OffscreenCanvas 和 transferControlToOffscreen()。Worker 内是否暴露 WebGPU 仍会在初始化时检查。 |
init(): Promise<void> | 绑定输入/resize,转移 canvas 并等待 Worker 的 initialized 消息;重复调用返回同一个 Promise。 |
request<T>(command, payload?) | 调用 Worker 已注册或内置的 command。必须在 init() resolve 后调用;业务 command 通常还应等待 ready。 |
dispose(): void | 停止事件与尺寸转发、reject 未完成的 Promise、移除 canvas,并通知 Worker 逆序释放业务资源和 Viewer。 |
Host 事件:
| 事件 | detail | 说明 |
|---|---|---|
initialized | 无 | 与 host.init() resolve 相同的 Viewer 初始化边界。 |
ready | unknown | Worker 调用一次 worker.ready(detail) 后的业务就绪边界。 |
status | string | Worker 通过 worker.status(message) 上报的加载状态。 |
stats | WorkerRendererStats | renderer 计数和滚动帧间隔统计。 |
| 自定义事件 | unknown | Worker 通过 worker.emit(type, detail) 上报;事件名为传入的 type。 |
error | Error | Worker 初始化、未捕获异常或资源清理错误。 |
disposed | 无 | Worker 已完成清理并关闭。 |
WorkerRendererStats 包含 drawCalls、frameCalls、triangles、points、lines、作为滚动平均帧间隔的 frameTime,以及 frameTiming.samples / fps / average / p95 / max / missedFramePercent。其中 missedFramePercent 表示滚动窗口内超过 25ms 的样本占比;demand 模式的静止空闲间隔不会被作为渲染帧计入。
Worker(runtime 使用独立入口和 top-level await,避免主线程打包 Viewer/Three.js):
typescript
import { createWorkerViewer } from 'u-space/worker/runtime';
const worker = await createWorkerViewer();
const { viewer } = worker;
worker.status('loading scene');
const scene = await loadScene(viewer);
viewer.scene.add(scene);
await viewer.render();
worker.registerCommand('getObjectCount', () => viewer.scene.children.length);
worker.onDispose(() => disposeScene(scene));
worker.ready({ objects: viewer.scene.children.length });createWorkerViewer() 必须在 module Worker 顶层立即调用。函数会先同步注册 host 消息监听,再返回一个等待 OffscreenCanvas 传入和 viewer.init() 完成的 Promise,因此可以安全使用 top-level await,不会丢失 init 消息。
返回的 WorkerViewerRuntime 提供:
| 属性 / 方法 | 说明 |
|---|---|
viewer / canvas | Worker 内的完整 Viewer 和已转移的 OffscreenCanvas。 |
status(message) | 向主线程发送加载状态。 |
emit(type, detail?) | 向主线程派发自定义事件;detail 必须可 structured clone。 |
registerCommand(name, handler) | 注册 host.request() 可调用的业务命令,返回注销函数;重复命令名会抛错。 |
onDispose(handler) | 注册 Worker 销毁回调,返回注销函数;多个回调按注册的逆序执行。 |
ready(detail?) | 场景准备完成后发送一次 ready 事件;重复调用会抛错。 |
主线程应等待 ready 事件后,再用 await host.request('getObjectCount') 调用业务命令。runtime 还内置 getStats、getViewpoint、setViewpoint 和 render 命令,这些命令在 host.init() resolve 后即可调用。命令参数和返回值必须可被 structured clone;返回值中重复引用或嵌套在对象、数组、Map、Set、TypedArray / DataView 中的 ArrayBuffer 会去重后零拷贝转移,并从 Worker 端 detach。Viewer、Object3D、材质和 Geometry 等 Three.js 对象始终留在 Worker 内。
ready() 前发生的顶层未捕获错误或 Promise rejection 会先发送主线程 error 事件,再按逆序执行 onDispose()、释放 Viewer、发送 disposed 并关闭 Worker,避免失败的场景加载继续占用动画循环和 GPU 资源。Host 在初始化完成前被销毁,canvas 转移或初始 postMessage() 抛错,或收到 Worker 主动发送的 disposed 时,会 reject 尚未完成的 init() 和 request(),同时解除 DOM/resize 监听、清空待发送 pointer move、移除 canvas,并按 terminateWorkerOnDispose 完成 Worker 收尾。Host 一旦进入 disposed 状态,会忽略队列中迟到的 initialized / ready / status / stats / 自定义事件和 response,只保留清理阶段的 error / disposed 通知。
UManager 与 editable batching
完整示例 examples/offscreen/test_umanager2_offscreen.html 会在 Worker 中加载 HDR、建筑语义模型和场景模型,并将 SceneLoader.setEditableBatching() 与 OffscreenCanvas 组合:
typescript
import { SceneLoader } from 'u-space/plugins/u-manager';
import { createWorkerViewer } from 'u-space/worker/runtime';
const worker = await createWorkerViewer();
const { viewer } = worker;
// 在初始静态层级和相机定位完成后冻结每帧场景矩阵遍历。
viewer.scene.updateMatrixWorld(true);
viewer.scene.matrixWorldAutoUpdate = false;
const sceneLoader = new SceneLoader(viewer);
worker.onDispose(() => sceneLoader.dispose());
sceneLoader.setPath(SCENE_PATH);
sceneLoader.setKey(SCENE_KEY);
sceneLoader.setEditableBatching({
maxVerticesPerBatch: 1_500_000,
maxIndicesPerBatch: 4_500_000,
freezeAnimations: true,
});
const scene = await sceneLoader.loadAsync();
const controlsEnabled = viewer.controls.enabled;
viewer.controls.enabled = false;
try {
viewer.scene.add(scene);
scene.updateWorldMatrix(true, true);
viewer.invalidate();
await viewer.renderer.compileAsync(viewer.scene, viewer.camera);
} finally {
viewer.controls.enabled = controlsEnabled;
}
await viewer.render();
worker.ready({
editableBatch: scene.userData.editableBatch,
semanticOverlayHidden: false,
});Geometry clone、矩阵烘焙、mergeGeometries()、TSL batch material 创建和 WebGPU 渲染提交都会在 Worker 中完成,不阻塞主线程 UI。ready 事件中的 detail.editableBatch 是普通统计对象,可以安全传回主线程;示例 HUD 同时显示 drawCalls、frameTiming.fps、1 秒滚动窗口的 average / p95 / max、超过 25ms 的 missedFramePercent,以及 sourceMeshes / batches / drawCallsSaved / unsupportedInstances 和 static scene matrices 状态。frameTime 保留为滚动平均值;demand 模式长时间静止产生的空闲间隔不会纳入统计。
目标场景的压平语义楼层是半透明分析叠加层,并会有意覆盖建筑表面。示例保持 SemanticGroup.visible = true,并调用 showAllFloors().showAllFacilities(),让楼层、墙体、空间、门窗和设备等全部语义对象默认参与渲染。设备选择、飞向、高亮和清除命令均不改变语义对象显隐,“清除调试”只撤销高亮。业务项目可以按自身展示策略切换语义层,并继续通过跨楼层语义合批或 LOD 降低提交成本。示例保留标准 RenderPipeline scene pass,以维持源模型透明对象的正确离屏合成,不修改 drawing-buffer pixel ratio。
目标场景主要由静态普通 Mesh 组成,因此相机定位完成后先调用 Three.js 原生 viewer.scene.updateMatrixWorld(true) 提交当前层级,再设置 viewer.scene.matrixWorldAutoUpdate = false,移除每个渲染帧的整树更新;加载完场景模型后只调用 scene.updateWorldMatrix(true, true) 提交新增子树。后续 Worker command 修改单个普通 Mesh 或 InstanceObject 时调用 object.updateWorldMatrix(true, false),修改 Group 时调用 group.updateWorldMatrix(true, true),随后调用 viewer.invalidate() 请求渲染。InstanceObject 覆盖了 Three.js 原生 updateWorldMatrix(),会在显式提交后检测世界矩阵/祖先显隐变化并触发 dirty callback;ModelInstancedLayer 因而能同步 instance buffer,editable batch 中与烘焙矩阵不同的 SceneInstanceObject 也会按现有规则 materialize。对 Group 使用 updateChildren: true 时,其后代 InstanceObject 同样会执行该检测。相机矩阵不受此模式影响。
OffscreenCanvas 不会自动减少 draw calls;setEditableBatching() 也不会把主线程 UI 搬入 Worker。组合使用时,两者分别处理主线程 CPU 压力和 draw submission 压力。需要按 ID 显隐、变色、移动或 materialize() 时,应使用 worker.registerCommand() 注册业务 command,再由主线程调用 host.request(),不要尝试跨线程传输 SceneInstanceObject。
通过以下命令启动:
bash
pnpm example:offscreen当前边界:
- 需要同时支持
transferControlToOffscreen()和 Worker WebGPU(WorkerNavigator.gpu);目前应以最新版 Chromium 为主要目标,并保留普通Viewer回退路径。 - CSS2D/CSS3D、
Info等真实 DOM 叠加层不能放进 Worker,应在主线程用 Worker 状态/统计消息重建 UI。 - Worker 能移走场景解析、矩阵更新和渲染提交等主线程 CPU 工作,但不会减少 draw calls、三角形数量或 GPU 时间;GPU 瓶颈仍需用 instancing、LOD、遮挡/视锥裁剪等方式处理。
setEditableBatching()与 Worker 兼容,但它会展开重复 Geometry,仍需根据vertices/indices统计评估 GPU 内存;透明、蒙皮、morph 和自定义 shader 子集继续走安全 fallback。- WebGPU OffscreenCanvas 截图尚未作为 Worker 内置命令开放;需要截图时应实现独立的 render-target readback 流程。
事件
Viewer 继承自 EventDispatcher,会触发以下事件:
beforeControlsUpdate:在CameraControls更新之前触发,提供{ time: number; delta: number }。afterControlsUpdate:在CameraControls更新之后触发,提供{ time: number; delta: number }。beforeRender:在renderer.render调用之前立即触发,提供{ time: number; delta: number }。afterRender:在renderer.render完成之后立即触发,提供{ time: number; delta: number }。cameraChange:调用setCamera时触发,提供{ camera: Camera }。
其中 delta 为上一帧到当前帧的时间间隔(秒),可用于动画更新等场景。