Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

Pixel water material

<PixelWaterMaterial> is the material of the mesh it sits in, drawn as pixel-art water, as Luminati draws its rivers. <PixelWaterfallMaterial> draws a falling sheet the same way. A block map’s water cells draw with the same water and need no mesh of yours. Use these with pixel textures under the diorama look; the water material and the waterfall material draw smooth water for a game that is not pixel art.

Their props are in PixelWaterMaterialProps and PixelWaterfallMaterialProps.

Every cell of pixelsPerMetre, 16 unless you set it, takes one colour, so the water lines up with the ground’s texels. The water draws the following:

  • Bands of bandColors, two to seven from the shore to the deepest, each cell exactly one of them. The bed shelves 0.6 m down for each metre out from the shore, and the bands span shallowDepth of it, so a channel 0.35 m deep still shows its shallow bands along its banks. A rock or a post stands on the bed and raises none of it, so deep water rings it. A cell near a band’s edge is dithered on a 4 by 4 Bayer pattern.
  • Wave crests of rippleColor: scattered strokes carried downstream at flowSpeed, each an arc 0.3 to 1.4 m long with its own place and bend, one to three texels thick, the thicker among the longer, lying within about 35 degrees of across the flow. Each fades in or out whole. Open water holds a stroke in about nine of every ten 0.8 m squares, and the wake below what stands in the flow holds one in each. Crests keep off a bank’s rim, and four times foamWidth off a rock or a post, leaving its rings in calm water. rippleDensity sets how many, from 0, none, to 1; calm water holds one in every square from about 0.56. Still water’s strokes shimmer in place.
  • Patches of open water, a band deeper or shallower, drifting with the flow, so its deep colour comes and goes; the bands along a bank stay.
  • Foam of foamColor: a rim broken pixel by pixel along the shore, foamWidth wide. Round a rock or a post, rings roll in toward it from four times as far, two texels wide in rippleColor. One at a time crosses the outer half of that reach, breaking in and out at its edges, so dark water parts the rings from the rock. A shore is dry ground beside the water at or over its bed; the map’s edge and a drop are not.
  • Glints, plus shapes in 4 by 4 cells that come and go on the crests, oftener where the water is stirred, the key light’s colour times glintIntensity, 3, so they pass the bloom’s threshold. At night the key light is the moon and the multiple is nightGlintIntensity, 1.7.

The water reflects nothing. Three’s Lambert lighting lights it with the scene’s sun, fill and lamps, the fill greyed by 0.35 toward its own brightness and the result resaturated by 1.12, so a lilac golden hour or a blue night never tints it. It turns toward the fog’s colour at a grazing angle, half as far where the sun is shaded. By night the surface takes 0.6 of its lit colour, so the moon leaves a river deep blue; a fall keeps its full colour. The surface glows: nightEmissiveIntensity of that darkened colour, 0.06, blended in by the hour’s night share, so a river reads under the moon. Under an overcast sky from weather() it dims to 0.66 and its glints to 0.55.

The numbers are Luminati’s, read from its live build: the fill’s 0.35 and 1.12, the 3.2 s ripple period, the 4 by 4 glint cells at 3 and 1.7, and the overcast’s 0.66 and 0.55. The material is opaque, so it writes its depth and the depth of field blurs what stands behind it, and it draws in one pass. Under WebGPU it draws nothing, as the WebGPU page lists.

On a plane, the shore is the edge of the plane’s texture coordinates, or the circle inside them with round, measured in metres as the mesh draws. The flow runs at flowSpeed toward flowDirectionRadians, turned from +z toward +x, +z unless you set it. A fall’s sheet takes its texture coordinates as the waterfall material’s do: u across, the water falling from the lip at v = 1 to the foot at v = 0. Its streaks are a texel wide and step down the grid at fallSpeed, each column at a pace of its own, with a crest at the lip and broken foam at the lip and the foot.

packages/engine/test/outside/PixelFallPool.tsx
import {
PixelWaterfallMaterial,
PixelWaterMaterial,
Views,
} from "@spawnite/engine";
// A pixel waterfall down a cliff face into a round pixel pool at its
// foot: a sheet 3.5 m tall stood upright, its lip at the top of its v,
// and a pond plane laid flat just over the ground, drifting away from
// the fall.
export function PixelFallPool() {
return (
<Views>
<mesh position={[0, 1.75, -1.8]}>
<planeGeometry args={[2, 3.5]} />
<PixelWaterfallMaterial fallSpeed={4} bottomFoamHeight={0.8} />
</mesh>
<mesh position={[0, 0.05, 0.2]} rotation={[-Math.PI / 2, 0, 0]}>
<planeGeometry args={[4, 4]} />
<PixelWaterMaterial round flowSpeed={0.45} />
</mesh>
</Views>
);
}

The diorama village kit’s waterfall object builds the sheet from a river’s end to the pool below, with its mist and splash.

A block with water: true draws this water with its defaults; water takes the same options, so a block sets its river’s colours and flow, and two rivers of two looks are two blocks. On a block map the bands, the foam and the flow read a shore texture the view bakes at 8 texels a metre, holding each point’s metres to the shore, its depth and its drift. Where the map’s edge cuts the water, its side shows a crest of foam over the bands, down from the surface.

What a map object stands in the water is shore too, so a rock in a stream gets a rim, foam lines round it and a wake below it. A collider of the object’s counts where it crosses the water’s surface and is no trigger: a box, a sphere, a capsule, a cylinder or a cone. A mesh or a hull collider stands in no shore. A deck clear over the water is no shore either.

A block’s flowDirectionRadians sends its water one way. Without it, the water drifts downhill within its reach, the cells of one surface joined side to side, at the block’s flowSpeed:

  • A reach that falls into lower water flows toward the fall.
  • A reach with no fall flows toward its cells at the map’s edge.
  • A reach with neither lies still, as a pond does, and no reach drains uphill through a higher one.
  • The walk to the outlet takes diagonal steps too, so a channel that steps a cell aside still flows along itself.

A flowing reach with no fall that meets the map’s edge at two places, such as a level river across the map, leaves its way open. Development throws, naming a cell and asking for a flowDirectionRadians; a deployed game logs it and holds that reach still.

Unity’s Stylized Water 2, Unreal’s water material and Godot’s shader examples band their colour by depth from the depth buffer, draw foam at the shore and drift ripples on a flow map, and every one reflects or refracts. Unreal’s rivers take their flow from a spline, Unity HDRP’s water from a current map. Luminati’s pixel water does neither: it lights itself and turns toward the fog. Unity’s and Godot’s foam round a rock comes from the depth buffer’s intersection; this water draws in one pass with no depth texture, so a block map bakes its objects’ colliders into the shore texture instead. Here the look is a material of its own beside the smooth water, as three’s toon material stands beside its standard one, and on a block map the look lives on the block, as a Godot GridMap item carries its own material.