Liquid Surface

The Liquid Shader (3D) animates the liquid by displacing the vertices of its mesh, so it needs a mesh that is dense and evenly tessellated. It also needs to know where the liquid meets the shore, in order to fade out the emission there.

LiquidSurface.cs provides both. It turns the first spline of the Spline Container on the same GameObject into a liquid surface mesh: it fills the spline with an evenly tessellated grid of triangles, generates the UVs for it, keeps optional colliders in sync, and writes an edge mask into the red channel of the vertex colors, which the shader uses to fade the emission out towards the outline of the surface.

The surface faces the local +Y axis, so draw the spline in the local XZ plane, as seen from above. How the mesh is built depends on whether the spline is closed or open:

To raise or lower the liquid without editing the spline, use the Vertical Offset of the material, or move the GameObject up or down. The material's offset is applied on the GPU, so it shifts every surface that shares the material, and the colliders stay at the height of the spline. Moving the GameObject takes the colliders along.

Add the component via Add Component > Project GAMEDEV > Essential 3D Shaders > Liquid Surface. It exposes the following settings:

Mesh Detail

The density of the generated mesh (1 – 128), i.e., the number of vertex columns and rows per local unit. The cells of the mesh are kept square, so the waves and the ripples keep their shape whichever direction they travel in. This is the single most important setting of the component: higher values produce smoother, more detailed vertex displacement, at the cost of a heavier mesh. Small pools can afford the maximum, while large lakes and seas need far lower values – see the warning below. Each liquid surface is limited to 65,535 vertices and indices, so that its mesh can keep the compact 16-bit index format. The component respects that limit and reduces the effective detail of very large surfaces rather than fail. If a huge surface looks coarser than a small one at the same Mesh Detail, this is why – split it into several smaller liquid surfaces instead.

Open Spline Width

The total width of the generated strip when the spline is open rather than closed. The spline stays centered inside the strip. This setting is ignored for closed splines, which are simply filled.

UV Mode

How the textures are projected onto the surface. The projection is always planar, from above. Three modes are available:

Tile Size

The number of units per texture repeat in the Local Tiling and World Tiling modes. Larger values make the liquid textures appear larger.

Stretch Tiling

The number of texture repeats across the bounds of the surface in the Stretch To Bounds mode.

Edge Mask Width

The horizontal distance from the outline of the surface over which the edge mask fades out. The mask is stored in the red channel of the vertex colors and is used by the shader to darken the emission near the outline – a wide value produces a broad, cooled rim, while a very small value effectively disables the rim.

Edge Mask Softness

The softness of that falloff (0 – 1). At 0 the mask ends abruptly, giving a hard border; at 1 it fades smoothly across the full edge mask width.

Bounds Padding

The distance added around the bounds of the mesh on every side. Unity culls a mesh based on its bounds, which do not account for the waves and the ripples, so without padding the displaced parts of the liquid could disappear at the edges of the screen. The waves and the ripples raise the surface by at most a tenth of Wave Amplitude plus Ripple Amplitude, in local units; add the Vertical Offset on top of that. The default of 2 comfortably covers all of the included liquids.

Update Colliders

When enabled, a Mesh Collider on the same GameObject receives the generated mesh, and a Box Collider on the same GameObject is fitted to the surface, extended by Bounds Padding above and below. Use them, for example, as a trigger that detects the player falling into lava. The component does not add the colliders itself – add the ones you need.

Liquid Surface rebuilds its mesh automatically, both in the Editor and in Play Mode, whenever the spline, the settings, or the attached colliders change. The mesh is not saved with your scene – it is regenerated whenever the scene loads, so it does not bloat your scene files. A Liquid Surface uses only the first spline of its Spline Container. To create several separate bodies of liquid, give each one its own GameObject with a Liquid Surface. Holes, such as islands, cannot be cut into a surface – let the terrain or other geometry rise above the liquid instead. Keep the bends of an open spline wider than its strip. Tight corners are cleaned up automatically, but where a river passes closer to itself than the width of its strip, the strip will overlap itself.

All settings are also available in code. Each one is exposed as a property of the same name, with the exception of UV Mode, which is exposed as UVProjection. Changes are picked up on the next Update; call Rebuild() to apply them immediately. The component lives in the ProjectGamedev.Shaders.Essential3DShaders.LiquidShader3D namespace.

With liquidSurface being the Liquid Surface component of your liquid, change the settings like so:
liquidSurface.MeshDetail = 32;
liquidSurface.UVProjection = LiquidSurface.UVMode.WorldTiling;
liquidSurface.EdgeMaskWidth = 1f;
liquidSurface.UpdateColliders = false;
liquidSurface.Rebuild();

public int MeshDetail

See Mesh Detail. Values are clamped between 1 and 128.

public float OpenSplineWidth

See Open Spline Width. Values below 0.01 are clamped to 0.01.

public LiquidSurface.UVMode UVProjection

See UV Mode. One of LocalTiling, WorldTiling, or StretchToBounds.

public float TileSize

See Tile Size. Values below 0.01 are clamped to 0.01.

public float StretchTiling

See Stretch Tiling. Values below 0.01 are clamped to 0.01.

public float EdgeMaskWidth

See Edge Mask Width. Values below 0.001 are clamped to 0.001.

public float EdgeMaskSoftness

See Edge Mask Softness. Values are clamped between 0 and 1.

public float BoundsPadding

See Bounds Padding. Negative values are clamped to 0.

public bool UpdateColliders

public void Rebuild()

Rebuilds the mesh immediately. Changes to the spline, the settings, the attached colliders, or (in the World Tiling mode) the transform are otherwise picked up on the next Update.

Mesh Detail Open Spline Width UV Mode Tile Size Stretch Tiling Edge Mask Width Edge Mask Softness Bounds Padding Update Colliders Scripting