# 示例指南

`u-space` 的 `examples/` 目录中包含了大量演示引擎功能的示例。以下是几个关键示例及其所展示的系统特性。

## 运行方式与版本

示例统一通过 `examples/importmap.js` 注入 Import Map。本地通过 `localhost`、`127.0.0.1`、`0.0.0.0` 或 `192.168.x.x` 访问时会加载仓库里的 `../dist/` 构建产物；在线部署或非本地域名访问时会从 jsDelivr 加载当前发布版本 `u-space@0.0.32`。Vercel 文档部署会继续把 `__VERSION__` 占位符替换为 `package.json` 中的版本号，源码里的 `0.0.32` 作为直接托管 `examples/` 时的 fallback。

插件示例可以在页面加载 `importmap.js` 前通过 `window.__IMPORTS__` 声明额外依赖。将 `u-space/plugins/<name>` 的值设为 `true` 时，`importmap.js` 会自动在本地和 CDN 路径之间切换。

## 1. `demo01.html`：基础配置与交互

该示例演示了通过 WebGPU 渲染 3D 场景所需的最少配置，与 [快速上手](./getting-started.md) 指南中的内容一致。

### 核心要点：

- 将 `Viewer` 注入 DOM 元素。
- 使用 WebGPU `Color` 设置场景背景。
- 添加基础几何体（如 `BoxGeometry`）。
- 通过 `viewer.interactionManager.pointerMoveEventsEnabled` 启用全局交互。
- 像操作 DOM 元素一样，为 3D 网格原生绑定事件监听（`click`、`pointerenter`、`pointerleave`）。

## 2. `test_loader.html`：模型缓存机制

该示例深入解析了 `u-space` 的模型加载管线，阐明运行时缓存与浏览器持久化缓存之间的区别。

### 核心要点：

- 使用全局 `Model` 类通过 `Model.prototype.loadAsync()` 异步加载 `.glb` 文件。
- 理解 `cache: true`（将模型节点保留在内存中，可实现即时克隆/实例化）。
- 理解 `persistent: true`（使用 Service Worker / Cache Storage API 对实际网络请求数据进行持久化缓存）。
- 演示如何将这些异步加载的资源安全地注册到 `viewer.objectManager`。
- 提供通过 `Model.clearMemoryCache()` 和 `Model.clearPersistentCache()` 手动清除缓存的示例。

## 3. `load_tiles.html`：GIS 与地球渲染

该示例使用外部对等依赖 `3d-tiles-renderer`，通过 `u-space` 插件安全封装后，渲染大规模 LOD GIS 地球。

### 核心要点：

- 动态实例化复杂插件（`ArcgisTilesRenderer`），并传入核心 viewer 引用。
- 由于瓦片是异步流式加载并持续更新视口的，需使用 `viewer.frameloop = 'always'`。
- 通过插件的 `invalidate(lon, lat, alt)` 方法定位到指定的经纬高位置。
- 使用 `lonLatHeightToPosition()` / `positionToLonLatHeight()` 在 WGS84 经纬高和场景世界坐标之间互转。

## 4. `test_umanager.html`：场景组合与解析器

该综合示例融合了多种工具，展示如何构建完整的应用程序，而不仅仅是放置单个模型。

### 核心要点：

- 同时初始化多个插件（minimap、tracking、keyboard-controls），并通过 `.enable()` / `.disable()` 切换其状态。
- 流式传输结构化场景布局，解析场景描述（`SceneLoader`、`TopologiesLoader`）。
- 使用相机动画（`VisionsLoader` 和 `VisionsParser`）在场景中编排预定义的飞行路径。
- 获取复杂的节点材质，并自定义 WebGPU 专属的 TSL（Three Shading Language）节点，如 `TSLEffects.fluid()`。

## 5. `test_umanager_loader.html`：UManagerLoader 一体化加载

