Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

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

  1. 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.
  2. Narrow-range filter (fluid_narrow_x → fluid_narrow_y, ping-ponged iterations times). 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 the fluid_narrow_filter shader module, included by both axes.
  3. Resolve (one of fluid_resolve, fluid_resolve_col, fluid_resolve_foam, fluid_resolve_col_foam). Packs smoothed depth, blurred thickness and the foam fraction into data, and the mean liquid colour along each ray into liq. Which one runs is decided by whether the records carry a colour and a foam weight, because that changes which textures the pass binds.
  4. 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.