以前搞3D建模得用专业软件或者云端跑几小时,现在拿着HarmonyOS 7手机,拍一圈就能在端侧生成3D模型,今天就带大家亲手把这个流程跑通。

聊聊这玩意儿到底多香

兄弟们,不知道你们有没有这种经历:想给自家猫主子建个3D模型,结果不是要啃Blender几个月,就是得把照片传到云端等半天。说实话,门槛真不是一般的高。

但HarmonyOS 7(API 26)搞了个大事情——把3DGS(3D Gaussian Splatting,三维高斯溅射)端侧重建做成了系统级API。啥概念?就是你拿着手机拍一圈,本地就能生成3D模型,数据都不用上传,隐私安全拉满。

这玩意儿跟之前的NeRF比,最大的区别就是。NeRF训练一次几小时,渲染一帧好几秒;3DGS训练几分钟,渲染直接跑到30FPS以上。鸿蒙这套还专门用了NPU加速,中端手机几十秒就能出活。

不废话了,咱们直接上手干。


第一步:先把环境支棱起来

权限配置

打开你的module.json5,把该申请的都申请了:

"requestPermissions": [
  {
    "name": "ohos.permission.CAMERA",
    "reason": "拍物体照片做3D重建",
    "usedScene": {"abilities": ["EntryAbility"]}
  },
  {
    "name": "ohos.permission.READ_MEDIA_IMAGES",
    "reason": "读取图片用于重建"
  },
  {
    "name": "ohos.permission.WRITE_MEDIA_FILES",
    "reason": "保存生成的3D模型"
  }
]

重点来了:系统能力声明不能少,不然API调了直接报错:

"SystemCapability.Graphics.SpatialRecon",
"SystemCapability.Graphics.SpatialRender"

引入依赖

import { spatialRecon, spatialRender, spatialImage } from '@kit.ArkGraphics3D';
import { camera } from '@kit.CameraKit';
import { fileIo } from '@kit.CoreFileKit';

第二步:判断设备能不能跑

不是所有鸿蒙设备都支持3DGS重建,得先做个检测:

import { spatialImage } from '@kit.ArkGraphics3D';

function checkDeviceSupport(): boolean {
  const status = spatialImage.SpatialImageGenerator.isSupport();
  
  switch(status) {
    case spatialImage.SpatialImageStatus.STATUS_SUCCESS:
      console.log('✅ 设备支持,模型已就绪');
      return true;
    case spatialImage.SpatialImageStatus.STATUS_AIMODEL_NOT_EXIST:
      console.log('⚠️ 设备支持,但需要下载AI模型');
      downloadModel(); // 调用 prepareEnv 下载
      return true;
    case spatialImage.SpatialImageStatus.STATUS_NOT_SUPPORT:
      console.log('❌ 设备不支持3DGS重建');
      return false;
    default:
      return false;
  }
}

第三步:核心——图像采集 + 重建

配置重建参数

const reconConfig: spatialRecon.ReconstructionConfig = {
  mode: spatialRecon.ReconstructionMode.HIGH_QUALITY, // 高质量模式
  maxFrames: 120,           // 最多采集120帧
  enableDensification: true,// 开启稠密化,细节更丰富
  gaussianCount: 500000     // 高斯球数量上限,低端机调低到20万
};

创建会话并开始重建

这段代码是核心中的核心,建议直接抄走:

import { spatialRecon } from '@kit.ArkGraphics3D';

async function startReconstruction(
  imagePixelMaps: Array<image.PixelMap>,
  onProgress: (stage: string, percent: number) => void
): Promise<string> {
  
  // 1. 创建重建会话
  const session = await spatialRecon.createSession(reconConfig);
  
  // 2. 订阅进度回调
  session.on('progress', (stage: string, percent: number) => {
    console.log(`阶段: ${stage}, 进度: ${(percent * 100).toFixed(0)}%`);
    onProgress(stage, percent);
  });
  
  // 3. 逐帧喂入图像
  for (let img of imagePixelMaps) {
    await session.addFrame(img);
  }
  
  // 4. 执行重建,输出PLY模型文件
  const outputPath = '/data/storage/el2/base/haps/entry/files/model.ply';
  const result = await session.reconstruct(outputPath);
  
  console.log('🎉 重建完成!模型保存在:', outputPath);
  return outputPath;
}

