Builtin Library
README.md
fluidSurface
A GPU particle simulation, rendered as a continuous liquid.
Water made of particles has a problem: the particles are spheres, and a pile of spheres looks like a pile of spheres. Turning them into a mesh every frame is expensive and the mesh is thrown away immediately. This package skips the mesh — it renders the particles into screen-space fields and shades those, so the particle data never leaves the GPU and the cost scales with screen pixels rather than with the surface's geometric complexity.
What it does
local fluidSurface = require("@builtin::systems.fluidSurface.modules.fluidSurface")
local rts = fluidSurface.attach("lake", {
particles = { buffer = sim, stride = 16, posOffset = 0, count = 400000, velOffset = 4 },
radius = 0.35,
})
particles describes the record layout of the buffer the caller's simulation
writes: how wide a record is, where the position sits in it, and how many are
live. Pass the handle a compute shader's createBuffer answered. Everything
after that happens per frame until detach.
update moves the live knobs — the particle count as a sim grows, the sun
direction as the day turns, any composite property:
fluidSurface.update("lake", { count = active, sunDir = { 0.3, 0.8, 0.2 } })
The chain
- Splat (engine raster). Every particle draws as a velocity-stretched billboard: nearest-surface depth, hardware z-tested; additive thickness and thickness-weighted colour; and, when the records carry a foam weight, an additive foam sprite layer.
- Narrow-range filter (
fluid_narrow_x→fluid_narrow_y, ping-pongediterationstimes). Neighbour depths are clamped into a band around the centre before averaging, so particles fuse into a surface while silhouettes stay crisp. The body is thefluid_narrow_filtershader module, included by both axes. - Resolve (one of
fluid_resolve,fluid_resolve_col,fluid_resolve_foam,fluid_resolve_col_foam). Packs smoothed depth, blurred thickness and the foam fraction intodata, and the mean liquid colour along each ray intoliq. Which one runs is decided by whether the records carry a colour and a foam weight, because that changes which textures the pass binds. - Composite (
fluid_surface_composite, a post-process shader). Refracts the already-rendered scene through the surface normals, absorbs along the view ray by Beer–Lambert, marches the reflected ray against scene depth so the water mirrors what is above it, adds sun specular, and lays foam on the thin edges. Depth-tested against the scene, so fluid behind geometry stays hidden.
Every stage runs at resolutionScale of the screen (half by default).
Sharing
The passes are compiled once and shared by every surface. What a surface owns is its render targets, its own filter-parameter buffer, and its own composite — which is why two lakes at different particle radii filter at different widths without interfering.