【鸿蒙心迹】3DGS端侧重建初探:让手机也能做三维重建

摘要:本文记录了一次在鸿蒙端侧实现 3DGS(3D Gaussian Splatting)三维重建的可行性探索。文章从 3DGS 的基本原理讲起——用高斯点云显式表示三维场景,渲染快、可编辑,比 NeRF 更适合端侧;随后拆解渲染管线与端侧算力评估,指出 50 万点场景在骁龙 8 Gen3 上约 15-20fps、麒麟 9000S 上约 8-12fps,深度排序是主要瓶颈。核心结论是:端侧只能做渲染、不能做训练,可行路径为「云端训练 + 端侧渲染」,配合透明度裁剪、FP16 量化、SH 降阶把 200 万点压缩到 30 万点,并用 Vulkan compute shader 做并行排序,从而在小物体场景(<20 万点)实现 30fps 流畅浏览。文中还给出四步工程链路、四类典型踩坑的排查方案与最佳实践清单。

3DGS(3D Gaussian Splatting)是近两年最火的三维重建技术——用一组照片就能重建出可自由视角浏览的三维场景。在PC和服务器上,3DGS已经跑得相当成熟,但把它搬到手机端?大多数人觉得不可能。我花了两周做了一次端侧3DGS的可行性探索,这篇就是探索过程的完整记录——包括哪些能做、哪些做不了、以及未来的路径。

如果你对三维重建、3DGS技术感兴趣,想了解在鸿蒙端侧实现3DGS的可行性,这篇适合你。读完后你会掌握:3DGS的基本原理、端侧的算力评估、工程化落地的思路和挑战。

本文分三层:第一层建立3DGS的整体认知;第二层拆解3DGS的渲染和训练原理;第三层是端侧落地的可行性分析和探索方案。

入门认知:3DGS到底"重建"了什么

一句话定义

3DGS是用一组多角度照片,通过高斯点云表示三维场景——每个点是一个3D高斯椭球,拥有位置、大小、颜色、透明度属性,渲染时把所有高斯点投影到屏幕上叠加成像。

打个比方:传统三维建模像用积木一块块搭——每个三角形面片就是一个积木块,搭出一个完整的房子。3DGS像用无数个半透明的彩色水滴喷洒——每个水滴是一个高斯点,从不同角度看,水滴的叠加效果就构成了房子的三维外观。

3DGS vs 传统三维重建

对比维度传统(NeRF)3DGS
表示方式神经网络隐式表示显式高斯点云
渲染方式逐像素射线采样点云投影叠加
渲染速度慢(需要网络推理)快(直接GPU渲染)
训练速度慢(数小时)较快(数十分钟)
编辑能力难(隐式表示)易(显式点云可操作)
端侧可行性极低有探索空间

表1:NeRF与3DGS的核心差异

从表示方式到端侧可行性,两者的差异可以用一张图直观对比:

3DGS 路线

传统 NeRF 路线

多角度照片

神经网络隐式表示

逐像素射线采样

渲染慢 / 难编辑

多角度照片

显式高斯点云

点云投影叠加

渲染快 / 易编辑

端侧可行性

NeRF:极低

3DGS:有探索空间

图1-1:NeRF 与 3DGS 的技术路线对比

全景图示

多角度照片输入

COLMAP: 相机位姿估计

点云初始化

3DGS训练优化

高斯点云模型

渲染: 投影+叠加

自由视角浏览

位置/大小优化

颜色/透明度优化

密度调整: 分裂/删除

图1:3DGS从照片到可浏览模型的完整链路

适用边界

3DGS适合:场景重建(室内、室外)、物体重建(商品展示)、文化遗产数字化。不适合:动态场景(3DGS是静态场景技术)、大范围地形(点云数量爆炸)、实时重建(训练阶段需要离线完成)。

进阶原理:3DGS的渲染与训练

渲染原理:高斯投影叠加

3DGS渲染的核心是"把3D高斯投影到2D屏幕":

  1. 变换:把3D高斯从世界坐标变换到相机坐标
  2. 投影:把3D高斯投影为2D高斯(椭圆)
  3. 排序:按深度排序所有高斯点(从远到近)
  4. 叠加:逐点alpha混合,近处的点覆盖远处的点

3D高斯点云

相机变换

2D投影: 椭圆

深度排序

Alpha混合叠加

最终图像

图2:3DGS渲染管线——从3D点到2D图像

关键概念辨析

概念含义端侧关注点
高斯点3D椭球,有位置/大小/颜色/透明度点的数量决定渲染负载
Splatting把3D高斯投影到2D屏幕投影计算量与点数成正比
Alpha混合按透明度叠加颜色需要排序,排序是性能瓶颈
自适应密度控制自动分裂/删除高斯点训练阶段操作,渲染不需要

表2:3DGS核心概念

端侧算力评估

