Rerun 中的 SphericalHarmonics3Rgb:3D 高斯溅射视角相关颜色的球谐系数编码解析
2026/9/17 3:24:50 网站建设 项目流程

Rerun 中的 SphericalHarmonics3Rgb:3D 高斯溅射视角相关颜色的球谐系数编码解析

【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun

导读:本文深入解析 Rerun 类型系统中用于 3D 高斯溅射(3D Gaussian Splatting)视角相关颜色编码的SphericalHarmonics3Rgb类型。你将掌握其 15 个半精度 RGB 球谐系数的排列顺序(系数主序、度 1–3 的分段规则)、它与 3DGS PLY 文件f_rest_*属性的差异及导入时的转置要求、DC 项(度 0)的分离表示、低阶数据零填充与高阶截断的数学依据,并顺着源码链路理解从 PLY 导入、Arrow 序列化到 GPU 渲染的完整实现。

一、类型概览:什么是 SphericalHarmonics3Rgb

SphericalHarmonics3Rgb是 Rerun 中的一个encoding(编码)类型,用于表示度数 1 到 3的 RGB 球谐系数,共15 个半精度(Float16)RGB 三元组。它被 GaussianSplats3D archetype 用作"视角相关颜色"(view-dependent color)的载体:每个高斯溅射除了有一个与视角无关的基础颜色外,还可以携带一组球谐系数,在渲染时根据视线方向重建随视角变化的颜色。

需要特别注意的是,Rerun 官方将该类型标记为unstable(不稳定)

⚠️This type isunstableand may change significantly in a way that the data won't be backwards compatible.

即该类型可能在未来发生不保证向后兼容的显著变化,跨版本迁移数据时需要留意这一点。

1.1 在类型体系中的位置

Rerun 的类型体系包含三层:archetype(原型)→ component(组件)→ encoding(编码)SphericalHarmonics3Rgb位于最底层的 encoding 层,定义文件为 crates/build/re_type_definitions/rerun/encodings/spherical_harmonics3rgb.def.rs,并由re_types_builder自动生成各语言绑定。围绕它有两条主要链路:

  • 组件层SphericalHarmonics3Rgb组件(rerun.components.SphericalHarmonics3Rgb)直接包装该 encoding;
  • Archetype 层GaussianSplats3D通过with_sh_coefficients()方法接收该组件,作为可选的"高阶球谐系数"字段(见 gaussian_splats3d.def.rs)。

1.2 官方定义原文

Spherical harmonics coefficients of degrees 1 through 3 for RGB, as 15 half-precision RGB triples.

The coefficients are stored coefficient-major:[c1.rgb, c2.rgb, …, c15.rgb].

The 15 coefficientsc1…c15are ordered by ascending degreel = 1, 2, 3, and within each degree by ascending orderm = -l … +l: degree 1 isc1…c3, degree 2 isc4…c8, and degree 3 isc9…c15.

二、系数排列顺序:系数主序与度结构

理解该类型的关键是系数在内存中的排列顺序。15 个系数采用coefficient-major(系数主序)存储,即一个系数的 RGB 三个通道紧挨在一起:

[c1.rgb, c2.rgb, c3.rgb, …, c15.rgb]

其中每个cN.rgb是一个由 R、G、B 三个f16组成的半精度三元组。c1…c15的编号规则如下:

  • 度数(degree)升序排列:l = 1, 2, 3
  • 同一度数内阶数(order)升序排列:m = -l … +l

由此得出各度数对应的系数区间:

度数 l系数编号系数个数对应 m 取值范围
1c1 … c33-1, 0, +1
2c4 … c85-2, -1, 0, +1, +2
3c9 … c157-3, -2, -1, 0, +1, +2, +3

因为每个度数包含的系数个数不同(3、5、7),Rerun不将不同度数暴露为独立的分组——数据就是一份扁平的 15 个三元组,度数边界需要调用方自行掌握(边界位于第 3 个和第 8 个三元组之后)。

2.1 Rust 中的底层表示

在 Rust 绑定中,该 encoding 被定义为#[repr(transparent)]的元组结构体(spherical_harmonics3rgb.rs):

