Water

BeamNG has several water scene object types:

Class Shape Main use
WaterPlane Infinite horizontal plane Oceans, large lakes, horizon water.
WaterBlock Bounded rectangular water volume Pools, ponds, reservoirs, boxed water areas.
River Spline-shaped water volume Rivers, canals, streams, curved water paths.

All three inherit common water behavior from WaterObject, including water materials, underwater checks, fog/underwater effect support, and optional planar reflections.


Which water object should I use?

Use case Recommended class
Ocean around an island WaterPlane
Very large flat water surface stretching to horizon WaterPlane
Swimming pool / small lake / rectangular pond WaterBlock
Bounded flat water area with rotation/scale WaterBlock
River following a path River
Canal with variable width/depth River
Twisting water body with flow direction River

WaterPlane

WaterPlane represents an infinite horizontal water surface.

It is designed mainly for ocean-style water viewed from near ground level. It extends infinitely in X/Y and is positioned only by height.

WaterPlane ignores rotation and scale. Only the Z value of its position matters.


WaterPlane example

{
  "class": "WaterPlane",
  "name": "ocean_water",
  "position": [0, 0, 0],
  "material": "water_ocean",
  "gridSize": 101,
  "gridElementSize": 1
}

The important part is:

"position": [0, 0, 0]

Only position[2], the Z height, is used as the water level.


WaterPlane fields

Field Type Description
class string Must be "WaterPlane".
name string Scene object name.
position array[3] Water height is taken from position.z. X/Y are not meaningful for coverage.
material string Water material used for rendering.
gridSize integer Render grid size.
gridElementSize number Spacing between water grid vertices.

Infinite bounds

WaterPlane uses global bounds.

This means it is considered to exist everywhere horizontally. It is useful for oceans, but it is not a good fit for small water bodies.

Use WaterBlock instead if the water should have a finite footprint.


Transform behavior

WaterPlane only accepts height changes.

Rotation and scale fields are removed from persistent fields and are not used.

Example:

{
  "class": "WaterPlane",
  "name": "sea_level",
  "position": [0, 0, -2]
}

This creates a water plane at Z = -2.


Grid settings

gridSize and gridElementSize control the render mesh used by the water plane.

"gridSize": 101,
"gridElementSize": 1

WaterPlane renders a camera-centered grid rather than a literal infinite mesh. The water shader projects this grid as a large water surface.

Very large or unusual grid settings can increase render cost or cause visual artifacts.


WaterPlane limitations

WaterPlane is intended for ground-level ocean-style rendering.

Because it is effectively infinite and projected toward the far clip distance, artifacts can appear when large objects intersect the far clip distance or visually line up with the water plane.

Common problem cases:

  • Very high camera altitude
  • Tight far clip distance
  • Large objects near the far clip distance
  • Terrain extending close to or beyond the far clip distance

For best results:

  • Keep terrain and large objects inside the camera far clip range.
  • Avoid using WaterPlane for small pools or bounded areas.
  • Use WaterBlock for local water volumes.

WaterBlock

WaterBlock is a finite rectangular water volume.

It supports position, rotation, and scale. It is useful for bounded water such as lakes, pools, ponds, basins, and other local water areas.

Unlike WaterPlane, WaterBlock has finite bounds and can be tested for containment.


WaterBlock example

{
  "class": "WaterBlock",
  "name": "lake_water",
  "position": [100, 200, 5],
  "rotationMatrix": [1,0,0,0,1,0,0,0,1],
  "scale": [200, 150, 20],
  "material": "water_lake",
  "gridElementSize": 5
}

WaterBlock fields

Field Type Description
class string Must be "WaterBlock".
name string Scene object name.
position array[3] Position of the water block. The water surface is based around the object position plane.
rotationMatrix array[9] Object rotation.
scale array[3] Size of the water volume. X/Y define footprint; Z defines vertical volume/depth behavior.
material string Water material used for rendering.
gridElementSize number Spacing between generated water mesh vertices.
gridSize number Compatibility alias for gridElementSize.

Scale and surface height

WaterBlock uses its object transform and scale to define a rectangular water volume.

Typical setup:

"position": [0, 0, 10],
"scale": [100, 100, 20]

