# Overview

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FvxfWkJqHDIpho06Lpplh%2Fimage.png?alt=media&amp;token=ec0d9b9e-f3c6-4651-b218-41a257fb79fe" alt=""><figcaption></figcaption></figure>

{% embed url="<https://www.fab.com/listings/ea195c94-00af-43fb-bb1d-85a478320a27>" %}
Planetary Oceans of Fab
{% endembed %}

{% embed url="<https://discord.gg/SvHcuCcjMX>" %}
Our Discord
{% endembed %}

{% hint style="success" %}
The plugin is targeted at Unreal Engine versions 5.5, 5.6, 5.7+.
{% endhint %}

The **Planetary Ocean Plugin** is designed to create highly customizable and scalable ocean systems for planetary environments. It leverages a combination of advanced techniques for wave simulation, mesh generation, buoyancy, and atmospheric effects, ensuring both visual fidelity and performance. Below is an overview of the core principles and architecture:

***

## FFT Waves

The plugin uses **FFT waves** to simulate realistic ocean. The resulting wave data (World Position Offset (WPO) and normals) is bent around sphere to create a seamless ocean surface. Waves are mirrored at negative X, Y, and Z axes, and everything is combined using a **triplanar mask** to ensure smooth transitions and avoid seams.

## Mesh generation

The ocean mesh is generated using **6 instances of a quadtree**, one for each face of the cube. The quadtree dynamically adjusts the level of detail (LOD) based on the camera's distance. Mesh geometry is computed on a **background thread** for efficiency, and the data is passed to the GPU through a vertex factory. This approach ensures that mesh generation is **extremely fast and reliable**, even for large-scale oceans.

All calculations are done in 64-bit doubles, and once computed, transformed into a camera relative space (mesh origin rebasing), converted to 32-floats and sent to the GPU. This allows to have an ocean sphere of virtually any size without precision errors. Ocean material also utilizes camera relative space and periodic world space to avoid precision issues with WPO and normals.

## Buoyancy

Buoyancy is calculated by applying physics at specific locations. **Buoyancy points**, defined by the user around a vessel, sample ready-to-use waves data on the CPU which is updated in parallel using ISPC task system and doesn't block the game thread.

## Other features

* **Multiple atmospheres**: The plugin includes a custom multiple atmosphere system that also includes space background.
* **Underwater effects**: causitics, fog with ray-marching screen space occlusion, distortion etc.
* **Fog Effects**: Screen-space depth-based fog (both linear and exponential) is supported, but somewhat experimental.


# Version differences

There are two versions of the plugin available on Fab.

UE 5.3 -> v. 1.41

UE 5.4 -> v. 1.41

UE 5.5 -> v. 1.5

UE 5.6 -> v. 1.5

{% hint style="success" %}
Plugin version can be checked in the `PlanetaryOceans.uplugin` file in the root directory of the plugin.
{% endhint %}

The main difference between 1.41 and 1.5 is that 1.5 supports large oceans, up to almost any radius. For more info check this page:

{% content-ref url="/pages/Yql3imulLbtqIZ96BSFq" %}
[Scalability and Precision](/guide-v.-2.1/scalability-and-precision)
{% endcontent-ref %}

Here's a full change log of the 1.5 update:

**What's new:**

* DirectX 11 and Vulkan support,
* Large ocean support:

  * The mesh generator and material now support any radius — including planets over 100,000 km in size,
  * Buoyancy now supports oceans with radii up to 10,000 km (exceeding Earth’s size),

  ,
* New Example Blueprint: `BP_GetWaveLocationExample`. Demonstrates how to retrieve wave data on the CPU — useful for custom buoyancy, splash effects, and more,

**Fixes:**

* Resolved aliasing jitter by smoothly fading normals with distance,
* Restricted foam rendering on the inner surface of the water,

**Misc**:

* Removed outdated maps: the flat ocean map and the map using a static mesh for the ocean (instead of a quadtree-based mesh),
* Restriced ocean actor rotation (that introduces complications for mesh rebasing and other features that eliminate floating point imprecision). Translation (moving) is still supported by everything including buoyancy,