#[derive(Clone, Debug, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable, SizeBytes)] #[repr(transparent)] pub struct SphericalHarmonics3Rgb( /// Spherical harmonics coefficients of degrees 1 through 3, coefficient-major. pub [[half::f16; 3usize]; 15usize], );
  • 外层[T; 15]对应 15 个系数,内层[f16; 3]对应每个系数的 RGB 三通道,f16即半精度浮点(Rust 侧使用halfcrate);
  • bytemuck::Pod/Zeroable表明该类型的内存布局是纯数据、可零初始化,这也让后续从 Arrow 缓冲区直接bytemuck::try_cast_slice零拷贝式反序列化成为可能。

在扩展实现 spherical_harmonics3rgb_ext.rs 中,还提供了两个关键成员:

impl SphericalHarmonics3Rgb { /// The number of coefficients of degrees 1 through 3, i.e. the number of RGB triples. pub const NUM_COEFFICIENTS: usize = 15; /// Create from 15 RGB coefficient triples of `f32`, converting each to half-precision. #[inline] pub fn from_f32(coefficients: [[f32; 3]; Self::NUM_COEFFICIENTS]) -> Self { Self(coefficients.map(|rgb| rgb.map(f16::from_f32))) } }

其中NUM_COEFFICIENTS = 15是全仓库统一引用的常量;from_f32接收[[f32; 3]; 15]并逐元素降精度为f16,方便用户用f32表达系数。文件头部还有一个编译期const _: () = assert!(...)校验,保证结构体字节大小严格等于15 × 3 × size_of::<f16>(),防止布局漂移。

三、与 3DGS PLY 文件的对应关系:通道主序 vs 系数主序

该类型的系数排列与3D Gaussian Splatting(Kerbl et al., 2023,参考 INRIA 实现)使用的 PLY 文件中的f_rest_*属性在系数顺序上一一对应,但布局约定不同

  • Rerun 的SphericalHarmonics3Rgb:系数主序,即[c1.rgb, c2.rgb, …, c15.rgb],每个系数的三个通道相邻;
  • 3DGS PLY 文件:通道主序(channel-major),即先存 R 通道的全部 15 个系数,再存 G 通道的全部 15 个,最后是 B 通道的全部 15 个:[R1 R2 … R15 | G1 G2 … G15 | B1 B2 … B15]

因此,在导入 PLY 时必须进行转置(transpose)。这条转换规则同时适用于度数的增补:如果文件只包含较低度数(如度 1,每通道 3 个系数,共 9 个f_rest_*属性),则导入时需要零填充到 15 个三元组;如果文件包含更高度数(如度 4,每通道 24 个系数),则只保留前 15 个并按度 3 截断。

3.1 导入器中的转置实现

转置逻辑位于 gaussian_splats3d_ext.rs 的slot_of函数中。它把 PLY 的属性名解析为Splat.values中的目标槽位:

fn slot_of(name: &str, num_sh_per_channel: usize) -> Option<usize> { if let Some(index) = name.strip_prefix(prop::F_REST_PREFIX) { if num_sh_per_channel == 0 { return None; } let index = index.parse::<usize>().ok()?; let channel = index / num_sh_per_channel; let coefficient = index % num_sh_per_channel; (channel < 3 && coefficient < NUM_SH_COEFFICIENTS) .then(|| slot::SH + 3 * coefficient + channel) } else if let Some(i) = prop::F_DC.iter().position(|p| *p == name) { Some(slot::F_DC + i) } else if let Some(i) = prop::SCALE.iter().position(|p| *p == name) { Some(slot::SCALE + i) } else if let Some(i) = prop::ROT.iter().position(|p| *p == name) { Some(slot::ROT + i) } else { match name { prop::X => Some(slot::X), prop::Y => Some(slot::Y), prop::Z => Some(slot::Z), prop::OPACITY => Some(slot::OPACITY), _ => None, } } }

其中prop::F_REST_PREFIX = "f_rest_"slot::SH = 14。对每个形如f_rest_k的属性:

