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// Persistent reprojection, decay, whitecaps, and shore source.
2#import aqua::foam::contract::{FOAM_LOD_COUNT, FOAM_TEXTURE_RESOLUTION, FOAM_TEXTURE_RESOLUTION_U32}
4// Decoded "no bed data" depth: matches a cleared full-depth capture texel.
5const NO_BED_DEPTH: f32 = 256.0;
6const UV_CENTER: f32 = 0.5;
8struct CascadeParams {
9 center: vec2<f32>,
10 scale: f32,
11 texture_res: f32,
12 inv_texture_res: f32,
13 texel_width: f32,
14 weight: f32,
15 max_wavelength: f32,
18struct CascadeLayout {
19 cascades: array<CascadeParams, 6>,
20 center: vec4<f32>,
21 // XY bed-map first-texel world origin, ZW inverse world extent.
22 bed_transform: vec4<f32>,
23 // X height minimum, Y height span (negative = no bed map), Z sea level.
24 bed_range: vec4<f32>,
27struct FoamUniform {
28 source_layout: CascadeLayout,
29 target_layout: CascadeLayout,
30 step: vec4<u32>,
31 wave: vec4<f32>,
32 shore: vec4<f32>,
35@group(0) @binding(0) var source_foam: texture_2d_array<f32>;
36@group(0) @binding(1) var source_sampler: sampler;
37@group(0) @binding(2) var anim_waves: texture_2d_array<f32>;
38@group(0) @binding(3) var waves_sampler: sampler;
39@group(0) @binding(4) var bed_height: texture_2d<f32>;
40@group(0) @binding(5) var target_foam: texture_storage_2d_array<rgba16float, write>;
41@group(0) @binding(6) var<uniform> foam: FoamUniform;
42@group(0) @binding(7) var fft_surface: texture_2d_array<f32>;
44fn world_to_uv(world_xz: vec2<f32>, cascade: CascadeParams) -> vec2<f32> {
45 let coverage = cascade.texel_width * cascade.texture_res;
46 return (world_xz - cascade.center) / coverage + vec2(UV_CENTER);
49fn texel_world(id: vec2<u32>, cascade: CascadeParams) -> vec2<f32> {
50 let uv = (vec2<f32>(id) + vec2(0.5)) / FOAM_TEXTURE_RESOLUTION;
51 let coverage = cascade.texel_width * cascade.texture_res;
52 return cascade.center + (uv - vec2(UV_CENTER)) * coverage;
55fn inside_source(uv: vec2<f32>) -> bool {
56 let radius = UV_CENTER - 1.0 / FOAM_TEXTURE_RESOLUTION;
57 // Chebyshev distance preserves the square cascade footprint.
58 return max(abs(uv.x - UV_CENTER), abs(uv.y - UV_CENTER)) <= radius;
61fn reproject(world_xz: vec2<f32>, slice: u32, source: CascadeLayout) -> f32 {
62 let cascade = source.cascades[slice];
63 let uv = world_to_uv(world_xz, cascade);
64 if inside_source(uv) {
65 return textureSampleLevel(source_foam, source_sampler, uv, i32(slice), 0.0).r;
66 }
67 if slice + 1u < FOAM_LOD_COUNT {
68 let coarse = source.cascades[slice + 1u];
69 let coarse_uv = world_to_uv(world_xz, coarse);
70 if inside_source(coarse_uv) {
71 return textureSampleLevel(
72 source_foam,
73 source_sampler,
74 coarse_uv,
75 i32(slice + 1u),
76 0.0,
77 ).r;
78 }
79 }
80 return 0.0;
83fn displacement(world_xz: vec2<f32>, slice: u32) -> vec4<f32> {
84 let cascade = foam.target_layout.cascades[slice];
85 return textureSampleLevel(
86 anim_waves,
87 waves_sampler,
88 world_to_uv(world_xz, cascade),
89 i32(slice),
90 0.0,
91 );
94fn jacobian_foam_source(
95 world_xz: vec2<f32>,
96 slice: u32,
97 cascade: CascadeParams,
98) -> f32 {
99 if foam.step.z != 0u {
100 let determinant = textureSampleLevel(
101 fft_surface,
102 waves_sampler,
103 world_to_uv(world_xz, cascade),
104 i32(slice),
105 0.0,
106 ).w;
107 return clamp(foam.wave.w - determinant, 0.0, 1.0);
108 }
109 let center = displacement(world_xz, slice);
110 let offset_x = displacement(
111 world_xz + vec2(cascade.texel_width, 0.0),
112 slice,
113 );
114 let offset_z = displacement(
115 world_xz + vec2(0.0, cascade.texel_width),
116 slice,
117 );
118 let tangent_x = vec2(cascade.texel_width, 0.0)
119 + offset_x.xz - center.xz;
120 let tangent_z = vec2(0.0, cascade.texel_width)
121 + offset_z.xz - center.xz;
122 let determinant = (tangent_x.x * tangent_z.y - tangent_x.y * tangent_z.x)
123 / (cascade.texel_width * cascade.texel_width);
124 return clamp(foam.wave.w - determinant + 0.7 * center.w, 0.0, 1.0);
127fn averaged_foam_source(
128 world_xz: vec2<f32>,
129 slice: u32,
130 cascade: CascadeParams,
131) -> f32 {
132 if foam.step.z == 0u {
133 return jacobian_foam_source(world_xz, slice, cascade);
134 }
135 let foam_texel_width = cascade.texel_width
136 * cascade.texture_res / FOAM_TEXTURE_RESOLUTION;
137 let quarter_texel = 0.25 * foam_texel_width;
138 let offset = vec2(quarter_texel);
139 return 0.25 * (
140 jacobian_foam_source(world_xz - offset, slice, cascade)
141 + jacobian_foam_source(
142 world_xz + vec2(offset.x, -offset.y),
143 slice,
144 cascade,
145 )
146 + jacobian_foam_source(
147 world_xz + vec2(-offset.x, offset.y),
148 slice,
149 cascade,
150 )
151 + jacobian_foam_source(world_xz + offset, slice, cascade)
152 );
155// Water depth below sea level. Unmapped samples use the cleared full-depth value.
156fn bed_water_depth(world_xz: vec2<f32>) -> f32 {
157 let range = foam.target_layout.bed_range;
158 if range.y < 0.0 {
159 return NO_BED_DEPTH;
160 }
161 let uv = (world_xz - foam.target_layout.bed_transform.xy)
162 * foam.target_layout.bed_transform.zw;
163 if any(uv < vec2(0.0)) || any(uv > vec2(1.0)) {
164 return NO_BED_DEPTH;
165 }
166 let dimensions = vec2<f32>(textureDimensions(bed_height));
167 let texel_position = uv * (dimensions - vec2(1.0));
168 let base = vec2<i32>(floor(texel_position));
169 let fraction = fract(texel_position);
170 let maximum = i32(dimensions.x) - 1;
171 let maximum_y = i32(dimensions.y) - 1;
172 let p00 = clamp(base, vec2(0), vec2(maximum, maximum_y));
173 let p10 = clamp(base + vec2(1, 0), vec2(0), vec2(maximum, maximum_y));
174 let p01 = clamp(base + vec2(0, 1), vec2(0), vec2(maximum, maximum_y));
175 let p11 = clamp(base + vec2(1), vec2(0), vec2(maximum, maximum_y));
176 let row_0 = mix(
177 textureLoad(bed_height, p00, 0).r,
178 textureLoad(bed_height, p10, 0).r,
179 fraction.x,
180 );
181 let row_1 = mix(
182 textureLoad(bed_height, p01, 0).r,
183 textureLoad(bed_height, p11, 0).r,
184 fraction.x,
185 );
186 let height = mix(row_0, row_1, fraction.y) * range.y + range.x;
187 return max(range.z - height, 0.0);
190fn update(id: vec3<u32>, use_previous_layout: bool, dt: f32) {
191 if any(id.xy >= vec2<u32>(FOAM_TEXTURE_RESOLUTION_U32)) || id.z >= FOAM_LOD_COUNT {
192 return;
193 }
194 let slice = id.z;
195 let cascade = foam.target_layout.cascades[slice];
196 let world_xz = texel_world(id.xy, cascade);
197 var density = reproject(world_xz, slice, foam.target_layout);
198 if use_previous_layout {
199 density = reproject(world_xz, slice, foam.source_layout);
200 }
202 density *= max(0.0, 1.0 - foam.wave.y * dt);
204 let center = displacement(world_xz, slice);
205 density += 5.0 * dt * foam.wave.z
206 * averaged_foam_source(world_xz, slice, cascade);
208 var depth = bed_water_depth(world_xz + center.xz) + center.y;
209 // Two world-anchored finite bands: a wet edge and a breaking-wave band.
210 // Persistent reprojection and decay filter both under camera motion.
211 let wet_edge = 1.0 - smoothstep(foam.shore.z, 2.0 * foam.shore.z, depth);
212 let breaker_rise = smoothstep(foam.shore.z, foam.shore.w, depth);
213 let breaker_fall = 1.0 - smoothstep(foam.shore.w, foam.shore.x, depth);
214 let shore_source = max(wet_edge, 0.18 * breaker_rise * breaker_fall);
215 density += foam.shore.y * dt * shore_source;
217 density = clamp(density, 0.0, 1.0);
218 textureStore(target_foam, vec2<i32>(id.xy), i32(slice), vec4(density, 0.0, 0.0, 0.0));
221#ifdef FOAM_REPROJECT_PREVIOUS
222@compute @workgroup_size(8, 8, 1)
223fn main(@builtin(global_invocation_id) id: vec3<u32>) {
224 update(id, true, 0.0);
226#endif
228#ifdef FOAM_UPDATE_CURRENT
229@compute @workgroup_size(8, 8, 1)
230fn main(@builtin(global_invocation_id) id: vec3<u32>) {
231 update(id, false, foam.wave.x);
233#endif
235#ifndef FOAM_REPROJECT_PREVIOUS
236#ifndef FOAM_UPDATE_CURRENT
237@compute @workgroup_size(8, 8, 1)
238fn main(@builtin(global_invocation_id) id: vec3<u32>) {
239 update(id, true, foam.wave.x);
241#endif
242#endif
Contents