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// Bed-depth and river-body contributions to the composed cascade material.
3#define_import_path aqua::shore::water
5#import aqua::cascade::{bed_height, cascade_layout, caustics_sampler, caustics_texture, surface}
7// Decoded "no bed data" depth: matches a cleared full-depth capture texel.
9const NO_BED_DEPTH: f32 = 256.0;
10// Must match the 16-cell startup texture generated by bevy-aqua-shore.
11const CAUSTIC_CELLS_PER_TILE: f32 = 16.0;
12// Restores bright focused ridges after multiplying two sparse samples.
13const CAUSTIC_FOCUS_GAIN: f32 = 12.0;
14// Suppress moonlight-scale directional sources while smoothly admitting daylight.
15const CAUSTIC_DAYLIGHT_MIN_LUX: f32 = 64.0;
16const CAUSTIC_REFERENCE_SUN_LUX: f32 = 120000.0;
17const CAUSTIC_LUMINANCE_WEIGHTS: vec3<f32> = vec3(0.2126, 0.7152, 0.0722);
19// River-wave synthesis lives in bevy_aqua_core::river (shared with the
20// wave-query pass).
23/// Water depth under the sea level from the game's bed height map. Outside
24/// the mapped area (or with no map at all) the sample keeps the deep default,
25/// mirroring a full-depth capture texel.
26fn bed_water_depth(world_xz: vec2<f32>) -> f32 {
27 let range = cascade_layout.bed_range;
28 if range.y < 0.0 {
29 return NO_BED_DEPTH;
30 }
31 let uv = (world_xz - cascade_layout.bed_transform.xy)
32 * cascade_layout.bed_transform.zw;
33 if any(uv < vec2(0.0)) || any(uv > vec2(1.0)) {
34 return NO_BED_DEPTH;
35 }
36 let dimensions = vec2<f32>(textureDimensions(bed_height));
37 let texel_position = uv * (dimensions - vec2(1.0));
38 let base = vec2<i32>(floor(texel_position));
39 let fraction = fract(texel_position);
40 let maximum = i32(dimensions.x) - 1;
41 let maximum_y = i32(dimensions.y) - 1;
42 let p00 = clamp(base, vec2(0), vec2(maximum, maximum_y));
43 let p10 = clamp(base + vec2(1, 0), vec2(0), vec2(maximum, maximum_y));
44 let p01 = clamp(base + vec2(0, 1), vec2(0), vec2(maximum, maximum_y));
45 let p11 = clamp(base + vec2(1), vec2(0), vec2(maximum, maximum_y));
46 let row_0 = mix(
47 textureLoad(bed_height, p00, 0).r,
48 textureLoad(bed_height, p10, 0).r,
49 fraction.x,
50 );
51 let row_1 = mix(
52 textureLoad(bed_height, p01, 0).r,
53 textureLoad(bed_height, p11, 0).r,
54 fraction.x,
55 );
56 let height = mix(row_0, row_1, fraction.y) * range.y + range.x;
57 return max(range.z - height, 0.0);
60// The bed map is world-static, so Crest's cascade blending for SeaFloorDepth
61// collapses to one lookup; only the shallow-attenuation cap remains.
62fn blended_water_depth(world_xz: vec2<f32>) -> f32 {
63 return min(bed_water_depth(world_xz), surface.sea_floor.x);
66// Direct sunlight reaches the bed before Beer-Lambert mixing.
67fn caustic_bed_radiance(
68 scene_colour: vec3<f32>,
69 world_xz: vec2<f32>,
70 water_depth: f32,
71 time: f32,
72 sun_direction: vec3<f32>,
73 sun_unexposed_radiance: vec3<f32>,
74 sun_shadow: f32,
75 extinction: vec3<f32>,
76) -> vec3<f32> {
77 let strength = surface.caustics.x * surface.sea_floor.w;
78 if strength <= 0.0 {
79 return scene_colour;
80 }
81 let maximum_depth = surface.caustics.w;
82 if water_depth >= maximum_depth || sun_direction.y <= 0.0 || sun_shadow <= 0.0 {
83 return scene_colour;
84 }
85 let scale = surface.caustics.y * CAUSTIC_CELLS_PER_TILE;
86 let scroll = surface.caustics.z * time / scale;
87 // Incommensurate scale and directions prevent the two layers from locking.
88 let uv_a = world_xz / scale + vec2(scroll, 0.63 * scroll);
89 let uv_b = world_xz * 1.37 / scale + vec2(-0.71 * scroll, 0.43 * scroll);
90 let a = textureSampleLevel(caustics_texture, caustics_sampler, uv_a, 0.0).r;
91 let b = textureSampleLevel(caustics_texture, caustics_sampler, uv_b, 0.0).r;
92 let pattern = CAUSTIC_FOCUS_GAIN * a * b;
93 let depth_gate = 1.0 - smoothstep(0.0, maximum_depth, water_depth);
94 let direct_lux = dot(
95 max(sun_unexposed_radiance, vec3(0.0)),
96 CAUSTIC_LUMINANCE_WEIGHTS,
97 );
98 let daylight = clamp(
99 (direct_lux - CAUSTIC_DAYLIGHT_MIN_LUX)
100 / (CAUSTIC_REFERENCE_SUN_LUX - CAUSTIC_DAYLIGHT_MIN_LUX),
101 0.0,
102 1.0,
103 );
104 // The incoming sunlight path lengthens toward the horizon. Outgoing bed
105 // radiance receives the normal camera-path attenuation after this function.
106 let bed_incidence = max(sun_direction.y, 0.0);
107 let incoming_path = water_depth / max(bed_incidence, 0.02);
108 let incoming_transmission = exp(-extinction * incoming_path);
109 let sun_chroma = max(sun_unexposed_radiance, vec3(0.0))
110 / max(direct_lux, CAUSTIC_DAYLIGHT_MIN_LUX);
111 // Focus the bed's existing pre-exposed radiance rather than adding raw lux
112 // as an emissive overlay. This preserves bed color and camera exposure.
113 let focus = strength * depth_gate * pattern * daylight
114 * bed_incidence * sun_shadow;
115 return scene_colour + scene_colour * sun_chroma
116 * incoming_transmission * focus;
Contents