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// Depth, medium, far-tier, and transmission optics. Final light/foam
2// composition remains in the terminal material so imports stay one-way.
4#define_import_path aqua::optics
6#import bevy_pbr::{
7 prepass_utils,
8 mesh_view_bindings::{globals, lights, view},
9}
10#import bevy_pbr::mesh_view_bindings as view_bindings
11#import aqua::cascade::{DEBUG_MODE_BEAUTY, DEBUG_MODE_BEER_LAMBERT, DEBUG_MODE_REFRACTION_VALIDITY, DEBUG_MODE_SEA_FLOOR, DEBUG_MODE_TRANSMISSION, DEBUG_MODE_UNREFRACTED, DEBUG_MODE_WATER_PATH, LUMINANCE_EPSILON, MIN_NORMAL_Y, capillary_resolved_weight, cascade_layout, godot_fresnel, invocation_extinction, invocation_ripple, sample_planar_reflection, screen_xz_footprint, surface}
12#import aqua::waves::displace::{FFT_JONSWAP_SLOPE_VARIANCE, GERSTNER_SLOPE_VARIANCE, WAVE_NORMALS_SLOPE_VARIANCE, capillary_normal_slope, detail_normal_sample}
13#import aqua::foam::shade::{sample_foam_density}
14#import aqua::shore::water::{blended_water_depth, caustic_bed_radiance}
15#import aqua::light::incident::{GODOT_NORMAL_FADE_RATE, GODOT_NORMAL_MINIMUM_STRENGTH, GODOT_SSS_MODIFIER, GODOT_WATER_ALBEDO, LUMINANCE_WEIGHTS, filtered_primary_light_color, ggx_distribution, safe_normalize, sample_diffuse_environment, sample_environment, smith_masking_shadowing, strongest_incident_directional_light}
16#import bevy_aqua_core::material::{CameraDepthDebug, CameraDepthPath, FoamState, MediumState, NearSurface, PrimaryLightState, SurfaceVertexOutput, TransmissionState}
18// A 2^-10 residual in the least-attenuated channel bounds body error to
19// 0.0977% of scene/scatter contrast before Fresnel. At Crest's shipped minimum
20// extinction (0.3 / m), the gate begins at 23.10 m.
21const TRANSMISSION_OPAQUE_OPTICAL_DEPTH: f32 = 6.931471806;
23// Dupuy et al. 2013 LEADR-style slope filtering. A pixel cannot resolve waves
24// shorter than twice its projected world footprint. Integrate each shipped
25// wavelength band's measured slope variance below that Nyquist cutoff. The
26// logarithmic partial-band term matches the generator's octave partition.
27fn unresolved_wave_roughness(
28 world_xz: vec2<f32>,
29 to_view: vec3<f32>,
30 lod_alpha: f32,
31 lighting_normal_strength: f32,
32 filtered_detail_variance: f32,
33) -> f32 {
34 let footprint = screen_xz_footprint();
35 let unresolved_wavelength = 2.0 * footprint;
36 var unresolved_variance = 0.0;
37 var resolved_variance = 0.0;
38 for (var band = 0u; band < 5u; band++) {
39 let maximum_wavelength = cascade_layout.cascades[band].max_wavelength;
40 let minimum_wavelength = 0.5 * maximum_wavelength;
41 let unresolved_fraction = clamp(
42 log2(max(unresolved_wavelength / minimum_wavelength, 1.0)),
43 0.0,
44 1.0,
45 );
46 let fft_variance = FFT_JONSWAP_SLOPE_VARIANCE[band];
47 let band_variance = mix(
48 GERSTNER_SLOPE_VARIANCE[band],
49 fft_variance,
50 surface.reflection.x,
51 );
52 unresolved_variance += unresolved_fraction * band_variance;
53 resolved_variance += (1.0 - unresolved_fraction) * band_variance;
54 }
56 let lod_blend_energy = 2.0 * (
57 (1.0 - lod_alpha) * (1.0 - lod_alpha) + lod_alpha * lod_alpha
58 );
59 let detail_variance = WAVE_NORMALS_SLOPE_VARIANCE
60 * lod_blend_energy
61 * surface.detail.y * surface.detail.y
62 * surface.detail.z * surface.detail.z;
63 let filtered_variance = min(filtered_detail_variance, detail_variance);
64 unresolved_variance += filtered_variance;
65 resolved_variance += detail_variance - filtered_variance;
66 let capillary_resolved = capillary_resolved_weight(world_xz);
67 let capillary_variance = WAVE_NORMALS_SLOPE_VARIANCE
68 * surface.capillary.y * surface.capillary.y;
69 unresolved_variance += capillary_variance
70 * (1.0 - capillary_resolved * capillary_resolved);
71 resolved_variance += capillary_variance
72 * capillary_resolved * capillary_resolved;
74 // GodotOceanWaves fades all lighting slopes with distance. Move the
75 // removed resolved variance into LEADR roughness instead of deleting it.
76 let removed_fraction = 1.0
77 - lighting_normal_strength * lighting_normal_strength;
78 var slope_variance = unresolved_variance
79 + removed_fraction * resolved_variance;
80 let grazing_boost = mix(1.0, 1.5, 1.0 - abs(to_view.y));
81 slope_variance *= surface.reflection.y * grazing_boost;
82 return min(sqrt(max(slope_variance, 0.0)), surface.reflection.w);
85fn deep_water_weight(water_depth: f32) -> f32 {
86 return smoothstep(0.35, surface.shallow_color.a, water_depth);
89fn depth_aware_body_albedo(
90 water_depth: f32,
91 deep_body_albedo: vec3<f32>,
92) -> vec3<f32> {
93 return mix(
94 surface.shallow_color.rgb,
95 deep_body_albedo,
96 deep_water_weight(water_depth),
97 );
100fn far_field_water(
101 world_position: vec4<f32>,
102 surface_level: f32,
103 geometric_normal: vec3<f32>,
104 to_view: vec3<f32>,
105 wave_height: f32,
106 water_depth: f32,
107) -> vec3<f32> {
108 let geometric_slope = geometric_normal.xz / max(geometric_normal.y, MIN_NORMAL_Y);
109 let lighting_normal = safe_normalize(
110 vec3(
111 geometric_slope.x * GODOT_NORMAL_MINIMUM_STRENGTH,
112 1.0,
113 geometric_slope.y * GODOT_NORMAL_MINIMUM_STRENGTH,
114 ),
115 vec3(0.0, 1.0, 0.0),
116 );
117 let view_vertical = abs(to_view.y);
118 let deep_body_albedo = mix(
119 surface.grazing_color.rgb,
120 surface.deep_color.rgb,
121 view_vertical,
122 );
123 // Camera distance must not turn a shallow lake into deep ocean. Keep the
124 // near path's bed-depth color classification while omitting transmission.
125 let body_albedo = depth_aware_body_albedo(water_depth, deep_body_albedo);
126 let diffuse_irradiance = sample_diffuse_environment(vec3(0.0, 1.0, 0.0));
127 var body = diffuse_irradiance * (body_albedo + GODOT_WATER_ALBEDO);
129 let perceptual_roughness = max(surface.reflection.w, 0.05);
130 let reflection = reflect(-to_view, lighting_normal);
131 var reflected_radiance = sample_environment(
132 reflection,
133 lighting_normal,
134 perceptual_roughness,
135 );
136 let planar = sample_planar_reflection(world_position.xyz, surface_level, lighting_normal);
137 reflected_radiance = mix(reflected_radiance, planar.color, planar.weight);
138 if lights.n_directional_lights > 0u {
139 let light = lights.directional_lights[0u];
140 let light_direction = safe_normalize(
141 light.direction_to_light,
142 vec3(0.0, 1.0, 0.0),
143 );
144 let filtered_light_color = filtered_primary_light_color(
145 world_position,
146 light.direction_to_light,
147 light.sun_disk_angular_size,
148 light.color.rgb,
149 );
150 let light_radiance = filtered_light_color * view.exposure;
151 let lambertian = 0.5 * max(dot(lighting_normal, light_direction), 2e-5);
152 body += lambertian * light_radiance * GODOT_WATER_ALBEDO;
153 // Preserve broad SSS without near-only texture samples.
154 let dot_nv = max(dot(lighting_normal, to_view), 2e-5);
155 let sss_light_mask = smith_masking_shadowing(surface.sun.y, dot_nv);
156 let sss_near = 0.5 * dot_nv * dot_nv;
157 let sss_height = max(0.0, wave_height + 2.5)
158 * pow(max(dot(light_direction, -to_view), 0.0), 4.0)
159 * pow(
160 0.5 - 0.5 * dot(light_direction, lighting_normal),
161 3.0,
162 );
163 body += (sss_height + sss_near)
164 * GODOT_SSS_MODIFIER / (1.0 + sss_light_mask)
165 * light_radiance * GODOT_WATER_ALBEDO;
166 let light_luminance = max(
167 dot(filtered_light_color, LUMINANCE_WEIGHTS),
168 LUMINANCE_EPSILON,
169 );
170 let light_color = filtered_light_color / light_luminance;
171 let light_strength = clamp(light_luminance / surface.reflection.z, 0.0, 1.0);
172 let sun_roughness = min(sqrt(
173 surface.sun.y * surface.sun.y
174 + perceptual_roughness * perceptual_roughness,
175 ), 1.0);
176 let halfway = safe_normalize(light_direction + to_view, lighting_normal);
177 let dot_nl = max(dot(lighting_normal, light_direction), 2e-5);
178 let dot_nv_sun = max(dot(lighting_normal, to_view), 2e-5);
179 let light_mask = smith_masking_shadowing(sun_roughness, dot_nv_sun);
180 let view_mask_sun = smith_masking_shadowing(sun_roughness, dot_nl);
181 let distribution = ggx_distribution(
182 clamp(dot(lighting_normal, halfway), 0.0, 1.0),
183 sun_roughness,
184 );
185 let geometric_attenuation = 1.0 / (1.0 + light_mask + view_mask_sun);
186 let sun_specular = distribution
187 * geometric_attenuation / (4.0 * dot_nv_sun + 0.1);
188 reflected_radiance += sun_specular
189 * surface.sun.x
190 * light_color
191 * light_strength;
192 }
194 let view_alignment = clamp(dot(lighting_normal, to_view), 0.0, 1.0);
195 let reflection_weight = clamp(
196 godot_fresnel(view_alignment) * surface.fresnel.z,
197 0.0,
198 1.0,
199 );
200 return mix(body, reflected_radiance, reflection_weight);
203fn camera_eye_depth(uv: vec2<f32>, raw_depth: f32) -> f32 {
204 let ndc = vec3(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), raw_depth);
205 let view_position = view.view_from_clip * vec4(ndc, 1.0);
206 return max(-view_position.z / max(view_position.w, LUMINANCE_EPSILON), 0.0);
209fn empty_camera_depth_path() -> CameraDepthPath {
210 var result: CameraDepthPath;
211 result.path_length = 0.0;
212 result.screen_uv = vec2(0.0);
213 result.scene_z = 0.0;
214 result.has_background = false;
215 return result;
218fn camera_depth_path(in: SurfaceVertexOutput) -> CameraDepthPath {
219 var result = empty_camera_depth_path();
220#ifdef DEPTH_PREPASS
221 let viewport_origin = view.viewport.xy;
222 let viewport_size = view.viewport.zw;
223 result.screen_uv = clamp(
224 (in.position.xy - viewport_origin) / viewport_size,
225 vec2(0.0),
226 vec2(1.0),
227 );
228 let scene_raw_depth = prepass_utils::prepass_depth(in.position, 0u);
229 result.has_background = scene_raw_depth > 0.0;
230 result.scene_z = camera_eye_depth(result.screen_uv, scene_raw_depth);
231 let pixel_z = max(-(view.view_from_world * in.world_position).z, 0.0);
232 result.path_length = max(result.scene_z - pixel_z, 0.0);
233#endif
234 return result;
237// Reimplementation of the approach in Crest `OceanEmission.hlsl:195-242`. The opaque camera depth,
238// never SeaFloorDepth LodData, determines the view-ray water path and whether
239// a refracted sample landed on geometry in front of the water.
240fn camera_depth_debug_from_path(
241 in: SurfaceVertexOutput,
242 normal: vec3<f32>,
243 path: CameraDepthPath,
244) -> CameraDepthDebug {
245 var result: CameraDepthDebug;
246 result.path_length = path.path_length;
247 result.screen_uv = path.screen_uv;
248 result.refracted_uv = path.screen_uv;
249 result.refracted_sample_valid = false;
250 result.has_background = path.has_background;
251#ifdef DEPTH_PREPASS
252 let shallow_gap = min(1.0, 0.5 * path.path_length);
253 let refract_offset = surface.debug.y * normal.xz
254 * shallow_gap / max(path.scene_z, LUMINANCE_EPSILON);
255 result.refracted_uv = clamp(path.screen_uv + refract_offset, vec2(0.0), vec2(1.0));
256 let refracted_pixel = result.refracted_uv * (view.viewport.zw - vec2(1.0))
257 + view.viewport.xy;
258 let refracted_position = vec4(refracted_pixel, in.position.zw);
259 let refracted_raw_depth = prepass_utils::prepass_depth(refracted_position, 0u);
260 result.refracted_sample_valid = refracted_raw_depth < in.position.z;
261#endif
262 return result;
265// Transmission samples the opaque buffer at full resolution. Distortion
266// comes only from the displacement normal; no roughness mip or blur is used.
267fn opaque_background(subview_uv: vec2<f32>) -> vec3<f32> {
268 let dimensions = vec2<f32>(textureDimensions(view_bindings::view_transmission_texture));
269 let full_uv = (
270 subview_uv * view.viewport.zw + view.viewport.xy
271 ) / dimensions;
272 return textureSampleLevel(
273 view_bindings::view_transmission_texture,
274 view_bindings::view_transmission_sampler,
275 full_uv,
276 0.0,
277 ).rgb;
280fn resolve_near_surface(
281 in: SurfaceVertexOutput,
282 surface_lod: u32,
283 geometric_normal: vec3<f32>,
284 far_tier: f32,
285 mode: u32,
286) -> NearSurface {
287 var normal = geometric_normal;
288 var filtered_detail_variance = 0.0;
289 if mode >= DEBUG_MODE_BEAUTY {
290 let near_weight = 1.0 - far_tier;
291 var resolved_slope = normal.xz / max(normal.y, MIN_NORMAL_Y);
292 let detail = detail_normal_sample(
293 in.undisplaced_xz,
294 surface_lod,
295 in.sample_data.y,
296 invocation_ripple(),
297 );
298 resolved_slope += near_weight * detail.xy;
299 filtered_detail_variance = near_weight * near_weight * detail.z;
300 resolved_slope += near_weight * capillary_normal_slope(in.undisplaced_xz, invocation_ripple())
301 * capillary_resolved_weight(in.undisplaced_xz);
302 normal = safe_normalize(
303 vec3(resolved_slope.x, 1.0, resolved_slope.y),
304 vec3(0.0, 1.0, 0.0),
305 );
306 }
307 let lighting_distance = length(in.world_position.xz - view.world_position.xz);
308 let lighting_normal_strength = mix(
309 GODOT_NORMAL_MINIMUM_STRENGTH,
310 1.0,
311 exp(-lighting_distance * GODOT_NORMAL_FADE_RATE),
312 );
313 let full_slope = normal.xz / max(normal.y, MIN_NORMAL_Y);
314 let lighting_normal = safe_normalize(
315 vec3(
316 full_slope.x * lighting_normal_strength,
317 1.0,
318 full_slope.y * lighting_normal_strength,
319 ),
320 vec3(0.0, 1.0, 0.0),
321 );
322 return NearSurface(
323 normal,
324 lighting_normal,
325 lighting_distance,
326 lighting_normal_strength,
327 filtered_detail_variance,
328 );
331fn sample_water_medium(
332 in: SurfaceVertexOutput,
333 surface_lod: u32,
334 lighting_normal: vec3<f32>,
335 to_view: vec3<f32>,
336 mode: u32,
337) -> MediumState {
338 let water_depth = blended_water_depth(in.undisplaced_xz);
339 let view_vertical = abs(to_view.y);
340 let deep_body_albedo = mix(
341 surface.grazing_color.rgb,
342 surface.deep_color.rgb,
343 view_vertical,
344 );
345 // Metric SeaFloorDepth shifts only the volume-scatter endpoint. Reflection,
346 // foam, and camera-depth transmission remain on their existing lanes.
347 let body_albedo = depth_aware_body_albedo(water_depth, deep_body_albedo);
348 var diffuse_irradiance = vec3(0.0);
349 if mode >= DEBUG_MODE_BEAUTY {
350 diffuse_irradiance = sample_diffuse_environment(lighting_normal);
351 }
352 var foam_density = 0.0;
353 if mode != DEBUG_MODE_SEA_FLOOR {
354 foam_density = sample_foam_density(
355 in.undisplaced_xz,
356 surface_lod,
357 in.sample_data.y,
358 );
359 }
360 return MediumState(
361 body_albedo,
362 diffuse_irradiance,
363 water_depth,
364 foam_density,
365 );
368fn illuminate_bed(
369 scene_colour: vec3<f32>,
370 in: SurfaceVertexOutput,
371 medium: MediumState,
372 primary: PrimaryLightState,
373) -> vec3<f32> {
374 let incident = strongest_incident_directional_light(
375 in,
376 vec3(0.0, 1.0, 0.0),
377 primary.view_z,
378 );
379 if !incident.valid {
380 return scene_colour;
381 }
382 return caustic_bed_radiance(
383 scene_colour,
384 in.undisplaced_xz,
385 medium.water_depth,
386 globals.time,
387 incident.direction,
388 incident.color,
389 incident.shadow,
390 invocation_extinction(),
391 );
394fn resolve_transmission(
395 in: SurfaceVertexOutput,
396 normal: vec3<f32>,
397 scatter_colour: vec3<f32>,
398 medium: MediumState,
399 foam: FoamState,
400 primary: PrimaryLightState,
401 mode: u32,
402) -> TransmissionState {
403 var body = scatter_colour;
404 var shared_depth_path = foam.depth_path;
405 var has_shared_depth_path = foam.has_depth_path;
406 if mode >= DEBUG_MODE_WATER_PATH && mode <= DEBUG_MODE_SEA_FLOOR {
407 // Diagnostic transmission modes preserve the complete sampling path.
408 if !has_shared_depth_path {
409 shared_depth_path = camera_depth_path(in);
410 has_shared_depth_path = true;
411 }
412 let depth_debug = camera_depth_debug_from_path(in, normal, shared_depth_path);
413 if mode == DEBUG_MODE_WATER_PATH {
414 let path = clamp(depth_debug.path_length / surface.debug.z, 0.0, 1.0);
415 return TransmissionState(body, vec4(vec3(path), 1.0), true);
416 }
417 if mode == DEBUG_MODE_REFRACTION_VALIDITY {
418 let output = select(
419 vec4(1.0, 0.0, 0.0, 1.0),
420 vec4(0.0, 1.0, 0.0, 1.0),
421 depth_debug.refracted_sample_valid,
422 );
423 return TransmissionState(body, output, true);
424 }
425 let refraction_enabled = mode == DEBUG_MODE_TRANSMISSION
426 || mode == DEBUG_MODE_BEER_LAMBERT
427 || mode == DEBUG_MODE_SEA_FLOOR;
428 let use_refraction = refraction_enabled
429 && depth_debug.refracted_sample_valid;
430 let background_uv = select(
431 depth_debug.screen_uv,
432 depth_debug.refracted_uv,
433 use_refraction,
434 );
435 let scene_colour = opaque_background(background_uv);
436 if mode == DEBUG_MODE_TRANSMISSION || mode == DEBUG_MODE_UNREFRACTED {
437 return TransmissionState(body, vec4(scene_colour, 1.0), true);
438 }
440 // Crest `OceanEmission.hlsl:254-266`: per-channel Beer-Lambert fog.
441 // The colour ramp emerges from extinction; there is no authored ramp.
442 let lit_scene = illuminate_bed(scene_colour, in, medium, primary);
443 let alpha = 1.0 - exp(-invocation_extinction() * depth_debug.path_length);
444 body = mix(lit_scene, scatter_colour, alpha);
445 if mode == DEBUG_MODE_BEER_LAMBERT {
446 return TransmissionState(body, vec4(body, 1.0), true);
447 }
448 } else if mode == DEBUG_MODE_BEAUTY {
449 if !has_shared_depth_path {
450 shared_depth_path = camera_depth_path(in);
451 has_shared_depth_path = true;
452 }
453 let depth_path = shared_depth_path;
454 // Reduce extinction in the first few metres so the seabed stays
455 // visible while the coastal scatter endpoint supplies turquoise color.
456 let deep_weight = smoothstep(0.35, surface.shallow_color.a, medium.water_depth);
457 let shallow_extinction_scale = mix(vec3(0.52, 0.42, 0.62), vec3(1.0), deep_weight);
458 let extinction = invocation_extinction() * shallow_extinction_scale;
459 let minimum_extinction = min(extinction.r, min(extinction.g, extinction.b));
460 let optical_depth = minimum_extinction * depth_path.path_length;
461 if depth_path.has_background
462 && depth_path.path_length > LUMINANCE_EPSILON
463 && optical_depth < TRANSMISSION_OPAQUE_OPTICAL_DEPTH {
464 let depth_debug = camera_depth_debug_from_path(in, normal, depth_path);
465 let use_refraction = depth_debug.refracted_sample_valid;
466 let background_uv = select(
467 depth_debug.screen_uv,
468 depth_debug.refracted_uv,
469 use_refraction,
470 );
471 let scene_colour = opaque_background(background_uv);
472 let lit_scene = illuminate_bed(scene_colour, in, medium, primary);
473 let alpha = 1.0 - exp(-extinction * depth_debug.path_length);
474 body = mix(lit_scene, scatter_colour, alpha);
475 }
476 // A clear depth prepass has no transmissive background. Preserve the
477 // deep-water body there. Once all channels transmit <= 2^-10, use the same
478 // scatter endpoint without a refracted depth or opaque-colour sample.
479 }
480 return TransmissionState(body, vec4(0.0), false);
Contents