**Important**:

* This update is **Unreal Engine 5.5+ only**. It requires Periodic World Space, a feature introduced in UE 5.5 and used in the material

{% hint style="success" %}
You **can** use 1.41 in UE 5.5. You will have to manually compile the plugin with this engine version.
{% endhint %}

{% hint style="danger" %}
You **cannot** use 1.5 in UE 5.3–5.4 as it utilizes new Periodic World Space that was added in UE 5.5.
{% endhint %}


# Getting started

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FHMtriTKhNM5TFBSreUiH%2Fimage.png?alt=media&amp;token=62dd9b5f-9a67-4832-9b50-c07d991c073a" alt=""><figcaption></figcaption></figure>

To add a planetary ocean to your level, follow these steps:

1. **Add BP\_PlanetaryOcean to your level**\
   Drag and drop the `BP_PlanetaryOcean` actor into your level. This actor controls the ocean’s quad tree and mesh generation settings, such as resolution, radius, and LOD (Level of Detail).
2. **If you don’t see the ocean** — you’re inside it. Since backface culling is enabled by default, there’s no visible geometry either. Disable `Nodes Backface Culling` in a `Quadtree` section of `BP_PlanetaryOcean` and press `F` to focus on the ocean sphere. You can then re-enable backface culling.
3. **Configure the ocean’s settings**\
   In `BP_PlanetaryOcean`, adjust the ocean’s mesh settings (radius, resolution, etc.) and quadtree settings to fit your needs. Hover over variables to see detailed explanations of what they do. Switch to wireframe mode to better understand how mesh generation behaves.

<div align="left"><figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FbTcHAsTMbMtPBMlmXxe2%2Fimage.png?alt=media&amp;token=75087674-d8bd-483f-8f8e-127682cac9d8" alt=""><figcaption></figcaption></figure></div>

Explore the `Ocean Parameters` section of the BP that controls waves generation.

<div align="left"><figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FnlEy7aDL3o7dPvS5nyXv%2Fimage.png?alt=media&amp;token=f7e9e32a-5b2d-42a8-a89a-2770f7d5006e" alt=""><figcaption></figcaption></figure></div>

1. **Additional notes**\
   Ocean material surface settings are defined in the `MI_PlanetOcean` material instance.

{% content-ref url="/pages/Yql3imulLbtqIZ96BSFq" %}
[Scalability and Precision](/guide-v.-2.1/scalability-and-precision)
{% endcontent-ref %}


# Using with Static Mesh

Add `BP_PlanetaryOcean` to your level. Optionally, place `BP_Boat` , `BP_FishBoat`, or `BP_Buoy` on the ocean surface, and hit `Play`. You should immediately observe these objects floating, regardless of their placement on the ocean sphere.

`BP_PlanetaryOcean` utilizes the `SM_OceanMeshGenerated` as its default mesh. It includes:

* A radius of 30,000 Unreal units (300 m).
* 3,486,252 triangles.
* The size of one quad is 78.68 cm.
* This gives you 1.27 quads per meter. Another words, that there are 2.54 quads along the height of the character, that is 200 units.

If you wish to modify any of these parameters, see [Generating ocean mesh](/guide-v.-2.1/getting-started/using-with-static-mesh/generating-ocean-mesh).

{% hint style="danger" %}
Avoid scaling the Blueprint or its Static Mesh component.
{% endhint %}

{% content-ref url="/pages/5FWRTTDy0tRfiH8up9qZ" %}
[Generating ocean mesh](/guide-v.-2.1/getting-started/using-with-static-mesh/generating-ocean-mesh)
{% endcontent-ref %}


# Generating ocean mesh

{% hint style="danger" %}
Ensure that the `BP_PlanetaryOcean` is placed in world zero. Following the completion of mesh generation, it can be relocated to the preferred location. Generating mesh outside of world zero may result in artifacts appearing on the generated mesh.
{% endhint %}

