Files
spark/docs/docs/procedural-splats.md
T
2025-06-09 16:47:22 -07:00

291 lines
11 KiB
Markdown

# Procedural Splats
Spark makes it easy to create Gaussian splat collections procedurally, and includes some splat constructors that are useful for common tasks like creating a grid or text made of splats. The example "Procedural Splats" puts some of these to use in a scene.
## Adding splats to a collection
To create a `PackedSplats` with custom, procedurally-derived splats use the methods `pushSplat` or `setSplat`:
```javascript
const splats = new PackedSplats();
const center = new THREE.Vector3(0, 0, 0);
const scales = new THREE.Vector3(0.1, 0.1, 0.1);
const quaternion = new THREE.Quaternion();
const opacity = 1.0;
const color = new THREE.Color()
splats.pushSplat(center, scales, quaternion, opacity, color);
...
```
Note that calling `.pushSplat` and `.setSplat` will automatically resize the `PackedSplats` array to fit the splat. Additionally, the center, scales, etc parameters are encoded into 16 bytes, and the original objects passed in (such as `center: THREE.Vector3`) are no longer used. Re-using these objects for all the splats is a good idea.
Alternatively, you can use the `construct` initializer callback:
```javascript
const splats = new PackedSplats({
construct: (splats) => {
...
for (let i = 0; i < NUM_SPLATS; ++i) {
// Compute splat #i
...
splats.pushSplat(center, scales, quaternion, opacity, color);
}
},
});
```
Once you've created your `PackedSplats`, render it to the scene via a `SplatMesh`:
```javascript
const mesh = new SplatMesh({ packedSplats: splats });
scene.add(mesh);
```
Alternatively, you can create the `SplatMesh` and its splats in the initializer, which internally passes your constructor to its contained `PackedSplats`:
```javascript
const mesh = new SplatMesh({
constructSplats: (splats) => {
for (let i = 0; i < NUM_SPLATS; ++i) {
// Compute splat #i
...
splats.pushSplat(center, scales, quaternion, opacity, color);
}
},
});
scene.add(mesh);
```
## Grid
```javascript
import { constructGrid } from "@sparkjsdev/spark";
const grid = new SplatMesh({
constructSplats: (splats) => constructGrid({
splats,
extents: new THREE.Box3(
new THREE.Vector3(-10, -10, -10),
new THREE.Vector3(10, 10, 10),
),
}),
});
scene.add(grid);
```
### Required parameters
| Parameter | Description |
|-----------|-------------|
| `splats` | PackedSplats object to add splats to |
| `extents` | min and max box extents of the grid |
### Optional parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `stepSize` | `1` | step size along each grid axis |
| `pointRadius` | `0.01` | spherical radius of each splat |
| `pointShadowScale` | `2.0` | relative size of the "shadow copy" of each splat placed behind it |
| `opacity` | `1.0` | splat opacity |
| `color` | RGB-modulated grid | splat color (THREE.Color) or function to set color for position: ((THREE.Color, THREE.Vector3) => void) |
## XYZ axis
```javascript
import { constructAxes } from "@sparkjsdev/spark";
const axes = new SplatMesh({
constructSplats: (splats) => constructAxes({ splats }),
});
axes.position.set(0, 0, -1);
scene.add(axes);
```
### Required parameters
| Parameter | Description |
|-----------|-------------|
| `splats` | PackedSplats object to add splats to |
### Optional parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `scale` | `0.25` | scale (splat scale along axis) |
| `axisRadius` | `0.0075` | radius of the axes (splat scale orthogonal to axis) |
| `axisShadowScale` | `2.0` | relative size of the "shadow copy" of each splat placed behind it |
| `origins` | `[new THREE.Vector3()]` | origins of the axes (default single axis at origin) |
## Splat sphere
```javascript
import { constructSpherePoints } from "@sparkjsdev/spark";
const sphere = new SplatMesh({
constructSplats: (splats) => constructSpherePoints({
splats,
maxDepth: 4,
}),
});
scene.add(sphere);
```
### Required parameters
| Parameter | Description |
|-----------|-------------|
| `splats` | PackedSplats object to add splats to |
### Optional parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `origin` | `new THREE.Vector3()` | center of the sphere (default: origin) |
| `radius` | `1.0` | radius of the sphere |
| `maxDepth` | `3` | maximum depth of recursion for subdividing the sphere. Warning: splat count grows exponentially with depth |
| `filter` | `null` | filter function to apply to each point, for example to select points in a certain direction or other function ((THREE.Vector3) => boolean) |
| `pointRadius` | `0.02` | radius of each oriented splat |
| `pointThickness` | `0.001` | flatness of each oriented splat |
| `color` | `new THREE.Color(1, 1, 1)` | color of each splat (THREE.Color) or function to set color for point: ((THREE.Color, THREE.Vector3) => void) |
## Rasterizing Text
```typescript
const splats = textSplats({
text: string;
font?: string;
fontSize?: number;
color?: THREE.Color;
rgb?: THREE.Color;
dotRadius?: number;
textAlign?: "left" | "center" | "right" | "start" | "end";
lineHeight?: number;
});
scene.add(splats);
```
### Required parameters
| Parameter | Description |
|-----------|-------------|
| `text` | text string to display |
### Optional parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `font` | `"Arial"` | browser font to render text with |
| `fontSize` | `32` | font size in pixels/splats |
| `color` | `new THREE.Color(1, 1, 1)` | SplatMesh.recolor tint assuming white splats |
| `rgb` | `new THREE.Color(1, 1, 1)` | Individual splat color |
| `dotRadius` | `0.8` | splat radius (0.8 covers 1-unit spacing well) |
| `textAlign` | `"start"` | text alignment: "left", "center", "right", "start", "end" |
| `lineHeight` | `1.0` | line spacing multiplier, lines delimited by "\n" |
## Turning images into splats
```typescript
const image = imageSplats({
url: string;
dotRadius?: number;
subXY?: number;
forEachSplat?: (
width: number,
height: number,
index: number,
center: THREE.Vector3,
scales: THREE.Vector3,
quaternion: THREE.Quaternion,
opacity: number,
color: THREE.Color,
) => number | null;
});
scene.add(image);
```
### Required parameters
| Parameter | Description |
|-----------|-------------|
| `url` | URL of the image to convert to splats (example: `url: "./image.png"`) |
### Optional parameters
| Parameter | Default | Description |
|-----------|---------|-------------|
| `dotRadius` | `0.8` | Radius of each splat, default covers 1-unit spacing well |
| `subXY` | `1` | Subsampling factor for the image. Higher values reduce resolution, for example 2 will halve the width and height by averaging |
| `forEachSplat` | `undefined` | Optional callback function to modify each splat before it's added. Return null to skip adding the splat, or a number to set the opacity and add the splat with parameter values in the objects center, rgba etc. were passed into the forEachSplat callback. Ending the callback in `return opacity;` will retain the original opacity. |
### Example
```javascript
// Load RGBA image from image.png, subsample it 2x
// horizontally and vertically, and create splats for
// the resulting pixels that have at least 10% opacity.
const image = imageSplats({
url: "./image.png",
subXY: 2,
forEachSplat: (width, height, index, center, scales, quaternion, opacity, color) => {
// Only keep splats with opacity 10% or higher
return (opacity >= 0.1) ? opacity : null;
},
});
scene.add(image);
```
## Particle Effects
Some of the building blocks are meant to serve as "code inspiration", showing how a particle effect animation can be achieved using a "stateless" `dyno` computation graph that uses only the splat `index` input to produce pseudo-random numbers that drive various randomized particle effects.
Note that splat sorting takes a little bit of time and can "lag behind" the splat updates each frame, so it's important that there is a reasonably stable correspondence between each frame's splat output for the same `index`.
### `generators.staticBox(...options)`
In `staticBox` you provide a 3D box to render random "static" splats within. The AABB is sliced up into a 3D grid of cells, and random X/Y/Z points within those cells are sampled each frame, retaining consistency in splat position between successive frames.
### `generators.snowBox(...options)`
Similarly, `snowBox` produces splat trajectories that move in a deterministic fashion over time, with high similarity between adjacent frames. See "VFX - Particle Simulation" for an example that creates a `snowBox`.
A snowBox instance has a collection of properties that can be tuned to achieve different particle effects. DEFAULT_SNOW and DEFAULT_RAIN are example parameter sets that look a lot like snow and rain, and can be used as a starting point for further tweaking: `const mySnow = { ...DEFAULT_SNOW, density: 500 };`
```typescript
const snowControls = generators.snowBox({
box,
minY,
numSplats,
density,
anisoScale,
minScale,
maxScale,
fallDirection,
fallVelocity,
wanderScale,
wanderVariance,
color1,
color2,
opacity,
onFrame,
});
scene.add(snowControls.snow);
```
| Parameter | Default Value | Description |
|-----------|---------------|-------------|
| `box` | `new THREE.Box3(new THREE.Vector3(-1, -1, -1), new THREE.Vector3(1, 1, 1))` | min and max box extents of the snowBox |
| `minY` | `Number.NEGATIVE_INFINITY` | minimum y-coordinate to clamp particle position, which can be used to fake hitting a ground plane and lingering there for a bit |
| `numSplats` | calculated from box and density | number of splats to generate |
| `density` | `100` | density of splats per unit volume |
| `anisoScale` | `new THREE.Vector3(1, 1, 1)` | The xyz anisotropic scale of the splat, which can be used for example to elongate rain particles |
| `minScale` | `0.001` | Minimum splat particle scale |
| `maxScale` | `0.005` | Maximum splat particle scale |
| `fallDirection` | `new THREE.Vector3(0, -1, 0)` | The average direction of fall |
| `fallVelocity` | `0.02` | The average speed of the fall (multiplied with fallDirection) |
| `wanderScale` | `0.01` | The world scale of wandering overlay motion |
| `wanderVariance` | `2` | Controls how uniformly the particles wander in sync, more variance means more randomness in the motion |
| `color1` | `new THREE.Color(1, 1, 1)` | Color 1 of the two colors interpolated between |
| `color2` | `new THREE.Color(0.5, 0.5, 1)` | Color 2 of the two colors interpolated between |
| `opacity` | `1` | The base opacity of the splats |
| `onFrame` | `undefined` | Optional callback function to call each frame |