尧图精选

从Sceneform到Filament:Android点云渲染与3DGS落地实践

🕒 发布时间:2026/10/2 15:33:37 📁 来源:尧图网络
2026 年开工第一篇本来想先整理点别的结果还是绕到了渲染上。Sceneform 这个老库Google 官方已经停更很久了但社区分支 Sceneform-EQR 一直有人在往里填东西。我们这版主要做的是把渲染后端从原来的 OpenGL ES 整体切到 Filament同时打通 PLY 点云和 Mesh 的加载渲染链路再往前探了一步 3D Gaussian Splatting 在移动端的落地路径。这篇文章适合给三类人看想把点云渲染跑在 Android 上做 AR 展示的在做标注工具或三维重建后处理想找个靠谱渲染核心的以及研究 3DGS 但卡在工程化落地的人。我会把项目背景、Filament 接入细节、PLY 解析与点云渲染优化、3DGS 的移动端探索方案以及一路踩过的坑都理一遍。搞过 Android AR 的都知道Sceneform 当年最大的价值是把 ARCore 的相机、平面检测、锚点和 glTF 模型加载封装得很舒服。官方一停更这套生态就断档了。社区保留的分支里Sceneform-EQR 算比较活跃的核心思路是保留原来的 API 设计把底层渲染器换成 Filament。为什么要这么折腾等我把几个关键逻辑捋清楚你就知道这笔投入值在哪了。1. 项目定位为什么还在折腾 Sceneform 生态1.1 Sceneform 停更之后社区分支在补哪些课Sceneform 在 2019 年开源、2020 年停止更新后留下的是一堆已经上线的 App 和无数依赖它的 SDK。这些项目不可能一夜之间全用 Vulkan 重写所以社区分支的价值就是继续用原来的 API 习惯把底层能力往上顶一顶。原来的 Sceneform 渲染器基于 OpenGL ES 2.0/3.0材质体系也很老HDR、PBR、tonemapping 这些概念基本没有。你在手机上加载一个 glTF 模型画面要么发灰要么高光处死白跟主流渲染引擎比明显差一代。社区分支要做的事情首先是补材质表达然后是补渲染质量最后是补性能调度。我用 Sceneform-EQR 的过程中最直观的感受是Filament 接管后同一台设备上加载同一个模型画面干净了不止一个档次。当然代价是接入成本变高毕竟 Filament 不是拿来就能用的库它要求你先理解渲染管线的基本结构。1.2 用 Filament 替换老渲染器解决的是三类问题Filament 是 Google 维护的跨平台 PBR 渲染引擎后端支持 Vulkan、Metal、OpenGL ES在 Android 上天然合适。替换老渲染器我总结下来解决了三类问题第一类是兼容性。老渲染器在新设备上经常出 shader 编译问题和纹理格式支持问题。Filament 做了非常多驱动兼容处理它内部有一套材质编译和驱动回退机制实测下来在高低端设备上的表现稳定很多。第二类是渲染质量。Filament 自带完整的物理光照、IBL、SSAO、Bloom、动态阴影还有完善的 tonemapping 和色彩管理。点云这种东西虽然不一定要 PBR但 Mesh 场景会非常受益。我们加载带贴图的 glTF 模型时Filament 的 PBR 材质还原度比老渲染器好太多。第三类是线程模型。老渲染器的绘制调用基本在 UI 线程或 GL 线程串行执行动画一多就卡。Filament 的核心渲染循环在后台线程跑主线程只做命令提交这种异步架构对交互类 App 很关键。我们在多窗口、多场景切换时帧率稳定性提升非常明显。2. Filament 渲染管线与 PLY 点云/网格加载的实现2.1 Filament 核心架构Engine、View、Scene 的最小链路第一次接触 Filament 的人最容易被它的概念绕住。其实核心就四个东西Engine、Scene、View、Renderer。Engine 管资源和命令生成Scene 管渲染对象集合View 管视角状态和一个渲染目标上的绘制参数Renderer 负责把 View 里的东西真正画出来。一个最小可用的初始化骨架大概是这个流程Engine* engine Engine::Builder() .backend(Backend::OPENGL) .build(androidContext); Renderer* renderer engine-createRenderer(); Scene* scene engine-createScene(); View* view engine-createView(); view-setScene(scene); // 每一帧 renderer-render(view);注意一点View 需要绑定 CameraCamera 可以通过 engine-createCamera(entity) 生成然后 setProjection 设置透视或正交参数。不用相机就往 View 里塞 Renderable画面会空白。Filament 的资源对象不是裸指针绝大多数是句柄Handle用完要手动 destroy。比如 engine-destroyMesh、engine-destroyVertexBuffer。忘了释放的话内存会只增不减这在长时间跑 AR 流程时非常致命。我们封装了一个 RendererWrapper把 Engine 初始化、View 配置、Camera 更新、帧循环都收敛到一个类里。上层业务只关心扔进去一个 Mesh 或者 PointCloud不用直接碰 Filament API。这个封装对团队协作很关键因为并不是每个写业务的人都愿意去啃 Filament 文档。2.2 PLY 文件解析头部、顶点属性、面索引的处理细节PLY 格式本身不复杂核心由一个文本头部和一个数据区组成。头部用 element 和 property 描述顶点的布局ply format binary_little_endian 1.0 element vertex 184538 property float x property float y property float z property uchar red property uchar green property uchar blue element face 34211 property list uchar int vertex_indices end_header解析时要重点注意三件事第一格式分支。ASCII 和 binary 都要兼容但实际场景里强烈建议用 binary。我们曾经在处理一个 80MB 的 ASCII PLY 时直接卡了几秒换 binary 后瞬间加载完。如果上游工具只导 ASCII先离线转一遍再交付。第二属性顺序不能写死。有些点云文件有 nx、ny、nz有些没有颜色可能是 uchar 的 rgb也可能是 float 的 rgb。解析器必须根据 header 里的 property 列表动态定位偏移量。我们自己写了一个轻量解析器核心就几十行遍历 property 名称匹配偏移量再按类型读取不依赖第三方库省去一堆兼容性问题。第三3DGS 导出的 PLY 很特殊。它的 vertex 属性不是 xyzrgb而是位置、法向、球谐系数、opacity、scale、rot 这一大串。一个高斯点占了几十个 float属性顺序是固定的x y z nx ny nz f_dc_0 f_dc_1 f_dc_2 f_rest_0...f_rest_44 opacity scale_0 scale_1 scale_2 rot_0 rot_1 rot_2 rot_3。解析普通的 PLY 不能直接套到 3DGS 上要单独写一个解析入口。2.3 点云的渲染提交顶点缓冲、点精灵材质与性能取舍点云在渲染上本质就是一堆顶点加颜色不需要索引。在 Filament 里我们要做的是创建一个 VertexBuffer塞入 POSITION 和 COLOR 属性然后用 RenderableManager 绑定 POINTS 图元类型。VertexBuffer* vb VertexBuffer::Builder() .vertexCount(pointCount) .attribute(VertexAttribute::POSITION, 0, AttributeType::FLOAT3, 0, stride) .attribute(VertexAttribute::COLOR, 1, AttributeType::UBYTE4, 12, stride) .build(*engine); vb-setBufferAt(*engine, 0, buffer);材质方面Filament 默认材质不支持点精灵需要自己写一个 vertexDomain 为 points 的材质在 fragment shader 里处理圆形点裁剪在 vertex shader 里设置 gl_PointSize。这个点大小要跟点云密度的 LOD 联动不然远了糊成一片近了全是马赛克。性能上最关键的优化是顶点内存布局把颜色打包成 UBYTE4而不是用 FLOAT4。一百万个点FLOAT3FLOAT4 是 28MBFLOAT3UBYTE4 只要 16MB。省掉的位宽直接反映在带宽和整体内存占用上。再往大了做就是用八叉树做视锥裁剪和 LOD。点云数据量到千万级别时全量提交毫无意义相机只会看到一部分点。我们发现只做视锥裁剪就能提升 30% 到 40% 的帧率再做 LOD 后大场景基本能稳在 60 帧。3. 3D Gaussian Splatting 渲染探索3.1 3DGS 到底在渲染什么3D Gaussian Splatting 的核心思路是用一大簇三维高斯分布来表示场景。每一颗高斯有位置 μ、协方差 Σ、透明度 α 和颜色一般用球谐系数 SH 编码。协方差矩阵为了保证半正定训练时用旋转四元数 q 和缩放向量 s 参数化。渲染时每一颗高斯都会被投影到相机平面上变成二维高斯然后按深度从近到远排序再用 alpha blending 一层层叠上去。最终像素颜色公式是C Σ_{i1..N} T_i α_i c_i其中 T_i Π_{ji}(1-α_j)这就是为什么 3DGS 能跑得比 NeRF 快很多它把神经渲染问题变成了普通光栅化问题不需要每条光线走一遍 MLPGPU 可以并行处理。对我们来说有一个隐藏的利好3DGS 训练完成后导出的标准格式就是 PLY。前面把 PLY 解析做扎实了3DGS 的数据入口就已经解决了一半。3.2 移动端跑 3DGS 的最大瓶颈在哪3DGS 在桌面显卡上可以实时到了移动端就完全另一回事了。瓶颈主要是三个第一个是数据量。一个场景的 3DGS 模型动辄几十万到上百万颗高斯每颗高斯带 SH 系数后占大量内存。百万颗高斯的模型初始加载就可能吃掉几百 MB 内存这还不算渲染时需要的中间变换矩阵。第二个是排序。这是最大的性能洼地。3DGS 渲染要求所有高斯按深度排序CPU 上排序百万个浮点数就算排到 30fps 也很勉强。GPU 排序需要特殊的数据结构一般做法是把屏幕切分成 tile每个 tile 内用 bucket sort。这个实现起来工程量大Android 驱动兼容性还参差不齐。第三个是alpha blending 的稳定性。高斯多了以后哪颗在近处哪颗在远处排序稍微不稳定就会导致画面闪烁尤其在运动视角下非常明显。3.3 我们的探索路径预排序、计算着色器与分块加载在 Sceneform-EQR 里我们走了一条循序渐进的路先把 3DGS 当做增强版点云渲染出来再做深度的计算着色器方案。第一步是低质量点渲染验证。把 PLY 里的高斯中心点直接当普通点云画用颜色近似表达。这一步能快速看到模型的整体轮廓也能在手机上流畅转动用来验证整个工程链路加载、渲染、交互是否顺畅。第二步是在 Filament 的 support 下用 compute shader 做 splatting。Filament 在 Vulkan 后端支持 compute我们用它做高斯的投影和排序。因为 Filament 的 compute API 比较底层驱动兼容性问题比预想多这一版我们还在推进中。目前比较稳的替代方案是用计算着色器并行投影排序放回 CPU 做配合分块策略密度控制到几十万颗以内。第三步打算做渐进式加载。第一帧先画低分辨率低密度的高斯后续按深度逐步补充细节这样内存占用和首帧延迟都能压下来。虽然还没有完全跑通但从架构和数据流来看这条路是可行的。移动端跑 3DGS老实说还没有一个像点云渲染这么成熟的标准答案任何宣称开箱即用的方案都值得多留个心眼。我们目前的判断是先保帧率再保画质。4. 从点云采集到标注、配准的联动场景4.1 用 RealSense D435 快速产出一份 PLY做渲染之前得有数据。最近我常用 Intel RealSense D435 出点云整个链路很成熟。用 pyrealsense2大概这么几步import pyrealsense2 as rs pipeline rs.pipeline() config rs.config() config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) profile pipeline.start(config) # 对齐 RGB 到深度帧 align rs.align(rs.stream.color) frames pipeline.wait_for_frames() aligned align.process(frames) # 获取外参生成顶点并保存为 PLY depth_frame aligned.get_depth_frame() color_frame aligned.get_color_frame()要出干净的 PLY几个细节得注意曝光自动调节会导致深度跳跃建议固定手动曝光深度范围按场景设置一般 0.3 到 3 米比较稳背景物体用深度阈值直接去掉避免远处杂点进来。我拿到 D435 的数据后会先用 Open3D 看一眼确认坐标系、单位、法向朝向再导成 PLY 交给渲染端。这个习惯大大减少了后面在 Filament 里排查坐标问题的时间。4.2 点云标注“拉框”对渲染层的要求点云标注里说的“拉框”就是在三维空间里拖着标出目标物体的立方体包围盒。常见表达分两种7 参数中心点 xyz、长宽高 lwh、航向角 yaw和 9 参数带完整刚体变换的 3D box。拉框在自动驾驶数据集制作里是基本操作。想把这件事做顺渲染层必须提供三个能力一是正交/透视切换。拉框标注时俯视图Birds Eye View和侧视图是刚需。透视视角下手抖一点框就画歪了。Filament 的 Camera 支持 setProjection(Camera::ORTHO)切换只需重设一下投影矩阵非常方便。二是射线拾取。点击屏幕时要把屏幕坐标反算成三维射线再和点云求交返回被点中的点作为框的锚点。这个接口我们自己实现了一套内部把 Viewport 坐标映射到 NDC再乘相机逆矩阵生成射线跟点云碰撞时用八叉树加速。三是线框叠加渲染。标注框要画成半透明的线框立方体颜色清晰还能区分选中的面和顶面。我们在 Filament 里用 Line primitive 渲染线框叠加在点云上并且每帧同步 7 参数或 9 参数的变换。有了这三个基础标注工具的体验就不会太差。4.3 点云配准可视化粗配准、精配准与实时反馈点云配准是另一个高频场景尤其是地形点云的粗配准和精配准。算法层面粗配准常用 FPFP 特征加 RANSAC 或 SAC-IA精配准用 ICP 或 NDT。库基本都是 PCL 和 Open3D这些不归渲染管。真正重要的是可视化。两块点云如果只用一种颜色渲染配准过程中完全看不出哪里对齐了哪里没对齐。我的做法是两块点云分别挂不同的颜色材质配准前用红色和蓝色渲染配准完成后重叠区域自然会呈现混合色。如果发现大片纯蓝或纯红说明变换还有问题。Sceneform-EQR 支持在一个 Scene 里挂多套 Renderable各自带独立的 TransformManager 节点。这样每次迭代粗配准或 ICP 的一步我们更新其中一个节点的 transform画面实时刷新算法调试效率提升非常大。5. 踩坑记录与排查速查表5.1 坐标系与单位Y-up 还是 Z-up这是所有 3D 渲染项目逃不掉的坑。点云工具Open3D、PCL通常输出 Z-up而 Filament 默认是 Y-up两者直接混在一起模型要么躺倒要么翻转。解决思路很清楚在文件解析阶段做完坐标变换不要在渲染阶段反复改。我们在 PLY 解析器里加了一个目标坐标系参数加载时就把坐标轴旋转到 Filament 的标准坐标系。单位上Filament 的默认单位是米PLY 里可能是毫米或者任意单位加载时也要统一缩放到米否则相机视锥参数会非常离谱。5.2 超大点云的内存与帧率优化点云数据一大第一个撞到的墙就是内存。百万点云用 FLOAT3FLOAT4 是 28MB看起来不大但如果你同时保留原始数据、几何数据、UI 缩略图数据马上就会翻很多倍。把颜色打包成 UBYTE4 后百万点压到 16MB整个内存压力小很多。帧率层面最大的收益来自视锥裁剪和 LOD。不做任何裁剪点云渲染的 drawcall 是一个没错但顶点处理的压力全在 GPU靠顶点着色器硬扛高密度场景还是会掉帧。加了八叉树裁剪后实测大场景帧率提升明显。再配合每帧只提交可视 LOD 级别内的点几乎所有场景都能稳帧运行。5.3 3DGS 渲染的闪烁问题3DGS 的画面闪烁八成是排序不稳定引起的。高斯数量多的时候相近深度的高斯之间深度差很小排序微变就吃掉了后面的高斯视觉上就是闪烁。我们试下来的有效手段渲染前做一个 alpha 阈值过滤把 alpha 小于 0.01 的高斯全部丢掉减少排序抖动合成时用固定分辨率并配合深度缓冲区稳定采样宁可牺牲一点高光细节也要先把排序和合成的稳定性做上去。5.4 Filament 材质与色彩空间坑Filament 默认工作在物理光照线性空间如果你把一个 sRGB 空间的纹理直接塞进去当 baseColor画面会明显发灰或者过曝。点云的顶点颜色基本都是 sRGB必须告诉 Filament 这是 sRGB 输入让它在内部正确转换到线性空间。材质方面点云的点精灵材质在网上能搜到一堆但真正能直接用的不多。写材质时注意顶点着色器里 gl_PointSize 的取值它跟屏幕分辨率有关一味的固定值会导致不同机型显示不一致。建议根据 viewport 的高度做一个比例换算保底逻辑是点的大小至少占满一个像素否则细小点云会直接消失。2026 年开年这段折腾下来我的体感是点云渲染这件事在移动端已经能做得非常扎实了PLY 解析、顶点缓冲、LOD、标注叠加、配准可视化整套链路都是可以闭着眼睛复现的。3DGS 则是另一个量级的问题它不只是把点变粗一点而是要把渲染范式从画点变成画高斯椭球每一步都在挑战移动端的性能和兼容性底线。如果你也想动手建议从 D435 取一份点云、用 Sceneform-EQR 的封装把 PLY 渲染跑通再拿一个训练好的 3DGS 模型试试纯点渲染。这条路走完你对移动端到底能承载多重的三维数据会有一个非常直观的判断。最后再分享一个小技巧所有渲染项目先把坐标变换和单位统一写在加载层后面会少踩一半的坑。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →