worldEffect.ts
examples/spark/js/worldEffect.ts · raw · open the example · all examples · site home
Exports: EffectPayloadworldEffectGeneratoreffectModel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
// Spark's per-mesh world modifiers (each mesh with its own uniforms, such as
// its index in a sequence) on Radix, whose renderer has one modifier for
// every object: one generator wraps fromSplats, and its bind (run before
// each object's dispatch) sets the object's index and world matrices, so
// `effect(splat, objectIndex)` sees world-space centers and scales and
// returns the changed splat. Rotations stay in object space (the effects
// here only move and scale splats). Objects' spherical harmonics are added
// first, in object space, seen from the generator context's view origin: a
// GpuShReader held by the generator, reading whatever SH each file has.
import * as THREE from "three";
import { GpuShReader, SplatGenerator, TSL, type GpuArrays, type SplatLayout, type SplatNode, type TslNode } from "@sparkjsdev/radix";
import { SplatModel } from "./objects.js";
const { uniform, vec4 } = TSL;
// An object's data: { splats, index, model, ... } (the effect's own values
// ride along)
export interface EffectPayload {
splats: GpuArrays;
index: number;
model: THREE.Object3D;
[extra: string]: unknown;
}
export function worldEffectGenerator(
effect: (splat: SplatNode, objectIndex: TslNode) => SplatNode,
{ layout = "extSplats", perObject }: { layout?: SplatLayout; perObject?: (data: EffectPayload) => void } = {},
): SplatGenerator<EffectPayload> {
const base = SplatGenerator.fromSplats({ layout });
// Degree 3 ext reads files with less SH, or none (their bands cost nothing)
const sh = new GpuShReader({ codec: "ext", degree: 3 });
const objectIndex = uniform(0, "int");
const toWorld = uniform(new THREE.Matrix4());
const toObject = uniform(new THREE.Matrix4());
// The matrix's mean scale (scales of non-uniformly scaled objects are approximate)
const scale = uniform(1);
return {
generateSplat(index, context = SplatGenerator.context) {
const splat = sh.apply(base.generateSplat!(index, context), index, context.viewOrigin).toVar();
splat.get("center").assign(toWorld.mul(vec4(splat.get("center"), 1)).xyz);
splat.get("scales").assign(splat.get("scales").mul(scale));
const out = effect(splat, objectIndex).toVar();
out.get("center").assign(toObject.mul(vec4(out.get("center"), 1)).xyz);
out.get("scales").assign(out.get("scales").div(scale));
return out;
},
// perObject(data) sets the effect's own per-object uniforms
bind(data, context) {
base.bind?.(data, context);
sh.bind(data.splats);
objectIndex.value = data.index;
const matrix = data.model.matrixWorld;
toWorld.value.copy(matrix);
toObject.value.copy(matrix).invert();
scale.value = Math.cbrt(Math.abs(matrix.determinant()));
perObject?.(data);
},
};
}
// A SplatModel drawing `arrays` through such a generator, as object `index`
// (extra joins its payload: the effect's own values)
export function effectModel(generator: SplatGenerator<EffectPayload>, arrays: GpuArrays, index: number, extra: Record<string, unknown> = {}) {
const payload = { splats: arrays, index, ...extra } as EffectPayload;
const model = new SplatModel({ generator, count: arrays.count, payload });
payload.model = model;
return model;
}