ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

HarmonyOS 7 Spatial Recon Kit + ArkGraphics 3D:Tiled 3DGS 分片请求去重与相机驱动加载闭环【鸿蒙心迹】

2026/10/2 17:45:20 拓冰建站 浏览量
HarmonyOS 7 Spatial Recon Kit + ArkGraphics 3D:Tiled 3DGS 分片请求去重与相机驱动加载闭环【鸿蒙心迹】 这次没有继续写“3DGS 怎么显示出来”而是把问题往真实工程里再推一步当大场景被切成很多 tile镜头持续移动渲染器会不断提出新的分片请求。真正难处理的不是loadTiledGSNode()而是请求重复、文件未落盘就通知 ready、页面退出后回调还在继续以及 UI 状态和渲染状态互相打架。我给这个小工程起名叫TiledGSFieldLab页面是LargeScenePage。为了方便复现本文把一次运行固定成scene_20261001_01。截图里看到的状态也都用这一组数据STREAMING、24 个 tile 请求、18 个已就绪、6 个等待中当前可用进度 75%相机 yaw 为 32.4°。官方当前的spatialRender模块用于 3DGS 数据渲染。API 26 的TiledGSNode进一步提供setCamera()、setTileRequestCallback()和notifyTileReady()适合把大场景切片后的“渲染器需要什么、应用准备什么、准备好以后再通知渲染器”连成一条链路。这里最关键的一点是回调给你的不是“马上能画”的文件而是一批“渲染器现在想要”的 tile。一、我先把问题从“加载模型”改成“管理一条流”最开始的版本很直接场景加载完成以后注册回调收到 tile 就去拿文件拿完调用notifyTileReady()。Demo 能跑镜头不动时也没什么问题。但一旦我连续转动镜头HiLog 很快开始出现同一个 URI 多次进入下载队列的情况。网络快时只是多花流量网络稍慢一点后面的问题就一起出现了同一个 tile 被重复写入、ready 次数大于真实文件数、页面已经返回上一层异步任务仍在回写页面状态。这类问题如果只盯着GSNode很容易误判成“3DGS 渲染不稳定”。实际用下来更像是一个典型的流式资源管理问题。我最后把一次大场景加载拆成四个状态IDLE页面刚进入还没有可用场景SCENE_READYScene、Camera、TiledGSNode 已经创建STREAMING渲染器开始请求 tileSTABLE当前一轮没有等待中的 tile画面进入相对稳定状态。注意STABLE不是“以后再也不会请求文件”。镜头继续移动以后新的 tile 仍然可能出现所以这个状态只描述当前窗口不描述整个模型已经永久加载完成。二、先把 Scene、Camera 和 TiledGSNode 的关系搭稳当前这段代码解决的问题是不要在页面里到处散落 Scene、Camera、GSNode 的初始化。只有三者关系建立以后后面的 tile 回调才有意义。import{Scene,RenderContext,Camera,Node}fromkit.ArkGraphics3D;import{spatialRender}fromkit.SpatialReconKit;privatescene:Scene|nullnull;privatecamera:Camera|nullnull;privatetiledGSNode:spatialRender.TiledGSNode|nullnull;privateasyncprepareScene():Promisevoid{constrenderContext:RenderContext|nullScene.getDefaultRenderContext();if(!renderContext){thrownewError(RenderContext unavailable);}renderContext.loadPlugin(spatialRender.GSPlugin.PLUGIN_ID);constsceneawaitScene.load();constrootscene.rootasNode;constfactoryscene.getResourceFactory();constcameraawaitfactory.createCamera({name:streamCam,path:root.path});constnodeawaitspatialRender.GSPlugin.loadTiledGSNode(scene,{uri:file:///data/storage/el2/base/files/campus-lod/campus.scene.json},root);node.setCamera(camera);this.scenescene;this.cameracamera;this.tiledGSNodenode;this.streamStateSCENE_READY;}这里有两个地方我会特别注意。第一插件加载不能省。官方文档对GSPlugin.PLUGIN_ID的要求很明确在使用相关 3DGS 能力前要先加载插件。第二setCamera()不是为了“让页面多一个相机变量”而是告诉 TiledGSNode后续 tile 选择要跟着哪一个相机变化。也就是说镜头位置变化以后真正决定“下一批需要哪些 tile”的主体是渲染侧。应用侧要做的是响应这批请求不要擅自把所有 tile 一次性塞进内存。当前 API 26 的 TiledGS 相关接口是 Stage 模型能力所以正式项目里不要把这套封装想当然地搬到不满足模型约束的模块里。三、回调里不直接下载先做 URI 去重问题最明显的一次我快速左右拖动视角日志里 24 次请求并不等于 24 个不同文件。一个 tile 在短时间里可能再次进入需求列表。这段代码解决的就是“同一 URI 在上一次请求还没结束时又被第二次塞进队列”。privatependingUris:SetstringnewSet();privaterequestedCount:number0;privatereadyCount:number0;privatebindTileRequests():void{constnodethis.tiledGSNode;if(!node){return;}node.setTileRequestCallback((tiles:spatialRender.GSTile[]){if(!tiles||tiles.length0||this.disposed){return;}this.streamStateSTREAMING;for(consttileoftiles){if(this.pendingUris.has(tile.uri)){continue;}this.pendingUris.add(tile.uri);this.requestedCount1;this.loadTile(tile);}});}这里没有使用数组而是Setstring。原因很简单我要表达的是“某个 URI 现在是否已经处于处理中”不是维护严格的顺序。这个改动以后requestedCount才开始有工程意义。它记录的是进入本地加载流程的唯一请求数量而不是 callback 触发次数。正式项目还可以再加一层已完成缓存。例如本地文件已经存在并通过校验就不必再次走网络。本文 Demo 为了把链路讲清楚只保留“处理中去重”。四、文件真的可读以后再通知渲染器我第二次踩到的问题更隐蔽。最早的代码是发起下载以后就调用notifyTileReady()。从业务视角看好像“我已经开始准备这个 tile 了”但从渲染器视角看ready的含义不是“开始准备”而是对应 URI 的文件已经可访问、可读取。所以这段代码解决的是 ready 时机。privateasyncloadTile(tile:spatialRender.GSTile):Promisevoid{try{awaitthis.tileCache.ensureReadable(tile.uri);if(this.disposed||!this.tiledGSNode){return;}this.tiledGSNode.notifyTileReady(tile);this.readyCount1;this.pendingUris.delete(tile.uri);if(this.pendingUris.size0){this.streamStateSTABLE;}}catch(error){this.pendingUris.delete(tile.uri);console.error(Tile failed:${tile.uri},${JSON.stringify(error)});}}tileCache.ensureReadable()是我自己的工程封装不是 Spatial Recon Kit 的 API。它只负责一件事确保tile.uri对应的数据已经准备到渲染器能够读取的位置并且在 Promise resolve 之前完成必要的落盘。这层抽象很重要。否则网络请求、文件写入、重试、校验全部塞进LargeScenePage.ets页面会迅速变成第二个下载管理器。官方对notifyTileReady()的描述还有一个很实用的边界如果相机已经移开渲染器不再需要这个 tile这次通知不会继续产生有效加载动作。这个行为能兜住“请求过程中视角已经改变”的情况但它并不能替你完成网络请求去重所以应用侧的 pending 集合仍然有价值。五、页面退出时我更关心“回调还会不会回来”大场景页面最容易被忽略的不是首屏而是退出。用户返回上一页时如果 callback 仍然持有页面逻辑后面某个异步下载完成以后还可能继续更新readyCount、streamState甚至继续打印已经没有意义的 UI 日志。所以我把退出逻辑明确做成一个收口动作。privatedisposed:booleanfalse;aboutToDisappear():void{this.disposedtrue;if(this.tiledGSNode){this.tiledGSNode.setTileRequestCallback(null);this.tiledGSNode.visiblefalse;}this.pendingUris.clear();this.streamStateIDLE;}这里的重点不是“清空 Set”三个字而是先断掉新的 tile 请求来源再让已经在途的异步逻辑通过disposed自己失效。我没有在 Demo 里虚构一个不存在的destroyTiledGSNode()。Scene、资源对象和更底层资源的释放策略要结合实际工程持有关系处理。当前页面能明确做的是取消 callback、停止继续写 UI、清理页面级状态。如果你的资源管理层是单例还要继续处理“页面销毁不等于缓存销毁”这个差异。页面状态和资源缓存最好不要用同一个生命周期。六、调试时不要只看画面数值要能互相对上做到这里以后我才开始给页面加运行面板。这次固定运行数据是Task IDscene_20261001_01StateSTREAMINGProgress75%Tiles18 / 24Pending6Camera Yaw32.4°我把“进度”定义为当前已 ready 的 tile 数除以当前唯一请求数也就是 18 / 24。它不是整个 campus 场景的全局完成率更不是 3DGS 重建算法的训练进度。这点如果不写清楚75% 很容易制造一种“整个模型还有 25% 没加载完”的错觉。DevEco 这一张我更在意四块信息能不能互相验证左边是TiledGSFieldLab工程目录中间能看到LargeScenePage.ets里的请求回调右侧模拟器是Campus GS Stream底部 HiLog 同时打印scene_20261001_01、SCENE_READY - STREAMING、24、18、6。只要其中一个数对不上我就会先怀疑状态统计而不是立刻怀疑 Spatial Recon Kit。七、手机上的 75%是“当前可用画面”而不是假完成实际运行到截图这一刻前景已经有足够 tile 可以显示用户能看到校园大场景但后台仍有 6 个 tile 在补齐。这也是我最终保留STREAMING状态的原因。如果为了 UI 看起来“成功”18 个 tile ready 时就切成COMPLETED后面相机继续转动又重新出现请求状态机会非常别扭。流式场景更适合描述当前阶段而不是追求一个永久完成的终点。我在正式项目里还会继续补三件事。第一是失败退避。同一个 URI 连续失败不能无限重试至少要记录失败次数和下一次允许重试的时间。第二是缓存预算。大场景 tile 不能只进不出内存缓存、本地磁盘缓存都要有上限最好按最近使用和文件体积做淘汰。第三是相机快速移动时的优先级。距离当前视锥更重要的 tile 应该优先而不是严格按首次进入队列的时间处理。这些都属于资源调度不应该继续堆在 ArkUI 页面里。八、这次真正解决的不是“会不会调用 API”回头看这次 Demo核心 API 其实不多加载插件、创建场景、加载 TiledGSNode、绑定 Camera、注册 tile callback、文件准备完成后notifyTileReady()。真正把 Demo 和工程实现拉开差距的是中间那层状态管理渲染器发出需求应用做请求去重资源层保证文件可读页面生命周期决定结果还能不能回写最后 UI 只展示当前真实状态。这条链路理顺以后再往后加预取、并发限制、离线缓存、失败重试都比较自然。如果一开始就把每一次 tile callback 当成“下载一次文件”短场景可能看不出问题大场景一转相机网络、磁盘、UI 生命周期会一起把问题暴露出来。所以这次我最后留下的不是一段更长的 3DGS 示例代码而是一条更清楚的加载闭环Camera 变化 → Renderer 请求 tile → 应用去重与准备资源 → 文件可读 → notifyTileReady → Renderer 接管显示。这才是TiledGSNode在大场景里更值得花时间处理的部分。参考资料HarmonyOS Spatial Recon KitspatialRenderAPIhttps://developer.huawei.com/consumer/en/doc/harmonyos-references/spatial-recon-spatialrenderSpatial Recon Kit 6.0.1 API 变更https://developer.huawei.com/consumer/en/doc/harmonyos-releases/js-apidiff-spatialreconkit-6101