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// Foam contributions to the composed cascade material: bicubic cascade
2// sampling, breakup masks, bank streaks, bubble tint, and foam lighting.
4#define_import_path aqua::foam::shade
6#import bevy_pbr::mesh_view_bindings::globals
8#import aqua::cascade::{CascadeParams, LUMINANCE_EPSILON, LocalLightSample, MIN_SAMPLE_WEIGHT, RiverState, advected_world, capillary_resolved_weight, cascade_layout, foam_data, foam_pattern, foam_pattern_sampler, foam_sampler, lod_count, screen_texture_lod, surface, world_to_uv}
9#import aqua::foam::contract::{FOAM_PATTERN_RESOLUTION, FOAM_TEXTURE_RESOLUTION}
10const INV_PI: f32 = 0.31830988618;
11const CREST_FOAM_WHITE_COLOR: vec4<f32> = vec4(
12 1.0, 0.99634784, 0.97199994, 0.6862745,
13);
14// The blue tint and 0.14 parallax preserve the subsurface foam cue.
15const CREST_FOAM_BUBBLE_COLOR: vec3<f32> = vec3(0.64, 0.83, 0.82);
16const CREST_FOAM_BUBBLE_PARALLAX: f32 = 0.14;
17const CREST_FOAM_BUBBLE_COVERAGE: f32 = 1.68;
18const CREST_FOAM_NORMAL_STRENGTH: f32 = 3.5;
19const CREST_FOAM_SPECULAR_FALLOFF: f32 = 293.0;
20const CREST_FOAM_SPECULAR_BOOST: f32 = 0.15;
22// Rotation and independent scrolling avoid a close-range beat with the two main taps.
23const FINE_FOAM_ROTATION: mat2x2<f32> = mat2x2(
24 vec2(0.798636, 0.601815),
25 vec2(-0.601815, 0.798636),
26);
27const FINE_FOAM_SCROLL_DIRECTION: vec2<f32> = vec2(-0.819152, 0.573576);
28const FINE_FOAM_SCALE: f32 = 0.55;
29// Linear-light mean of the Unity-style 512px Foam2 import.
30const CREST_FOAM_LINEAR_MEAN: f32 = 0.426906;
32// GodotOceanWaves `water.gdshader`: cubic B-spline weights for the GPU Gems 2
33// four-bilinear-tap approximation to bicubic filtering.
34fn cubic_weights(value: f32) -> vec4<f32> {
35 let squared = value * value;
36 let cubed = squared * value;
37 return vec4(
38 -cubed + 3.0 * squared - 3.0 * value + 1.0,
39 3.0 * cubed - 6.0 * squared + 4.0,
40 -3.0 * cubed + 3.0 * squared + 3.0 * value + 1.0,
41 cubed,
42 ) / 6.0;
45fn sample_foam_bicubic(uv: vec2<f32>, layer: i32, resolution: f32) -> f32 {
46 let texel_position = uv * resolution + vec2(0.5);
47 let fraction = fract(texel_position);
48 let wx = cubic_weights(fraction.x);
49 let wy = cubic_weights(fraction.y);
50 let groups = vec4(
51 wx.x + wx.y,
52 wx.z + wx.w,
53 wy.x + wy.y,
54 wy.z + wy.w,
55 );
56 let positions = (
57 vec4(wx.y, wx.w, wy.y, wy.w) / groups
58 + vec4(-1.5, 0.5, -1.5, 0.5)
59 + floor(texel_position).xxyy
60 ) / resolution;
61 let blend = vec2(
62 groups.x / (groups.x + groups.y),
63 groups.z / (groups.z + groups.w),
64 );
65 let lower = mix(
66 textureSampleLevel(
67 foam_data,
68 foam_sampler,
69 positions.yw,
70 layer,
71 0.0,
72 ).r,
73 textureSampleLevel(
74 foam_data,
75 foam_sampler,
76 positions.xw,
77 layer,
78 0.0,
79 ).r,
80 blend.x,
81 );
82 let upper = mix(
83 textureSampleLevel(
84 foam_data,
85 foam_sampler,
86 positions.yz,
87 layer,
88 0.0,
89 ).r,
90 textureSampleLevel(
91 foam_data,
92 foam_sampler,
93 positions.xz,
94 layer,
95 0.0,
96 ).r,
97 blend.x,
98 );
99 return mix(lower, upper, blend.y);
102fn sample_foam_cascade(world_xz: vec2<f32>, cascade: CascadeParams, layer: i32) -> f32 {
103 let uv = world_to_uv(world_xz, cascade);
104 let bilinear = textureSampleLevel(
105 foam_data,
106 foam_sampler,
107 uv,
108 layer,
109 0.0,
110 ).r;
111 // Gerstner's smooth source already hides its texels; bicubic taps only help FFT.
112 if surface.reflection.x < 0.5 || surface.debug.w > 0.5 {
113 return bilinear;
114 }
115 let bicubic = sample_foam_bicubic(uv, layer, FOAM_TEXTURE_RESOLUTION);
116 let coverage = cascade.texel_width * cascade.texture_res;
117 let pixels_per_meter = FOAM_TEXTURE_RESOLUTION / coverage;
118 return mix(bicubic, bilinear, min(1.0, pixels_per_meter * 0.1));
121fn sample_foam_density(world_xz: vec2<f32>, lod: u32, alpha: f32) -> f32 {
122 let smaller = cascade_layout.cascades[lod];
123 let bigger = cascade_layout.cascades[lod + 1u];
124 let smaller_weight = (1.0 - alpha) * smaller.weight;
125 let bigger_weight = (1.0 - smaller_weight) * bigger.weight;
126 var density = 0.0;
127 if smaller_weight > MIN_SAMPLE_WEIGHT {
128 density += smaller_weight
129 * sample_foam_cascade(world_xz, smaller, i32(lod));
130 }
131 if bigger_weight > MIN_SAMPLE_WEIGHT && lod + 1u < lod_count() {
132 density += bigger_weight
133 * sample_foam_cascade(world_xz, bigger, i32(lod + 1u));
134 }
135 return clamp(density, 0.0, 1.0);
138fn surface_foam_mask(
139 advected_xz: vec2<f32>,
140 lod: u32,
141 alpha: f32,
142 density: f32,
143 sample_offset: vec2<f32>,
144) -> f32 {
145 let cascade = cascade_layout.cascades[lod];
146 let texture_scale = surface.foam.x * cascade.scale / 25.0;
147 // The breakup pattern rides the current so foam streaks do not slide
148 // against the water carrying them; callers hand over the advected
149 // position so this module never touches per-invocation state.
150 let offset = vec2(globals.time / 10.0) + sample_offset;
151 let pattern_xz = advected_xz;
152 let texture_width = textureDimensions(foam_pattern).x;
153 let near_lod = screen_texture_lod(texture_scale / 1.25, texture_width);
154 let near = textureSampleLevel(
155 foam_pattern,
156 foam_pattern_sampler,
157 (1.25 * pattern_xz + offset) / texture_scale,
158 near_lod,
159 ).r;
160 let far_scale = 2.0 * texture_scale;
161 let far_lod = screen_texture_lod(far_scale / 1.25, texture_width);
162 let far = textureSampleLevel(
163 foam_pattern,
164 foam_pattern_sampler,
165 (1.25 * pattern_xz + offset) / far_scale,
166 far_lod,
167 ).r;
168 var pattern = mix(near, far, alpha);
169 let fine_scroll = FINE_FOAM_SCROLL_DIRECTION * globals.time / 17.0;
170 let fine_coordinates =
171 FINE_FOAM_ROTATION * (1.25 * pattern_xz + sample_offset) + fine_scroll;
172 let fine_scale = FINE_FOAM_SCALE * texture_scale;
173 let fine_lod = screen_texture_lod(fine_scale / 1.25, texture_width);
174 let fine = textureSampleLevel(
175 foam_pattern,
176 foam_pattern_sampler,
177 fine_coordinates / fine_scale,
178 fine_lod,
179 ).r;
180 // Mean-one near-field modulation hides the 512-grid footprint without
181 // changing the far-field pattern.
182 let fine_modulation = 1.0 + 0.65 * (fine - CREST_FOAM_LINEAR_MEAN);
183 let fine_weight = capillary_resolved_weight(advected_xz);
184 let modulated_pattern = pattern * mix(1.0, fine_modulation, fine_weight);
186 pattern = select(pattern, modulated_pattern, fine_weight > 0.0);
187 let black_point = clamp(1.0 - density, 0.0, 1.0);
188 return smoothstep(black_point, black_point + surface.foam.y, pattern);
191// Add elongated, flow-aligned foam where fast water meets a bank.
192fn river_streak_density(state: RiverState, world_xz: vec2<f32>, lod: u32, alpha: f32) -> f32 {
193 if !state.enabled {
194 return 0.0;
195 }
196 let speed = length(state.sample.xy);
197 // Same 8 m near-bank band as the ripple scale.
198 let margin_ratio = clamp(state.sample.z / 8.0, 0.0, 1.0);
199 let strength = (1.0 - margin_ratio) * clamp(speed / 1.6 - 0.2, 0.0, 1.0);
200 if strength <= 0.001 {
201 return 0.0;
202 }
203 let dir = state.flow / max(speed, 1e-4);
204 let along = dot(dir, world_xz) / (1.0 + 0.9 * min(speed, 4.0));
205 let across = dot(vec2(-dir.y, dir.x), world_xz);
206 // The mask input is a synthetic along/across coordinate; advecting it
207 // here matches the previous in-mask advection of that coordinate.
208 return surface_foam_mask(
209 advected_world(vec2(along, across * 1.6)),
210 lod,
211 alpha,
212 strength * 1.4,
213 vec2(0.0),
214 );
217fn foam_bubble_colour(
218 displaced_xz: vec2<f32>,
219 undisplaced_xz: vec2<f32>,
220 lod: u32,
221 alpha: f32,
222 density: f32,
223 surface_normal: vec3<f32>,
224 to_view: vec3<f32>,
225 ambient_radiance: vec3<f32>,
226) -> vec3<f32> {
227 let wind_direction = vec2(0.866, 0.5);
228 let bubble_world = mix(undisplaced_xz, displaced_xz, 0.7)
229 + 0.5 * globals.time * wind_direction;
230 let bubble_uv = bubble_world / surface.foam.x
231 + 0.125 * surface_normal.xz;
232 let parallax = -CREST_FOAM_BUBBLE_PARALLAX * to_view.xz
233 / max(dot(surface_normal, to_view), LUMINANCE_EPSILON);
234 let smaller = cascade_layout.cascades[lod];
235 let bigger = cascade_layout.cascades[min(lod + 1u, lod_count())];
236 let smaller_sample = textureSampleLevel(
237 foam_pattern,
238 foam_pattern_sampler,
239 (0.74 * bubble_uv + parallax) / (smaller.scale / 25.0),
240 3.0,
241 ).r;
242 let bigger_sample = textureSampleLevel(
243 foam_pattern,
244 foam_pattern_sampler,
245 (0.74 * bubble_uv + parallax) / (bigger.scale / 25.0),
246 3.0,
247 ).r;
248 let bubble_texture = mix(smaller_sample, bigger_sample, alpha);
249 let coverage = clamp(density * CREST_FOAM_BUBBLE_COVERAGE, 0.0, 1.0);
250 return bubble_texture * CREST_FOAM_BUBBLE_COLOR
251 * coverage * ambient_radiance;
254fn local_foam_light(
255 sample: LocalLightSample,
256 foam_normal: vec3<f32>,
257 to_view: vec3<f32>,
258) -> vec3<f32> {
259 let foam_ndl = max(dot(foam_normal, sample.direction), 0.0);
260 let foam_reflection = reflect(-to_view, foam_normal);
261 let diffuse = CREST_FOAM_WHITE_COLOR.rgb
262 * INV_PI * surface.foam.z * sample.radiance * foam_ndl;
263 let specular = pow(
264 max(dot(foam_reflection, sample.direction), 0.0),
265 CREST_FOAM_SPECULAR_FALLOFF,
266 ) * CREST_FOAM_SPECULAR_BOOST * sample.radiance;
267 return diffuse + specular;
Contents