The X/Y scale controls the visible footprint. The Z scale controls the vertical water volume used for underwater/depth tests.

The water surface height is based on the object’s position.


gridElementSize

gridElementSize controls the spacing between generated vertices.

"gridElementSize": 5

Smaller values create denser geometry:

"gridElementSize": 1

Larger values create cheaper, lower-density geometry:

"gridElementSize": 10

If gridElementSize is larger than the block’s X or Y scale, it is clamped to the smaller scale dimension.

Very small gridElementSize values on large WaterBlocks can create a large amount of geometry.

Mesh generation

WaterBlock builds a regular grid over its footprint.

For large blocks, the mesh may be split into multiple vertex/index buffer blocks to stay within 16-bit index limits.

The object regenerates its water mesh when:

  • gridElementSize changes
  • scale changes
  • the editor applies changes
  • static fields are modified

Rotated WaterBlocks

WaterBlock supports rotation.

This can be useful for water volumes placed on custom geometry or non-axis-aligned areas.

Reflection behavior can use either an upward normal or the object’s surface normal depending on inherited reflection settings.


River

River is a spline-shaped water volume.

It is defined by a list of nodes. Each node stores position, width, depth, and normal.

Rivers are useful for streams, canals, drainage channels, winding water paths, or any water volume that needs to follow a curve.


River example

{
  "class": "River",
  "name": "main_river",
  "material": "water_river",
  "SegmentLength": 10,
  "SubdivideLength": 2.5,
  "FlowMagnitudePhysics": 3,
  "LowLODDistance": 50,
  "nodes": [
    [0, 0, 5, 20, 4, 0, 0, 1],
    [50, 10, 5, 22, 4, 0, 0, 1],
    [100, 40, 4, 18, 3, 0, 0, 1],
    [150, 60, 4, 16, 3, 0, 0, 1]
  ]
}

Each river node is stored as:

[x, y, z, width, depth, normalX, normalY, normalZ]

River fields

Field Type Description
class string Must be "River".
name string Scene object name.
material string Water material used for rendering.
nodes array River node list. Each node is [x, y, z, width, depth, normalX, normalY, normalZ].
SegmentLength number Approximate length in meters of generated river volume segments.
SubdivideLength number Maximum render subdivision size for high-detail geometry.
FlowMagnitudePhysics number River flow speed in meters per second.
LowLODDistance number Distance at which river segments switch to low-detail rendering.

River node data

Example node:

[50, 10, 5, 22, 4, 0, 0, 1]
Index Meaning
0 X position
1 Y position
2 Z position
3 Width in meters
4 Depth in meters
5 Surface normal X
6 Surface normal Y
7 Surface normal Z

Most normal values are usually:

0, 0, 1

for an upright water surface.


River generation

A River uses its control nodes to build a Catmull-Rom spline.

The generation process is roughly:

  1. Read river nodes.
  2. Build a spline through node positions.
  3. Generate slices along the spline.
  4. Interpolate width, depth, and normal.
  5. Build volume segments between slices.
  6. Generate render geometry.
  7. Generate bounds for containment, depth, flow, and water coverage queries.

A river needs at least two nodes to generate usable water geometry.


SegmentLength

"SegmentLength": 10

SegmentLength controls how long each generated river volume segment is.

Smaller values create more segments and more accurate containment/coverage queries.

Larger values create fewer segments and can be cheaper.

The minimum enforced value is approximately:

1 meter

SubdivideLength

"SubdivideLength": 2.5

SubdivideLength controls render subdivision for high-detail river geometry.

Smaller values create denser geometry and better visual undulation.

Larger values create cheaper geometry.


LowLODDistance

"LowLODDistance": 50

River rendering uses two detail levels:

LOD Description
High LOD Subdivided geometry with water undulation.
Low LOD Simpler strip geometry farther from the camera.

Segments closer than LowLODDistance use high-detail rendering.

Segments farther away use low-detail rendering.


FlowMagnitudePhysics

"FlowMagnitudePhysics": 3

Controls the river’s flow speed in meters per second.

The flow direction is based on the direction from one river segment slice to the next.

Node order matters:

node 0 -> node 1 -> node 2

Water flow follows this order.

