Home

Share:

Technology

Ocean Rendering on a Planetary Scale

To achieve our long-term Artemis vision of creating vast Earth-scale planets, we needed an ocean system that could operate across the planet, from orbit to coastline, seamlessly integrated with the terrain, while keeping development and runtime costs manageable.

Author: Ji Dong, Render Programmer

Quadtree Ocean

At first we also considered implementing CDLOD (Continuous Distance-Dependent Level of Detail) or clipmaps. However, given our requirements for large-scale curvature, tight integration with terrain data, and consistent behaviour across both land and water, we decided that reusing the existing quadtree would be the most suitable solution. The ocean surface therefore adopts the same quadtree-based LOD structure as the terrain system, rather than being implemented as a separate ocean-specific representation.

This approach brought a few immediate advantages:

  • A shared coordinate system and update logic

  • Easy access to biome and elevation data

  • Minimal additional development effort

We then implemented and tested this quadtree ocean to evaluate how much geometric detail it could provide in practice, particularly for near-surface waves, and found it sufficient for our needs.

Vertex Sharing for Seamless Tiles

Even though the quadtree ocean was sufficient for our needs, we still needed to prevent cracks between tiles. We designed the system so that each tile stores shared data from its neighbors. This ensures watertight joins without relying on dynamic stitching.

Ownership of each vertex is deterministic and considers:

  • Tile size: larger tiles dominate smaller ones

  • Corner priority: Bottom left > Top left > Bottom right > Top right

  • Border priority: Bottom > Top, Left > Right

  • Neighbour relations: borders from larger or more dominant neighbours take precedence

  • Masks to Remap Tile Position: precomputed masks to remap tile coordinates

  • Tree priority: right-side tree dominates the left-side tree, while top and bottom trees are the least dominant

Corner Priority: Bottom left > Top left > Bottom right > Top right

Why this order?

Because we use quadrilateral patches to project the quadtree onto a spherical planet. In this case, the bottom left corner is the most well-preserved.

Border Priority: Bottom > Top, Left > Right

See Appendix B.

Masks to Remap Tile Position

When tiles of different sizes or trees meet, the local coordinates of border vertices
need to be remapped so that shared vertices align correctly.

In such cases the engine uses precomputed masks that encode how the current tile's coordinates
transform relative to its neighbour.

Different masks apply depending on:

  • Whether the neighbour is larger

  • Whether the neighbour is from the same tree or a different tree

  • The tile's local quadrant

See Appendix C, Appendix D and Appendix E.

Tree Priority: Right-side tree dominates the left-side tree, while top and bottom trees are the least dominant.

Tree Index

Tree Adjacency Map

TreeLeftRightTopBottom
05423
14523
21045
31054
40123
51023

Shared Corners

Shared Borders

Summary

As a result, each shared vertex is now owned by exactly one tile, chosen deterministically based on
tile size, tree dominance, and corner priority. This guarantees watertight joins across all LOD transitions without runtime stitching.

The corresponding shader code of Vertex Sharing is located in getVertexPosition() on GitHub.

Lighting and Shading

Lighting is intentionally simplified, focused on readability and scale rather than physical correctness.

In order to prioritize readability of the lighting, we applied the following techniques:

  • Scale normal fade-out by tile size

  • Add roughness control for IBL reflections

  • Implement screen-space refraction, fading offsets near the bottom of the screen

  • Discard refraction hits behind the water surface

  • Color derived from view direction and depth

  • Add direct sun specular and a light crest brightening approximation

Rather than pursuing physically accurate ocean lighting, the goal here is visual stability across extreme LOD transitions.

Global UV Mapping

All ocean tiles use a local UV range of [0, 1]. To keep texture sampling continuous across tiles at different quadtree levels, these UVs are mapped into a shared global UV space.

The corresponding shader code is located in convertUvToGlobalScale() on GitHub.

Precision Issues

Precision issues can sneak in through blending. When fading out the ocean normal (for example by depth or distance), if the fade factor is effectively zero, we bypass normalize(lerp()) and directly use the planet normal.

That avoids subtle artifacts from near-flat normalization.

With Precision Issue

Without Precision Issue

Displacement

Displacement is driven by a baked 100-frame animation sampled from an offline simulation.

The animation textures are taken from an open-source project and credited accordingly.
It’s a straightforward approach, no runtime simulation, but enough temporal variation to feel alive.

Displacement gradually fades out:

  • By water depth: deeper regions appear calmer

  • By distance to camera: distant waves stay visually stable

It’s predictable, consistent across quadtree levels, and fast enough to update globally.

Closing Thoughts

This version of the ocean system is designed to maintain seamless, consistent behavior across all tiles and levels of detail.
It integrates efficiently with the planetary terrain, balancing scale and performance.

Future improvements could include dynamic waves, enhanced scattering, and foam simulation.