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.
This commit is contained in:
Andreas Sundquist
2025-07-23 10:04:24 -07:00
committed by GitHub
parent f4b4522c56
commit f60c2b0cf9
19 changed files with 568 additions and 149 deletions
+47 -6
View File
@@ -1,7 +1,11 @@
import * as THREE from "three";
import init_wasm, { raycast_splats } from "spark-internal-rs";
import { PackedSplats } from "./PackedSplats";
import {
DEFAULT_SPLAT_ENCODING,
PackedSplats,
type SplatEncoding,
} from "./PackedSplats";
import { type RgbaArray, readRgbaArray } from "./RgbaArray";
import { SplatEdit, SplatEditSdf, SplatEdits } from "./SplatEdit";
import {
@@ -11,6 +15,7 @@ import {
} from "./SplatGenerator";
import type { SplatFileType } from "./SplatLoader";
import type { SplatSkinning } from "./SplatSkinning";
import { LN_SCALE_MAX, LN_SCALE_MIN } from "./defines";
import {
DynoFloat,
DynoUsampler2DArray,
@@ -27,6 +32,7 @@ import {
mul,
normalize,
readPackedSplat,
split,
splitGsplat,
sub,
unindent,
@@ -80,6 +86,9 @@ export type SplatMeshOptions = {
// Gsplat modifier to apply in world-space after transformations.
// (default: undefined)
worldModifier?: GsplatModifier;
// Override the default splat encoding ranges for the PackedSplats.
// (default: undefined)
splatEncoding?: SplatEncoding;
};
export type SplatMeshContext = {
@@ -183,6 +192,9 @@ export class SplatMesh extends SplatGenerator {
});
this.packedSplats = options.packedSplats ?? new PackedSplats();
this.packedSplats.splatEncoding = options.splatEncoding ?? {
...DEFAULT_SPLAT_ENCODING,
};
this.numSplats = this.packedSplats.numSplats;
this.editable = options.editable ?? true;
this.onFrame = options.onFrame;
@@ -226,8 +238,15 @@ export class SplatMesh extends SplatGenerator {
}
async asyncInitialize(options: SplatMeshOptions) {
const { url, fileBytes, fileType, fileName, maxSplats, constructSplats } =
options;
const {
url,
fileBytes,
fileType,
fileName,
maxSplats,
constructSplats,
splatEncoding,
} = options;
if (url || fileBytes || constructSplats) {
const packedSplatsOptions = {
url,
@@ -236,6 +255,7 @@ export class SplatMesh extends SplatGenerator {
fileName,
maxSplats,
construct: constructSplats,
splatEncoding,
};
this.packedSplats.reinitialize(packedSplatsOptions);
}
@@ -321,13 +341,32 @@ export class SplatMesh extends SplatGenerator {
const { center } = splitGsplat(gsplat).outputs;
const viewDir = normalize(sub(center, viewCenterInObject));
function rescaleSh(
sNorm: DynoVal<"vec3">,
minMax: DynoVal<"vec2">,
) {
const { x: min, y: max } = split(minMax).outputs;
const mid = mul(add(min, max), dynoConst("float", 0.5));
const scale = mul(sub(max, min), dynoConst("float", 0.5));
return add(mid, mul(sNorm, scale));
}
// Evaluate Spherical Harmonics
let rgb = evaluateSH1(gsplat, sh1Texture, viewDir);
const sh1Snorm = evaluateSH1(gsplat, sh1Texture, viewDir);
let rgb = rescaleSh(sh1Snorm, this.packedSplats.dynoSh1MinMax);
if (this.maxSh >= 2 && sh2Texture) {
rgb = add(rgb, evaluateSH2(gsplat, sh2Texture, viewDir));
const sh2Snorm = evaluateSH2(gsplat, sh2Texture, viewDir);
rgb = add(
rgb,
rescaleSh(sh2Snorm, this.packedSplats.dynoSh2MinMax),
);
}
if (this.maxSh >= 3 && sh3Texture) {
rgb = add(rgb, evaluateSH3(gsplat, sh3Texture, viewDir));
const sh3Snorm = evaluateSH3(gsplat, sh3Texture, viewDir);
rgb = add(
rgb,
rescaleSh(sh3Snorm, this.packedSplats.dynoSh3MinMax),
);
}
// Flash off for 0.3 / 1.0 sec for debugging
@@ -539,6 +578,8 @@ export class SplatMesh extends SplatGenerator {
this.packedSplats.numSplats,
this.packedSplats.packedArray,
RAYCAST_ELLIPSOID,
this.packedSplats.splatEncoding?.lnScaleMin ?? LN_SCALE_MIN,
this.packedSplats.splatEncoding?.lnScaleMax ?? LN_SCALE_MAX,
);
for (const distance of distances) {