Press the `Edit Mode` button in the `BP_PlanetaryOcean`. This will spawn the `BP_OceanMeshGenerator`  that utilizes the Dynamic Mesh Component to create the mesh and bake it into a Static Mesh. Adjust the `Sphere Radius`, `Steps`, and `Tessellation Level` in the `BP_PlanetaryOcean`.

<div align="left"><figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2F0avHL4hV3wliQ7xhi1TL%2Fimage.png?alt=media&amp;token=151c7ab1-1fbb-4c15-ad2f-77c937b3df51" alt=""><figcaption></figcaption></figure></div>

Different values give different polygon density.

<div align="left"><figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FLPPrn3KPr85DaFEQijgK%2Fimage.png?alt=media&amp;token=4a4559e1-c965-44b6-92d1-011baaed6028" alt=""><figcaption><p>Tesselation Level: 10</p></figcaption></figure></div>

<div align="left"><figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FVM6FiT8FBlO2CZFIw77Q%2Fimage.png?alt=media&amp;token=9daa991e-7567-43bb-a233-ab9e6ec79674" alt=""><figcaption><p>Tesselation Level: 2</p></figcaption></figure></div>

Whenever you modify any of these values, the corresponding mesh stats are displayed on the screen. If not, it is printed in the output log.

<div align="left"><figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2F9Y6Z6SJmOHSCWcQG9s3l%2Fimage.png?alt=media&amp;token=735760f4-6ec8-44c1-8bf8-355d7645b23c" alt=""><figcaption></figcaption></figure></div>

Once the desired values have been set, click on the `Generate Static Mesh` button and give it some time, as it may cause the editor to freeze.

At this stage, the wireframe dynamic mesh visible on the screen is currently being baked into the Static Mesh. It is then assigned to the `Generated Static Mesh` slot within in the ocean BP.

This action will override the default `SM_OceanMeshGenerated`, however, you have the option to substitute it with an alternative mesh prior to pressing the `Generate Static Mesh` button or before entering `Edit Mode`.

Once the generation is finished, the wireframe dynamic mesh is removed from the level, and you should now see the ocean.

{% hint style="warning" %}
The more triangle count you have, the more expensive it gets for the GPU. The vertex shader calculates waves for every vertex, so you should have as few vertices as possible.&#x20;
{% endhint %}

The current limitation of this approach is the inability to achieve a large ocean sphere with sufficient polygon density to produce visually acceptable waves. In the next major update, a dynamic Level of Detail (LOD) system in the form of a quadtree will be implemented, enabling users to create ocean spheres of any size without experiencing any significant performance impact.

Meanwhile, some users are [utilizing](/guide-v.-2.1/getting-started/voxel-plugin-integration) the Voxel Plugin's mesh, which features an octree LOD system that is dynamically updated at runtime.


# Voxel Plugin Integration

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2F37lMgwZzQ7jCVS38AbIJ%2Fimage.png?alt=media&amp;token=27701866-2055-42f9-8b9a-59e90dfe3d08" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}
Only Voxel Plugin 2.0 versions 340.0, 340.1, and 340.3 are supported. Compatibility with `dev` versions may be possible by replacing outdated nodes in the Voxel Graph.
{% endhint %}

Integration with the Voxel Plugin made as easy as it gets.