一个典型3DGS场景的点云规模和计算需求:

场景规模点云数量渲染所需算力端侧可行性
小物体5万-20万低可行
室内场景50万-200万中有挑战
室外场景200万-500万高困难
大型场景500万以上极高不可行

表3:不同规模3DGS场景的端侧可行性

[注意] 渲染一个50万点的3DGS场景,在骁龙8 Gen3上约15-20fps,在麒麟9000S上约8-12fps。要达到30fps的流畅浏览,点云必须压缩到20万以内。

把点云规模与端侧帧率放在一起看,瓶颈位置一目了然:

5万-20万

50万-200万

200万-500万

500万以上

点云规模

点数区间

小物体:可行

室内场景:有挑战

室外场景:困难

大型场景:不可行

骁龙8 Gen3:30fps+

骁龙8 Gen3:15-20fps
麒麟9000S:8-12fps

帧率不足,需大幅压缩

端侧暂不具备条件

目标:压缩到20万点以内
才能稳定30fps

图2-1:点云规模与端侧帧率的对应关系

常见误区

误区1:3DGS可以在手机上训练

3DGS训练需要大量梯度计算和自适应密度控制(点的分裂/删除),这些操作在手机GPU上几乎不可能实时完成。训练必须在PC/服务器上离线完成,端侧只做渲染。

误区2:3DGS渲染只需要GPU

3DGS渲染中有一个关键步骤——深度排序,这是CPU密集型操作。每帧都需要对所有可见点按深度排序,50万个点的排序在CPU上约30-50ms,这往往是端侧的帧率瓶颈。

高级应用:端侧3DGS渲染的探索方案

端侧落地的整体策略

由于端侧算力限制,完整3DGS管线不可能全部在手机上跑。可行的策略是云端训练 + 端侧渲染:

拍照采集

上传云端

云端COLMAP位姿估计

云端3DGS训练

点云压缩与优化

下载压缩点云到端侧

端侧渲染浏览

图3:云端训练+端侧渲染的混合策略

实战步骤

第一步:端侧渲染器实现

3DGS渲染器的核心是高斯投影 + Alpha混合。在鸿蒙端可以用Vulkan或OpenGL ES实现:

// 渲染管线伪代码
function renderGaussianSplatting(
  gaussians: GaussianPoint[],
  camera: Camera,
  viewport: Size
): PixelMap {
  // 1. 变换:世界坐标 → 相机坐标
  const viewMatrix = camera.getViewMatrix()
  const transformed = gaussians.map(g => transformGaussian(g, viewMatrix))

  // 2. 投影:3D高斯 → 2D椭圆
  const projected = transformed.map(g => projectGaussian(g, camera.projection))

  // 3. 剔除:去掉视锥外的点
  const visible = projected.filter(g => isInFrustum(g, camera.frustum))

  // 4. 排序:按深度从远到近
  visible.sort((a, b) => b.depth - a.depth)

  // 5. Alpha混合:逐点叠加到帧缓冲
  const framebuffer = new Float32Array(viewport.width * viewport.height * 4)
  for (const g of visible) {
    splatToFramebuffer(g, framebuffer, viewport)
  }

  return framebufferToPixelMap(framebuffer, viewport)
}

[注意] 排序是最大的性能瓶颈。50万点的JavaScript排序约200ms,完全不可行。必须用C/C++实现排序(通过NAPI调用),或者用GPU compute shader做并行排序。

第二步:点云压缩

云端训练完成的点云通常有数百万点,必须压缩到端侧可接受的范围:

厸缩策略压缩比质量损失适用场景
透明度阈值裁剪3-5x极小通用,首选
体素下采样5-10x中等大场景
SH系数降阶2-3x小颜色细节丰富场景
量化(FP32→FP16)2x极小通用

表4:3DGS点云压缩策略

推荐组合:透明度裁剪 + FP16量化 + SH降阶,通常能把200万点压缩到30万点左右,端侧可流畅渲染。

第三步:用Vulkan实现高效渲染

在鸿蒙端通过Vulkan实现3DGS渲染是最优路径——Vulkan支持compute shader做并行排序,渲染管线高度可控:

// Vulkan compute shader做并行排序(伪代码)
void sortGaussiansWithCompute(VkDevice device, VkQueue queue,
                               GaussianBuffer& buffer) {
  // 1. 创建compute pipeline
  auto pipeline = createComputePipeline(device, "sort.comp.spv");

  // 2. 绑定高斯点buffer
  bindBuffer(pipeline, buffer);

  // 3. 分发compute任务
  // 每个工作组处理256个点的排序
  uint32_t groupCount = (buffer.count + 255) / 256;
  vkCmdDispatch(cmdBuffer, groupCount, 1, 1);

  // 4. 等待完成
  vkQueueWaitIdle(queue);
}