  1. num_sh_per_channel(文件每通道实际存储的系数个数,由f_rest_*属性总数除以 3 得到)反解出其所属通道channel = k / num_sh_per_channel和系数序号coefficient = k % num_sh_per_channel
  2. 映射到系数主序的槽位slot::SH + 3 * coefficient + channel,从而在解析阶段就完成了转置
  3. coefficient >= 15(更高度数),该属性被丢弃——这正是"截断到度 3"的实现方式。

源码注释明确指出,这种"一次性把属性名重命名为槽位索引"的做法(read_plan)让每个顶点每次写入只做一次整数解析,避免了在大文件(数亿次调用)上的字符串比较开销;同时"零填充"由Splat::default()的全零初始化天然完成(gaussian_splats3d_ext.rs)。

最终,Splat::sh_coefficients()把槽位中的f32值转为f16三元组数组并包装成 encoding(gaussian_splats3d_ext.rs):

fn sh_coefficients(&self) -> SphericalHarmonics3Rgb { let coefficients = std::array::from_fn(|coefficient| { std::array::from_fn(|channel| { f16::from_f32(self.values[slot::SH + 3 * coefficient + channel]) }) }); crate::encodings::SphericalHarmonics3Rgb(coefficients).into() }

3.2 测试验证:转置与零填充

仓库在 gaussian_splats3d_ext.rs 内嵌了针对性单元测试,直接验证转置、零填充与截断行为:

  • sh_transposed_and_zero_padded:构造一个度 1 的 PLY(每通道 3 个系数,通道主序[R1 R2 R3 | G1 G2 G3 | B1 B2 B3],值为1..=9),断言导入结果等于系数主序[[R1 G1 B1], [R2 G2 B2], [R3 G3 B3], [0,0,0], …],即[1,4,7, 2,5,8, 3,6,9, 0…]
  • full_degree_3:每通道 15 个系数的完整度 3 文件,逐系数逐通道校验值channel * 15 + coefficient
  • higher_degree_is_truncated:度 4 文件(每通道 24 个系数,R=0..24, G=24..48, B=48..72),断言只保留前 15 个且仍按正确的跨通道步长读取,不会被错误步长打乱;
  • ragged_f_rest_drops_shf_rest_*总数不是 3 的倍数时(如 10 个),通道布局不可知,直接丢弃全部球谐系数。

此外,crates/data_flow/re_importer/tests/test_ply_importer.rs 提供了端到端测试:用真实的cactus.ply(139,410 个高斯)走完"文件 →ArchetypeImporter→ chunk"全链路,并断言sh_coefficientsscalesquaternionscolors每个组件都恰好有一个值对应每个高斯,且该文件不会被误判为普通点云(Points3D组件为空)。

四、DC 项(度 0)的分离表示

SphericalHarmonics3Rgb不包含度 0(DC,直流)项。度 0 项是与视角无关的基色,在 Rerun 中单独表示为encodings.Rgba32颜色(即GaussianSplats3Dcolors组件)。这样设计的好处是:基础颜色可以作为普通 RGBA 直接消费,而无需对每个高斯都做一次球谐求值。

在 3DGS PLY 中,DC 项对应f_dc_0 / f_dc_1 / f_dc_2三个属性(每通道一个)。导入器在Splat::color()中通过求值度 0 球谐得到 RGB:

// See http://en.wikipedia.org/wiki/Table_of_spherical_harmonics let sh_c0 = 0.5 * (1.0 / std::f32::consts::PI).sqrt(); let [r, g, b] = std::array::from_fn::<_, 3, _>(|i| to_u8(0.5 + sh_c0 * self.values[slot::F_DC + i])); // The opacity is stored as a logit; convert it to alpha via the sigmoid. let a = to_u8(1.0 / (1.0 + (-self.values[slot::OPACITY]).exp())); Color::new((r, g, b, a))

即:color_channel = 0.5 + c0 × f_dc_channel(其中c0 = 0.5 × sqrt(1/π)to_u8会 clamp 到[0,1]后量化到u8),不透明度则对 PLY 中以 logit 存储的opacity施加 sigmoid。这也再次印证了 gaussian_splats3d.def.rs 中colors字段的文档说明:"RGB 部分是视角无关的基色,即球谐的度 0 (DC) 项;alpha 部分是高斯峰值不透明度"。

五、零填充与截断:正交基下的数学性质

文档给出了两条关于度数转换的重要结论,其依据是球谐基的正交归一性