1. Create a Voxel Graph with a sphere surface and assign a `MI_PlanetOcean` in the Generate Marching Cube Surface node. Alternatively, you can download a simple graph made for convenience (don't forget to assign the material instance in there).

{% file src="/files/6m1G5pKITTUbhL7ZXu6U" %}

2. Drag and drop the Voxel Graph to the world. Go to the `BP_PlanetaryOcean` and switch the `MeshMode` to the `VoxelPluginsMesh`.

### Tips

* Make sure that `SphereRadius` in `BP_PlanetaryOcean` matches the radius of the sphere in the Voxel Graph. Otherwise, buoyancy will have issues.
* If the surface has not enough polygon density for your waves settings, lower the `Voxel Size` in the Voxel Graph.
* World position of `BP_PlanetaryOcean` should match the world position of the Actor with your Voxel Surface, if you want to use buoyancy.
* You don't necessarily have to have a solid sphere in the Voxel Graph. You may remove parts of the sphere using noise that represents your continents, so that the surface appears only where the water is supposed to be. This approach may be used to remove water from the areas below continents, if you'd like to dig in there. The only requirement is, all parts of the surface should match the overall shape of a perfect sphere.
* Ocean material uses material collection parameters that are updated from a `BP_PlanetaryOcean`'s parent class `APlanetaryOcean`. This way, all wave parameters are synchronized with the buoyancy system.

### Fixing WPO in Voxel Plugin in UE 5.3

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FVugeI4A53legoAhHFqT9%2Fimage.png?alt=media&amp;token=066f12e0-86a4-4671-b592-7362dcceaaf4" alt=""><figcaption><p>This is how the ocean looks when WPO doesn't do anything.</p></figcaption></figure>

In versions 340.0, 340.1, and 340.3 of the Voxel Plugin, the material's world position offset doesn't work. To fix that, go to `Voxel/Shaders/VoxelMarchingCubeVertexFactory.ush` file and on line 135, replace:

```hlsl
FMaterialVertexParameters Parameters = (FMaterialVertexParameters)0;
```

with

```hlsl
#if VOXEL_ENGINE_VERSION >= 503
	FMaterialVertexParameters Parameters = MakeInitializedMaterialVertexParameters();
#else
	FMaterialVertexParameters Parameters = (FMaterialVertexParameters)0;
#endif
```

{% hint style="info" %}
This edit was taken from the commit [3b7b6b6](https://github.com/VoxelPlugin/VoxelPlugin/commit/3b7b6b6d3ce16eb555bbc757dd50128298223d4f) in the Voxel Plugin's dev branch.
{% endhint %}

Restart the editor. Now you should see the waves.

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FxOlFVdCvkJihXz3HVdC2%2Fimage.png?alt=media&amp;token=649d3d34-a3ad-4e50-b8ab-6ecaca8b03dc" alt=""><figcaption></figcaption></figure>

### Fixing WPO in Voxel Plugin in UE 5.4

{% hint style="danger" %}
If you're using Unreal Engine 5.4, you must make one extra edit (see commit [15c86df](https://github.com/VoxelPlugin/VoxelPlugin/commit/15c86df02b7819b4977da843954fba47f765bf3c)) in addition to the one mentioned above.
{% endhint %}

In the `Voxel/Shaders/VoxelMarchingCubeVertexFactory.ush` on line 145 (considering you have already made the edit above), paste:

```hlsl
#if VOXEL_ENGINE_VERSION >= 504
    Parameters.LWCData = MakeMaterialLWCData(Parameters);
#endif
```


# Scalability and Precision

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FlVqMR3zdQxWQWiTorezd%2Fquadtree.png?alt=media&amp;token=4be548fe-b3b3-44b2-804d-85f5ab48472e" alt=""><figcaption></figcaption></figure>

Planetary Oceans is designed to simulate ocean surfaces on both small planetary bodies and massive planets, with support (with some caveats described below) for oceans exceeding **10,000,000,000 Unreal Units** (100,000 km) in radius.

## Mesh Generation (CPU)

Vertex position calculations are performed using **64-bit doubles**. After the mesh data is finalized, both the mesh origin and vertex positions are **rebased to the camera location** to avoid floating-point imprecision during rendering. The vertex data is then **converted to 32-bit floats** and passed to the engine for rendering, as the engine expects all vertex data to be represented in floats.

#### Parameters in `BP_PlanetaryOcean` (comes from `SphereMeshComponent`):

| Parameter                  | Description                                                                                                                                                                                         |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GeometryUpdateDistanceCm` | Distance in centimeters camera (both editor and in-game camera) must be traveled before the ocean geometry is regenerated to keep dense geometry near the observer. This does not shift the origin. |
| `OriginShiftDistanceCm`    | Distance in centimeters the camera must move before the mesh origin is rebased (shifted) to the current camera location. This helps prevent precision loss in large-radius scenarios.               |

#### How and When the Mesh Origin Is Rebased

* Once the level is loaded, the mesh origin is initially placed at the **editor camera** location.
* Once the game starts, the mesh origin is placed at the **in-game camera** location (e.g., the camera component of your character).
* Both before and after `BeginPlay`, the mesh origin is **rebased every time** the camera travels a distance greater than the value specified by the `OriginShiftDistanceCm` parameter. The distance is measured from the most recent mesh origin location.

## Waves generation (GPU)

The ocean material tries to avoid absolute world space as much as possible. It also takes into account the mesh origin and calculates vertex positions relative to it.

{% hint style="warning" %}
Each origin shift causes the entire wave system to visually shift as well. It takes one frame to shift the waves. Chances you will see this artifact are slim to none for the following reasons:

* If you keep `OriginShiftDistanceCm` high (around **10,000,000 units**, or **100 km**), you will need to move really far to see it happening.
* You will have to move relatively slowly to notice this artifact.
* You will have to be really close to the ocean surface to see the artifact.

Generally speaking, keep the `OriginShiftDistanceCm` value around the default (**10,000,000**). If you want to disable origin shifting completely, set `OriginShiftDistanceCm` to a very large number. For instance, a planet with a radius of \~10,000,000 doesn't suffer from floating-point issues at all. If you have several planets like this, you will still need origin shifting.
{% endhint %}

## Buoyancy (CPU)

The **buoyancy system** takes the mesh origin into account and adapts to wave positions when the origin is rebased. All material calculations related to avoiding floating point imprecision are mirrored in the buoyancy component (check `UOceanFunctionLibrary::WorldToPeriodicWorldSpace`).


# Configuring ocean material

`MI_PlanetOcean` has quite a few features:

* **Single Layer Water** shading model, that provides scattering and absorption
* **Subsurface scattering**. Depending on your ocean sphere radius you might want to tweak subsurface parameters to meet your expectations.
* **Waves attenuation at the shoreline**
* **Wave foam** and **foam around objects** (both distance field based)
* Ocean **slicks** and **sparkles**.

{% hint style="warning" %}
Waves attenuation requires distance field enabled on the planet mesh.
{% endhint %}


# Buoyancy

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FLfJYwoVl3NVatogTdv9w%2Fimage.png?alt=media&amp;token=07058363-4110-4548-8f2c-341caa827f14" alt=""><figcaption></figcaption></figure>

## How it works

The buoyancy system calculates the entire FFT grid on the CPU, mirroring the GPU implementation. For performance reasons, these calculations are triggered every `BuoyancyUpdateTickRate` (0.2 seconds by default). This should be sufficient for most use cases, such as vessel buoyancy and character swimming. However, if you need updates to occur more frequently, this parameter is exposed in `BP_PlanetaryOcean`.

These CPU calculations run in parallel using the ISPC task system and do not block the game thread. The ready-to-use data can then be sampled by the buoyancy component (for vessels) or the swimming component (for characters) to apply forces. Reading these values is virtually free, because the computed data covers every possible location across all wave cascades. Since the cascades are tiled indefinitely, data is available for the entire ocean sphere.

{% hint style="success" %}
For visualizing Buoyancy Points check `bShowBuoyancyPoints` in the `PlanetaryBounancy` component.
{% endhint %}

`PlanetaryBuoyancy` component must be attached to an Actor to enable floating. This component reads the depth below (or above) water for a specified array of `BuoyancyPoints`. Using this data, it applies forces to the BuoyancyPoints. It also includes built-in gravity.

{% hint style="success" %}
Buoyancy system reacts to the movement of the ocean actor at runtime.
{% endhint %}

## Setting up buoyancy

To set up buoyancy on your Actor, follow these steps:

* Add `PlanetaryBuoyancy` component to your Actor blueprint.
* In the component settings fill the `Buoyancy Points` array. Vector locations it is asking for are in local space (relative to the root). It is recommended to have more than 4 Buoyancy Points for smoother buoyancy simulation. System will sample the water height and apply buoyancy forces at these locations.

<div align="left"><figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FgU8bF2Ic1dJ5PPKGavPk%2Fimage.png?alt=media&amp;token=f8ebb7ed-edc7-4f79-abf5-821654181fd1" alt=""><figcaption></figcaption></figure></div>

* Make the Static Mesh Component you want to float as root component. Static Mesh can have any other components attached to it (meshes, cameras, particles etc.)

{% hint style="warning" %}

* Root component of the vessel should be a static mesh with `Simulate Physics` enabled.
* `Enable Gravity` should be disabled as the plugin applies custom gravity (with gravity direction pointing to the ocean sphere center with the ocean location taken into account)
  {% endhint %}

{% hint style="warning" %}
Static Mesh has to have simple collision added. Polygon count doesn't matter, but the complexity of the collision may affect performance. Make sure, you don't have excessive amount of collision primitives with complex geometry.
{% endhint %}

* If your Actor is added to the level, you can navigate to the Primary Ocean category in the component settings and set the existing in the level `BP_PlanetaryOcean` to the ocean you'd like this Actor to float on. If not set, the Buoyancy Component will assign the first found in the level object of class `APlanetaryOcean` on BeginPlay and use it.

<div align="left"><figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2F0mLvNjuX48os8i12hfgs%2Fimage.png?alt=media&amp;token=f31fb2a0-ca42-42dd-8359-136b0b42c67e" alt=""><figcaption></figcaption></figure></div>

{% hint style="success" %}
To change the primary ocean at runtime, use `SetPrimaryOcean` function that is exposed to blueprint.
{% endhint %}

* Now you can press Play and tweak the buoyancy settings.

{% hint style="warning" %}
Currently buoyancy does not take into account waves attenuation feature since distance field is only accessible on GPU. That means a boat will float near the shoreline assuming waves have full strength there, although they are faded out using distance field. This might be fixed in the future by introdusing a distance field approximation on CPU using line or sphere traces.
{% endhint %}


# Buoyancy Performance & Optimization

The Buoyancy system in Planetary Oceans calculates wave displacement on the CPU to apply physics forces to floating objects.&#x20;

{% hint style="success" %}

#### Asynchronous Computation

CPU FFT calculations run on **background worker threads** using ISPC's (Intel SPMD Program Compiler) parallel task system. They do not block the game thread, so the performance cost shows up as increased **overall CPU usage** rather than frame time spikes. This means:

* Your game thread remains responsive even during FFT computation

However, if your CPU cores are already saturated, additional FFT work can still impact frame rate indirectly through resource contention.
{% endhint %}

This page covers performance considerations and the new **per-cascade CPU compute controls** that allow you to fine-tune buoyancy performance based on your specific needs.

#### Understanding FFT Cascades

Planetary Oceans uses **4 FFT cascades** to represent different wave frequencies:

* **Cascade 0**: Shortest waves (ripples) — \~25m tile size by default
* **Cascade 1**: Medium-short waves — \~28m tile size
* **Cascade 2**: Medium-long waves — \~432m tile size
* **Cascade 3**: Longest waves (swell) — \~2000m tile size

All 4 cascades are combined to create the final ocean surface you see. The GPU always renders all cascades regardless of CPU settings.

#### Per-Cascade CPU Compute Toggle

Starting in version 2.1, you can independently **enable or disable each cascade** for CPU buoyancy calculations. This is found in the **Buoyancy** section of `BP_PlanetaryOcean`:

* `Compute Cascade 0 on CPU` (shortest waves)
* `Compute Cascade 1 on CPU`
* `Compute Cascade 2 on CPU`
* `Compute Cascade 3 on CPU` (longest waves / swell)

**Default**: All cascades enabled (preserves behavior from previous versions)

**How It Works**

When you disable a cascade:

* **GPU rendering continues** — the ocean surface visually displays all wave frequencies
* **CPU buoyancy ignores that cascade** — floating objects don't respond to those wave frequencies
* **Performance improves** — each disabled cascade reduces buoyancy computation cost

{% hint style="info" %}
The visual ocean surface and physical buoyancy are intentionally decoupled. If you disable short-wave cascades (0 and 1), you'll still **see** small ripples on the water, but large ships won't **react** to them — which is physically correct, as large vessels don't respond noticeably to small ripples.
{% endhint %}

#### Performance Benchmarks

Performance was measured with a **256×256 FFT grid** and **AVX2 CPU** (AMD Ryzen 9 5950X) on a typical scene:

| Configuration            | CPU Time | Speedup vs All |
| ------------------------ | -------- | -------------- |
| All 4 cascades (default) | 8.0 ms   | 1.0×           |
| Cascades 2 + 3 only      | 4.0 ms   | 2.0×           |
| Cascade 3 only           | 2.0 ms   | **4.0×**       |

{% hint style="warning" %}
The measured milliseconds represent **worker thread time**, not game thread stalls. On multi-core CPUs, this work happens in parallel with gameplay logic.
{% endhint %}

Each disabled cascade saves approximately **2.0 ms** of CPU time per buoyancy update.

#### Recommended Configurations

Choose cascade settings based on **which cascades make the most impact** on the ocean shape (it depends on your per-cascade parameters) and based on the **size of your floating objects**:

**Large Ships (>20 meters):** Cascade 3 (or 2) only

**Medium Boats (5-20 meters):** Cascades 2 + 3 (or 1 + 2)

**Small Boats and characters (<5 meters)**: Cascades 1 + 2 + 3 (or all 4)

#### Performance Debugging

To monitor buoyancy CPU cost in real-time, enable **Show CPU FFT Performance** in the Buoyancy section of the ocean actor:

```
✓ Show CPU FFT Performance
```

{% hint style="warning" %}
Buoyancy calculations run at the rate specified by `Buoyancy Update Tick Rate` (default: 0.15 seconds). If you need more responsive physics, reduce this value — but remember each update costs the full FFT calculation time.
{% endhint %}


# Character swimming

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FYeGZynToVjozGM97MEZW%2Fimage.png?alt=media&amp;token=9ee20e03-aa8e-4f96-ae21-74eb180208e8" alt=""><figcaption></figcaption></figure>

Swimming on the water surface works similar to buoyancy except there's only one point that reads the wave data (typically located at the character's chest). Explore `BP_DemoCharacter` for better understanding how swimming works.

Core functionality is defined in the `BP_WaterSurfaceSwimming` component that is attached to the character.

{% hint style="warning" %}
Diving under the water currently is not implemented. It introduces a set of challenges related to animation and character rotation, so it was left for future updates. You can see the initial attempt of making it dive in the even graph in the `Movement Input` section.
{% endhint %}

The demo character switches from swimming to walking states by checking the overlapping actors around the feet area and for that to work they have `Generate Overlap Events` set to true.

Post process control rig is skipped in the character animation graph when the character swims, since it snaps the feet to the ground whenever there's something close to the feet.

Character gravity direction is updated accordingly. You might experience character rotation issues when away from the North Pole of the ocean sphere.

Swimming is experimental and is subject to change, but in its current state gives you a good starting point.

{% hint style="warning" %}
Currently swimming does not take into account waves attenuation feature since distance field is only accessible on GPU. That means a character will swim near the shoreline assuming waves have full strength there, although they are faded out using distance field. This might be fixed in the future by introdusing a distance field approximation on CPU using line or sphere traces.
{% endhint %}


# Masking out water

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FhiKUsHMhDw6sNBPSS0Bt%2Fimage.png?alt=media&amp;token=bbde1023-d781-42e7-b5b3-d6976fbba0a1" alt=""><figcaption><p>Water is masked out of the boat hull interior</p></figcaption></figure>

There are two ways of masking out water:

1. Using `M_WaterInvalidator` material that gets water removed from the volume covered by the mesh it is assigned to. Mesh should be ceiled.&#x20;

{% hint style="success" %}
Check the `BP_Boat`. There's a `hull_mask` static mesh attached with the `M_WaterInvalidator` material assigned. Mesh covers the whole volume of the boat hull.
{% endhint %}

2. Using distance field mask. In the `M_OceanFFT` check the reroute node called `Masking Out Water` that is unused. Plug it into the `Opacity Mask` input in the root node. `Opacity Mask` accepts either `0` or `1` value per-pixel and discards the pixel based on this value.

<div align="left"><figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2Fl9AzdYC2V8eUsV4sMYAX%2Fimage.png?alt=media&amp;token=705c9aa2-ccc2-4e36-bfc9-bb9c2c06a273" alt=""><figcaption></figcaption></figure></div>

This mask removes water wherever the distance field is negative, meaning it affects the interior of the mesh. Use the `Mask Offset` parameter to adjust the mask's position. Ensure that the distance field is enabled on the mesh you want to exclude water from.

{% hint style="warning" %}
The distance field method gives you less control as it will be applied for every mesh that has distance field enabled. Whereas the first method, which is based on the material, give you full control over the areas you want to exclude water from.
{% endhint %}


# Atmospheres & space

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2F1KwMwuZIVOwCsjkZ9ldP%2Fimage.png?alt=media&amp;token=f581222d-4ac0-4901-b689-fa8e6f20c1bd" alt=""><figcaption></figcaption></figure>

* Plugin supports multiple atmospheres with unique settings on each and a background space skybox.
* Everything is rendered by the engine into the `Ambient Cubemap` that is used by `SkyLight` to lit the shadows and apply ambient reflections that are drawn before any other reflections (Lumen or SSR) are applied.
* Compare the results between a setup where the `Ambient Cubemap` is left blank (rendered as black) and one where the atmosphere material is rendered into it.

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FxGKZtka8EaqldMYFilCp%2Fimage.png?alt=media&amp;token=f00f7d50-f4eb-42d6-9115-e3cc6858f7c4" alt=""><figcaption></figcaption></figure>

## How-to

{% embed url="<https://www.youtube.com/watch?v=KNYEarjtvvA>" %}

1. Add `BP_SkyManager` to the level, place it in the middle of your 'primary' planet. The placement only affects how mie scattering (halo around the sun) is distributed along the sky, it needs to know the relation between the planet surface next to you and the sun direction.
2. In the `Atmosphere Settings` array specify the `PlanetActor` reference. This is an actor you want this atmosphere to be tied to.

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FQMnoyuc7tUesKowP8pQN%2Fimage.png?alt=media&amp;token=2d46cce4-d091-4311-8679-193facd79958" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2237393798-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FmyMovt6gEgotygp1N9oc%2Fuploads%2FGy5KM7z8BVy3TGZyfGs8%2Fimage.png?alt=media&amp;token=78b0211e-d4ac-4089-a107-104d6b6bcad9" alt=""><figcaption></figcaption></figure>

1. If `PlanetActor` is not set, you're not paying the cost of rendering this atmosphere, but the engine will throw a warning. To get rid of it, remove the entry from the array.
2. Adjust `PlanetRadius` and `AtmosphereRadius` params to match the radius of your planet. The first determines at what distance from the planet center the atmosphere will fade in, the second — at what distance from the planet center it will fade off.
3. Play around with other parameters while also changing the sun direction. `WaveLengths` parameter determines how each RGB color is absorbed over the distance travelled though the atmosphere. The default values are physically accurate for Earth's atmosphere, but adjusting them allows you to create the appearance of alien planets.
4. <mark style="color:red;">**Be very careful**</mark> with the `ViewRaySteps` and `OpticalDepthSteps` parameters. They determine the quality of the atmosphere. There is a nested loop in the shader and the overall iteration count is equal to `ViewRaySteps * OpticalDepthSteps`. The lower these values the better performance you get.
5. In the `MI_PlanetaryAtmosphere` you have `Sun Disc`, `Mie Scattering` and also `Night Sky` (space background) settings. Check the `...\PlanetaryOceans\Content\Sky\SpaceCubemaps` folder for the additional space sky textures you might like to try.

{% hint style="warning" %}
Because you cannot pass arrays to the material, the max amount of the atmospheres in the is hardcoded to 5. If you want to extend the amount, contact me on our Discord server, I'll show how to do it. It's a matter of a little bit of a copy-paste type of work.&#x20;
{% endhint %}

{% embed url="<https://discord.com/invite/SvHcuCcjMX>" %}


# Blueprint API

Coming soon...


# C++ API

Coming soon...