[在线演示](https://u-space-phi.vercel.app/examples/test_umanager_loader.html)

该示例演示通过 `UManagerLoader` 在同一个路径下同时加载语义楼层、Facilities 和非语义场景树内容。加载结果会返回一个 `UManagerSceneGroup`，其中 `semanticGroup` 指向 `SemanticGroup`，`sceneGroup` 指向非语义场景组。

### 核心要点：

- 使用 `UManagerLoader.setPath()` 配置场景根目录，并通过一次 `loadAsync()` 完成 `SemanticLoader` + `SceneLoader` 组合加载。
- `SceneLoader` 会自动跳过语义文件中已存在的 `id`，避免建筑、楼层或设备重复加载。
- 通过 `root.semanticGroup.getDefaultFacilityLayer()` 和 `root.sceneGroup.getDefaultSceneLayer()` 直接获取默认设备合批层和场景模型合批层。
- 通过 `viewer.objectManager` 获取 `SceneInstanceObject` 和 `FacilityInstanceObject`，再直接使用 `viewer.controls.flyToObject()` 飞向单个实例。
- 使用 `setInstanceHighlight()`、`setInstanceVisible()`、`setInstanceOpacity()` 等统一 API 控制实例，不需要关心底层是合并几何、`InstancedMesh` 还是 fallback `Model`。
- 示例把 `viewer`、`loader`、`root`、`semanticGroup`、`sceneGroup`、`facilityLayer`、`sceneLayer`、`sceneInstances` 和 `facilities` 暴露到 `window.uManagerExample`，方便在控制台调试。

## 6. `test_umanager_dynamic_instances.html`：运行时动态实例

[在线演示](https://u-space-phi.vercel.app/examples/test_umanager_dynamic_instances.html)

该示例把运行时实例新增、删除、恢复和按 ID 查询拆到独立场景中，避免和 `UManagerLoader` 的一体化加载流程混在一起。

### 核心要点：

- 创建 `SceneInstancedLayer` 并调用 `reserveBatch(url, template, capacity)` 预分配容量。
- 通过 `SceneInstanceObject.setInstanceIdentity({ id, kind, name })` 设置实例独立身份字段，不依赖 `userData` 参与 layer 内部索引。
- 同一个 layer 内 `instanceId` 必须唯一；重复 ID 的新增会返回 `false`，加入后修改身份字段会在下一次按 ID 查询时刷新索引。
- 使用 `addInstances()` 批量新增实例，使用 `removeInstance(id)` / `removeInstances(id)` 按完整 `instanceId` 删除实例。
- 使用 `getInstanceById(id)` 快速获取实例，返回对象可直接用于 `setInstanceHighlight()` 和 `viewer.controls.flyToObject()`。
- 示例把 `viewer`、`layer`、`template`、`allInstances` 和 `removedInstances` 暴露到 `window.dynamicInstanceExample`。

## 7. `test_umanager2.html`：大型场景可编辑静态合批

该性能目标场景同时加载语义对象和完整场景树，并通过 `SceneLoader.setEditableBatching()` 把兼容的普通 Mesh 烘焙合并到 `SceneEditableBatchLayer`。它适合 draw calls 和可渲染 Mesh 数量成为主要 CPU/GPU 提交瓶颈、但业务仍需要按对象 ID 显隐、高亮或偶尔移动对象的场景。

### 核心要点：

- 使用 `setEditableBatching({ maxVerticesPerBatch, maxIndicesPerBatch, freezeAnimations })` 显式开启；默认 `SceneLoader` 行为不变。
- 兼容的不透明 Mesh 按材质、Geometry attribute 布局、阴影状态和 `renderOrder` 合并；透明、蒙皮、morph、自定义 shader 和不兼容 Geometry 保留 fallback。
- 合批对象仍保留 `SceneInstanceObject`，显隐、颜色和不透明高亮通过 TSL 状态纹理更新；transform 或半透明修改只 materialize 当前对象。
- 页面使用 `pixelRatio: 1` 控制高 DPI drawing buffer 成本，并关闭不需要的交互 raycast。
- 首帧前通过 `renderer.compileAsync()` 预热 WebGPU material pipeline，避免把编译中的中间画面展示给用户。
- 使用 `?editableBatching=false` 可关闭该路径，与默认 path-based `SceneInstancedLayer`/普通模型加载结果做性能和画面对比。
- `sceneGroup.userData.editableBatch` 提供 `sourceMeshes`、`batches`、`drawCallsSaved`、`vertices`、`indices` 和 fallback 原因统计。

该模式通过展开重复顶点换取更少 draw calls；高重复、大 Geometry 的同模板对象可能仍更适合 `SceneInstancedLayer`。详见 [u-manager `setEditableBatching()` API](./api-plugin-u-manager.md#可编辑静态合批seteditablebatching)。

## 8. `test_umanager2_offscreen.html`：OffscreenCanvas Worker + editable batching

该示例是 `test_umanager2.html` 的 Worker 版本。完整 `Viewer`、Three.js 对象重建、editable batch 构建、相机控制和 WebGPU 渲染运行在 dedicated render Worker；主线程只持有可见 canvas、转发输入/resize，并显示 Worker 返回的状态与统计。静态 SBMX/glTF 的 fetch、字节/JSON/base64 解析和图片解码继续拆到 render Worker 创建的第二个 decode Worker，因此加载新模型时已有 3D 场景仍可响应输入。

通过专用 Vite 配置启动：

```bash
pnpm example:offscreen
```

然后访问 `http://127.0.0.1:5510/offscreen/test_umanager2_offscreen.html`。该示例的 HTML、主线程入口、Worker 入口和专用 Vite 配置集中在 `examples/offscreen/` 目录。可用 `pnpm build:example:offscreen` 验证生产构建。

[在线演示](https://u-space-phi.vercel.app/examples/offscreen/test_umanager2_offscreen.html)由一键发布流程单独打包：HTML 加载生成的 JavaScript，Worker 保持 ES module 格式，Draco/Basis WASM 等资源写入 `/examples/assets/`。线上页面不直接执行源码 `.ts`，HDR 则复用 `/examples/textures/puresky_1k.hdr`。

### 核心要点：

- 主线程使用 `OffscreenViewerHost` 调用 `transferControlToOffscreen()`，以 structured-clone 消息转发 pointer、wheel、resize 和业务 command。
- Worker 在模块顶层通过 `const worker = await createWorkerViewer()` 创建真正的 `Viewer`，随后以顺序式代码加载 HDR、`SemanticLoader` 和 `SceneLoader`，无需把整个入口包进 setup callback。
- render Worker 通过 `ModelLoaderManager.setDecodeWorker()` 启用 `test_umanager2_offscreen.decode.worker.ts`，并把返回的实例绑定 disposer 注册到 runtime 清理；decode Worker 从 `u-space/worker/model-decoder` 调用 `installModelDecodeWorker()`，以 transferable `ArrayBuffer` / `ImageBitmap` 返回静态 glTF 数据，Three.js/GPU 对象仍只在 render Worker 创建。外部 buffer/贴图继续经过 render Worker 的 LoadingManager URL modifier 与加载统计，跨域依赖不会继承模型请求的敏感 headers/credentials。
- 解码队列一次只允许一个待确认数据包；render Worker 重建后发送 ACK 再取下一项，防止 1GB 级场景同时堆积多个模型副本。相同图片通过有界 LRU 内容指纹复用 bitmap 身份，避免把原本 25 个 editable material batches 拆成大量重复批次，同时不永久持有所有历史贴图。
- animation、skin、morph、camera、sparse accessor、glTF extension/压缩扩展和非 triangle primitive 不进入快速路径，会自动使用现有 loader。访问 `?modelDecodeWorker=off` 可关闭嵌套 Worker，与原始 loader 做画面、批次和加载响应 A/B 对比；默认开启。
- Worker runtime 同时接管 Three.js `ImageLoader` 与 `ImageBitmapLoader`：JPEG/PNG/GIF/WebP 任一边超过 WebGPU 默认 `8192` 上限时，会在第一次 `createImageBitmap()` 解码时等比缩小，保留 loader options/cache/abort 语义且不修改源文件，避免 `Texture size` 引发的 WebGPU validation 级联错误。
- `await host.init()` / `initialized` 只表示 Worker 内 `viewer.init()` 完成；HDR、模型、editable batch、pipeline 预热和业务首帧完成后，Worker 才显式调用一次 `worker.ready(detail)`。主线程应先注册 `ready` 监听，再执行 `host.init()`，并在 `ready` 后调用业务 command。
- `SceneLoader.setEditableBatching()` 在 Worker 中完成 Geometry 克隆、变换烘焙、合并和 TSL 状态纹理创建；透明、蒙皮、morph、自定义 shader 等不支持子集保持 fallback。
- 首帧前关闭 controls invalidation 并等待 `renderer.compileAsync()`，预热 WebGPU 合批管线后再显示完整画面。
- HUD 显示实际 `drawCalls`、三角形、`static scene matrices` 状态，以及 1 秒滚动窗口的 FPS、平均/p95/最大帧间隔、超过 25ms 的占比和样本数；demand 模式恢复时的空闲间隔不会被误算成渲染帧。
- 示例调用 `semanticGroup.showAllFloors().showAllFacilities()` 并保持 `SemanticGroup.visible = true`，楼层、墙体、空间、门窗和设备等所有语义对象均保留且默认显示。HUD 的设备调试区通过 Worker command 选择、飞向、高亮或清除当前 Facility；这些操作都不修改任何语义对象或建筑场景的显隐状态，“清除调试”只撤销高亮。
- `SemanticLoader` 的压平语义 Geometry 属于分析叠加层，本示例按调试需求默认显示完整语义模型，因此其半透明颜色会与建筑楼壳叠加；业务项目可以按自身展示策略单独控制该层。
- 标准 RenderPipeline scene pass 保持启用，editable batch 只接收源模型中的不透明 Mesh；透明子 Mesh 保留标准 fallback，以兼顾透明合成正确性和不降低 pixel ratio 的运镜帧率。
- 相机拖拽继续由主线程事件桥接到 Worker；业务对象不能跨线程传输，按 ID 显隐、变色、transform 或 `materialize()` 应包装成 Worker command，通过 `host.request()` 调用。
- 相机定位后先调用 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` 的原生方法覆盖会继续触发 instance buffer dirty 或 editable-batch materialize。
- `ready()` 前的加载或管线预编译异常会触发 error、逆序资源清理和 Worker 关闭；初始化期间销毁 Host、canvas 转移/初始消息失败或 Worker 主动关闭会 reject 尚未完成的 `host.init()` / `request()` 并清理 DOM、resize 和待发送 pointer move。Host dispose 后会忽略队列中迟到的业务事件与 response。
- Offscreen 负责释放主线程，editable batching 负责减少 draw submission；两者都不会自动减少三角形或透明 overdraw。

浏览器必须同时支持 `transferControlToOffscreen()` 和 Worker WebGPU。CSS2D/CSS3D、`Info` 等真实 DOM 叠加层仍应保留在主线程。完整接口和边界见 [Viewer OffscreenCanvas Worker API](./api-viewer.md#offscreencanvas-worker实验性)。

## 9. `test_model_animation.html`：模型内置动画

[在线演示](https://u-space-phi.vercel.app/examples/test_model_animation.html)

该示例演示了如何加载含内置动画的 glTF 模型，并通过 `Model` 类的动画 API 控制播放。

### 核心要点：

- 使用 `Model.loadAsync()` 加载带动画的 `.glb` 文件，动画片段自动初始化。
- 使用 `viewer.frameloop = 'always'` 开启持续渲染以驱动动画。
- 监听 `beforeRender` 事件，通过 `model.updateAnimation(e.delta)` 推进动画时间。
- 使用 `model.playAnimation(name)` 按名称播放单个动画，`model.playAllAnimations()` 播放全部。
- 使用 `model.stopAnimation()` 停止动画播放。

## 10. `test_css_renderer.html`：CSS2D / CSS2.5D / CSS3D 渲染器

该示例演示了如何在 3D 场景中叠加渲染 HTML 元素，展示三种 CSS 渲染模式的效果差异。

### 核心要点：

- 使用 `viewer.cssRenderer.createCSS2DObject()` 创建始终面向相机、固定屏幕尺寸的 2D 标签。
- 使用 `viewer.cssRenderer.createCSS25DObject()` 创建面向相机但随距离缩放的 2.5D 精灵。
- 使用 `viewer.cssRenderer.createCSS3DObject()` 创建具有完整 3D 变换的面板。
- CSS2.5D 和 CSS3D 对象需要设置 `scale`（通常 `0.01`），因为 HTML 元素的像素尺寸远大于 3D 世界单位。
- 渲染器采用懒加载，仅在首次创建对应类型对象时初始化。

## 11. `test_postprocessing.html`：后处理效果

该示例演示了 `RenderPipeline` 的内置后处理功能，包括 Bloom、SSGI、TRAA 以及自定义后处理组合器。

### 核心要点：

- 使用 `viewer.renderPipeline.enableBloom()` / `disableBloom()` 运行时切换 Bloom 效果。
- 使用 `viewer.renderPipeline.updateBloom()` 实时调整 strength、radius、threshold 参数。
- 使用 `viewer.renderPipeline.enableSSGI()` / `disableSSGI()` 切换屏幕空间全局光照。
- 使用 `viewer.renderPipeline.updateSSGI()` 实时调整 SSGI 参数（直接修改 uniform，无需重建节点图）。
- 使用 `viewer.renderPipeline.enableTRAA()` / `disableTRAA()` 切换时间抗锯齿。
- 使用 `viewer.renderPipeline.setOutputComposer()` 自定义后处理链（示例中实现了灰度滤镜）。
- 使用 `setOutputComposer(null)` 恢复默认渲染管线。

## 12. `test_viewer_utils.html`：Viewer 工具方法

该示例演示了 `Viewer` 新增的便捷方法：截图导出、背景切换、雾效控制和阴影开关。

### 核心要点：

- 使用 `viewer.screenshot()` 导出当前渲染画面为 PNG 文件。
- 使用 `viewer.setBackground(color)` 快速切换场景背景颜色。
- 使用 `viewer.enableFog()` / `viewer.disableFog()` 控制雾效。
- 使用 `viewer.enableShadow()` / `viewer.disableShadow()` 控制阴影渲染。
- 使用 `viewer.resize(width, height)` 手动触发尺寸更新。

## 13. `test_camera_controls.html`：CameraControls 增强方法

该示例演示了 `CameraControls` 的飞行定位、视角存取、锁定和二三维切换功能。

### 核心要点：

- 使用 `viewer.controls.flyTo(position, target)` 将相机飞行到指定坐标。
- 使用 `viewer.controls.getCameraViewpoint()` 获取当前视角数据。
- 使用 `viewer.controls.setCameraViewpoint(data)` 恢复保存的视角。
- 使用 `viewer.controls.lock()` / `unlock()` 锁定或解锁相机控制。
- 使用 `viewer.controls.setViewMode('2d')` / `setViewMode('3d')` 在二三维视图间切换。

## 14. `test_object_manager.html`：ObjectManager 增强方法

该示例演示了 `ObjectManager` 的显隐控制、孤立显示、透明度设置、包围盒查询和过滤功能。

### 核心要点：

- 使用 `viewer.objectManager.show(id)` / `hide(id)` 控制对象可见性。
- 使用 `viewer.objectManager.isolate(ids)` 孤立显示指定对象。
- 使用 `viewer.objectManager.setOpacity(id, value)` 设置材质透明度。
- 使用 `viewer.objectManager.getBoundingBox()` 查询所有对象的包围盒。
- 使用 `viewer.objectManager.filter(predicate)` 按条件过滤对象。

## 15. `test_material_effects.html`：MaterialEffects 增强效果

该示例演示了材质效果系统的线框、半透明、X 光和淡入淡出功能。

### 核心要点：

- 使用 `MaterialEffects.wireframe(object)` 开启线框渲染模式。
- 使用 `MaterialEffects.highlightColor(object, { opacity })` 设置半透明效果。
- 使用 `MaterialEffects.highlightColor(object, { color, opacity, overwrite: true, depthWrite: false })` 实现 X 光透视效果。
- 使用 `MaterialEffects.fadeOut(object)` / `fadeIn(object)` 执行淡入淡出动画。
- 使用 `MaterialEffects.removeHighlightColor(object)` 恢复原始材质状态。

## 16. `test_outline.html`：TSLEffects Outline 描边

[在线演示](https://u-space-phi.vercel.app/examples/test_outline.html)

该示例演示 `TSLEffects.outline()` 作为 RenderPipeline output effect 对普通 Mesh、Group 和核心 `InstanceObject` 进行屏幕空间描边。

### 核心要点：

- 使用 `viewer.renderPipeline.addOutputEffect(outline)` 叠加 Outline，不修改原始材质，也不占用业务侧 `setOutputComposer()`。
- 使用 `outline.setSelectedObjects()` 在 Box、Sphere、`InstanceObject`、全部对象和空选择之间切换。
- 使用 `outline.update()` 实时调整强度、厚度、Glow 和 `instanceBoundsPadding`。
- `InstanceObject` 有实际渲染对象时直接描边；没有可渲染子树时可通过 `getInstanceBoundingBox()` 自动生成不可见 bounds proxy。
- 销毁时先调用 `removeOutputEffect(outline)`，再调用 `outline.dispose()` 释放 OutlineNode 与代理资源。

## 17. `test_selection.html`：Selection 选择系统

该示例演示了对象选择系统的单选和框选功能。

### 核心要点：

- 创建 `Selection` 实例并监听 `select`、`deselect`、`change` 事件。
- 使用 `selection.select()` / `deselect()` / `toggle()` / `clear()` 进行程序化选择操作。
- 使用 `selection.enableBoxSelection()` 开启鼠标框选模式。
- 框选模式下可配合 `viewer.controls.lock()` 避免框选与相机旋转冲突。

## 18. `test_measure.html`：MeasureTool 测量工具

该示例演示了 3D 场景中的距离、面积和角度测量。

### 核心要点：

- 使用 `measureTool.measureDistance(pointA, pointB)` 测量两点距离。
- 使用 `measureTool.measureArea(points)` 测量多边形面积。
- 使用 `measureTool.measureAngle(pointA, vertex, pointC)` 测量夹角。
- 测量结果以可视化线条渲染到场景中，返回的 `MeasureResult` 包含数值和单位。

## 19. `test_annotation.html`：AnnotationManager 标注管理

该示例演示了 3D 标注的创建、更新和管理，配合 `CSSRenderer` 实现 HTML 浮动标签。

### 核心要点：

- 创建 `AnnotationManager` 实例并通过 `setCSSObjectFactory()` 绑定 `CSSRenderer`。
- 使用 `annotationManager.add(id, options)` 添加带引线和自定义样式的标注。
- 使用 `annotationManager.addText(text, position)` 快捷添加纯文本标注。
- 使用 `updateContent()` 动态更新标注内容，`updatePosition()` 移动标注位置。
- 使用 `hideAll()` / `showAll()` 批量控制标注显隐。

## 20. `test_clipping.html`：ClippingTool 剖切工具

该示例演示了剖切面的创建和实时调整。

### 核心要点：

- 创建 `ClippingTool` 实例并调用 `attach()` 将场景物体移入 ClippingGroup。
- 使用 `clippingTool.addPlane(id, options)` 添加剖切面。
- 使用滑块通过 `clippingTool.setPlaneConstant()` 实时调整剖切位置。
- 使用 `showHelper()` / `hideHelper()` 切换剖切面可视化辅助。

## 21. `test_lights.html`：LightManager 灯光管理

该示例演示了灯光管理器的灯光预设和 Helper 可视化功能。

### 核心要点：

- 创建 `LightManager` 实例并使用 `applyPreset()` 一键应用灯光预设。
- 支持 4 种预设：`indoor`（室内）、`outdoor`（室外）、`studio`（摄影棚）、`warehouse`（仓库）。
- 使用 `showAllHelpers()` / `hideAllHelpers()` 显示或隐藏灯光辅助可视化。
- 每种预设会自动清除旧灯光并创建新的灯光组合。

## 22. `test_fire.html`：WebGPU 体积火焰

该示例演示了 `u-space/plugins/fire` 的 `FireEffect` 插件，封装 Three.js 官方体积火焰示例，在独立 volumetric pass 中模拟火焰和烟雾后叠加到主场景。

### 核心要点：

- 使用 `FireEffect` 创建 WebGPU 体积火焰、烟雾、扰动、动态点光源和体积阴影。
- 通过 `position` 和 `size` 设置体积盒底部中心和烟雾扩散约束范围。
- 默认使用 `TeapotGeometry(0.8, 28)` 作为 emitter；也可以传入业务 `Object3D` 和自定义 `emitterGeometry`。
- 通过 `fire.update()` 实时调整 `fireIntensity`、`smokeLifespan`、`emitterRadius` 等参数。
- 插件使用 RenderPipeline output effect 接入，不占用业务侧的 `setOutputComposer()`。

## 23. `test_ssr.html`：SSR 时空降噪

[在线演示](https://u-space-phi.vercel.app/examples/test_ssr.html)

该示例使用程序化室内场景演示完整的屏幕空间反射链路：Stochastic SSR、Temporal Reproject 和 Recurrent Denoise。

### 核心要点：

- 使用 `viewer.renderPipeline.enableSSR()` / `disableSSR()` 开关完整 SSR 后处理链。
- 使用 `viewer.renderPipeline.updateSSR()` 实时调整 ray marching、历史帧数和降噪参数。
- SSR 自动写入打包的法线/粗糙度、漫反射/金属度及 velocity MRT 通道。
- 默认 `Viewer` 的 reversed depth 可直接使用；RenderPipeline 会在三个内部 pass 中正确跳过背景，同时保持原始深度用于位置与历史重建。
- 使用 `viewer.frameloop = 'always'` 为时域重投影和循环降噪提供连续帧。
- 场景包含移动发光球体，可直接观察运动向量重投影与残影收敛效果。
- 示例通过 `pixelRatio: 1` 使用 1× drawing buffer，避免 Retina 屏幕把 Temporal Reproject 和 Recurrent Denoise 放大到 2.25 倍像素量。
- `maxDistance` 从默认值 `0.4` 开始，保留滑块用于观察追踪距离对质量和性能的影响。
- 移动物体继续生成 velocity，但不投射动态阴影；2048² 方向光阴影只更新一次。

## 24. `test_ao.html`：环境光遮蔽

[在线演示](https://u-space-phi.vercel.app/examples/test_ao.html)

该示例使用程序化墙角与紧密摆放的几何体，独立展示 RenderPipeline 的 SSGI AO 分量。页面默认开启 AO，并固定 `giIntensity: 0`，因此画面只保留 `scene.rgb × AO` 遮蔽效果。

### 核心要点：

- 使用 `viewer.renderPipeline.enableSSGI()` 开启 AO，并通过 `giIntensity: 0` 关闭间接漫反射。
- 使用 `disableSSGI()` 对比无 AO 画面，再使用当前参数重新开启。
- 使用 `updateSSGI()` 实时调整 `aoIntensity`、`radius` 和 `thickness`。
- 使用 `pixelRatio: 1` 控制全屏 SSGI 在高 DPI 屏幕上的成本。
- 场景不加载外部资源，墙角、堆叠物体和窄缝可以直接观察接触遮蔽。
