FFT ocean

an open sea driven by a Tessendorf spectrum, not analytic waves.

▶ Run in browser

Tags: 3d ocean fft water transparent

An OceanSurface3D is a built-in transparent pass that reuses the WaterMaterial shading of the Gerstner water pass but replaces the analytic vertex displacement with a sampled FFT displacement map: a Phillips spectrum with the WorldEnvironment wind, inverse-transformed each frame into per-cascade displacement, slope and foam. Three cascades (long swell / mid chop / fine ripple) sum into a believable open sea: rolling swell, choppy crests, foam laced onto the steep folding faces, sun glint tracking the wind, and depth-faded refraction of the submerged island. The cascade texture size + count come from the graphics quality tier.

Controls: Escape - Quit

Usage: uv run python examples/features/3d/ocean.py

Source

  1"""FFT ocean: an open sea driven by a Tessendorf spectrum, not analytic waves.
  2
  3An OceanSurface3D is a built-in transparent pass that reuses the WaterMaterial
  4shading of the Gerstner water pass but replaces the analytic vertex displacement
  5with a sampled FFT displacement map: a Phillips spectrum with the WorldEnvironment
  6wind, inverse-transformed each frame into per-cascade displacement, slope and foam.
  7Three cascades (long swell / mid chop / fine ripple) sum into a believable open
  8sea: rolling swell, choppy crests, foam laced onto the steep folding faces, sun
  9glint tracking the wind, and depth-faded refraction of the submerged island. The
 10cascade texture size + count come from the graphics quality tier.
 11
 12# /// simvx
 13# tags = ["3d", "ocean", "fft", "water", "transparent"]
 14# screenshot_frame = 70
 15# ///
 16
 17Controls:
 18    Escape - Quit
 19
 20Usage:
 21    uv run python examples/features/3d/ocean.py
 22"""
 23
 24from simvx.core import (
 25    Camera3D,
 26    DirectionalLight3D,
 27    Input,
 28    InputMap,
 29    Key,
 30    Material,
 31    Mesh,
 32    MeshInstance3D,
 33    Node,
 34    OceanSurface3D,
 35    WaterMaterial,
 36    WorldEnvironment,
 37)
 38from simvx.graphics import App
 39
 40WIDTH, HEIGHT = 1280, 720
 41
 42
 43class OceanScene(Node):
 44    def on_ready(self):
 45        InputMap.add_action("quit", [Key.ESCAPE])
 46
 47        # The ocean refracts through the HDR scene copy, so a WorldEnvironment
 48        # (which enables the HDR chain) is required; its wind drives the Phillips
 49        # spectrum direction + strength via FrameGlobals.
 50        env = self.add_child(WorldEnvironment())
 51        env.wind_direction = (0.8, 0.35)
 52        env.wind_strength = 0.7
 53
 54        # Camera sits well above the sea so the crests never reach eye level: a
 55        # calm vantage looking out across the open water toward the island.
 56        self.add_child(Camera3D(position=(0, 5.0, 20.0), look_at=(0, 1.5, -30.0), up=(0, 1, 0)))
 57        sun = self.add_child(DirectionalLight3D(intensity=3.2))
 58        sun.direction = (-0.6, -0.7, -0.5)
 59
 60        # A rocky island breaking the surface: gives the shoreline foam a shape to
 61        # curl around and submerged geometry for the depth-fade + refraction to read.
 62        self.add_child(
 63            MeshInstance3D(
 64                mesh=Mesh.sphere(6.0),
 65                material=Material(colour=(0.42, 0.38, 0.32, 1.0), roughness=0.9),
 66                position=(-6.0, -1.5, -14.0),
 67            )
 68        )
 69        # A couple of half-submerged pillars near the camera for refraction wobble.
 70        for x, z in [(5.0, -6.0), (9.0, -12.0)]:
 71            self.add_child(
 72                MeshInstance3D(
 73                    mesh=Mesh.cube(1.0),
 74                    material=Material(colour=(0.30, 0.28, 0.26, 1.0), roughness=0.7),
 75                    position=(x, -0.5, z),
 76                    scale=(1.4, 4.0, 1.4),
 77                )
 78            )
 79
 80        # The open-sea plane at y=0. Its presence switches on the scene
 81        # colour/depth copy the refraction reads. Big + finely tessellated so the
 82        # FFT cascades resolve into swell + chop across the view.
 83        self.ocean = self.add_child(OceanSurface3D(position=(0, 0, 0), size=(800.0, 800.0), subdivisions=320))
 84        self.ocean.material = WaterMaterial(
 85            shallow_colour=(0.06, 0.30, 0.36),
 86            deep_colour=(0.01, 0.06, 0.12),
 87            depth_fade_distance=8.0,
 88            wave_amplitude=1.4,
 89            wave_steepness=0.75,
 90            foam_colour=(0.92, 0.96, 1.0),
 91            foam_amount=1.2,
 92            refraction_strength=0.4,
 93        )
 94
 95    def on_update(self, dt):
 96        if Input.is_action_just_pressed("quit"):
 97            self.app.quit()
 98
 99
100def main():
101    App(width=WIDTH, height=HEIGHT, title="SimVX - FFT Ocean").run(OceanScene())
102
103
104if __name__ == "__main__":
105    main()