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 -5
View File
@@ -1,6 +1,10 @@
import { unzipSync } from "fflate";
import { FileLoader, Loader, type LoadingManager } from "three";
import { PackedSplats } from "./PackedSplats";
import {
DEFAULT_SPLAT_ENCODING,
PackedSplats,
type SplatEncoding,
} from "./PackedSplats";
import { SplatMesh } from "./SplatMesh";
import { PlyReader } from "./ply";
import { withWorker } from "./splatWorker";
@@ -110,11 +114,14 @@ export class SplatLoader extends Loader {
await Promise.all(promises);
if (onLoad) {
const splatEncoding =
this.packedSplats?.splatEncoding ?? DEFAULT_SPLAT_ENCODING;
const decoded = await unpackSplats({
input,
extraFiles,
fileType,
pathOrUrl: resolvedURL,
splatEncoding,
});
if (this.packedSplats) {
@@ -396,11 +403,13 @@ export async function unpackSplats({
extraFiles,
fileType,
pathOrUrl,
splatEncoding,
}: {
input: Uint8Array | ArrayBuffer;
extraFiles?: Record<string, ArrayBuffer>;
fileType?: SplatFileType;
pathOrUrl?: string;
splatEncoding?: SplatEncoding;
}): Promise<{
packedArray: Uint32Array;
numSplats: number;
@@ -422,7 +431,11 @@ export async function unpackSplats({
await ply.parseHeader();
const numSplats = ply.numSplats;
const maxSplats = getTextureSize(numSplats).maxSplats;
const args = { fileBytes, packedArray: new Uint32Array(maxSplats * 4) };
const args = {
fileBytes,
packedArray: new Uint32Array(maxSplats * 4),
splatEncoding,
};
return await withWorker(async (worker) => {
const { packedArray, numSplats, extra } = (await worker.call(
"unpackPly",
@@ -441,6 +454,7 @@ export async function unpackSplats({
"decodeSpz",
{
fileBytes,
splatEncoding,
},
)) as {
packedArray: Uint32Array;
@@ -456,6 +470,7 @@ export async function unpackSplats({
"decodeAntiSplat",
{
fileBytes,
splatEncoding,
},
)) as { packedArray: Uint32Array; numSplats: number };
return { packedArray, numSplats };
@@ -465,7 +480,7 @@ export async function unpackSplats({
return await withWorker(async (worker) => {
const { packedArray, numSplats, extra } = (await worker.call(
"decodeKsplat",
{ fileBytes },
{ fileBytes, splatEncoding },
)) as {
packedArray: Uint32Array;
numSplats: number;
@@ -478,7 +493,7 @@ export async function unpackSplats({
return await withWorker(async (worker) => {
const { packedArray, numSplats, extra } = (await worker.call(
"decodePcSogs",
{ fileBytes, extraFiles },
{ fileBytes, extraFiles, splatEncoding },
)) as {
packedArray: Uint32Array;
numSplats: number;
@@ -491,7 +506,7 @@ export async function unpackSplats({
return await withWorker(async (worker) => {
const { packedArray, numSplats, extra } = (await worker.call(
"decodePcSogsZip",
{ fileBytes },
{ fileBytes, splatEncoding },
)) as {
packedArray: Uint32Array;
numSplats: number;