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
| Tree | Left | Right | Top | Bottom |
|---|---|---|---|---|
| 0 | 5 | 4 | 2 | 3 |
| 1 | 4 | 5 | 2 | 3 |
| 2 | 1 | 0 | 4 | 5 |
| 3 | 1 | 0 | 5 | 4 |
| 4 | 0 | 1 | 2 | 3 |
| 5 | 1 | 0 | 2 | 3 |
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.