FFT ocean¶
an open sea driven by a Tessendorf spectrum, not analytic waves.
▶ Run in browserTags: 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()