鸿蒙端侧 3DGS 重建落地实录:重建在C层,ArkTS 只负责“看“和“改“
本文涉及 HarmonyOS 6.1.0(23) 起的视觉输入重建与 7.0(26) 新增能力。文中代码是为说明问题编写的完整示例,不是官方示例的搬运;API 名称、枚举取值与版本号等事实性信息均标注官方出处;涉及真机表现的部分已明确标注,未做任何实测数据编造。

引子:一句"拍一圈就行",我调研了三天
产品在需求单上写了一句很轻的话:
用户拿着手机绕着东西走一圈,App 里就能 360° 转着看这个模型。
听起来像"调个相机 + 调个模型加载"的活。我打开官方文档准备抄一段示例,结果第一眼就发现事情不对:我在网上搜到的那些 ArkTS 重建代码,在官方文档里根本找不到对应接口。
这件事值得先说,因为它决定了你这三天的调研方向是不是从一开始就跑偏了。
一、先纠正一个认知:重建在 C 层,ArkTS 只做"看"和"改"
Spatial Recon Kit(空间建模套件)的官方 ArkTS API 文档下只有两个模块:
| 模块 | 干什么 | 起始版本 |
|---|---|---|
spatialRender | 3DGS 模型的加载与渲染,含滤镜效果 | 6.0.1(21) |
spatialEdit | 3DGS 模型的选择、上色、删除、导出 | 26.0.0 |
注意:这里没有"重建"模块。
重建管线是**纯 C/C++(NDK)**的,官方指南标题就写着「重建三维场景(C/C++)」,从 6.1.0(23) 开始支持通过视觉输入重建(重建三维场景(C/C++))。
所以整个能力是分层的,我用一张图说清楚:

- C 层(NDK):检测能力 → 建会话 → 喂数据帧 → 启动重建 → 查进度 → 暂停/继续 → 保存结果 → 销毁会话。
- ArkTS 层:把 C 层产出的模型文件(MP4 / PLY / GLB)加载进
ArkGraphics3D场景,做滤镜、做编辑、做交互。
这就是第一个原创判断:你不可能只用 ArkTS 完成"端侧重建"。 但凡看到一个"纯 ArkTS 三行代码生成 3D 模型"的示例,先别急着抄——先去官方 API 总览里核对模块名。我这次核对的结论是:官方 ArkTS 侧只有 spatialRender 和 spatialEdit,没有重建入口。
另外两条 Kit 级约束必须提前知道(Spatial Recon Kit 简介):
- 本 Kit 仅支持中国境内(香港特别行政区、澳门特别行政区、中国台湾除外);
- 本 Kit 是 ArkGraphics 3D 模块的扩展,必须与它联合使用。
二、第一关:设备门槛,三重限制叠在一起
在写第一行代码之前,先回答"这台设备配不配跑"。官方给了三重限制,任何一条不满足,后面全是白干:
| 限制维度 | 具体要求 |
|---|---|
| 地域 | 仅中国境内(不含港澳台) |
| 设备形态 | Phone、Tablet、PC/2in1、TV |
| 芯片 | 仅保证旗舰芯片(Kirin 9020 / 9030S / 9030 / 9030 Pro 及以后) |
| 模拟器 | 不支持 |

