Splats · function

sortSplatsByDepth

Order the splats worth drawing far to near, writing indices into request.out.

Explained in Gaussian splats.

function sortSplatsByDepth(request: SplatSortRequest, scratch: SplatSortScratch): number
import { sortSplatsByDepth } from '@driftengine/splats';

Parameters

ParameterTypeDescription
requestSplatSortRequest
scratchSplatSortScratch

In depth

Returns how many were written, which is count unless a budget cut it.

Far to near, and the direction is the one thing a test can catch that a screenshot cannot. A near-to-far order looks plausible — the cloud is still a cloud — and is wrong at every silhouette, because over compositing is not commutative. dir is the camera's forward in the capture's own space, so a larger projection along it is farther away and this sorts descending.

A counting sort rather than a comparison sort, which is what makes it affordable at a million splats: one pass for the range, one to bucket, one prefix sum, one to scatter, all linear. Array.prototype.sort on a million indices is tens of milliseconds and allocates.

Sixteen bits is 65,536 distinct depths across the capture's whole extent. At a capture ten metres deep that is 0.15 mm a bucket, far below what any ordering error could show. What it gives up is exactness: two splats inside one bucket keep their input order rather than their true order, which is a tie broken arbitrarily and invisible. What would make it wrong is a capture whose depth range is dominated by one distant outlier, which squeezes everything else into a handful of buckets — the same failure a histogram always has, and the reason the range is measured rather than assumed.

A budget selects by screen-space size, and taking a prefix of the order instead would be wrong in a way that looks deliberate. The order is far to near, so its front is the far end: splatBudget.test.ts measures that, rather than leaving it to be recalled. A prefix therefore keeps the distant half of a capture and throws away everything close to the camera — the splats covering the most pixels, and most of what a viewer is looking at. A suffix inverts the mistake and drops the backdrop. Neither is a budget; both are a capture with a piece missing.

So the splats kept are the ones largest on screen, which is the extent over the distance, and the ones dropped are by construction smaller than every one kept. What that gives up is stability: as the camera closes on a capture, splats cross the threshold and appear, which is a pop bounded by the size of the smallest splat still being drawn — around a pixel at any budget worth setting, and unmissable at a budget of a few thousand. What would make it wrong is a capture of very flat splats seen edge-on, where the largest sigma over-estimates the pixels covered; SplatData.extents carries that same caveat, because it is the same approximation.