Skip to content

Commit f60c2b0

Browse files
authored
Custom/extended splat encoding ranges, Premultiplied alpha for hyper-saturated colors (#134)
* Externalized SparkRenderer.maxPixelRadius and .minAlpha settings. Added to documentation. Added to examples/editor under Debug folder, moved sort32 there. * Added SplatEncoding concept, applied individually to SparkRenderer (rgbMin/Max, lnScaleMinMax), SplatMesh (additionally sh1Min/Max, sh2, sh3). Updated examples/editor to control settings via gui. Updated all related code to pipe along settings where needed. Added SparkRenderer.premultipliedAlpha to control blend mode and enable hyper-saturated color contributions by default. * Fix SplatLoader not passing along encoding. Complete renaming to splat encoding.
1 parent f4b4522 commit f60c2b0

19 files changed

Lines changed: 568 additions & 149 deletions

examples/editor/index.html

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
import * as THREE from "three";
7070
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
7171
import { GUI } from "lil-gui";
72-
import { constructGrid, SparkControls, SparkRenderer, SplatMesh, textSplats, dyno, transcodeSpz, isMobile, isPcSogs } from "@sparkjsdev/spark";
72+
import { constructGrid, SparkControls, SparkRenderer, SplatMesh, textSplats, dyno, transcodeSpz, isMobile, isPcSogs, LN_SCALE_MIN, LN_SCALE_MAX } from "@sparkjsdev/spark";
7373
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
7474

7575
const scene = new THREE.Scene();
@@ -230,15 +230,21 @@
230230
applyCameraFromQuery();
231231

232232
const cameraFolder = gui.addFolder("Camera");
233-
cameraFolder.add(camera.position, "x", -10, 10, 0.01).name("X").listen();
234-
cameraFolder.add(camera.position, "y", -10, 10, 0.01).name("Y").listen();
235-
cameraFolder.add(camera.position, "z", -10, 10, 0.01).name("Z").listen();
236-
const rotX = cameraFolder.add(camera.rotation, "x", -Math.PI, Math.PI, 0.01).name("RotateX").listen();
237-
const rotY = cameraFolder.add(camera.rotation, "y", -Math.PI, Math.PI, 0.01).name("RotateY").listen();
238-
const rotZ = cameraFolder.add(camera.rotation, "z", -Math.PI, Math.PI, 0.01).name("RotateZ").listen();
239-
cameraFolder.add(camera, "fov", 1, 179, 1).name("Fov Y degrees").listen().onChange((value) => {
233+
const cameraPose = cameraFolder.addFolder("Camera Pose");
234+
cameraPose.add(camera.position, "x", -10, 10, 0.01).name("X").listen();
235+
cameraPose.add(camera.position, "y", -10, 10, 0.01).name("Y").listen();
236+
cameraPose.add(camera.position, "z", -10, 10, 0.01).name("Z").listen();
237+
const rotX = cameraPose.add(camera.rotation, "x", -Math.PI, Math.PI, 0.01).name("RotateX").listen();
238+
const rotY = cameraPose.add(camera.rotation, "y", -Math.PI, Math.PI, 0.01).name("RotateY").listen();
239+
const rotZ = cameraPose.add(camera.rotation, "z", -Math.PI, Math.PI, 0.01).name("RotateZ").listen();
240+
cameraPose.add(camera, "fov", 1, 179, 1).name("Fov Y degrees").listen().onChange((value) => {
240241
camera.updateProjectionMatrix();
241242
});
243+
cameraPose.close();
244+
245+
function touch() {
246+
spark.needsUpdate = true;
247+
}
242248

243249
// Progress bar functions
244250
const progressBar = document.getElementById('progress-bar');
@@ -383,6 +389,7 @@
383389
}
384390

385391
const init = url ? { url } : { fileBytes: fileBytes.slice(), fileName };
392+
init.splatEncoding = { ...splatEncoding };
386393
const splatMesh = new SplatMesh(init);
387394
const translate = guiOptions.loadOffset * index
388395
splatMesh.position.set(translate, 0.5 * translate, 0.1 * translate);
@@ -480,8 +487,6 @@
480487
stats.dom.style.display = value ? "block" : "none";
481488
});
482489
gui.add(spark.defaultView, "sortRadial").name("Radial sort").listen();
483-
spark.defaultView.sort32 = true;
484-
gui.add(spark.defaultView, "sort32").name("Float32 sort").listen();
485490
gui.add(grid, "opacity", 0, 1, 0.01).name("Grid opacity").listen();
486491
gui.add({
487492
logFocalDistance: 0.0,
@@ -520,6 +525,37 @@
520525
debugFolder.add(spark, "maxPixelRadius", 1, 1024, 1).name("Max pixel radius").listen();
521526
debugFolder.add(spark, "minAlpha", 0, 1, 0.001).name("Min alpha").listen();
522527

528+
debugFolder.add(spark, "premultipliedAlpha").name("Premultiplied alpha").listen();
529+
const accumFolder = debugFolder.addFolder("Accumulator encoding").close();;
530+
accumFolder.add(spark.splatEncoding, "rgbMin", -1, 1, 0.1).name("RGB min").onChange(touch);
531+
accumFolder.add(spark.splatEncoding, "rgbMax", 0, 4, 0.1).name("RGB max").onChange(touch);
532+
accumFolder.add(spark.splatEncoding, "lnScaleMin", -14, -2.5, 0.1).name("Ln scale min").onChange(touch);
533+
accumFolder.add(spark.splatEncoding, "lnScaleMax", -14, 14, 0.1).name("Ln scale max").onChange(touch);
534+
535+
const splatEncoding = {
536+
rgbMin: 0.0,
537+
rgbMax: 1.0,
538+
lnScaleMin: LN_SCALE_MIN,
539+
lnScaleMax: LN_SCALE_MAX,
540+
sh1Min: -1,
541+
sh1Max: 1,
542+
sh2Min: -1,
543+
sh2Max: 1,
544+
sh3Min: -1,
545+
sh3Max: 1,
546+
};
547+
const splatFolder = debugFolder.addFolder("SplatMesh encoding").close();
548+
splatFolder.add(splatEncoding, "rgbMin", -1, 1, 0.1).name("RGB min").onChange(touch);
549+
splatFolder.add(splatEncoding, "rgbMax", 0, 4, 0.1).name("RGB max").onChange(touch);
550+
splatFolder.add(splatEncoding, "lnScaleMin", -14, -2.5, 0.1).name("Ln scale min").onChange(touch);
551+
splatFolder.add(splatEncoding, "lnScaleMax", -14, 14, 0.1).name("Ln scale max").onChange(touch);
552+
splatFolder.add(splatEncoding, "sh1Min", -6, 6, 0.1).name("SH1 min").onChange(touch);
553+
splatFolder.add(splatEncoding, "sh1Max", -6, 6, 0.1).name("SH1 max").onChange(touch);
554+
splatFolder.add(splatEncoding, "sh2Min", -6, 6, 0.1).name("SH2 min").onChange(touch);
555+
splatFolder.add(splatEncoding, "sh2Max", -6, 6, 0.1).name("SH2 max").onChange(touch);
556+
splatFolder.add(splatEncoding, "sh3Min", -6, 6, 0.1).name("SH3 min").onChange(touch);
557+
splatFolder.add(splatEncoding, "sh3Max", -6, 6, 0.1).name("SH3 max").onChange(touch);
558+
523559
const splatsFolder = secondGui.addFolder("Files");
524560

525561
const clipFolder = gui.addFolder("Clip Splats").close();

rust/spark-internal-rs/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub fn raycast_splats(
8282
near: f32, far: f32,
8383
num_splats: u32, packed_splats: Uint32Array,
8484
raycast_ellipsoid: bool,
85+
ln_scale_min: f32, ln_scale_max: f32,
8586
) -> Float32Array {
8687
let mut distances = Vec::<f32>::new();
8788

@@ -94,9 +95,9 @@ pub fn raycast_splats(
9495
subarray.copy_to(subbuffer);
9596

9697
if raycast_ellipsoid {
97-
raycast_ellipsoids(subbuffer, &mut distances, [origin_x, origin_y, origin_z], [dir_x, dir_y, dir_z], near, far);
98+
raycast_ellipsoids(subbuffer, &mut distances, [origin_x, origin_y, origin_z], [dir_x, dir_y, dir_z], near, far, ln_scale_min, ln_scale_max);
9899
} else {
99-
raycast_spheres(subbuffer, &mut distances, [origin_x, origin_y, origin_z], [dir_x, dir_y, dir_z], near, far);
100+
raycast_spheres(subbuffer, &mut distances, [origin_x, origin_y, origin_z], [dir_x, dir_y, dir_z], near, far, ln_scale_min, ln_scale_max);
100101
}
101102

102103
base += chunk_size;

rust/spark-internal-rs/src/raycast.rs

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,19 @@ use half::f16;
22

33
const MIN_OPACITY: f32 = 0.1;
44

5-
pub const LN_SCALE_MIN: f32 = -12.0;
6-
pub const LN_SCALE_MAX: f32 = 9.0;
7-
pub const LN_RESCALE: f32 = (LN_SCALE_MAX - LN_SCALE_MIN) / 254.0; // 1..=255
8-
9-
// pub fn encode_scale(scale: f32) -> u8 {
10-
// if scale == 0.0 {
11-
// 0
12-
// } else {
13-
// // Allow scales below LN_SCALE_MIN to be encoded as 0, which signifies a 2DGS
14-
// ((scale.ln() - LN_SCALE_MIN) / LN_RESCALE + 1.0).clamp(0.0, 255.0).round() as u8
15-
// }
16-
// }
17-
18-
pub fn decode_scale(scale: u8) -> f32 {
5+
pub fn decode_scale(scale: u8, ln_scale_min: f32, ln_scale_max: f32) -> f32 {
196
if scale == 0 {
207
0.0
218
} else {
22-
(LN_SCALE_MIN + (scale - 1) as f32 * LN_RESCALE).exp()
9+
let ln_scale_scale = (ln_scale_max - ln_scale_min) / 254.0;
10+
(ln_scale_min + (scale - 1) as f32 * ln_scale_scale).exp()
2311
}
2412
}
2513

2614
pub fn raycast_spheres(
2715
buffer: &[u32], distances: &mut Vec<f32>,
2816
origin: [f32; 3], dir: [f32; 3], near: f32, far: f32,
17+
ln_scale_min: f32, ln_scale_max: f32,
2918
) {
3019
let quad_a = vec3_dot(dir, dir);
3120

@@ -36,7 +25,7 @@ pub fn raycast_spheres(
3625
}
3726

3827
let origin = vec3_sub(origin, extract_center(packed));
39-
let scale = extract_scale(packed);
28+
let scale = extract_scale(packed, ln_scale_min, ln_scale_max);
4029

4130
// Model the Gsplat as a sphere for faster approximate raycasting
4231
let radius = (scale[0] + scale[1] + scale[2]) / 3.0;
@@ -58,6 +47,7 @@ pub fn raycast_spheres(
5847
pub fn raycast_ellipsoids(
5948
buffer: &[u32], distances: &mut Vec<f32>,
6049
origin: [f32; 3], dir: [f32; 3], near: f32, far: f32,
50+
ln_scale_min: f32, ln_scale_max: f32,
6151
) {
6252
for packed in buffer.chunks(4) {
6353
let opacity = ((packed[0] >> 24) as u8) as f32 / 255.0;
@@ -66,7 +56,7 @@ pub fn raycast_ellipsoids(
6656
}
6757

6858
let origin = vec3_sub(origin, extract_center(packed));
69-
let scale = extract_scale(packed);
59+
let scale = extract_scale(packed, ln_scale_min, ln_scale_max);
7060
let quat = extract_quat(packed);
7161
let inv_quat = [-quat[0], -quat[1], -quat[2], quat[3]];
7262

@@ -139,10 +129,10 @@ fn extract_center(packed: &[u32]) -> [f32; 3] {
139129
[x, y, z]
140130
}
141131

142-
fn extract_scale(packed: &[u32]) -> [f32; 3] {
143-
let scale_x = decode_scale(packed[3] as u8);
144-
let scale_y = decode_scale((packed[3] >> 8) as u8);
145-
let scale_z = decode_scale((packed[3] >> 16) as u8);
132+
fn extract_scale(packed: &[u32], ln_scale_min: f32, ln_scale_max: f32) -> [f32; 3] {
133+
let scale_x = decode_scale(packed[3] as u8, ln_scale_min, ln_scale_max);
134+
let scale_y = decode_scale((packed[3] >> 8) as u8, ln_scale_min, ln_scale_max);
135+
let scale_z = decode_scale((packed[3] >> 16) as u8, ln_scale_min, ln_scale_max);
146136
[scale_x, scale_y, scale_z]
147137
}
148138

0 commit comments

Comments
 (0)