
适用版本:HarmonyOS 7.0.0 (API 26) | DevEco Studio NEXT | 旗舰芯片真机
本文基于真实项目开发经验,完整记录从权限配置、图像采集、C++层重建、ArkTS渲染到性能优化的全流程,附可运行的工程代码与踩坑总结。
一、为什么端侧3D重建是空间计算的"入场券"
2026年的今天,我们谈论空间计算时已经不再停留在概念层面。大众点评用3DGS做实景探店、京东用它做商品3D展示、文旅App里你可以"走"进一座古建筑------这些体验的底层,都是同一项技术:3D Gaussian Splatting(3DGS,3D高斯溅射)。
但真正改变游戏规则的,不是3DGS本身,而是它从云端走到了端侧。
HarmonyOS 7把3DGS重建能力下放到手机NPU,意味着:
- 隐私安全:原始照片不用上传服务器,全部计算在本地完成
- 低延迟:室内、户外无网环境照样建模
- 普惠化:不再需要专业扫描设备,一部旗舰手机就能搞定
我们团队最近做了一款空间重建Demo App,从立项到跑通完整链路踩了不少坑。这篇文章把完整过程写下来,希望能帮到同样在探索空间计算的开发者。
二、先搞清楚:重建在C层,ArkTS只负责"看"和"改"
这是最容易踩的第一个坑。
网上很多文章说"三行ArkTS代码实现3D重建",但如果你去翻官方API文档,会发现 Spatial Recon Kit 的 ArkTS 接口只有两个模块:
| 模块 | 职责 | 起始版本 |
|---|---|---|
spatialRender |
3DGS模型的加载与渲染,含滤镜效果 | 6.0.1 (API 21) |
spatialEdit |
3DGS模型的选择、上色、删除、导出 | 7.0 (API 26) |
注意,这里面没有"重建"模块。
重建管线是纯 C/C++(NDK)的,官方指南标题就叫「重建三维场景(C/C++)」。整个架构是分层的:
┌─────────────────────────────────────────────┐
│ ArkTS 层(UI / 交互 / 渲染) │
│ ┌─────────────┐ ┌──────────────────────┐ │
│ │ spatialRender│ │ spatialEdit │ │
│ │ 加载+渲染 │ │ 选择/上色/导出 │ │
│ └──────┬──────┘ └──────────┬───────────┘ │
│ └─────────┬──────────┘ │
│ │ ArkGraphics 3D │
└───────────────────┼───────────────────────────┘
│ NAPI 桥接
┌───────────────────┼───────────────────────────┐
│ C/C++ 层(重建核心) │
│ ┌────────────────────────────────────────┐ │
│ │ HMS_SpatialRecon_* 系列 C API │ │
│ │ 检测→建会话→喂帧→重建→查进度→保存 │ │
│ └────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
这个认知非常关键------你不可能只用ArkTS完成端侧重建。但凡看到"纯ArkTS生成3D模型"的示例,先去官方API总览核对模块名。
三、第一关:设备门槛,三重限制
写第一行代码之前,先确认你的设备"配不配跑"。官方给了三重限制:
| 限制维度 | 具体要求 |
|---|---|
| 地域 | 仅中国境内(不含港澳台) |
| 设备形态 | Phone、Tablet、PC/2in1、TV |
| 芯片 | 仅保证旗舰芯片(Kirin 9020/9030S/9030/9030 Pro 及以后) |
| 模拟器 | 不支持 |
芯片这一条官方说得很克制但很明确:由于空间重建对性能开销较大,当前仅保证旗舰芯片上的用户体验。其他芯片即使接口返回"支持",也无法保证重建耗时和质量。
翻译一下:"支持"是三态,不是布尔值。
所以能力检测要这样写:
cpp
// 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 isFlagshipChip() ? ReconGate::READY : ReconGate::RISKY;
}
对于 RISKY 级别的设备,我们的策略是:允许使用但给出提示,同时默认降低高斯点上限(从50万降到20万),优先保证速度和稳定性。
四、工程搭建:从module.json5到CMakeLists
4.1 权限与系统能力声明
entry/src/main/module.json5 中需要声明相机、媒体读写权限和两项关键系统能力:
json
{
"requestPermissions": [
{
"name": "ohos.permission.CAMERA",
"reason": "用于采集物体图像进行3D重建",
"usedScene": { "abilities": ["EntryAbility"] }
},
{
"name": "ohos.permission.READ_MEDIA_IMAGES",
"reason": "读取图片用于3D重建"
},
{
"name": "ohos.permission.WRITE_MEDIA_FILES",
"reason": "保存生成的3D模型文件"
}
],
"abilities": [
{
"name": "EntryAbility",
"systemCapabilities": [
"SystemCapability.Graphics.SpatialRecon",
"SystemCapability.Graphics.SpatialRender"
]
}
]
}
⚠️ 重点提醒 :
SpatialRecon和SpatialRender两大系统能力缺一不可。少声明任何一个,API调用都会直接返回失败,而且报错信息不一定明显------我在这里卡了两个小时。
4.2 Native工程配置
entry/build-profile.json5 中配置CMake构建:
json
{
"apiType": "stageMode",
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "-v -DCMAKE_VERBOSE_MAKEFILE=ON",
"cppFlags": ""
}
}
}
CMakeLists.txt 核心配置:
cmake
cmake_minimum_required(VERSION 3.4.1)
project(spatialrecon_napi)
set(CMAKE_CXX_STANDARD 17)
add_library(spatialrecon_napi SHARED
spatial_recon_native.cpp
spatial_recon_napi.cpp
)
target_link_libraries(spatialrecon_napi
ace_napi
hilog_ndk
spatial_recon_ndk # 空间重建 NDK 库
)
五、核心实现:C++层重建管线
这是整个3DGS能力的"发动机"。我把重建过程封装成了一个 ReconstructionManager 类,管理从创建会话到保存结果的完整生命周期。
5.1 完整重建流程
创建会话 → 推送帧数据 → 启动重建 → 监听进度 → 保存结果 → 销毁会话
↓ ↓ ↓ ↓ ↓
CreateSession PushFrame StartRecon Progress SaveResult
5.2 创建会话
cpp
bool ReconstructionManager::createSession(SpatialReconMode mode,
SpatialReconQuality quality,
int maxGaussianCount) {
HMS_SpatialReconConfig config;
config.mode = mode; // 物体/空间模式
config.quality = quality; // 质量等级
config.maxGaussianCount = maxGaussianCount; // 高斯点上限
config.enableDensification = true; // 开启稠密化
HMS_SpatialReconStatus ret =
HMS_SpatialRecon_CreateSession(&config, &session_);
return ret == SPATIAL_RECON_STATUS_SUCCESS;
}
参数选择建议:
| 场景 | 模式 | 质量 | 高斯点上限 | 预计耗时 |
|---|---|---|---|---|
| 小物件(杯子、手办) | OBJECT | HIGH | 50万 | 1~3分钟 |
| 中等物体(椅子、雕塑) | OBJECT | BALANCE | 30万 | 2~4分钟 |
| 室内场景(房间、办公室) | SPACE | BALANCE | 100万 | 5~10分钟 |
5.3 推送帧数据
从相机获取的NV21格式帧,直接推送给重建引擎:
cpp
bool ReconstructionManager::pushFrame(const uint8_t* imageData,
int width, int height,
int64_t timestamp) {
HMS_SpatialReconFrame frame;
frame.data = imageData;
frame.width = width;
frame.height = height;
frame.timestamp = timestamp;
frame.format = SPATIAL_RECON_IMAGE_FORMAT_NV21;
HMS_SpatialReconStatus ret =
HMS_SpatialRecon_PushFrame(session_, &frame);
if (ret == SPATIAL_RECON_STATUS_FRAME_DROPPED) {
// 帧被丢弃是正常的------系统会自动选取关键帧
return true;
}
return ret == SPATIAL_RECON_STATUS_SUCCESS;
}
💡 关键帧无需手动筛选 。调用
PushFrame时系统会自动选取关键帧,开发者不需要自己做去重或采样。这是鸿蒙这套方案非常省心的一点。
5.4 启动重建与进度监听
帧采集完毕后,调用 StartReconstruction 启动高斯优化:
cpp
bool ReconstructionManager::startReconstruction() {
HMS_SpatialReconStatus ret = HMS_SpatialRecon_StartReconstruction(
session_,
// 进度回调
[](float progress, void* userData) {
auto* mgr = static_cast<ReconstructionManager*>(userData);
if (mgr->progressCallback_) {
mgr->progressCallback_(progress, "reconstructing");
}
},
// 完成回调
[](HMS_SpatialReconStatus status, void* userData) {
auto* mgr = static_cast<ReconstructionManager*>(userData);
mgr->setState(status == SPATIAL_RECON_STATUS_SUCCESS
? ReconState::COMPLETED
: ReconState::FAILED);
},
this
);
return ret == SPATIAL_RECON_STATUS_SUCCESS;
}
5.5 保存结果
支持三种输出格式:PLY、MP4、GLB。
cpp
bool ReconstructionManager::saveResult(const std::string& outputPath,
SpatialReconOutputFormat format) {
return HMS_SpatialRecon_SaveResult(
session_,
outputPath.c_str(),
format,
progressCallback,
completionCallback,
this
) == SPATIAL_RECON_STATUS_SUCCESS;
}
格式选择建议:
- PLY:通用格式,文件大但兼容性好,适合导出到PC端进一步处理
- MP4:鸿蒙特有格式,文件最小(有压缩),加载最快,推荐移动端使用
- GLB:glTF格式,适合需要和现有3D管线集成的场景
六、ArkTS层:渲染与交互
重建完成后,模型文件就生成了。接下来要在UI层展示它------这部分完全用ArkTS就能搞定。
6.1 加载3DGS模型
SpatialReconKit 是 ArkGraphics 3D 模块的扩展,加载3DGS模型需要先加载插件:
typescript
import { spatialRender } from '@kit.SpatialReconKit';
import { Scene, RenderContext } from '@kit.ArkGraphics3D';
async function loadGSModel(xComponentId: string, modelUri: string) {
// 1. 获取渲染上下文
const renderContext = Scene.getDefaultRenderContext();
if (!renderContext) return null;
// 2. 加载 GSPlugin 插件
renderContext.loadPlugin(spatialRender.GSPlugin.PLUGIN_ID);
// 3. 创建场景
const scene = await Scene.create(xComponentId);
// 4. 加载 3DGS 模型
const gsNode = await spatialRender.GSPlugin.loadGSNode(
scene,
{ uri: modelUri, offset: 0 },
scene.root
);
return { scene, gsNode };
}
6.2 支持的模型格式
| 格式 | 文件大小 | 加载速度 | 编辑能力 | 适用场景 |
|---|---|---|---|---|
| MP4 | ⭐⭐⭐ 最小 | ⭐⭐⭐ 最快 | 一般 | 移动端展示 |
| PLY | ⭐ 较大 | ⭐⭐ 中等 | ⭐⭐⭐ 最强 | 跨平台、二次编辑 |
| GLB | ⭐⭐ 中等 | ⭐⭐ 中等 | ⭐⭐ 中等 | 3D管线集成 |
6.3 相机与交互
加载模型后,需要设置相机和用户交互。3DGS模型的浏览体验和传统3D模型类似,支持旋转、缩放、平移:
typescript
// 设置透视相机
const cameraNode = scene.root.createChild('main_camera');
const camera = cameraNode.createComponent(CameraType.CAMERA_TYPE_PERSPECTIVE);
camera.setIsMain(true);
camera.setFOV(60); // 视场角
camera.setNear(0.1); // 近裁剪面
camera.setFar(1000); // 远裁剪面
cameraNode.translate(0, 0, 5); // 相机初始位置
6.4 风格化滤镜
GSPlugin 支持对3DGS渲染结果做风格化处理,比如卡通、素描、怀旧等效果。这是一个很容易出亮点的功能,在文旅、教育类应用中特别受欢迎。
七、沉浸光感:让UI也"空间化"
空间计算不只是3D模型的事,UI层面的"空间感"同样重要。HarmonyOS 7的HDS(HarmonyOS Design System)组件提供了沉浸光感材质能力,让导航栏、底部页签直接获得系统级的通透质感。
7.1 基础用法
typescript
import { HdsNavigation, HdsTabs, hdsMaterial } from '@kit.UIDesignKit';
// 底部悬浮页签 + 沉浸光感
HdsTabs({ controller: this.tabsController }) {
// ... TabContent
}
.barFloatingStyle({
barBottomMargin: 28,
// 开启沉浸光感:自适应模式,系统根据设备性能自动调整
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
}
})
7.2 两种模式怎么选
- 自适应模式(推荐默认) :
ADAPTIVE + ADAPTIVE,系统自动匹配设备能力。效果稳定,不会因为性能不够导致掉帧 - 自定义模式 :先调用
hdsMaterial.getSystemMaterialTypes()查询设备支持情况,如果返回包含IMMERSIVE,就可以使用EXQUISITE(精美/高耗能)或GENTLE(轻柔)档位
⚠️ 不要在未查询设备能力的情况下直接强开
EXQUISITE,低端机会出现明显掉帧和发热。
7.3 自定义蒙版颜色
默认的渐变蒙版是系统预设的,如果需要匹配应用的品牌色,可以自定义:
typescript
.barFloatingStyle({
gradientMask: {
maskColor: '#66007AFF', // 蒙版颜色+透明度
maskHeight: 166 // 渐变高度
},
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
}
})
在我们的3DGS查看器中,我用沉浸光感做了上下两层控制栏------顶部导航栏和底部滤镜选择栏,都是半透明毛玻璃效果。模型在后面旋转,控制栏浮在上面,层次感一下子就出来了。
八、性能优化:让端侧重建"跑得动、跑得稳"
3DGS重建对算力的要求很高,性能优化是能否落地的关键。以下是我们实践中验证有效的优化手段:
8.1 重建前:合理设置参数
| 优化项 | 做法 | 效果 |
|---|---|---|
| 高斯点上限 | 根据设备性能分档设置(旗舰50万/中端20万) | 减少内存占用与计算量 |
| 稠密化开关 | 物体细节要求不高时关闭 | 重建速度提升约30% |
| 质量模式 | 快速预览用 FAST,最终生成用 HIGH |
平衡效率与质量 |
8.2 重建中:精细化功耗管理
订阅温控事件,设备过热时自动暂停重建:
typescript
// 订阅电池温控事件
commonEventManager.subscribe(
'usual.event.THERMAL_LEVEL_CHANGED',
(err, data) => {
const level = data?.parameters?.level ?? 0;
if (level >= 4) {
// 温度过高,暂停重建
reconManager.pause();
} else if (level <= 2) {
// 温度回落,恢复重建
reconManager.resume();
}
}
);
前后台模式切换,应用切后台时自动降为低功耗模式:
cpp
// 前台高性能
HMS_SpatialRecon_SetRunningMode(session,
SPATIAL_RECON_RUNNING_MODE_FOREGROUND);
// 后台低功耗
HMS_SpatialRecon_SetRunningMode(session,
SPATIAL_RECON_RUNNING_MODE_BACKGROUND);
8.3 渲染时:大场景用Tiled分块加载
对于空间场景(100万+高斯点),一次性加载全量数据会导致:
- 加载时间长(5~10秒)
- 内存占用高(500MB+)
- 低端设备直接OOM
TiledGS分块方案把模型切成一块块"瓦片",视野内的才加载,能显著降低内存峰值。HarmonyOS 7的MP4格式原生支持分块LOD。
8.4 一个真实的优化案例
我们做过一个室内客厅场景的对比测试:
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 重建耗时 | 8分30秒 | 5分10秒 | ↓39% |
| 内存峰值 | 720MB | 410MB | ↓43% |
| 加载耗时 | 6.8s | 1.2s | ↓82% |
| 平均帧率 | 28fps | 55fps | ↑96% |
主要优化手段:高斯点从150万降到80万 + Tiled分块加载 + 关闭非必要的稠密化迭代。
九、工程结构总览
我们的Demo App工程结构如下,供参考:
entry/src/main/
├── ets/
│ ├── entryability/
│ │ └── EntryAbility.ets # 入口Ability
│ ├── pages/
│ │ ├── IndexPage.ets # 首页(功能入口)
│ │ ├── CapturePage.ets # 图像采集页
│ │ ├── ViewerPage.ets # 模型查看器
│ │ ├── GalleryPage.ets # 模型库
│ │ └── SettingsPage.ets # 设置页
│ ├── service/
│ │ └── SpatialReconService.ets # 重建服务封装
│ └── utils/
│ └── DeviceCapability.ets # 设备能力检测
├── cpp/
│ ├── spatial_recon_native.h # C++重建管理器头文件
│ ├── spatial_recon_native.cpp # C++重建管理器实现
│ ├── spatial_recon_napi.cpp # NAPI桥接层
│ └── CMakeLists.txt # CMake构建配置
└── resources/
├── base/media/ # 图标资源
└── rawfile/ # 3D模型示例文件
十、踩坑总结:这些坑我替你踩过了
10.1 "为什么接口调用一直失败?"
→ 先查 module.json5 里的 systemCapabilities 是否声明了 SpatialRecon 和 SpatialRender。漏声明会导致Kit加载失败,但报错信息往往不够明确。
10.2 "为什么模拟器上跑不起来?"
→ 3DGS不支持模拟器,必须用真机调试。而且需要旗舰芯片设备才能获得流畅体验。
10.3 "重建过程中应用切后台就崩了"
→ 需要在Ability的onBackground回调中调用 SetRunningMode(BACKGROUND),让系统降低算力分配。直接切后台可能因资源抢占被系统杀掉。
10.4 "保存的PLY文件特别大,怎么减小?"
→ 推荐用MP4格式,鸿蒙对MP4做了专门的压缩优化,同等质量下体积只有PLY的1/3到1/5。如果必须用PLY,可以降低高斯点数量。
10.5 "同一时刻能有多个重建会话吗?"
→ 不行。同一时刻仅支持一个session进行重建或保存MP4,避免并发操作。如果需要处理多个任务,用队列串行执行。
10.6 "拍摄时有什么技巧?"
→ 三个关键:
- 重叠率 > 60%:相邻帧之间要有足够重叠,系统才能匹配特征点
- 匀速移动:不要忽快忽慢,不要突然转向
- 光线充足:逆光、暗光环境下重建质量会明显下降
十一、未来展望:端侧3D重建的"平民化"时代
做这个项目的过程中,我最深的感受是:3D建模正在从专业工作室走向每个人的口袋。
五年前,给一个物体做3D模型需要专业扫描仪 + 建模师 + 数天工时。今天,一个普通消费者掏出手机绕一圈,几分钟就能得到一个可以360°旋转查看的高质量3D模型。
HarmonyOS 7把3DGS做成系统级能力,大大降低了开发门槛------你不需要懂CUDA、不需要搭服务器、不需要研究深度学习模型,只要调用几个API,就能在自己的应用里加入空间重建功能。
接下来可以想象的空间很大:
- 电商:用户拍一下自己的房间,直接试摆家具
- 教育:历史文物、生物标本的三维数字化
- 社交:把真实空间变成3D模型分享给朋友
- 房产:二手房、租房的3D实景带看
技术的价值,最终要落到体验上。当3D重建像拍照一样简单时,空间计算才真正走进了普通人的生活。而鸿蒙在做的,就是把这个门槛再往下压一压。
参考资料
- Spatial Recon Kit 简介 --- HarmonyOS 开发者官网
- 加载3DGS模型 --- HarmonyOS 开发者官网
- HDS 组件沉浸光感材质 --- 华为开发者联盟
- ArkGraphics 3D 简介 --- HarmonyOS 开发者官网
- 应用审核指南 --- 华为应用市场