HarmonyOS端侧3DGS重建:从多视角采集到实时渲染实战
去年在做一个家居电商项目的3D商品展示模块时,团队内部争论最多的一个问题不是“渲染效果够不够炫”,而是“能不能让用户在手机上直接拍出商品模型”。当时的主流方案清一色是端云协同:手机拍影像,回传服务器,GPU农场跑3DGS(3D Gaussian Splatting)重建,少则半小时,多则隔天才能拿到模型。这套流程稳定,但用户在等待、数据回传体积、隐私合规上都让人头疼。直到HarmonyOS 7的设备和配套能力出来,我们才把端侧重建从“试试看”列进了正式技术方案。这篇就把整条链路拆开讲清楚:从多视角采集、端侧位姿估计、高斯场生成,到商品展示端的渲染集成与性能调优,踩过的坑和实测数据一并给出。
这篇文章适合两类人阅读:一类是准备在移动端做3D重建或者空间计算应用的技术负责人,想评估3DGS端侧方案的可行性与边界;另一类是已经在做NeRF或者传统Mesh重建,想了解3DGS在HarmonyOS上如何选型、如何落地到真实产品里的开发者。我会把原理揉到工程细节里讲,尽量不堆公式,但关键参数和实现思路一个不少。
1. 端侧3DGS的边界:为什么这个需求值得在HarmonyOS 7上做
1.1 3DGS为什么天然适合商品展示
先聊一个可能反直觉的结论:对商品展示来说,3DGS比传统Mesh重建更适合,也比NeRF更适合移动端落地。
传统Mesh重建(比如用Kinect或者手机深度相机扫描)的一个老大难问题是材质边界不干净。商品里最常见的高光、金属拉丝、半透明玻璃、绒毛表面,在Mesh重建里分别对应着反光噪点全部乱掉、表面拉花、厚度不可控、细节磨平。就算勉强建出来,后续还要花大量时间做减面、修UV、烘焙贴图,一个小商品模型外包建模需要两三天,根本赶不上电商上新速度。
NeRF看起来效果好一些,但它的核心问题在于渲染速度。一个NeRF推理现场要做几百次采样才能算一个像素的颜色,手机GPU跑起来基本就是幻灯片。而且NeRF训练出来的模型是网络权重,不方便局部编辑、裁剪和做LOD。
3DGS走的完全是另一条路:用几百万个带颜色、带透明度的3D高斯椭球去拟合场景。因为它本质上还是点云+光栅化的思路,渲染效率远高于NeRF,在手机GPU上做到实时帧率是可行的。更重要的是,商品表面那些让Mesh重建头疼的材质特征,3DGS天然能保持住。我们实测过一个不锈钢保温杯,3DGS重建出来的杯身高光区域是连续的,而Mesh重建版本在同样区域已经碎成了十几片。对商品展示这种“用户要看得清细节”的场景,3DGS的胜出几乎是碾压级的。
1.2 “端侧重建”的正确打开方式:不是把训练改小,而是换管线
很多人听到“端侧重建”,下意识觉得就是把PC上的3DGS训练代码搬到手机GPU上跑。这个理解不能说全错,但和工程现实差距很大。
原始3DGS训练通常要跑两三万次迭代,依赖COLMAP提前算好相机位姿,中间还有自适应稠密化、网格细分这些步骤。一台旗舰手机GPU算力再强,跟桌面GPU比也差一个数量级,内存带宽更是差得多。所以“把同样的训练管线缩小”在端侧是走不通的。
端侧重建的正确打开方式,是把重建管线本身换掉。完整3DGS重建可以拆成三件事:影像采集、相机位姿估计、高斯场生成与优化。其中位姿估计在端侧不需要COLMAP,可以直接用系统自带的AR视觉追踪能力(HarmonyOS的AR Engine)实时输出相机姿态;高斯场生成则从“逐场景长迭代优化”改成“前馈式预测+轻量微调”。
前馈式预测是最近两年3DGS领域一个重要方向,代表工作包括Splatt3R、InstantSplat这些,核心思想是训练一个神经网络,输入几张照片直接预测出高斯场的初始参数,不再需要为每个新场景从零训练。这样端侧只需要做一次前向推理加少量微调,重建时间从小时级压缩到分钟级。我们在HarmonyOS 7设备上落地的就是这条管线:AR Engine输出位姿,端侧推理网络生成初始高斯场,再用商品图集做几百步轻量优化清细节。
1.3 HarmonyOS 7设备能力与硬性约束盘账
在动手写代码之前,建议先盘点手里的设备到底有哪些牌可以打。我们主要以搭载当代旗舰芯片的HarmonyOS 7设备为基准,实际项目里也跑过中端设备,感受差异很大。
| 能力项 | 旗舰设备 | 中端设备 | 对重建的影响 |
|---|---|---|---|
| 内存 | 12GB-16GB | 8GB | 决定高斯场能加载到多少体量 |
| GPU | 当代旗舰GPU | 中端GPU | 决定渲染帧率和分辨率 |
| NPU算力 | 较高,支持int8量化加速 | 受限 | 决定前馈推理能不能实时 |
| 相机能力 | 多摄+防抖+高帧率 | 基本够用 | 决定采集清晰度和覆盖 |
| AR Engine | 支持 | 支持但追踪更易漂移 | 决定位姿质量 |
硬性约束这块,我建议项目启动时就写进技术选型文档:端侧单次重建的高斯数量控制在80万以内,训练/微调迭代次数控制在500步以内,推理模型用INT8量化。超过这个量级就老老实实走端云协同,不要硬扛。我们早期在端侧硬跑120万高斯的微调,设备发热直接触发降频,重建时间反而翻了倍,得不偿失。
2. 从影像序列到高斯场:重建管线在端侧的裁剪
2.1 采集环节:不要拍视频,要拍“覆盖”
3DGS重建对输入影像的要求和拍短视频完全不同。短视频追求运镜流畅,重建追求的是“每个表面都被充足视角覆盖”。我们第一版采集功能让用户对着商品拍一段10秒环绕视频,结果重建出来的商品背面糊成一团,侧面还有肉眼可见的断层。原因非常典型:用户在手持环绕时,相机的俯仰角度变化太小,商品底部和顶部基本没拍到。
后来我们把采集流程改成了引导式拍摄:围绕商品走一圈,每走一段停下拍一张;然后相机抬高俯拍一圈;最后放低仰拍一圈。每一圈大约20-30张照片,总计60-90张。这个数量对端侧重建来说算比较充裕,同时采集时间控制在两分钟左右,用户不会烦。
采集阶段还有个容易忽略但影响巨大的细节:相机参数必须锁定。我们遇到过一次重建出来的颜色在转台旋转时一闪一闪,排查半天发现是自动白平衡在不断调整。商品在不同角度下反射的高光不同,如果白平衡也跟着变,3DGS训练时会把色温变化当成材质变化学进去。正确做法是把ISO、快门速度、白平衡全部固定,最好连对焦也锁定。
如果做转台方案,建议用慢速匀速旋转加固定机位,商品放在带标记的转盘上。标记点可以辅助位姿校正,转台转速控制在每圈20秒以上,不然运动模糊会毁掉细节。
2.2 相机位姿:不用COLMAP,用AR Engine的追踪结果
桌面端3DGS流程的第一步是COLMAP:对着图片序列特征提取、特征匹配、增量式重建,输出每张图的相机位姿和稀疏点云。COLMAP在PC上跑一组60张图大概要几分钟到十几分钟,但在手机端跑COLMAP属于自讨苦吃——内存占用大、计算时间长,而且很多商品表面纹理太弱,特征点数量根本不够,重建直接失败。
HarmonyOS端更合理的位姿来源是AR Engine。它的视觉追踪能力持续输出相机在空间中的6DoF位姿,拿到位姿之后的工作就变成了“把拍摄时刻对应到追踪轨迹上”。具体实现时,我们每拍一张照片,同时从AR Engine读取当时的位姿矩阵,和时间戳一起写入元数据,后面喂给重建网络时直接就能做坐标对齐。
AR Engine位姿的坑在于漂移,尤其是环绕拍摄这种大范围转向场景。AR追踪在快速转动或纹理重复环境下会累积漂移,位姿偏了重建就重影。我们的对策是双保险:一方面引导用户转慢一点,每张照片之间位姿变化别超过15度;另一方面存完位姿后跑一次重投影误差过滤,把误差超过阈值的照片剔除。通常60-90张照片里会剔掉5-8张,剩下的用来重建,质量反而更稳。
2.3 高斯场生成的端侧剪枝版
重建网络在端侧的输出是高斯场初始参数,这个参数可以直接渲染,但直接渲染往往有明显模糊和浮点噪声。原因在于前馈网络只看到有限视角,没见过的表面只能靠猜测。所以端侧重建管线保留了最后的轻量微调环节。
微调的目标不是从零学,而是让高斯参数适应当前商品的具体影像。我们用TensorFlow Lite / MindSpore Lite在端侧运行时做这个优化,损失函数沿用原始3DGS的L1色彩损失加D-SSIM项。考虑到端侧算力,图像分辨率缩到原本的四分之一,迭代次数从原始3DGS的两三万步砍到三百步左右。大约三分钟时间完成微调。实测数据显示,三百步微调相比零微调,PSNR能提升2-3dB,视觉效果从“能看出轮廓”变成“能看清logo”,对商品展示来说已经是可商用水平。
| 参数 | 桌面完整训练 | 端侧微调 |
|---|---|---|
| 输入分辨率 | 原生分辨率 | 1/4分辨率 |
| 迭代次数 | 20000-30000 | 300 |
| 高斯数量 | 150万-300万 | 50万-80万 |
| 单场景耗时 | 30分钟-2小时 | 3-5分钟 |
| 输出PSNR | 28-32dB | 24-27dB |
| 输出模型体积 | 100MB-300MB | 15MB-30MB |
这个表格看起来牺牲很大,但放在商品展示场景里是划算的:用户端生成预览模型足够放到详情页展示,而在办公室PC上用完整流程精修的版本仍然可以输出高质量模型供官方使用。两条管线共用同一套采集和位姿数据,只是微调深度不同。
3. 移动端渲染集成:从磁盘到屏幕的全链路设计
3.1 渲染器选型:自研Vulkan渲染器 vs 移植现有库
端侧重建只是手段,最终用户看到的是渲染效果。在HarmonyOS 7里做3DGS渲染,主流有两条路:移植桌面端的开源渲染器(比如图形学社区常用的gsplat、libgs这类C++库),或者基于Vulkan自研一个精简渲染器。
移植开源库的好处是算法正确性好,社区踩过的坑都替你踩过了;坏处是HarmonyOS的图形栈和Linux/Windows差异不小,尤其是窗口系统对接、EGL上下文创建、Shader编译链路这些地方,几乎没有现成binding,需要自己写胶水层。我们一开始试图移植libgs,光是把它的Vulkan后端从依赖GLFW改成对接XComponent的Native窗口,就花了一周。
自研渲染器的优点是可以按商品展示场景裁剪:不需要加载超大场景、不需要做离线烘焙、不需要复杂的材质系统,只需要把高斯场快速画出来。我们把渲染器拆成四个模块:模型解析器、裁剪器、排序器、光栅化器,加起来核心代码不到两千行。对移动端来说,自研反而更好控制性能。
3.2 端侧模型前后处理:压缩、量化、LOD
原始3DGS模型每个高斯原语包含位置(3个float)、缩放(3个float)、旋转四元数(4个float)、不透明度(1个float)和一组球谐系数(默认三阶球谐48个float)。如果原样保存,100万高斯大概要200MB以上,这对移动端是不可接受的,加载就要好几秒。
我们在导出端侧模型时做了四层压缩。第一层是球谐截断,商品展示场景主要看静态外观,把球谐从三阶截到一阶,只保留漫反射颜色和轻微视角变化,视觉差异很小,但每个高斯原语的数据量从约60字节降到约22字节。第二层是量化,位置用半精度float16,旋转和缩放也可以用半精度,透明度和颜色用int8归一化。第三层是剪枝,把不透明度接近0的高斯原语直接删掉,这一步通常能砍掉10%-20%的冗余原语。第四层是熵编码/Zstd压缩存盘,运行时解压到内存,冷启动加载时间可以控制在200毫秒以内。
端侧渲染时还应该做LOD。我们实现了按距离和屏幕覆盖率动态调整渲染分辨率:商品被放大细看时用全分辨率,缩小或者旋转快速浏览时渲染分辨率降到70%,用户基本感知不到区别,但GPU负载能降不少。
3.3 Vulkan渲染管线关键点:高斯投影、深度排序、Alpha混合
3DGS渲染的本质是“把一堆3D高斯椭球投影到屏幕上的2D椭圆,然后按深度排序做Alpha混合”。听起来简单,实现起来有几个关键点需要处理到位。
第一个关键点是高斯投影。每个3D高斯在相机坐标系下的协方差矩阵需要经过投影变换变成2D协方差,然后生成一个屏幕空间的椭圆足迹。实际工程里不会在CPU上做这件事,而是在GPU上每个高斯原语起一个线程并行算。我们用compute shader完成投影,输出每个高斯的中心、椭圆参数、颜色、深度和屏幕包围盒,供后续排序使用。
第二个关键点是深度排序。Alpha混合必须按从远到近的顺序进行,不排序画面就会闪烁。桌面端gsplat用GPU的radix sort做全量键值排序,手机GPU上这个操作开销非常大。我们做了一个优化:先按每个高斯所在的屏幕瓦片(tile)分桶,桶内再做radix sort。排序量从百万级降到十万级,带宽压力大幅下降。
第三个关键点是混合。Vulkan里做Alpha混合最直接的方式是逐图元Blend,但高斯原语数量太大,逐图元提交的overhead高到没法看。我们的方案是原子计数加动态数组:每个像素位置上先清空一个穿针引线的Buffer,所有高斯原语把自身索引推进到这个Buffer里,然后第二个pass再从后往前读取并做Blend。这套方案在Mali GPU上实测比逐图元方式快3倍以上。
// 片段着色器核心:计算2D高斯分布贡献
float computeGaussianWeight(vec2 point, vec2 center, mat2 cov2D) {
vec2 delta = point - center;
float det = cov2D[0][0] * cov2D[1][1] - cov2D[0][1] * cov2D[1][0];
float invDet = 1.0 / max(det, 1e-6);
mat2 invCov = mat2(
cov2D[1][1] * invDet, -cov2D[0][1] * invDet,
-cov2D[1][0] * invDet, cov2D[0][0] * invDet
);
return exp(-0.5 * dot(delta, invCov * delta));
}
这段代码就是3DGS片段着色器的核心权重计算,其余部分就是把高斯颜色乘上权重再叠加到帧缓冲上。理解了这段代码,渲染器的主干逻辑就基本清楚了。
3.4 XComponent + ArkUI桥接:Native渲染窗口接入
HarmonyOS里做高性能图形渲染的通用姿势是XComponent。XComponent提供一个Native窗口句柄,应用可以把Vulkan或者OpenGL ES的渲染面绑上去,同时外侧用ArkUI搭交互UI。
我们踩过最深的坑是XComponent的窗口生命周期。ArkUI页面销毁时,XComponent的Surface可能先于渲染线程销毁,如果渲染线程还在等下一帧的swapchain就绪,直接崩溃。解决方式是在页面onPageHide和onPageDestroy回调里先暂停渲染循环,再等GL/Vulkan队列空闲,最后才销毁Surface。顺序不能反,反了大概率闪退。
事件绑定上,XComponent原生渲染交互有两种做法:一种是在ArkUI侧用 Gesture 识别手势,把旋转量、缩放宽高通过NAPI塞给Native层;另一种是在XComponent内部直接接InputEvent。我们最终选了前一种。原因很简单:ArkUI的Gesture系统成熟稳定,捏合、旋转、惯性滑动都现成,Native侧只管吃数据就行,省去大量手势判断代码。
4. 商品展示场景的交互设计与性能调优
4.1 商品交互:旋转、缩放、AR摆放三个核心动作
商品展示场景的交互设计核心是降低用户理解成本。我们没有做复杂的轨道控制器,只保留三个动作:单指拖动旋转商品,双指捏合缩放,点击AR按钮把商品放到真实环境里预览尺寸。
旋转的实现要特别注意物体坐标系和惯性。用户手指滑动结束后的惯性效果是否丝滑,直接决定质感好坏。我们的做法是在Native渲染层维护一个角速度变量,手指离开后每帧按指数衰减更新旋转角度,衰减系数调成0.92左右,手感比较接近真实物理转台。这个惯性效果千万别放在ArkUI的动画系统里做,每一帧从UI侧跨进程推旋转矩阵给Native侧,延迟根本压不下来,手感会发飘。
AR摆放入口是本项目里转化率提升最明显的一个功能。点击后,系统把商品的高斯场模型渲染到AR Engine识别的平面上,用户可以看到一个“悬浮在桌面上的真实商品”,可以绕着走一圈看背面。这个功能对选品决策极有帮助,我们灰度测试时,使用AR摆放的用户最终下单率比只看静态详情页的用户高了差不多两成。
4.2 性能预算与实测数据
性能这块直接上我们在一台旗舰HarmonyOS 7手机上跑出的数据。渲染分辨率按屏幕真实分辨率,测试场景是一个单件商品模型,背景固定。
| 高斯数量 | 渲染帧率 | 单帧耗时 | 内存占用 | 备注 |
|---|---|---|---|---|
| 30万 | 120fps | 8ms | 1.1GB | 极简模式 |
| 80万 | 75fps | 13ms | 1.6GB | 端侧重建默认档 |
| 150万 | 45fps | 22ms | 2.4GB | 精修模型标准 |
| 300万 | 25fps | 40ms | 4.2GB | 不建议移动端使用 |
注意这里的内存占用包括了显存、导入的纹理、渲染缓冲区和排序所需的临时Buffer。80万高斯的模型端侧加载到渲染,大概要吃1.6GB内存,如果设备本身只有8GB内存,同时后台还有一堆App,还是可能被系统回收的。所以我们上线时把默认档定为50万高斯,渲染帧率更稳,内存占用也更安全。
从帧率曲线看,最大瓶颈出现在排序器。高斯数量超过150万后,排序耗时曲线变得很陡。优化时优先看排序,其次才看光栅化。我们试过把排序从32位key改成24位key,顶点数不变的情况下帧率提升了12%。
4.3 面板数据:PSNR、SSIM、模型体积的权衡
商品展示这种业务场景,模型质量不能只看一个指标。PSNR和SSIM能反映整体重建误差,但用户真正关注的是“放大能不能看清logo”“反光连续不连续”“边缘有没有毛刺”。
| 模型版本 | 高斯数量 | PSNR(dB) | SSIM | 模型体积 | 适用场景 |
|---|---|---|---|---|---|
| 端侧快速版本 | 40万 | 24.5 | 0.86 | 18MB | 用户现场重建 |
| 端侧微调版本 | 70万 | 26.7 | 0.90 | 26MB | 用户现场重建+精修 |
| 桌面精修版本 | 180万 | 30.1 | 0.96 | 95MB | 官方商品建模 |
| 桌面精修+压缩 | 150万 | 29.4 | 0.94 | 38MB | 官方模型移动端投放 |
从业务角度,官方上架的商品模型我们要求PSNR做到29dB以上,SSIM做到0.94以上,否则在商品详情页的大图上经不起细看。而用户现场用端侧重建的模型,主要是用来“自己看一眼东西合不合适”,24-27dB够用了,反正不在公域页面展示。这组数据也再次说明端侧方案的定位是“快速、隐私、本地可用”,和传统高质量离线建模是互补关系,不是替代关系。
5. 落地过程中踩过的坑
5.1 AR Engine位姿漂移导致重建重影:完整排查链路
端侧重建第一版出来的模型有个诡异现象:商品正面清晰,侧面和背面重影特别严重,像喝了酒的双重曝光照片。当时第一个怀疑对象是重建网络本身,跑了一组样例对比后确认网络没问题——因为用COLMAP位姿替换AR Engine位姿,模型立刻变清晰。这样一来问题定位到AR Engine输出位姿上。
进一步排查发现,重影不是偶发性的,集中在相机转到侧面时出现。原因是商品表面高光区域多,AR Engine在侧面视角下特征跟踪不稳定,位姿发生了缓慢漂移,而照片与照片之间的漂移量在重建网络算梯度时被当成了“相机固定但物体在动”,模型自然就糊了。
修复分三层做。第一层,采集模块里增加位姿置信度输出,置信度低于阈值的照片直接不保存。第二层,保存完照片后跑一次轻量BA(Bundle Adjustment)优化,把AR Engine位姿和照片关键匹配点联合优化一轮,直接在端侧用少量迭代拟合,时间大约1秒,效果非常明显。第三层,重建前置一个重投影误差过滤逻辑,把误差最大的照片剔除后重新训练。三层叠完,端侧重建的清晰度接近桌面级。
5.2 Vulkan排序在移动GPU上的带宽瓶颈
自研渲染器第一版性能数据出来时,150万高斯只有18fps,离预期差了一半。用图形分析工具逐个pass抓时间,发现radix sort这一个pass就吃了整帧40%的时间。移动GPU的显存带宽本来就紧张,百万级的键值对排序又必须来回读写多轮,带宽直接打满。
当时试过两种优化,效果差别很大。一种是把key从32位压缩到24位,减少排序数据量,提升约10%。另一种是把全局排序改成“瓦片分桶+桶内排序”,先通过compute shader把高斯原语划分到屏幕对应的瓦片范围内,每个瓦片内再单独排序,排序数据量瞬间从百万级降到每瓦片几千级别,整体排序耗时降了一半以上。两种优化叠加后,150万高斯的帧率从18fps提到45fps,达到预期。
5.3 ArkUI手势与Native窗口事件冲突
商品缩放功能上线后收到反馈:双指捏合时商品旋转而不是缩放,旋转时偶尔没响应。复现后发现是手势冲突的经典问题。XComponent外层套了一层容器,容器上同时挂了单击旋转和双指缩放手势,但在ArkUI的默认手势竞争规则里,双指捏合和单指拖动同时触发时,系统会随机选一个响应,导致手势判定错乱。
解决方式是明确手势优先级和代理。我们把旋转手势和缩放手势统一收敛到Native层:ArkUI只上报触摸事件原始坐标,Native层自己根据触摸点数量和位移距离判断是旋转、缩放还是双击。这样一来手势逻辑集中在C++里,响应快且不会和ArkUI内置手势打架。这个改动让交互问题一次性清零。
5.4 发热与降频:不能只看平均帧率
性能测试时还有一件麻烦事:新设备上跑测试帧率很好看,但连续使用十分钟后掉帧明显。表面看是可感知的卡顿,本质是GPU持续高负载触发温控降频。平均帧率没变,但帧率方差变大,用户体感反而更差。
对我们的场景来说,用户不会持续操作三分钟不撒手,但AR摆放时确实需要稳定运行。我们做了三层温控策略:第一层,检测到设备温度升高时,动态降低渲染分辨率到80%;第二层,把排序帧率从“每帧重排”降为“每三帧重排一次”,因为旋转速度不快时相邻帧的高斯深度顺序变化很小,少排几次视觉几乎无差异;第三层,温度超过安全阈值时直接提示用户暂停AR模式,避免硬件损伤。这三层上线后,长时间AR摆放的帧率稳定性从抖动严重变为基本稳定在30fps左右。
端侧3DGS重建在HarmonyOS 7上做到商品展示可用,核心不在于把某个算法调到极致,而在于整条链路的工程取舍:位姿估计不堆COLMAP而是用AR Engine加快筛,高斯生成不硬扛几万次迭代而是前馈加轻量微调,渲染不追求全量精度而是压缩、LOD、动态分辨率三层联动。这套组合打下来,用户从拍摄到看到可预览的3D商品模型只要三到五分钟,在弱纹理商品上的表现远超传统方案。项目中踩过的那些坑,位姿漂移、排序带宽、手势冲突、温控降频,每一条都是实际设备上真金白银换来的经验,照着这个思路做,至少能少折腾一个月。
更多推荐



所有评论(0)