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
+20 -8
View File
@@ -1,3 +1,4 @@
import * as THREE from "three";
import { Dyno, unindentLines } from "./base";
import { Gsplat, defineGsplat } from "./splats";
import {
@@ -7,30 +8,41 @@ import {
type HasDynoOut,
} from "./value";
export const outputPackedSplat = (gsplat: DynoVal<typeof Gsplat>) =>
new OutputPackedSplat({ gsplat });
export const outputPackedSplat = (
gsplat: DynoVal<typeof Gsplat>,
rgbMinMaxLnScaleMinMax: DynoVal<"vec4">,
) => new OutputPackedSplat({ gsplat, rgbMinMaxLnScaleMinMax });
export const outputRgba8 = (rgba8: DynoVal<"vec4">) =>
new OutputRgba8({ rgba8 });
export class OutputPackedSplat
extends Dyno<{ gsplat: typeof Gsplat }, { output: "uvec4" }>
extends Dyno<
{ gsplat: typeof Gsplat; rgbMinMaxLnScaleMinMax: "vec4" },
{ output: "uvec4" }
>
implements HasDynoOut<"uvec4">
{
constructor({ gsplat }: { gsplat?: DynoVal<typeof Gsplat> }) {
constructor({
gsplat,
rgbMinMaxLnScaleMinMax,
}: {
gsplat?: DynoVal<typeof Gsplat>;
rgbMinMaxLnScaleMinMax?: DynoVal<"vec4">;
}) {
super({
inTypes: { gsplat: Gsplat },
inputs: { gsplat },
inTypes: { gsplat: Gsplat, rgbMinMaxLnScaleMinMax: "vec4" },
inputs: { gsplat, rgbMinMaxLnScaleMinMax },
globals: () => [defineGsplat],
statements: ({ inputs, outputs }) => {
const { output } = outputs;
if (!output) {
return [];
}
const { gsplat } = inputs;
const { gsplat, rgbMinMaxLnScaleMinMax } = inputs;
if (gsplat) {
return unindentLines(`
if (isGsplatActive(${gsplat}.flags)) {
${output} = packSplat(${gsplat}.center, ${gsplat}.scales, ${gsplat}.quaternion, ${gsplat}.rgba);
${output} = packSplatEncoding(${gsplat}.center, ${gsplat}.scales, ${gsplat}.quaternion, ${gsplat}.rgba, ${rgbMinMaxLnScaleMinMax});
} else {
${output} = uvec4(0u, 0u, 0u, 0u);
}
+5 -4
View File
@@ -118,6 +118,7 @@ export const definePackedSplats = unindent(`
struct PackedSplats {
usampler2DArray texture;
int numSplats;
vec4 rgbMinMaxLnScaleMinMax;
};
`);
@@ -137,10 +138,10 @@ export class NumPackedSplats extends UnaryOp<
}
const defineReadPackedSplat = unindent(`
bool readPackedSplat(usampler2DArray texture, int numSplats, int index, out Gsplat gsplat) {
bool readPackedSplat(usampler2DArray texture, int numSplats, vec4 rgbMinMaxLnScaleMinMax, int index, out Gsplat gsplat) {
if ((index >= 0) && (index < numSplats)) {
uvec4 packed = texelFetch(texture, splatTexCoord(index), 0);
unpackSplat(packed, gsplat.center, gsplat.scales, gsplat.quaternion, gsplat.rgba);
unpackSplatEncoding(packed, gsplat.center, gsplat.scales, gsplat.quaternion, gsplat.rgba, rgbMinMaxLnScaleMinMax);
return true;
} else {
return false;
@@ -173,7 +174,7 @@ export class ReadPackedSplat
let statements: string[];
if (packedSplats && index) {
statements = unindentLines(`
if (readPackedSplat(${packedSplats}.texture, ${packedSplats}.numSplats, ${index}, ${gsplat})) {
if (readPackedSplat(${packedSplats}.texture, ${packedSplats}.numSplats, ${packedSplats}.rgbMinMaxLnScaleMinMax, ${index}, ${gsplat})) {
bool zeroSize = all(equal(${gsplat}.scales, vec3(0.0, 0.0, 0.0)));
${gsplat}.flags = zeroSize ? 0u : GSPLAT_FLAG_ACTIVE;
} else {
@@ -238,7 +239,7 @@ export class ReadPackedSplatRange
statements = unindentLines(`
${gsplat}.flags = 0u;
if ((${index} >= ${base}) && (${index} < (${base} + ${count}))) {
if (readPackedSplat(${packedSplats}.texture, ${packedSplats}.numSplats, ${index}, ${gsplat})) {
if (readPackedSplat(${packedSplats}.texture, ${packedSplats}.numSplats, ${packedSplats}.rgbMinMaxLnScaleMinMax, ${index}, ${gsplat})) {
bool zeroSize = all(equal(${gsplat}.scales, vec3(0.0, 0.0, 0.0)));
${gsplat}.flags = zeroSize ? 0u : GSPLAT_FLAG_ACTIVE;
}