  1. 低阶数据零填充:当数据只包含较低度数(如度 1 或 2)的系数时,应当把缺失的高阶系数填充为零。由于球谐基是正交归一的,零填充后的 15 个系数所表示的函数与原函数完全相同——高阶系数为零意味着这些基函数对信号的贡献为零,不会引入任何偏差。

  2. 高阶数据按度边界截断:反过来,如果源数据包含更高阶(如度 4)系数,则在度边界处(第3个和第8个三元组之后)截断尾部系数,得到的是低一阶函数对原函数的最优最小二乘近似。这正是 3DGS PLY 导入器"保留每通道前 15 个系数、丢弃更高度数"这一行为(见上文slot_ofcoefficient < NUM_SH_COEFFICIENTS的判断)的数学依据。

用大白话概括:零填充是"无损"的,截断是"最优有损"的。这也意味着同一份数据以不同度数存储/传输不会改变其语义,只是精度与体积的权衡。

六、Arrow 数据类型与内存布局

SphericalHarmonics3Rgb对应的 Arrow 数据类型(ArrowDataType)为:

FixedSizeList(15 x non-null FixedSizeList(3 x non-null Float16))

从 Rust 实现的 spherical_harmonics3rgb.rs 可以看到其构造:

impl ::re_types_core::ArrowDataType for SphericalHarmonics3Rgb { #[inline] fn arrow_data_type() -> arrow::datatypes::DataType { DataType::FixedSizeList( Arc::new(Field::new( "item", DataType::FixedSizeList( Arc::new(Field::new("item", DataType::Float16, false)), 3, ), false, )), 15, ) } }
  • 外层FixedSizeList(15):15 个球谐系数;
  • 内层FixedSizeList(3):每个系数的 R、G、B 三个通道;
  • 最底层Float16:半精度浮点,非空(non-null)。

to_arrow序列化时会把[[f16; 3]; 15]展平写入连续的Float16Array缓冲区(期间内层三元组也被展平,最终得到 45 个f16值);from_arrow反序列化则通过bytemuck::try_cast_slice直接从缓冲区切片强转,并校验外层value_length() == 15、内层value_length() == 3,不匹配即返回datatype_mismatch错误。固定大小的嵌套列表保证了每个值严格占用45 × 2 = 90字节,利于批量上传与 GPU 端对齐。

七、组件包装与在 GaussianSplats3D 中的用法

7.1 组件层包装

组件 spherical_harmonics3rgb.rs 是对 encoding 的透明包装:

#[repr(transparent)] pub struct SphericalHarmonics3Rgb(pub crate::encodings::SphericalHarmonics3Rgb); impl ::re_types_core::WrapperComponent for SphericalHarmonics3Rgb { type Encoding = crate::encodings::SphericalHarmonics3Rgb; #[inline] fn name() -> ComponentType { "rerun.components.SphericalHarmonics3Rgb".into() } ... }

其语义为"视角相关颜色,以度数 1 到 3 的球谐系数表示;视角无关(度 0)的基础颜色由独立的components::Color表示"。组件与 encoding 之间通过Deref/From无缝互转,用户在 SDK 中几乎感觉不到这层隔阂。

7.2 Archetype 中的字段与用法

GaussianSplats3Darchetype(定义见 gaussian_splats3d.def.rs,生成代码见 gaussian_splats3d.rs)共有 6 个字段,其中与本文直接相关的是两个可选组件:

字段组件说明
sh_coefficients(可选)SphericalHarmonics3Rgb高阶球谐系数(度 1–3),用于视角相关颜色
spherical_harmonics_degree(可选)SphericalHarmonicsDegree渲染时评估的最高度数(0–3),未设置时默认为 3

Rust 官方示例(gaussian_splats3d.rs)展示了如何用 15 个系数直接构造并记录高斯溅射:

rec.log( "gaussians", &rerun::GaussianSplats3D::new([ (0.0, 0.0, 0.0), (2.0, 0.0, 0.0), (4.0, 0.0, 0.0), ]) .with_scales([(1.0, 0.5, 0.25), (0.5, 1.0, 0.5), (0.25, 0.5, 1.0)]) .with_quaternions([ rerun::Quaternion::IDENTITY, rerun::Quaternion::from_xyzw([0.0, 0.0, 0.382683, 0.923880]), // 45 degrees around Z rerun::Quaternion::IDENTITY, ]) .with_colors([ rerun::Color::from_unmultiplied_rgba(255, 0, 0, 128), rerun::Color::from_unmultiplied_rgba(0, 255, 0, 200), rerun::Color::from_unmultiplied_rgba(0, 0, 255, 255), ]) // 15 view-dependent RGB coefficients (degrees 1-3) per splat, coefficient-major: .with_sh_coefficients([ [[0.5, 0.0, 0.0]; 15], [[0.0, 0.5, 0.0]; 15], [[0.0, 0.0, 0.5]; 15], ]), )?;

注意with_sh_coefficients接受"每高斯 15 个[f32; 3]三元组",排列方式正是前文介绍的系数主序

除了手工构造,更常见的做法是直接导入 3DGS 训练产出的 PLY 文件。同文件内的第二个示例使用RecordingStream::log_file_from_path一条语句完成整个导入(内部走的就是上文分析的 PLY 导入器,自动完成转置、零填充/截断、DC 项与 opacity 转换):

// No entity-path prefix, and log the data as temporal (not static): rec.log_file_from_path(path, None, false)?;

7.3 渲染时的度数控制

SphericalHarmonicsDegree组件(见 spherical_harmonics_degree.rs)控制渲染端实际评估的度数,取值范围 0–3,默认 3:

0renders the view-independent base color only, and is the fastest. Each higher degree brings in more view-dependent detail, at the cost of fetching and evaluating more coefficients: 3 of them for degree 1, 8 for degree 2, and all 15 for degree 3. Lowering this in the blueprint can make the rendering a lot faster.

即:度 0 只渲染基色(最快);度 1 需要 3 个系数,度 2 需要 8 个,度 3 需要全部 15 个。在 blueprint 中降低该值可以显著提升渲染速度——这与本文第五节的"截断 = 最优近似"性质一脉相承,也是性能与画质之间可随时调节的旋钮。

八、渲染链路:从组件到 GPU 纹理

在渲染端,re_view_spatial/src/visualizers/gaussian_splats3d.rs 负责把SphericalHarmonics3Rgb组件数据打包进渲染器。其中的关键一步是把每个 RGB 系数三元组扩展为 RGBA,使其可以直接 memcpy 进 GPU 端的半精度 SH 纹理(Rgba16Float):

// Widen each RGB coefficient to RGBA so the renderer can memcpy it into its texture. let sh_coefficients: Vec<[GaussianShCoefficient; 15]> = { re_tracing::profile_scope_if!(100_000 < num_instances, "sh_coefficients"); data.sh_coefficients .iter() .map(|sh| std::array::from_fn(|i| GaussianShCoefficient::from_rgb(sh.0.0[i]))) .collect() };

由此可见,从 SDK 侧的[[f16; 3]; 15]、到 Arrow 的嵌套FixedSizeList、再到渲染器的[GaussianShCoefficient; 15],15 个系数 + 系数主序的约定贯穿了导入 → 存储 → 渲染整条流水线。若某一环违反该约定(例如按 PLY 的通道主序直接上传),视角相关颜色就会完全错乱——这也是为何文档与导入器都如此强调"必须转置"。

九、实践要点小结

  1. 排列约定:始终以系数主序提供 15 个[R,G,B]三元组;度 1 = 前 3 个、度 2 = 第 4–8 个、度 3 = 第 9–15 个。
  2. DC 项分离:度 0 基色不放进SphericalHarmonics3Rgb,请使用colors(RGBA)组件。
  3. 导入 PLY:优先使用log_file_from_pathGaussianSplats3D::from_ply_file_contents,导入器会自动完成通道主序 → 系数主序的转置,以及对低阶文件的零填充、对高阶文件的截断。
  4. 性能调节:通过spherical_harmonics_degree(0–3)控制渲染评估度数,度越低越快;未设置时默认取全部系数(度 3)。
  5. 类型稳定性:该类型标记为 unstable,升级 Rerun 版本时注意数据兼容性。

延伸阅读:如需了解与之配套的类型,可继续阅读 SphericalHarmonics3Rgb 组件文档、SphericalHarmonicsDegree 组件文档以及 GaussianSplats3D archetype 文档;PLY 导入的完整实现与测试位于 gaussian_splats3d_ext.rs 和 test_ply_importer.rs。

【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询