SOG 格式(版本 2)
**SOG(Spatially Ordered Gaussians,空间有序高斯)**是紧凑的 3D 高斯泼溅容器。通过有损量化获得高压缩率,通常比等价 PLY 小约 15–20 倍。
可用 SplatTransform 创建,并在 PlayCanvas 查看器 预览。
本页为 SOG v2 规范。当前版本为 v3;所有 PlayCanvas 工具仍支持读取 v2,也可写出供早期查看器使用。
1. 文件集合
数据集由一组图像和元数据文件组成:
| 文件 | 用途 | 通道(8 位) |
|---|---|---|
meta.json | 场景元数据与文件名 | — |
means_l.webp | 位置低 8 位 | R,G,B |
means_u.webp | 位置高 8 位 | R,G,B |
scales.webp | 通过码本表示的各轴尺寸 | R,G,B |
quats.webp | 朝向,压缩四元数 | R,G,B,A |
sh0.webp | 基础颜色(直流分量)与不透明度 | R,G,B,A |
shN_centroids.webp | 球谐调色板系数(可选) | R,G,B |
shN_labels.webp | 球谐调色板索引,可选 | R,G |
- 默认应使用无损 WebP,精确保留量化值。
- meta.json 各属性指定文件名,因此可以使用其他支持 8 位 RGBA 的格式。
- 不要使用有损编码,它会破坏数值,造成可见或结构性伪影。
1.1 图像尺寸与索引
除 shN_centroids 外,所有属性图像的同一像素 x、y 都属于同一高斯单元。
- 像素按行优先排列,原点在左上。
- 宽 W、高 H 的图像可寻址
W*H个单元。 meta.count必须满足<= W*H,末尾多余像素忽略。
从零开始的索引公式:
- 索引转像素:
x = i % W,y = floor(i / W) - 像素转索引:
i = x + y * W
1.2 坐标系
SOG 与 3DGS PLY 使用同一坐标系。放入 +Y 向上的右手世界(如 PlayCanvas)时,将场景整体绕 Z 旋转 180 度,即 (x, y, z) → (−x, −y, z),与 v3 相同。
1.3 打包变体
打包 SOG 是上述文件的 ZIP。读取器应支持两种布局:
- 多文件目录,推荐制作期间使用。
- 单归档,例如 scene.sog,相同文件位于归档根目录。
读取器必须解压后,像多文件版本一样通过 meta.json 解析文件。
2. meta.json
interface Meta {
version: 2; // File format version (integer)
asset?: { // Optional tool/version metadata
generator?: string; // e.g. "splat-transform v1.2.3"
};
count: number; // Number of gaussians (<= W*H of the images)
model?: "antialiased" | "2dgs"; // Optional. How the scene was trained; omitted for ordinary gaussians
means: {
// Ranges for decoding *log-transformed* positions (see §3.1).
mins: [number, number, number]; // min of nx,ny,nz (log-domain)
maxs: [number, number, number]; // max of nx,ny,nz (log-domain)
files: ["means_l.webp", "means_u.webp"];
};
scales: {
codebook: number[]; // 256 floats (log-domain); see §3.3
files: ["scales.webp"];
};
quats: {
files: ["quats.webp"]; // §3.2
};
sh0: {
codebook: number[]; // 256 floats; DC coefficients for gamma-space color (§3.4)
files: ["sh0.webp"];
};
// Present only if higher-order SH exist:
shN?: {
count: number; // Palette size (up to 65536)
bands: number; // Number of SH bands (1..3). DC (=band 0) lives in sh0.
codebook: number[]; // 256 floats; shared for all AC coefficients (§3.5)
files: [
"shN_centroids.webp",// Palette of AC coefficients as pixels (§3.5)
"shN_labels.webp" // Per-gaussian palette index (0..count-1)
];
};
}
- scales 码本保存对数值,需 exp() 恢复线性尺寸(§3.3)。sh0 码本为伽马空间 DC 系数,shN 为伽马空间 AC 系数。
- 图像数据必须按原始 8 位整数处理,不做伽马转换。
- 除非另有说明,未提到的通道忽略。
- files 数组中文件名任意,但顺序有意义。
3. 属性编码
3.1 位置
means_l.webp、means_u.webp,RGB,每轴 16 位。
每轴通过两张图像量化为 16 位:
// 16-bit normalized value per axis (0..65535)
const qx = (means_u.r << 8) | means_l.r;
const qy = (means_u.g << 8) | means_l.g;
const qz = (means_u.b << 8) | means_l.b;
// Dequantize into *log-domain* nx,ny,nz using per-axis ranges from meta:
const nx = lerp(meta.means.mins[0], meta.means.maxs[0], qx / 65535);
const ny = lerp(meta.means.mins[1], meta.means.maxs[1], qy / 65535);
const nz = lerp(meta.means.mins[2], meta.means.maxs[2], qz / 65535);
// Undo the symmetric log transform used at encode time:
const unlog = (n: number) => Math.sign(n) * (Math.exp(Math.abs(n)) - 1);
const p = {
x: unlog(nx),
y: unlog(ny),
z: unlog(nz),
};
3.2 朝向
quats.webp,RGBA,26 位“最小三分量”。
四元数使用标准最小三分量编码:3×8 位分量 + 2 位模式,总计 26 位。
四元数分量始终按 w、x、y、z 排列。
- RGB 按 w、x、y、z 顺序保存三个保留的有符号分量,均匀量化到
[-√2/2, +√2/2]。编码器应舍入到最近值,而非截断。 - A 保存 252–255 的模式,
mode = A - 252,取 0、1、2、3,表示 w、x、y、z 中绝对值最大的分量;该分量被省略并重建。 - 令
norm = Math.SQRT2,即 √2。
// Dequantize the stored three components:
const toComp = (c: number) => (c / 255 - 0.5) * 2.0 / Math.SQRT2;
const a = toComp(quats.r);
const b = toComp(quats.g);
const c = toComp(quats.b);
const mode = quats.a - 252; // 0..3 → omitted component is w, x, y or z respectively
// Reconstruct the omitted component so that ||q|| = 1 and w.l.o.g. the omitted one is non-negative
const t = a*a + b*b + c*c;
const d = Math.sqrt(Math.max(0, 1 - t));
// Place components according to mode; q is ordered [w, x, y, z]
let q: [number, number, number, number];
switch (mode) {
case 0: q = [d, a, b, c]; break; // omitted = w
case 1: q = [a, d, b, c]; break; // omitted = x
case 2: q = [a, b, d, c]; break; // omitted = y
case 3: q = [a, b, c, d]; break; // omitted = z
default: throw new Error("Invalid quaternion mode");
}
有效性限制
- quats.a 必须为 252、253、254、255,其他值保留。
3.3 缩放
scales.webp,RGB,使用码本。
各轴尺寸存为码本索引,码本是对数域数值,需指数运算解码:
const sx = Math.exp(meta.scales.codebook[scales.r]); // r,g,b are 0..255
const sy = Math.exp(meta.scales.codebook[scales.g]);
const sz = Math.exp(meta.scales.codebook[scales.b]);
尺寸含义(例如主轴标准差或完整范围)遵循源训练设置;exp 后的解码值单位为场景单位。
3.4 基础颜色与不透明度(DC)
sh0.webp(RGBA)
sh0 保存各颜色通道的 **DC(l=0)**球谐系数和 alpha:
- RGB 是 sh0.codebook 中 0–255 的索引。
- A 是 [0,1] 不透明度,即 sh0.a / 255。编码器应舍入到最近量化值,而非截断。
将 DC 系数转换为伽马空间 RGB:
// SH_C0 = Y_0^0 = 1 / (2 * sqrt(pi))
const SH_C0 = 0.28209479177387814;
const r = 0.5 + meta.sh0.codebook[sh0.r] * SH_C0;
const g = 0.5 + meta.sh0.codebook[sh0.g] * SH_C0;
const b = 0.5 + meta.sh0.codebook[sh0.b] * SH_C0;
const a = sh0.a / 255;
3.5 高阶球谐(可选)
shN_centroids.webp,shN_labels.webp
存在高阶 AC 系数时,通过调色板存储:
- shN.count 为条目数,范围 [1,64k]。
- shN.bands 为每条阶数,范围 [1,3]。
质心(调色板)
- shN_centroids.webp 是保存球谐系数调色板的 RGB 图像。
- 每行固定 64 个条目,从左上开始按行优先打包。
纹理宽度取决于阶数:
| 阶数 | 系数数 | 纹理宽度(像素) |
|---|---|---|
| 1 | 3 | 64 * 3 = 192 |
| 2 | 8 | 64 * 8 = 512 |
| 3 | 15 | 64 * 15 = 960 |
计算球谐条目 n 的系数 c 对应像素位置:
const coeffs = [3, 8, 15];
const u = (n % 64) * coeffs[bands - 1] + c;
const v = Math.floor(n / 64);
标签
- shN_labels.webp 为每单元保存 16 位索引,范围 0 到 count-1。
const index = shN_labels.r + (shN_labels.g << 8);
解码
质心像素的 RGB 是共享 shN.codebook 的 0–255 码本索引,分别对应球谐系数三个颜色通道。
解码单个单元的完整 AC 球谐系数:
const coeffs = [3, 8, 15];
const shCoeffs = coeffs[meta.shN.bands - 1]; // coefficients per color channel
const codebook = meta.shN.codebook; // 256 shared float entries
// 1. Look up this gaussian's palette entry
const label = shN_labels.r + (shN_labels.g << 8);
// 2. For each coefficient, read RGB from the centroids pixel and map through the codebook
for (let c = 0; c < shCoeffs; c++) {
const u = (label % 64) * shCoeffs + c;
const v = Math.floor(label / 64);
// pixel (u, v) in shN_centroids.webp
const r = centroidsPixel(u, v).r;
const g = centroidsPixel(u, v).g;
const b = centroidsPixel(u, v).b;
sh_channel0[c] = codebook[r]; // SH AC coefficient for color channel 0
sh_channel1[c] = codebook[g]; // SH AC coefficient for color channel 1
sh_channel2[c] = codebook[b]; // SH AC coefficient for color channel 2
}
一至三阶的基函数、系数顺序和求值与 v3 相同。
4. meta.json 示例
{
"version": 2,
"asset": { "generator": "splat-transform v1.2.3" },
"count": 187543,
"model": "antialiased",
"means": {
"mins": [-2.10, -1.75, -2.40],
"maxs": [ 2.05, 2.25, 1.90],
"files": ["means_l.webp", "means_u.webp"]
},
"scales": {
"codebook": [/* 256 floats */],
"files": ["scales.webp"]
},
"quats": { "files": ["quats.webp"] },
"sh0": {
"codebook": [/* 256 floats */],
"files": ["sh0.webp"]
},
"shN": {
"count": 65536,
"bands": 3,
"codebook": [/* 256 floats */],
"files": ["shN_centroids.webp", "shN_labels.webp"]
}
}
5. 版本与兼容
- 读取器必须检查 version。本页是 v2,当前规范见 v3。
- 未来可能增加可选属性,读取器应忽略未知字段。