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