If the river appears to flow the wrong way, reverse the node order.


River width and depth

Width and depth are stored per node and interpolated along the spline.

Example:

"nodes": [
  [0, 0, 5, 10, 2, 0, 0, 1],
  [50, 0, 5, 30, 5, 0, 0, 1]
]

This creates a river that widens from 10 meters to 30 meters and deepens from 2 meters to 5 meters.

Width and depth are clamped internally to safe ranges.

Approximate limits from the implementation:

Value Minimum Maximum
Node width 0.25 1000
Node depth 0.25 500

River normals

Each node has a normal vector.

For normal horizontal rivers, use:

[0, 0, 1]

Tilted normals can be used for special cases, but they affect generated slice orientation, surface plane, containment, and reflection behavior.

For most rivers, keep normals upright.


Common water behavior

The water classes share common rendering and water-volume behavior.


Materials

Water objects use water materials.

Example:

"material": "water_lake"

A water material usually controls:

  • Base color
  • Normal maps
  • Wave appearance
  • Refraction/reflection appearance
  • Foam or ripple behavior where supported
  • Surface opacity and fresnel-like effects

If the material is missing or invalid, the object may render incorrectly or use a fallback material.


Reflections

Water objects support dynamic planar reflections through inherited water/reflection behavior.

This is commonly controlled by fields on the base water object/material setup, such as fullReflect where available.

Planar reflections work best for flat water.

Object Reflection quality notes
WaterPlane Good for oceans and flat surfaces.
WaterBlock Good for flat bounded surfaces.
River Can be less accurate on twisting, sloped, or strongly curved rivers.

For rivers, the reflection plane is chosen from the nearest river area and/or averaged river surface normals. Highly curved or twisted rivers may show reflection artifacts.

Use less reflective water materials for rivers that twist, tilt, or bend sharply.

Underwater checks

Water objects implement underwater/depth behavior differently:

Class Underwater behavior
WaterPlane Underwater if point is below the plane height.
WaterBlock Underwater if point is inside the water block volume below the surface.
River Underwater if point is inside one of the generated river volume segments.

This affects underwater fog, water coverage, buoyancy-style queries, and other gameplay/rendering systems that ask water objects for depth or containment.


Surface height

Water objects can report surface height:

Class Surface height behavior
WaterPlane Returns the plane Z height.
WaterBlock Returns object Z height if the queried XY point is inside the block bounds, otherwise -1.
River Casts downward against the river surface and returns the collision height, otherwise -1.

Serialization

WaterPlane JSON

{
  "class": "WaterPlane",
  "name": "ocean",
  "position": [0, 0, 0],
  "material": "water_ocean",
  "gridSize": 101,
  "gridElementSize": 1
}

WaterBlock JSON

{
  "class": "WaterBlock",
  "name": "pool_water",
  "position": [0, 0, 2],
  "rotationMatrix": [1,0,0,0,1,0,0,0,1],
  "scale": [20, 10, 4],
  "material": "water_pool",
  "gridElementSize": 1
}

River JSON

{
  "class": "River",
  "name": "small_stream",
  "material": "water_stream",
  "SegmentLength": 8,
  "SubdivideLength": 2,
  "FlowMagnitudePhysics": 1.5,
  "LowLODDistance": 40,
  "nodes": [
    [0, 0, 3, 6, 1, 0, 0, 1],
    [30, 5, 3, 7, 1.2, 0, 0, 1],
    [60, 20, 2.8, 8, 1.5, 0, 0, 1]
  ]
}

Legacy River Node fields

Older TorqueScript-style serialization used repeated Node fields:

Node = "0 0 3 6 1 0 0 1";
Node = "30 5 3 7 1.2 0 0 1";
Node = "60 20 2.8 8 1.5 0 0 1";

Modern JSON should use the nodes array instead.


Practical examples

Ocean around an island

{
  "class": "WaterPlane",
  "name": "level_ocean",
  "position": [0, 0, 0],
  "material": "water_ocean",
  "gridSize": 101,
  "gridElementSize": 1
}

Use WaterPlane for large open water.


Small lake

