【鸿蒙心迹】3DGS端侧重建初探:让手机也能做三维重建
【鸿蒙心迹】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的核心差异
从表示方式到端侧可行性,两者的差异可以用一张图直观对比:
图1-1:NeRF 与 3DGS 的技术路线对比
全景图示
图1:3DGS从照片到可浏览模型的完整链路
适用边界
3DGS适合:场景重建(室内、室外)、物体重建(商品展示)、文化遗产数字化。不适合:动态场景(3DGS是静态场景技术)、大范围地形(点云数量爆炸)、实时重建(训练阶段需要离线完成)。
进阶原理:3DGS的渲染与训练
渲染原理:高斯投影叠加
3DGS渲染的核心是"把3D高斯投影到2D屏幕":
- 变换:把3D高斯从世界坐标变换到相机坐标
- 投影:把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万以内。
把点云规模与端侧帧率放在一起看,瓶颈位置一目了然:
图2-1:点云规模与端侧帧率的对应关系
常见误区
误区1:3DGS可以在手机上训练
3DGS训练需要大量梯度计算和自适应密度控制(点的分裂/删除),这些操作在手机GPU上几乎不可能实时完成。训练必须在PC/服务器上离线完成,端侧只做渲染。
误区2:3DGS渲染只需要GPU
3DGS渲染中有一个关键步骤——深度排序,这是CPU密集型操作。每帧都需要对所有可见点按深度排序,50万个点的排序在CPU上约30-50ms,这往往是端侧的帧率瓶颈。
高级应用:端侧3DGS渲染的探索方案
端侧落地的整体策略
由于端侧算力限制,完整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()
})
)
把四个步骤串起来,端侧渲染的完整工程链路如下:
图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也要复用,不要每帧重新创建。
四个坑的排查路径可以归纳成一张决策图:
图3-2:四类典型问题的排查与解决路径
最佳实践
- 云端训练 + 端侧渲染:训练绝不在端侧做
- 点云压缩三件套:透明度裁剪 + FP16量化 + SH降阶
- 排序必须上GPU:compute shader做并行排序,CPU排序不可行
- 增量排序优化:相机静止时复用排序结果
- 渐进式加载:先低精度后高精度,缩短用户等待
- 异常点过滤:协方差矩阵退化的高斯点必须删除
- buffer对象池:预分配复用,防止内存增长
总结
核心要点:
- 3DGS用高斯点云表示三维场景——显式表示,渲染快,可编辑,比NeRF更适合端侧
- 端侧只能做渲染,不能做训练——云端训练+端侧渲染是唯一可行策略
- 排序是端侧渲染的最大瓶颈——必须用GPU compute shader,CPU排序不可行
- 点云必须压缩——透明度裁剪+FP16量化+SH降阶,200万点压到30万
最佳实践清单:
- 云端训练,端侧渲染
- 点云压缩:透明度裁剪+FP16+SH降阶
- 排序用Vulkan compute shader
- 相机静止时复用排序结果
- 渐进式加载:先低精度后高精度
- 异常点过滤:协方差退化必删
- buffer对象池防内存增长
- 小物体场景(<20万点)端侧可行,大场景暂不可行
3DGS端侧渲染你有什么想法?觉得哪些场景最值得先落地?评论区聊聊,一起推动端侧3D走向实用。
最后用一张图回顾整条端侧 3DGS 的落地路径:
图4:端侧 3DGS 从采集到浏览的完整落地路径
更多推荐

所有评论(0)