Appearance
示例指南
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 场景所需的最少配置,与 快速上手 指南中的内容一致。
核心要点:
- 将
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 一体化加载
该示例演示通过 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还是 fallbackModel。 - 示例把
viewer、loader、root、semanticGroup、sceneGroup、facilityLayer、sceneLayer、sceneInstances和facilities暴露到window.uManagerExample,方便在控制台调试。
6. 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-basedSceneInstancedLayer/普通模型加载结果做性能和画面对比。 sceneGroup.userData.editableBatch提供sourceMeshes、batches、drawCallsSaved、vertices、indices和 fallback 原因统计。
该模式通过展开重复顶点换取更少 draw calls;高重复、大 Geometry 的同模板对象可能仍更适合 SceneInstancedLayer。详见 u-manager setEditableBatching() API。
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 验证生产构建。
在线演示由一键发布流程单独打包: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(),以 transferableArrayBuffer/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。
9. 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 描边
该示例演示 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 时空降噪
该示例使用程序化室内场景演示完整的屏幕空间反射链路: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:环境光遮蔽
该示例使用程序化墙角与紧密摆放的几何体,独立展示 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 屏幕上的成本。 - 场景不加载外部资源,墙角、堆叠物体和窄缝可以直接观察接触遮蔽。