[提示] Vulkan compute shader做排序比CPU快10-20倍。50万点的排序在GPU上约5-10ms,在CPU上约200ms。这是端侧3DGS能否达到30fps的关键。

第四步:交互式浏览

用户通过触摸手势控制相机位置和方向,实现自由视角浏览:

// 旋转手势控制相机方向
.gesture(
  RotationGesture()
    .onActionUpdate((event: GestureEvent) => {
      camera.yaw += event.angleDelta
      requestRender()
    })
)

// 捏合手势控制相机距离
.gesture(
  PinchGesture()
    .onActionUpdate((event: GestureEvent) => {
      camera.distance *= (1 - event.scaleDelta)
      requestRender()
    })
)

把四个步骤串起来,端侧渲染的完整工程链路如下:

瓶颈

优化

第一步:端侧渲染器实现
投影 + Alpha 混合

第二步:点云压缩
裁剪 + FP16 + SH 降阶

第三步:Vulkan 高效渲染
compute shader 并行排序

第四步:交互式浏览
旋转 / 捏合手势

CPU 排序 200ms

GPU 排序 5-10ms

30fps 流畅浏览

图3-1:端侧 3DGS 渲染的四步工程链路

踩坑排错

坑1:渲染帧率极低(<5fps)

现象:30万点的场景渲染帧率只有3-5fps。

原因:排序在CPU上执行,每帧排序30万点约150ms。

解决:必须把排序移到GPU compute shader。如果Vulkan compute shader实现困难,退而求其次用增量排序——只在相机移动时重新排序,静止时复用上一帧的排序结果。

坑2:点云下载体积过大

现象:压缩后的点云仍有50MB以上,用户下载等待时间长。

解决:做渐进式加载——先下载低精度版本(5万点,约5MB),立即开始渲染;后台继续下载高精度版本,下载完成后无缝替换。

坑3:渲染出现"飞点"

现象:某些高斯点在视角变化时突然闪烁或飞出场景。

原因:个别高斯点的协方差矩阵退化(尺寸极大或极小),投影后覆盖整个屏幕。

解决:在压缩阶段过滤掉异常点——协方差矩阵的迹超过阈值的高斯点直接删除。

坑4:内存占用持续增长

现象:浏览3DGS场景时应用内存持续增长,最终OOM。

原因:每帧渲染都创建了新的buffer对象,没有复用。

解决:使用对象池模式——预分配固定大小的buffer,每帧复用而非新建。Vulkan的command buffer也要复用,不要每帧重新创建。

四个坑的排查路径可以归纳成一张决策图:

帧率 < 5fps

下载体积 > 50MB

点闪烁 / 飞出

内存持续增长

端侧渲染异常

现象类型

坑1:CPU 排序耗时

排序移到 GPU compute shader
或增量排序

坑2:点云过大

渐进式加载
先低精度后高精度

坑3:协方差退化

压缩阶段过滤异常点

坑4:buffer 未复用

对象池 + command buffer 复用

稳定 30fps

图3-2:四类典型问题的排查与解决路径

最佳实践

  • 云端训练 + 端侧渲染:训练绝不在端侧做
  • 点云压缩三件套:透明度裁剪 + FP16量化 + SH降阶
  • 排序必须上GPU:compute shader做并行排序,CPU排序不可行
  • 增量排序优化:相机静止时复用排序结果
  • 渐进式加载:先低精度后高精度,缩短用户等待
  • 异常点过滤:协方差矩阵退化的高斯点必须删除
  • buffer对象池:预分配复用,防止内存增长

总结

核心要点:

  1. 3DGS用高斯点云表示三维场景——显式表示,渲染快,可编辑,比NeRF更适合端侧
  2. 端侧只能做渲染,不能做训练——云端训练+端侧渲染是唯一可行策略
  3. 排序是端侧渲染的最大瓶颈——必须用GPU compute shader,CPU排序不可行
  4. 点云必须压缩——透明度裁剪+FP16量化+SH降阶,200万点压到30万

最佳实践清单:

  • 云端训练,端侧渲染
  • 点云压缩:透明度裁剪+FP16+SH降阶
  • 排序用Vulkan compute shader
  • 相机静止时复用排序结果
  • 渐进式加载:先低精度后高精度
  • 异常点过滤:协方差退化必删
  • buffer对象池防内存增长
  • 小物体场景(<20万点)端侧可行,大场景暂不可行

3DGS端侧渲染你有什么想法?觉得哪些场景最值得先落地?评论区聊聊,一起推动端侧3D走向实用。

最后用一张图回顾整条端侧 3DGS 的落地路径:

拍照采集

云端训练
COLMAP + 3DGS

点云压缩
200万 → 30万

端侧渲染
Vulkan + GPU 排序

交互浏览
30fps

小物体场景优先落地

图4:端侧 3DGS 从采集到浏览的完整落地路径

Logo

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

更多推荐