{
  "class": "WaterBlock",
  "name": "mountain_lake",
  "position": [250, -100, 42],
  "rotationMatrix": [1,0,0,0,1,0,0,0,1],
  "scale": [180, 120, 12],
  "material": "water_lake",
  "gridElementSize": 4
}

Use WaterBlock when the lake should have finite boundaries.


Swimming pool

{
  "class": "WaterBlock",
  "name": "pool_water",
  "position": [10, 20, 1.2],
  "rotationMatrix": [1,0,0,0,1,0,0,0,1],
  "scale": [12, 6, 2],
  "material": "water_pool",
  "gridElementSize": 0.5
}

Use a smaller gridElementSize for close-up water if needed.


River with flow

{
  "class": "River",
  "name": "valley_river",
  "material": "water_river",
  "SegmentLength": 10,
  "SubdivideLength": 2.5,
  "FlowMagnitudePhysics": 4,
  "LowLODDistance": 60,
  "nodes": [
    [-100, 0, 15, 18, 3, 0, 0, 1],
    [-50, 20, 14, 20, 3, 0, 0, 1],
    [0, 30, 13, 22, 4, 0, 0, 1],
    [70, 10, 12, 18, 3, 0, 0, 1],
    [130, -20, 11, 16, 3, 0, 0, 1]
  ]
}

Flow direction follows node order from first node to last node.


Best practices

  • Use WaterPlane only for very large/infinite water.
  • Use WaterBlock for bounded ponds, lakes, pools, and basins.
  • Use River for curved water with width, depth, and flow.
  • Keep WaterPlane near expected sea level and avoid extreme far-clip setups.
  • Do not use WaterPlane for small local water areas.
  • Keep WaterBlock.gridElementSize reasonable for the block size.
  • Avoid extremely dense river subdivision unless needed.
  • Use upright normals [0, 0, 1] for normal rivers.
  • Reverse river node order if flow direction is wrong.
  • Use less reflective materials on twisting or highly curved rivers.
  • Split extremely long or complex rivers into manageable sections if editing or rendering becomes expensive.
  • Check water material setup if the water renders black, invisible, or with missing reflections.

Common issues

Water does not render

Possible causes:

  • Missing or invalid material
  • Object not added to the scene
  • River has fewer than two nodes
  • WaterBlock scale is too small
  • Camera/render pass does not include diffuse water pass
  • Material failed to initialize

WaterPlane appears at the wrong height

Only position.z matters.

Check:

"position": [0, 0, desiredWaterHeight]

Rotation and scale do not affect WaterPlane.


WaterBlock does not cover the expected area

Check:

"scale": [width, length, depth]

Also check object rotation and position.


River does not appear

Check that it has at least two nodes:

"nodes": [
  [0, 0, 0, 10, 2, 0, 0, 1],
  [50, 0, 0, 10, 2, 0, 0, 1]
]

Also check material and subdivision settings.


River flow is backwards

River flow follows node order.

Reverse the node order, or edit the river so the first node is upstream and the last node is downstream.


River looks too angular

Decrease:

"SegmentLength": 10

or decrease:

"SubdivideLength": 2.5

Smaller values create more generated segments/subdivisions.


Water reflections look wrong

Possible causes:

  • Reflection is disabled
  • Water material is not set up for reflections
  • River is highly curved or tilted
  • Reflection plane does not match the visible water surface
  • Camera is underwater or near the water surface

For rivers, reduce material reflectivity if the shape twists heavily.


Water causes far-distance artifacts

This mostly affects WaterPlane.

Possible fixes:

  • Increase far clip distance
  • Keep large terrain/objects inside the far clip range
  • Avoid viewing the plane from very high altitude with a tight far clip
  • Use WaterBlock for bounded water instead

Summary

BeamNG provides three main water scene objects:

  • WaterPlane for infinite ocean-style water.
  • WaterBlock for finite rectangular water volumes.
  • River for spline-based flowing water with variable width and depth.

Use WaterPlane for oceans, WaterBlock for local water bodies, and River for path-shaped water. All water objects depend heavily on correct material setup, sensible geometry settings, and appropriate reflection configuration.

Last modified: June 2, 2026

Any further questions?

Join our discord
Our documentation is currently incomplete and undergoing active development. If you have any questions or feedback, please visit this forum thread.