ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【共创稿事节】HarmonyOS 7商品展示场景:3DGS 重建一个小体积摆件的完整路径与真机数据

【共创稿事节】HarmonyOS 7商品展示场景:3DGS 重建一个小体积摆件的完整路径与真机数据 电商商品详情页要一个能 360° 转着看的手办传统做法是请建模师按实物拉面片、烘焙贴图、出 GLB单件成本几百到几千周期按周算。3DGS 端侧重建换了一条路拍一段环绕视频端侧跑完出点云直接挂到 Scene 里渲染。这篇文章记录我们用一个 15cm PVC 手办走完整条链路的真机数据包括重建耗时波动、点云体量取舍、渲染帧率以及背景干扰这个最容易被低估的坑。能力面从采集到渲染的完整 API 链Spatial Recon Kit 在 API 26 暴露的核心入口是spatialRecon采集与重建和spatialRender加载与渲染。两者职责分开前者产出点云文件落盘后者把文件挂到 ArkGraphics3D 的 Scene 上。一条完整链路是采集 → 重建 → 落盘 → 加载 GSNode → 挂到 Scene → Component3D 渲染。先看这条链路的图形化版本下面每个环节逐段展开。相机帧流采集环绕 8 秒以上特征提取帧间对应关系点云初始化高斯分布初值迭代优化位置与 alpha 收敛裁剪导出受 maxPointCount 约束PLY 落沙箱按商品 ID 命名loadGSNode文件挂到 SceneComponent3D 渲染fillrate 受同屏点数约束链路里最影响体验的是裁剪导出和渲染挂载后面细说。采集侧的关键 API 是spatialRecon.ReconSession它消费相机帧流输出重建结果。渲染侧沿用设计向那篇已经讲过的spatialRender.GSPlugin.loadGSNode区别只在于uri指向的是本次重建刚落盘的 PLY/MP4而不是预置资产。需要主动指出的硬事实Spatial Recon Kit 当前仅保证麒麟 9020 / 9030S / 9030 / 9030 Pro 及后续旗舰芯片的体验。我们手上 Pura 80 Pro9020跑得动MatePad Pro 13.2 英寸9030 Pro也跑得动但一台中端机非上述芯片查询isSupported()返回 true实际重建到一半 OOM 退出。查询支持 ≠ 体验保证这条在中端机覆盖场景里必须写进降级路径。另一个硬事实同一时刻只允许一个重建 session。第二个 session 创建会直接报SESSION_BUSY。我们一开始在详情页和再拍一件按钮上各起一个 session第二个总是失败后来改成串行复用单例才稳定。约束面摆件场景的几条硬约束约束实测表现对工程的影响设备芯片仅 9020/9030S/9030/9030 Pro 旗舰保证商品详情页要做机型判断 Mesh 降级Session 单例第二个 session 报SESSION_BUSY详情页和再拍按钮共享一个 session采集时长官方建议 ≥ 8 秒环绕视频引导页要明确告诉用户拍多久输出体量15cm 手办 PLY 约 18-26 MB不能塞 rawfile必须落沙箱同屏高斯点fillrate 敏感 80 万点开始掉帧默认相机距离要卡住不能让用户贴脸看最后一条是渲染侧的隐性约束。3DGS 渲染开销不取决于总点数取决于同屏高斯点数。手办总点数 60 万不算大但相机贴到 5cm 距离时同屏能到 50 万帧率从 60 掉到 38。这个我们一开始没料到因为传统 Mesh 思维里模型总面数才是瓶颈。场景落地一个 15cm 手办的完整重建被测物是一只 15cm 高的 PVC 机甲手办表面有金属漆和高光细节放在 60×40cm 桌面上背景是浅灰墙面。采集设备 Pura 80 Pro主摄 4 镜环绕拍摄 10 秒约 300 帧。完整 ArkTS 流程如下分三段采集重建、落盘加载、渲染挂载。1. 采集与重建// entry/src/main/ets/recon/ProductRecon.etsimport{spatialRecon}fromkit.SpatialReconKit;import{camera}fromkit.CameraKit;exportclassProductRecon{privatesession:spatialRecon.ReconSession|nullnull;// 机型判断非保证芯片直接走 Mesh 降级不浪费用户时间staticshouldUse3DGS():boolean{// 实际项目里读 sysParam 维护一份白名单// 这里只示意9020/9030S/9030/9030 Pro 才返回 truereturnspatialRecon.DeviceCapability.isReconGuaranteed();}asyncstartRecon(cameraInput:camera.CameraInput):Promisestring{// 单例保护上一次 session 没释放就先销毁避免 SESSION_BUSYif(this.session!null){this.session.release();this.sessionnull;}constconfig:spatialRecon.ReconConfig{// targetObject 模式会尝试把主体外的背景剔除对手办这种小物体很关键mode:spatialRecon.ReconMode.TARGET_OBJECT,// 输出 PLY保真最高体积最大MP4 体积小但有损outputFormat:spatialRecon.OutputFormat.PLY,// 期望输出点数上限超过会触发内部裁剪maxPointCount:800_000,};this.sessionawaitspatialRecon.ReconSession.create(config);// 喂帧实际项目里从 CameraInput 流式拿 frame这里简化为回调this.session.bindCameraInput(cameraInput);// 重建完成回调返回落盘路径returnnewPromisestring((resolve,reject){this.session!.on(complete,(result:spatialRecon.ReconResult){// result.uri 形如 file:///data/storage/el2/base/files/recon/xxxx.plyresolve(result.uri);});this.session!.on(error,(err:spatialRecon.ReconError){reject(err);});// 主动开始引导页倒计时 8 秒后调 stopthis.session!.start();});}stopRecon():void{// 用户提前结束或引导页倒计时到主动 stop 触发 complete 回调this.session?.stop();}}TARGET_OBJECT模式是这次能跑出 11 秒级别的关键。它内部会做主体分割背景点不参与优化省掉了大量高斯点的迭代。我们一开始用默认的FULL_SCENE模式手办背后的墙面也被重建耗时直接翻倍到 23 秒输出体量也涨到 41 MB。2. 落盘与加载重建结果已经在ReconResult.uri里落了沙箱不需要再搬。但 PLY 18-26 MB 这个体量塞 rawfile 是不行的——APK 体积会被撑爆且每个商品都打包一份不现实。正确做法是重建结果按商品 ID 命名落沙箱详情页按 ID 复用。// entry/src/main/ets/recon/ReconCache.etsimport{fileIo}fromkit.CoreFileKit;exportclassReconCache{// 沙箱根实际项目按商品 ID 分目录加 LRU 清理privatestaticreadonlyROOT/data/storage/el2/base/files/recon;staticpathFor(productId:string):string{return${ReconCache.ROOT}/${productId}.ply;}staticasyncexists(productId:string):Promiseboolean{try{awaitfileIo.accessSync(ReconCache.pathFor(productId));returntrue;}catch{returnfalse;}}// LRU 清理沙箱总占用超 200MB 时按最久未用删staticasyncpruneIfOver(limitBytes:number200*1024*1024):Promisevoid{// 省略实现列目录 → 按 atime 排序 → 删到总占用 limitBytes}}3. 渲染挂载// entry/src/main/ets/pages/ProductDetail.etsimport{Scene,RenderContext,Node,Component3D}fromkit.ArkGraphics3D;import{spatialRender}fromkit.SpatialReconKit;import{ProductRecon}from../recon/ProductRecon;import{ReconCache}from../recon/ReconCache;EntryComponentstruct ProductDetail{privatescene:Scene|nullnull;Stateready:booleanfalse;StatereconMs:number0;asyncaboutToAppear():Promisevoid{constproductIdfigure-001;// 机型判断非保证机型直接走 Mesh 详情页不进 3DGS 分支if(!ProductRecon.shouldUse3DGS()){this.goMeshFallback(productId);return;}// 已有缓存就直接渲染没有就引导用户拍letplyUri:string;if(awaitReconCache.exists(productId)){plyUrifile://${ReconCache.pathFor(productId)};}else{plyUriawaitthis.guideUserAndRecon(productId);}awaitthis.loadAndRender(plyUri);}asyncloadAndRender(uri:string):Promisevoid{constctxScene.getDefaultRenderContext();if(ctxnull)return;// 必须先注册插件否则 loadGSNode 是未定义行为ctx.loadPlugin(spatialRender.GSPlugin.PLUGIN_ID);constsceneawaitScene.load();this.scenescene;constrootscene.rootasNode;constgsNodeawaitspatialRender.GSPlugin.loadGSNode(scene,{uri,offset:0},root);// 居中并缩放到统一尺寸避免不同手办大小不一gsNode.scale{x:0.5,y:0.5,z:0.5};gsNode.position{x:0,y:-0.15,z:0};// 限制相机最近距离防止贴脸看导致同屏高斯点爆炸掉帧this.clampCameraMinDistance(0.3);this.readytrue;}build(){Column(){if(this.readythis.scene){Component3D({scene:this.scene}).width(100%).height(100%)}else{Text(重建中… 已用${this.reconMs}ms).fontSize(16)}}.width(100%).height(100%)}}clampCameraMinDistance(0.3)这一行是我们加的私货官方示例没有。原因是用户双指捏合放大到最近 5cm 时同屏高斯点飙到 50 万帧率掉到 38 fps。卡住最近距离 30cm 后同屏稳定在 18 万点以内60 fps 不掉。这个值要按物体尺寸调手办 15cm 用 30cm更大的物体要等比放大。真机数据Pura 80 Pro 上的实测被测物15cm PVC 机甲手办浅灰墙面背景主摄环绕 10 秒约 300 帧。设备 Pura 80 Pro麒麟 9020室温 25°C电量 60%关闭其他后台。指标数值备注重建耗时11-14 秒背景干净 11s背景杂乱 14s输出 PLY 体量18-26 MB点数 60-78 万输出 MP4 体量4.2-5.8 MB有损高光细节有涂抹感加载到首帧0.4-0.7 秒PLY 落沙箱后 loadGSNode渲染帧率中景58-60 fps同屏约 12 万点渲染帧率近景 5cm36-42 fps同屏约 50 万点渲染内存增量78-95 MB含 GSNode 上采样缓冲11-14 秒这个区间波动 3 秒主要变量是背景杂乱程度。同一只手办桌面清空只放手办时 11.2 秒稳定桌上多放两本书、一个马克杯时跑到 13.8 秒。TARGET_OBJECT模式会尝试剔除背景但分割本身有开销背景越乱分割越慢。我们一开始以为耗时波动是芯片温控降频跑了三次都稳定在 11 秒后突然 14 秒反复对比才发现是同事顺手把马克杯放回桌上了。这个坑排查了小半天。输出体量上PLY 18-26 MB 对单商品详情页可以接受但一个电商 App 不可能只展示一件商品。我们试过 MP4 输出4-5 MB 体积友好很多但高光漆面有可见涂抹感金属反光细节糊掉。最终选 PLY 落沙箱 LRU 200MB 上限按商品 ID 复用用户看过的商品二次进入直接命中缓存。踩坑与取舍坑 1背景干扰导致耗时波动 3 秒已经提到TARGET_OBJECT模式依赖主体分割背景越乱分割越慢。引导页后来加了一条“请将商品放在干净桌面上移开周围杂物”。这条文案上线后耗时波动从 3 秒压到 1 秒以内。我们内部争论过要不要在采集前做一道用户手动框选主体的交互框外直接不喂给 session。最后没做原因是手办这种小物体用户框选的精度不如算法分割反而引入新问题。但如果是大件商品如家具手动框选可能值得。坑 2点云文件塞不进包第一版我们把重建结果当资产打进 APK 的 rawfile发版包从 80 MB 涨到 230 MB被商店拒了。改成用户首次查看时引导拍摄 沙箱缓存后APK 回到 80 MB。代价是首次查看有 11 秒等待二次查看 0.5 秒命中缓存。这个取舍对电商场景成立用户第一次点开详情页愿意等 11 秒看一个能转的 3D 模型第二次就不愿意再等了。但如果是高频切换商品的场景如比价页横向滑动11 秒等待不可接受得走预重建 CDN 下发的另一条路。坑 3贴脸看掉帧相机最近距离不卡用户双指捏合放大到 5cm 时帧率掉到 38 fps。排查后发现是同屏高斯点飙到 50 万fillrate 打满。卡住最近距离 30cm 后稳定 60 fps。这个限制要在 UI 上明确告诉用户已最近距离否则用户会以为卡了拼命再捏。坑 4中端机查询支持但跑不动一台中端机非保证芯片isSupported()返回 true重建到 60% 进度时 OOM 退出session 报OUT_OF_MEMORY。我们后来加了isReconGuaranteed()这道更严格的判断只对保证机型开放 3DGS 入口其他机型走 Mesh 详情页。查询支持 ≠ 体验保证这条要在文档里反复强调。工程约束清单机型判断用isReconGuaranteed()而非isSupported()非保证机型直接 Mesh 降级Session 全局单例新 session 创建前先 release 旧的重建结果按商品 ID 落沙箱LRU 200MB 上限不打包进 APK输出格式选 PLY保真或 MP4体积按场景对高光细节的敏感度定渲染侧卡相机最近距离按物体尺寸等比缩放阈值引导页明确告知放在干净桌面、移开杂物、环绕 8 秒以上loadPlugin必须在loadGSNode之前顺序反了不报错但行为未定义下一步可验证的动作哦在 MatePad Pro 13.2 英寸9030 Pro上跑同一只手办对比重建耗时和渲染帧率确认平板大屏是否因同屏点数更多而需要更激进的 LOD把 MP4 输出和 PLY 输出做 A/B找 20 个真实用户看高光漆面细节差异决定是否对低优先级商品切 MP4测一组 30cm 中型摆件如花瓶看TARGET_OBJECT分割是否还稳定耗时是否线性增长接 LRU 清理后跑 50 个商品连续查看确认沙箱占用稳定在 200MB 以下不溢出在我们测过的 3 台机器Pura 80 Pro、MatePad Pro 13.2、一台中端机范围内11-14 秒重建 60 fps 中景渲染这条链路是成立的。如果后续 API 26 不大改 ReconSession 接口这套方案可以直接进商品详情页灰度。
返回列表