Skip to main content

๐ŸŒŠ Rivers, Lakes & Oceans - Seamless Water Transitions

ยท 3 min read
Galidar
Founder, Galidar Studio

One of the most challenging aspects of realistic water simulation is handling the boundaries between different water body types. NextGen 2.0 introduces a sophisticated transition system that makes these boundaries seamless and visually stunning.

Water Body Typesโ€‹

NextGen 2.0 supports four distinct water body types:

enum class EOceanologyWaterBodyType : uint8
{
River, // Spline-based flowing water
Lake, // Enclosed body with optional flow
Ocean, // Infinite expanse with full wave systems
Transition // Custom/hybrid water bodies
};
TypeComponentKey Features
RiverUOceanologyRiverComponentSpline-based, variable width/depth, flow velocity
LakeUOceanologyLakeComponentEnclosed shape, calm waves, shore detection
OceanUOceanologyOceanComponentInfinite extent, full wave systems, collision boxes
CustomUOceanologyCustomWaterComponentUser-defined behavior

The Water Spline Systemโ€‹

Rivers are built on a powerful spline system with rich per-point metadata:

USTRUCT(BlueprintType)
struct FOceanologyWaterSplineCurveDefaults
{
float DefaultWidth; // Width at this point
float DefaultDepth; // Depth at this point
float DefaultVelocity; // Flow velocity
float DefaultAudioIntensity;
};

Variable Width and Depthโ€‹

// Query river dimensions at any point
float Width = RiverComponent->GetRiverWidthAtSplineInputKey(InputKey);
float Depth = RiverComponent->GetRiverDepthAtSplineInputKey(InputKey);

// Modify dimensions programmatically
RiverComponent->SetRiverWidthAtSplineInputKey(0.5f, 200.0f);
RiverComponent->SetRiverDepthAtSplineInputKey(0.5f, 50.0f);

River Transition Materialsโ€‹

When rivers meet other water bodies, specialized transition materials blend both:

River โ†’ Lake Transitionโ€‹

RiverComponent->SetLakeTransitionMaterial(MyLakeTransitionMat);

The transition material handles:

  • Gradual wave dampening
  • Color blending between river and lake
  • Flow velocity reduction
  • Foam generation at confluence

River โ†’ Ocean Transitionโ€‹

RiverComponent->SetOceanTransitionMaterial(MyOceanTransitionMat);

Ocean transitions handle:

  • Wave amplitude modulation
  • Tidal influence on flow
  • Salt/fresh water mixing
  • Breaking wave interaction

GPU QuadTree Material Selectionโ€‹

The GPU QuadTree automatically detects and applies transitions:

// From OceanologyWaterQuadTreeDraws.usf
if (WBRenderData.WaterBodyType == WATER_BODY_TYPE_RIVER &&
Node.TransitionWaterBodyRenderDataIndex > 0)
{
const FOceanologyWaterBodyRenderData TransitionWBRenderData =
WaterBodyRenderData[Node.TransitionWaterBodyRenderDataIndex];

if (TransitionWBRenderData.WaterBodyType == WATER_BODY_TYPE_LAKE)
MaterialIndex = WBRenderData.RiverToLakeMaterialIndex;
else if (TransitionWBRenderData.WaterBodyType == WATER_BODY_TYPE_OCEAN)
MaterialIndex = WBRenderData.RiverToOceanMaterialIndex;
}

Water Info Textureโ€‹

The Water Info Texture encodes flow and depth information:

ChannelContentUsage
RFlow Velocity XHorizontal flow
GFlow Velocity YVertical flow
BWater HeightSurface elevation
AGround HeightTerrain depth
float2 DecodeWaterInfoVelocity(float4 WaterInfoSample, float MaxVelocity)
{
const float2 NormalizedFlow = WaterInfoSample.xy;
return (NormalizedFlow - 0.5) * 2.0 * MaxVelocity;
}

Islands and Exclusion Volumesโ€‹

Islandsโ€‹

Islands create holes in water bodies:

UCLASS()
class AOceanologyIsland : public AActor, public IOceanologyWaterBrushActorInterface
{
virtual bool AffectsLandscape() const override { return true; }
virtual bool AffectsWaterMesh() const override { return false; }

UPROPERTY()
TObjectPtr<UOceanologyWaterSplineComponent> SplineComp;
};

Exclusion Volumesโ€‹

Prevent water in specific areas:

enum class EOceanologyWaterExclusionMode
{
AddWaterBodiesListToExclusion, // Only listed excluded
RemoveWaterBodiesListFromExclusion, // All except listed excluded
};

Practical Setup: River to Oceanโ€‹

Step 1: Create the Oceanโ€‹

AOceanologyOcean* Ocean = GetWorld()->SpawnActor<AOceanologyOcean>();
OceanComp->WaveSystemSelector = EOceanologyWaveSystemSelector::SpectralGerstnerWaves;
OceanComp->SpectralGerstner.BeaufortScale = 5;

Step 2: Create the Riverโ€‹

AOceanologyRiver* River = GetWorld()->SpawnActor<AOceanologyRiver>();
RiverComp->SetRiverWidthAtSplineInputKey(0.0f, 100.0f); // Narrow upstream
RiverComp->SetRiverWidthAtSplineInputKey(1.0f, 300.0f); // Wide at mouth

Step 3: Apply Transition Materialโ€‹

RiverComp->SetOceanTransitionMaterial(MyRiverToOceanMaterial);

Step 4: Connectโ€‹

Extend the river spline to overlap with the ocean - the system handles the rest automatically!

Tips for Beautiful Transitionsโ€‹

  1. Match Water Heights: River endpoint should match ocean base height
  2. Gradual Width Changes: Avoid sudden changes near transitions
  3. Flow Direction: River flow should point toward the ocean
  4. Transition Zone: Allow 50-100 units for smooth blending

Debug Commandsโ€‹

r.Oceanology.WaterMesh.ShowTileBounds 1
r.Oceanology.WaterInfo.DrawPerViewDebugInfo 1
r.Oceanology.WaterSplineResampleMaxDistance 50

Questions about water transitions? Join our Discord community for support!