芯片这一条官方说得很克制但很明确:由于空间重建对性能开销较大,当前仅保证旗舰芯片上的用户体验;在其他芯片上,即使查询接口返回"支持",也无法保证重建耗时和重建质量(重建三维场景(C/C++))。
我把这句话翻译成工程语言:"支持"是一个三态,不是布尔值。 于是能力检测要这样写:
// spatial_gate.h —— 我自己的能力检测封装
#include "spatial/spatial_recon_interface.h"
enum class ReconGate {
UNSUPPORTED, // 设备根本不支持
RISKY, // 接口说支持,但芯片不在保证名单里,可跑但别承诺效果
READY // 支持且芯片在保证名单里
};
// 只做接口层判断,芯片分档由业务层结合机型名单决定
ReconGate checkReconGate() {
HMS_SpatialReconStatus ret = HMS_SpatialRecon_IsSupport(SPATIAL_RECON_MODEL_TYPE_GS);
if (ret != SPATIAL_RECON_STATUS_SUCCESS) {
return ReconGate::UNSUPPORTED;
}
return ReconGate::READY;
}
HMS_SpatialRecon_IsSupport 的返回值只有两种可能:SPATIAL_RECON_STATUS_SUCCESS(支持)或 SPATIAL_RECON_STATUS_DEVICE_NOT_SUPPORT(不支持)——这一点在管理 Spatial Recon Kit 会话里有明确说明。
我建议把"不支持"做成一条完整的降级路径,而不是弹个 Toast 就完事。 因为按上面的限制,你的用户里必然有一大批设备跑不了。可行的降级是:换成本地预置的轻量模型、或者直接展示多角度实拍图,让功能"还在",只是效果降级。
三、第二关:数据输入,1.333 这个比例要背下来
重建要的不是"一段视频",而是一系列图像 + 每张图对应的相机内参和位姿。喂数据有两条路:
- 用 AR Engine 的数据结构——先更新一次 AR 引擎的计算结果,再把
ARSession/ARFrame推进来; - 按
HMS_SpatialRecon_DataFrame结构体自己组装——适合你已经有现成图像和内参的场景。
这里埋着整条链路最容易被忽略的一颗地雷:
为保证重建效果和鲁棒性,不论使用何种格式,当前仅支持输入宽度 1080 像素、高度 1440 像素的图像。输入其余尺寸,结果是未定义的。
1080×1440,宽高比正好 1 : 1.3333。记住这个数字,因为你的相机预览、相册取图、缩放开销全都要围着它转——相机默认给你的是 1920×1080 或者 4:3,都不是这个比例,必须自己裁或缩。
第二条:仅支持 RGB 格式输入(SPATIAL_RECON_IMAGEDATA_FORMAT_RGB)。
下面是我自己组装的推帧示例:
// recon_frame.cpp —— 把一张图组装成 DataFrame 推进会话
#include "spatial/spatial_recon_interface.h"
// 只推"有效帧",无效输入会直接返回 SPATIAL_RECON_STATUS_FAILED
HMS_SpatialReconStatus pushOneFrame(HMS_SpatialRecon_Session* session,
const uint8_t* rgb, uint32_t w, uint32_t h,
float fx, float fy, float cx, float cy) {
// 地雷一:尺寸不对,结果未定义,这里直接拦掉
if (w != 1080 || h != 1440) {
return SPATIAL_RECON_STATUS_FAILED;
}
HMS_SpatialRecon_DataFrame frame;
frame.focalX = fx; // 相机内参
frame.focalY = fy;
frame.principalX = cx; // 主点
frame.principalY = cy;
frame.imageWidth = w;
frame.imageHeight = h;
frame.format = SPATIAL_RECON_IMAGEDATA_FORMAT_RGB; // 地雷二:仅 RGB
frame.imageData = const_cast<uint8_t*>(rgb);
return HMS_SpatialRecon_PushFrame(session, &frame);
}
有个细节能省你不少事:关键帧是系统自动选取的。 调 PushFrame / PushARFrame 时,系统会自己挑关键帧保存用于后续重建——你不需要(也不应该)自己去算哪一帧是关键的。
四、第三关:会话管理,一段三段式状态机
重建不是"一个函数调用完就出结果",它是一段有状态的生命周期。我把它归纳成三段:
① 采集阶段:CreateSession → PushFrame × N (Stage = INIT)
② 重建阶段:StartSession → GetProgress / Pause / Resume (Stage = BUILDING)
③ 保存阶段:SaveResultToFile 或 StartSession 时传入 writeInfo (Stage = FINISHED)
↓
DestroySession
对应到接口,这一段是自写的完整骨架:
// recon_session.cpp —— 三段式会话骨架
#include "spatial/spatial_recon_interface.h"
// ① 创建会话:工作目录必须是应用内部文件目录的子目录
const char* kWorkDir = "/data/storage/el1/base/spatial_recon_files/";
HMS_SpatialRecon_Session* session = nullptr;
HMS_SpatialReconStatus ret =
HMS_SpatialRecon_CreateSession(SPATIAL_RECON_MODEL_TYPE_GS, kWorkDir, &session);
// ② 启动重建:writeInfo 非空 → 重建完成后自动保存;为空 → 稍后手动保存
HMS_SpatialRecon_ModelWriteInfo info;
info.modelFormat = SPATIAL_RECON_OUTPUT_FORMAT_MP4; // 也可保存为 PLY 点云
auto onFinished = [](HMS_SpatialReconStatus status) {
// 重建结束回调:这里只做通知,别在回调里做重活
return;
};
HMS_SpatialRecon_StartSession(session, &info, onFinished);
// ③ 运行模式:必须按应用是否在前台设置,否则可能性能/功耗劣化
HMS_SpatialRecon_SetRunningMode(SPATIAL_RECON_RUNNING_FOREGROUND_MODE);
// 进度查询:第二个出参还能带出当前 Stage
float progress = 0.0f;
HMS_SpatialRecon_GetProgress(session, &progress, nullptr);
// 结束后销毁
HMS_SpatialRecon_DestroySession(session);
三个必须记住的点:
- 工作目录有硬要求:
CreateSession时指定的工作目录必须已存在,且必须是应用内部文件目录的子目录(例如/data/storage/el1/base/下面)。传个外部路径或者不存在的目录,创建就失败。 SetRunningMode不是可选项。官方原话是"此标志位如未正确设置,可能导致性能或者功耗劣化"。前台就设前台模式,切后台就设后台模式——这是让系统给你分配计算资源的依据。- 销毁是有前提的:会话不保证并发安全。官方明确说明,在会话还在执行任务(重建或保存)时请求销毁,会导致未定义行为;一旦销毁,就不能再对该会话做任何操作。
保存结果也有两种姿势:StartSession 时把 writeInfo 传进去(重建完自动存),或者重建结束后手动调 SaveResultToFile。输出格式支持 PLY(点云) 和 MP4(运镜视频)。
五、第四关:两条硬边界——温度与串行
这一节是全文最该抄进架构评审的部分。
硬边界一:必须订阅温度事件
空间重建对系统资源的消耗量级,从官方这句话就能看出来:
由于空间重建计算量较大,强烈建议开发者通过 HMS 的公共事件接口,订阅热公共事件
COMMON_EVENT_THERMAL_LEVEL_CHANGED。当检测到设备温度过高时,自动暂停重建并提示用户,防止过热导致卡顿。
注意官方的用词是"强烈建议"——翻译过来就是"这是准入项,不是优化项"。这是我见过的第一个把"温控"写进主流程的 Kit。 实现上就是三步:订阅事件 → 温度超阈值 → 调 HMS_SpatialRecon_PauseSession,等温度回落再 HMS_SpatialRecon_ResumeSession。
// 过热保护:暂停与继续
HMS_SpatialRecon_PauseSession(session); // 温度过高时
HMS_SpatialRecon_ResumeSession(session); // 温度回落、用户确认后
官方还补了一句很实用的建议:在应用里提供开关,让用户自己控制何时暂停、何时继续。
硬边界二:同一时刻只有一个会话
这条是整个能力最硬的边界,官方说得斩钉截铁:
由于重建过程中对系统资源消耗较大,Spatial Recon Kit 仅支持同一时刻只有一个 session 正在进行重建。如果同一时刻有多个 session 同时进行重建,会导致未定义行为。
而且保存 MP4 也是串行的——同一时刻只能有一个会话在保存 MP4。
这不是性能建议,这是架构约束。 它的直接后果是:你不能让"相机页"和"模型页"各开一个会话,也不能让用户连点两次触发两轮重建。正确做法是把重建做成全局单例队列:
- 会话管理器全局唯一,同一时刻只有一个活跃会话;
- 重建请求进队列,前一个没结束时,后来的请求排队而不是并发;
- 页面销毁不等于会话结束,会话的生死必须由管理器统一管。
第二个原创判断:把"单会话"当成一个全局互斥锁来设计,而不是当成一个参数来传。 我在调研时看到过不少示例把 CreateSession 写在页面里——按官方这条约束,那种写法在真实场景里迟早撞车。
六、重建完怎么"看":ArkTS 侧的加载、滤镜与编辑
C 层把模型文件吐出来之后,剩下的事全在 ArkTS 层。
加载:先给渲染上下文装上 GSPlugin,再把模型加载成节点。支持 MP4 / PLY / GLB 三种格式(加载 3DGS 模型)。
// ModelStage.ets —— 自写的加载封装
import { spatialRender } from '@kit.SpatialReconKit';
import { Scene, RenderContext } from '@kit.ArkGraphics3D';
export async function mountGSModel(uri: string): Promise<spatialRender.GSNode | null> {
const ctx: RenderContext | null = Scene.getDefaultRenderContext();
if (ctx === null) {
return null;
}
// 1. 先注册 GSPlugin,不注册则场景不认识 3DGS 数据
ctx.loadPlugin(spatialRender.GSPlugin.PLUGIN_ID);
// 2. 加载场景
const scene: Scene = await Scene.load();
// 3. 加载 3DGS 节点:offset 是数据在文件中的偏移量,一般传 0
const node: spatialRender.GSNode =
await spatialRender.GSPlugin.loadGSNode(scene, { uri, offset: 0 }, scene.root);
// GSNode 继承自 Node,可以像普通节点一样摆位置、缩放、控可见性
node.position = { x: 0, y: 0, z: -3 };
node.scale = { x: 1, y: 1, z: 1 };
node.visible = true;
return node;
}
这里有个顺序坑:loadPlugin 必须在 loadGSNode 之前。少了这一步,GSNode 拿不到,场景也渲染不出高斯数据。
滤镜:spatialRender 提供了几套现成的风格化效果——RetroEffect(复古)、ComicEffect(漫画)、ObraDinnEffect(黑白 bit 风)、ColorEditingEffect(颜色编辑),参数类从 6.1.0(23) 起提供(spatialRender API)。对商品展示、文博复刻这类场景,"一键换风格"是很实用的差异点。
编辑:7.0(26) 新增的 spatialEdit 才是真正把"重建"变成"可再创作"的一环(spatialEdit API)。核心是 GSEdit 类:
- 选择:
selectBy2DBox/selectBy3DBox/selectByIndex/selectBy2DMask,选中结果都追加到当前选区; - 变换与上色:
transform(matrix)、paint(color, mode),其中PaintMode有REPLACE/MULTIPLY/ADD三种混合模式; - 删除与撤销:
remove()、undo(); - 导出:
saveToPLY(uri),把编辑后的模型存回 PLY; - 还有一个我觉得很实用的
extract3DMainBody(pressPoint)——按一个点把 3D 主体抠出来,相当于给模型做"抠图"。
大场景怎么办? 用 TiledGSNode(26.0.0 起),它是专门为大规模 3DGS 场景设计的分块渲染对象。模型一大就上分块,别指望单个 GSNode 硬扛。
七、上线自检清单
- 确认过 ArkTS 侧没有重建接口,重建代码写在 C/C++ 层了吗?
-
HMS_SpatialRecon_IsSupport调了吗?不支持时的完整降级路径做了吗? - 芯片不在保证名单(Kirin 9020 / 9030S / 9030 / 9030 Pro 及以后)时,有没有对用户降低效果预期的提示?
- 输入图像是 1080×1440 吗?不是的话有没有做裁切/缩放?格式是 RGB 吗?
-
CreateSession的工作目录存在、且在应用内部文件目录下吗? - 每一次
StartSession之后都紧跟了SetRunningMode吗?切前后台有同步更新吗? - 订阅
COMMON_EVENT_THERMAL_LEVEL_CHANGED了吗?过热能自动暂停吗?有用户手动暂停/继续的开关吗? - 重建和保存 MP4 都做了全局串行吗?会不会出现两个会话同时跑?
-
DestroySession之前,确认重建/保存任务都已经结束了吗? -
loadPlugin(GSPlugin.PLUGIN_ID)在loadGSNode之前调了吗? - 大场景用了
TiledGSNode吗? - 真机上验证过重建耗时、发热与渲染帧率吗?(别在模拟器上验收——本 Kit 不支持模拟器)
参考与出处
本文涉及的事实性信息(API 名称、枚举取值、版本号、官方约束)来自以下官方文档,文中的结构、代码示例、决策流程与自检清单为本人整理编写:
- Spatial Recon Kit 简介
- 重建三维场景(C/C++)
- 管理 Spatial Recon Kit 会话
- 加载 3DGS 模型
- Spatial Recon Kit ArkTS API
- spatialRender(ArkTS API)
- spatialEdit(ArkTS API)
- Spatial Recon Kit C API
最后一句:这个能力最反直觉的地方在于——你以为难点是"算法",其实算法系统都封装好了;真正的难点是承认它有多"重":重到要限定芯片、重到要盯温度、重到同一时刻只能跑一个。想清楚这三件事,剩下的就是按状态机把接口串起来。
更多推荐


所有评论(0)