注意on('progress')的回调里,percent是0到1之间的小数,显示百分比记得乘100。


第四步:渲染——把模型秀出来

用XComponent搭画布

import { sceneKit } from '@kit.SceneKit';

@Entry
@Component
struct ModelViewerPage {
  private sceneController: sceneKit.SceneController = new sceneKit.SceneController();
  private gaussianNode: sceneKit.GaussianSplattingNode | null = null;
  @State isLoading: boolean = true;
  
  build() {
    Stack() {
      XComponent({
        id: 'scene3d',
        type: XComponentType.SURFACE,
        controller: this.sceneController.getXComponentController()
      })
      .width('100%')
      .height('100%')
      .onLoad(() => {
        this.loadModel();
      })
      
      if (this.isLoading) {
        Column() {
          LoadingProgress()
            .width(60)
            .height(60)
          Text('模型加载中...')
            .fontSize(16)
            .fontColor('#FFFFFF')
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .backgroundColor('rgba(0,0,0,0.5)')
      }
    }
  }
  
  async loadModel() {
    const scene = this.sceneController.getScene();
    const rootNode = scene.getRootNode();
    
    // 加载3DGS模型
    this.gaussianNode = scene.createGaussianSplattingNode({
      uri: 'file:///data/storage/el2/base/haps/entry/files/model.ply'
    });
    
    if (this.gaussianNode) {
      rootNode.addChild(this.gaussianNode);
      this.isLoading = false;
    }
  }
}

第五步:进阶——模型编辑(能删能改)

鸿蒙7的Spatial Recon Kit还支持对生成的高斯球模型做编辑,比如框选删除多余物体:

import { spatialRecon } from '@kit.SpatialReconKit';

async function editGaussianModel(modelPath: string) {
  const gsModel = await spatialRecon.loadModel(modelPath);
  
  // 框选不想要的区域(比如桌面上多扫进去的茶杯)
  const boundingBox = {
    min: { x: -1.0, y: -0.5, z: -1.0 },
    max: { x: 1.0, y: 0.5, z: 1.0 }
  };
  
  // 删除框内的高斯球
  gsModel.deleteGaussiansInBox(boundingBox);
  
  // 导出编辑后的模型
  await gsModel.export('/data/storage/el2/base/haps/entry/files/model_edited.ply');
}

踩坑指南(血泪教训)

1. 拍视频有讲究

  • 别怼脸拍:物体占画面比例别超过70%
  • 转圈要平滑:最好间隔15度左右拍一张,一共拍20-30张
  • 光线要均匀:别半边亮半边暗,重建出来会崩

2. 模型下载失败

如果isSupport()返回STATUS_AIMODEL_NOT_EXIST,记得调用prepareEnv()下载模型文件,需要联网权限:

await generator.prepareEnv((progress: number) => {
  console.log(`模型下载进度: ${progress}%`);
});

3. 内存溢出

3DGS重建很吃内存,如果App闪退,可以降低gaussianCount或减少输入的图片数量。同一时间只能跑一个重建任务,别并发搞。

4. 渲染黑屏

检查两个地方:一是确认loadPlugin有没有调用,二是检查场景里有没有加光源。3DGS模型本身不带光照信息,场景环境光得配好。


能拿来干啥?

这套能力落地场景真不少,我列几个方向供参考:

  • 商品3D展示:电商App让用户拍一圈商品,直接生成3D预览,比图片直观多了
  • 文旅展陈:博物馆文物数字化,游客手机扫码就能看3D文物
  • 文化遗产保护:给古建筑建数字档案,数据不出设备,安全可控
  • 个人创作:普通人也能做3D建模师,门槛大幅降低

最后我要说

从拍视频到生成3D模型,HarmonyOS 7这套3DGS端侧重建API把流程压缩到了几分钟之内,而且全程在设备本地跑,数据不用上传。这在以前想都不敢想。

建议动手试一下,拿着手机拍个手办或者家里的盆栽,看看重建出来的效果有多惊艳。代码在真机上跑通了,欢迎来评论区交作业!

如果这篇文章帮到了你,点赞收藏不迷路,遇到问题评论区见,看到就回。(抖音: 黑马程序员就业指南)

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