Search across packages, versions, files, declarations, and comments. Use multiple terms to narrow results (AND), and add @vertex, @fragment, or @compute to filter by shader stage.
1// Reimplementation of the approach in Crest Scripts/Shapes/ShapeGerstnerBatched.cs and
2// Shaders/OceanInputs/GerstnerShared.hlsl.
4const LOD_COUNT: u32 = 5u;
5const CASCADE_COUNT: u32 = LOD_COUNT + 1u;
6const WAVE_COUNT: u32 = 40u;
7const TAU: f32 = 6.283185307179586;
8const PI: f32 = 3.141592653589793;
10struct CascadeParams {
11 center: vec2<f32>,
12 scale: f32,
13 texture_res: f32,
14 inv_texture_res: f32,
15 texel_width: f32,
16 weight: f32,
17 max_wavelength: f32,
19struct CascadeLayout {
20 cascades: array<CascadeParams, CASCADE_COUNT>,
21 center: vec4<f32>,
22 // XY bed-map first-texel world origin, ZW inverse world extent.
23 bed_transform: vec4<f32>,
24 // X height minimum, Y height span (negative = no bed map), Z sea level.
25 bed_range: vec4<f32>,
27struct GerstnerWave {
28 direction: vec2<f32>,
29 amplitude: f32,
30 wave_number: f32,
31 angular_frequency: f32,
32 phase: f32,
33 chop_amplitude: f32,
35struct AnimWavesUniform {
36 cascade_layout: CascadeLayout,
37 waves: array<GerstnerWave, WAVE_COUNT>,
38 ranges: array<vec4<u32>, LOD_COUNT>,
39 time: vec4<f32>,
40 flow: vec4<f32>,
43@group(0) @binding(0) var output: texture_storage_2d_array<rgba16float, write>;
44@group(0) @binding(1) var<uniform> params: AnimWavesUniform;
45@group(0) @binding(2) var bed_height: texture_2d<f32>;
47// Decoded "no bed data" depth: matches a cleared full-depth capture texel.
48const NO_BED_DEPTH: f32 = 256.0;
50/// Water depth under the sea level from the game's bed height map. Outside
51/// the mapped area (or with no map at all) the sample keeps the deep default,
52/// mirroring a full-depth capture texel. Nearest-texel read: the shoaling
53/// profile is smooth enough that one 5 m-class bed texel suffices.
54fn bed_water_depth(world_xz: vec2<f32>) -> f32 {
55 let range = params.cascade_layout.bed_range;
56 if range.y < 0.0 {
57 return NO_BED_DEPTH;
58 }
59 let uv = (world_xz - params.cascade_layout.bed_transform.xy)
60 * params.cascade_layout.bed_transform.zw;
61 if any(uv < vec2(0.0)) || any(uv > vec2(1.0)) {
62 return NO_BED_DEPTH;
63 }
64 let dimensions = vec2<u32>(textureDimensions(bed_height));
65 let maximum = vec2<i32>(dimensions - vec2<u32>(1u));
66 let texel = clamp(vec2<i32>(round(uv * vec2<f32>(maximum))), vec2(i32(0)), maximum);
67 let height = f32(textureLoad(bed_height, texel, 0).r) * range.y + range.x;
68 return max(range.z - height, 0.0);
71// A bounded finite-depth shoaling profile. The vertical lane retains more
72// energy than horizontal chop through the breaker zone, producing steeper
73// near-shore crests without changing deep-water phase or world anchoring.
74fn shoaling_weights(depth: f32, wave_number: f32) -> vec2<f32> {
75 let relative_depth = clamp(depth * wave_number / PI, 0.0, 1.0);
76 let vertical_base = smoothstep(0.0, 1.0, relative_depth);
77 let breaker = 4.0 * vertical_base * (1.0 - vertical_base);
78 let vertical = vertical_base * (1.0 + 0.18 * breaker);
79 let chop_ratio = mix(0.55, 1.0, smoothstep(0.15, 0.85, vertical_base));
80 return vec2(vertical * chop_ratio, vertical);
83@compute @workgroup_size(8, 8, 1)
84fn generate(@builtin(global_invocation_id) id: vec3<u32>) {
85 let cascade = params.cascade_layout.cascades[id.z];
86 if any(id.xy >= vec2<u32>(u32(cascade.texture_res))) || id.z >= LOD_COUNT {
87 return;
88 }
89 let uv = (vec2<f32>(id.xy) + vec2(0.5)) * cascade.inv_texture_res;
90 let coverage = cascade.texel_width * cascade.texture_res;
91 let world_xz = coverage * (uv - vec2(0.5)) + cascade.center;
92 let range = params.ranges[id.z];
93 let water_depth = bed_water_depth(world_xz);
94 var displacement = vec3(0.0);
96 for (var index = range.x; index < range.y; index += 1u) {
97 let wave = params.waves[index];
98 let temporal_phase = wave.phase + wave.angular_frequency * params.time.x;
99 let wrapped_phase = temporal_phase - floor(temporal_phase / TAU) * TAU;
100 let angle = wave.wave_number * dot(wave.direction, world_xz) + wrapped_phase;
101 // Extend Crest's per-wave attenuation with a smooth breaker profile.
102 // Apply it before coarse-to-fine combination so every owned wavelength
103 // responds to its own finite-depth ratio.
104 let shoaling = shoaling_weights(water_depth, wave.wave_number);
105 let attenuation = params.time.z * mix(vec2(1.0), shoaling, params.time.y);
106 let horizontal = attenuation.x * wave.chop_amplitude * sin(angle);
107 displacement += vec3(
108 horizontal * wave.direction.x,
109 attenuation.y * wave.amplitude * cos(angle),
110 horizontal * wave.direction.y,
111 );
112 }
113 textureStore(output, vec2<i32>(id.xy), i32(id.z), vec4(displacement, 0.0));
Contents