bevy-aqua-core 0.1.1

Aqua cascade and material ABI, water-field types, bed map, shared render plumbing, and WGSL contracts.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
// Composed cascade surface material: vertex snap/morph/displacement, the
// transmission/reflection/foam shading pipeline, and both stage entry
// points. Imports every feature module over the aqua::cascade contract.
// Owned by bevy-aqua-core (the material is part of the published contract).

#import bevy_pbr::{
    forward_io::Vertex,
    mesh_functions,
    mesh_view_types,
    clustered_forward as clustering,
    lighting,
    prepass_utils,
    shadows,
    atmosphere::functions::{calculate_visible_sun_ratio, clamp_to_surface},
    atmosphere::bruneton_functions::transmittance_lut_r_mu_to_uv,
    mesh_view_bindings::{globals, light_probes, lights, view},
    view_transformations::position_world_to_clip,
}
#import bevy_pbr::mesh_view_bindings as view_bindings

#import aqua::cascade::{CREST_SSS_RANGE, CREST_SSS_UNCOMPRESSED, DEBUG_MODE_BEAUTY, DEBUG_MODE_BEER_LAMBERT, DEBUG_MODE_FAR_TIER, DEBUG_MODE_FOAM, DEBUG_MODE_LIGHT_RADIANCE, DEBUG_MODE_REFLECTION, DEBUG_MODE_REFLECTION_FRACTION, DEBUG_MODE_REFRACTION_VALIDITY, DEBUG_MODE_SEA_FLOOR, DEBUG_MODE_TRANSMISSION, DEBUG_MODE_UNREFRACTED, DEBUG_MODE_WATER_PATH, DEBUG_MODE_WAVE_HEIGHT, LUMINANCE_EPSILON, LocalLightSample, MIN_NORMAL_Y, SAFE_LENGTH_SQUARED, advected_world, begin_invocation, capillary_resolved_weight, cascade_layout, effective_flow, far_tier_weight, field_params, godot_fresnel, invocation_extinction, invocation_ripple, invocation_river_state, invocation_scatter_scale, lod_count, owning_body, sample_displacement, sample_field_flow, sample_field_level, sample_planar_reflection, set_body_optics, set_effective_flow, set_effective_time, set_fragment_river, set_river_ripple, snap_and_transition, surface}

#import aqua::waves::displace::{FFT_JONSWAP_SLOPE_VARIANCE, GERSTNER_SLOPE_VARIANCE, WAVE_NORMALS_SLOPE_VARIANCE, capillary_normal_slope, crest_sss, detail_normal_sample, far_displacement, far_normal_cross, sample_fft_normal_cross}

#import aqua::foam::contract::FOAM_PATTERN_RESOLUTION
#import aqua::foam::shade::{CREST_FOAM_NORMAL_STRENGTH, CREST_FOAM_SPECULAR_BOOST, INV_PI, CREST_FOAM_SPECULAR_FALLOFF, CREST_FOAM_WHITE_COLOR, foam_bubble_colour, local_foam_light, river_streak_density, sample_foam_density, surface_foam_mask}

#import aqua::shore::water::{blended_water_depth, caustic_bed_radiance}
#import bevy_aqua_core::deform::{deform_current}
#import bevy_aqua_core::material::{BodyLightingState, CameraDepthDebug, CameraDepthPath, FoamState, LocalLightingState, MediumState, NearSurface, PrimaryLightState, SurfaceVertexOutput, TransmissionState}
#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, local_light_contribution, resolve_primary_light, safe_normalize, sample_diffuse_environment, sample_environment, sample_local_light, smith_masking_shadowing, strongest_incident_directional_light, view_direction}
#import aqua::optics::{camera_depth_path, deep_water_weight, empty_camera_depth_path, far_field_water, resolve_near_surface, resolve_transmission, sample_water_medium, unresolved_wave_roughness}

@vertex
fn vertex(vertex: Vertex) -> SurfaceVertexOutput {
    let deformation = deform_current(vertex, globals.time);
    var out: SurfaceVertexOutput;
    out.world_position = deformation.world_position;
    out.position = position_world_to_clip(deformation.world_position.xyz);
    out.world_normal = deformation.world_normal;
    out.undisplaced_xz = deformation.undisplaced_xz;
    out.sample_data = vec3(
        f32(deformation.lod),
        deformation.sample_alpha,
        deformation.wave_height,
    );
    out.base_world_position = deformation.base_world_position;
    return out;
}

fn prepare_surface_foam(
    in: SurfaceVertexOutput,
    surface_lod: u32,
    foam_density: f32,
    lighting_distance: f32,
) -> FoamState {
    let foam_distance_fade = exp(-lighting_distance * 0.0075);
    let visible_foam_density = foam_density * foam_distance_fade;
    var white_foam_density = visible_foam_density;
    var white_foam = 0.0;
    var shared_depth_path = empty_camera_depth_path();
    var has_shared_depth_path = false;
    if visible_foam_density > 0.0 {
        shared_depth_path = camera_depth_path(in);
        has_shared_depth_path = true;
        let foam_depth = shared_depth_path;
        let shoreline_fade = select(
            1.0,
            clamp(foam_depth.path_length / 0.27, 0.0, 1.0),
            foam_depth.has_background,
        );
        white_foam_density *= shoreline_fade;
        white_foam = surface_foam_mask(
            advected_world(in.undisplaced_xz),
            surface_lod,
            in.sample_data.y,
            white_foam_density,
            vec2(0.0),
        );
    }
    // River bank streaks are independent of the persistent foam buffer:
    // they exist wherever fast water runs close to a bank.
    let streak = river_streak_density(
        invocation_river_state(),
        in.world_position.xz,
        surface_lod,
        in.sample_data.y,
    );
    if streak > 0.0 {
        white_foam_density += streak;
        white_foam += surface_foam_mask(
            advected_world(in.undisplaced_xz),
            surface_lod,
            in.sample_data.y,
            streak,
            vec2(0.0),
        );
    }
    return FoamState(
        visible_foam_density,
        white_foam_density,
        white_foam,
        shared_depth_path,
        has_shared_depth_path,
    );
}

fn directional_scatter(
    in: SurfaceVertexOutput,
    surface_lod: u32,
    near: NearSurface,
    primary: PrimaryLightState,
    medium: MediumState,
    to_view: vec3<f32>,
    mode: u32,
) -> vec3<f32> {
    // Crest's authored deep/grazing colours are volume-scatter albedos. They
    // carry no radiance until the scene environment illuminates them.
    var scatter_colour = medium.deep_body_albedo * medium.diffuse_irradiance;
    // Crest `OceanEmission.hlsl::ScatterColour`: backlit subsurface tint is
    // driven by horizontal-displacement pinch, not absolute wave height.
    if mode >= DEBUG_MODE_BEAUTY
        && lights.n_directional_lights > 0u {
        let light = lights.directional_lights[0u];
        let light_direction = safe_normalize(
            light.direction_to_light,
            vec3(0.0, 1.0, 0.0),
        );
        // Crest consumes Unity's `_LightColor0` scene radiance directly. Bevy's
        // GPU light is linear RGB times lux, so one view-exposure multiplication
        // converts it to the same pre-tonemap domain as Bevy PBR and atmosphere.
        let light_radiance = primary.radiance;
        let towards_sun = pow(
            max(dot(light_direction, -to_view), 0.0),
            surface.sss.z,
        );
        let pinch = crest_sss(in.undisplaced_xz, surface_lod, in.sample_data.y);
        // Crest keeps a 0.48 SSS pedestal to hide outer-LOD transitions. With
        // physical Bevy lux this would light the entire uncompressed surface;
        // retain only its compression contrast so radiance pierces wave crests.
        let crest_transmission = clamp(
            (pinch - CREST_SSS_UNCOMPRESSED) / CREST_SSS_RANGE,
            0.0,
            1.0,
        );
        let view_vertical = abs(to_view.y);
        let grazing = max(1.0 - view_vertical * view_vertical, 0.0);
        let dot_nv = max(dot(near.lighting_normal, to_view), 2e-5);
        let sss_light_mask = smith_masking_shadowing(surface.sun.y, dot_nv);
        let sss_near = 0.5 * pow(dot_nv, 2.0);
        let sss_height = max(0.0, in.sample_data.z + 2.5)
            * pow(max(dot(light_direction, -to_view), 0.0), 4.0)
            * pow(
                0.5 - 0.5 * dot(light_direction, near.lighting_normal),
                3.0,
            );
        // GodotOceanWaves supplies broad view/body and height/backlight lanes;
        // Crest's Jacobian term remains the concentrated crest variation.
        scatter_colour += (sss_height + sss_near)
            * GODOT_SSS_MODIFIER
            / (1.0 + sss_light_mask)
            * light_radiance
            * GODOT_WATER_ALBEDO;
        scatter_colour += (surface.sss.x + surface.sss.y * towards_sun)
            * surface.sss_tint.rgb
            * light_radiance
            * grazing
            * crest_transmission
            // Budget transmission by irradiance incident on the mean water
            // plane. This preserves Crest pinch while preventing a grazing sun
            // from lighting every compressed crest at normal-incidence energy.
            * max(light_direction.y, 0.0);
    }
    return scatter_colour;
}

fn shade_water_body(
    in: SurfaceVertexOutput,
    near: NearSurface,
    primary: PrimaryLightState,
    medium: MediumState,
    foam: FoamState,
    to_view: vec3<f32>,
    input_body: vec3<f32>,
    mode: u32,
) -> BodyLightingState {
    var body = input_body;
    // Godot's engine substrate adds diffuse sky irradiance behind every
    // material light() function. Crest consumes the same scene ambient for
    // both the white-foam and sub-surface bubble lanes.
    var foam_ambient_radiance = vec3(0.0);
    if mode >= DEBUG_MODE_BEAUTY {
        // Crest consumes Unity SH L0 for foam. Bevy's up-facing diffuse
        // irradiance is the scene-driven equivalent; no constant radiance
        // floor is allowed when that environment is dark or absent.
        foam_ambient_radiance = sample_diffuse_environment(vec3(0.0, 1.0, 0.0));
        body += medium.diffuse_irradiance * GODOT_WATER_ALBEDO;
        if foam.visible_density > 0.0 {
            body += foam_bubble_colour(
                in.world_position.xz,
                in.undisplaced_xz,
                u32(round(in.sample_data.x)),
                in.sample_data.y,
                foam.visible_density,
                near.normal,
                to_view,
                foam_ambient_radiance,
            );
        }
    }
    // Crest's final Fresnel composition supplies the `(1.0 - fresnel)`
    // modulation exactly once.
    if mode >= DEBUG_MODE_BEAUTY && lights.n_directional_lights > 0u {
        let light = lights.directional_lights[0u];
        let light_direction = safe_normalize(
            light.direction_to_light,
            vec3(0.0, 1.0, 0.0),
        );
        let light_radiance = primary.radiance;
        let lambertian = 0.5 * max(dot(near.lighting_normal, light_direction), 2e-5);
        body += lambertian * light_radiance * GODOT_WATER_ALBEDO;
    }

    let perceptual_roughness = unresolved_wave_roughness(
        in.undisplaced_xz,
        to_view,
        in.sample_data.y,
        near.lighting_normal_strength,
        near.filtered_detail_variance,
    );
    let view_alignment = clamp(dot(near.lighting_normal, to_view), 0.0, 1.0);
    let fresnel = godot_fresnel(view_alignment);
    let foam_distance_fade = exp(-near.lighting_distance * 0.0075);
    let foam_factor = smoothstep(
        0.0,
        1.0,
        medium.foam_density * 0.75,
    ) * foam_distance_fade;
    let foam_roughness = (1.0 - fresnel) * foam_factor;
    let environment_roughness = clamp(
        perceptual_roughness + foam_roughness,
        0.0,
        1.0,
    );
    let has_local_lights = primary.point_start < primary.light_end;
    // Godot uses a 0.4 alpha floor. Aqua's unresolved slope variance adds
    // in quadrature so every direct emitter softens consistently with distance.
    // Skip this emitter-only work when the clustered fragment has no lights.
    var sun_roughness = surface.sun.y;
    if lights.n_directional_lights > 0u || has_local_lights {
        let foam_surface_roughness = clamp(
            surface.sun.y + foam_roughness,
            surface.sun.y,
            1.0,
        );
        sun_roughness = min(sqrt(
            foam_surface_roughness * foam_surface_roughness
                + perceptual_roughness * perceptual_roughness,
        ), 1.0);
    }
    return BodyLightingState(
        body,
        foam_ambient_radiance,
        fresnel,
        foam_roughness,
        environment_roughness,
        sun_roughness,
    );
}

fn shade_environment_and_sun(
    world_position: vec3<f32>,
    surface_level: f32,
    near: NearSurface,
    primary: PrimaryLightState,
    body_lighting: BodyLightingState,
    to_view: vec3<f32>,
) -> vec3<f32> {
    let reflection = reflect(-to_view, near.lighting_normal);
    var reflected_radiance = sample_environment(
        reflection,
        near.lighting_normal,
        body_lighting.environment_roughness,
    );
    let planar = sample_planar_reflection(world_position, surface_level, near.lighting_normal);
    reflected_radiance = mix(reflected_radiance, planar.color, planar.weight);

    // Crest `OceanReflection.hlsl::ApplyReflectionSky`: the directional light
    // is a bounded reflection-vector lobe added before the Fresnel blend.
    if lights.n_directional_lights > 0u {
        let light = lights.directional_lights[0u];
        let light_direction = safe_normalize(
            light.direction_to_light,
            vec3(0.0, 1.0, 0.0),
        );
        let light_luminance = max(
            dot(primary.color, LUMINANCE_WEIGHTS),
            LUMINANCE_EPSILON,
        );
        let light_color = primary.color / light_luminance;
        let light_strength = clamp(light_luminance / surface.reflection.z, 0.0, 1.0);
        let halfway = safe_normalize(
            light_direction + to_view,
            near.lighting_normal,
        );
        let dot_nl = max(dot(near.lighting_normal, light_direction), 2e-5);
        let dot_nv = max(dot(near.lighting_normal, to_view), 2e-5);
        let light_mask = smith_masking_shadowing(body_lighting.sun_roughness, dot_nv);
        let view_mask = smith_masking_shadowing(body_lighting.sun_roughness, dot_nl);
        let distribution = ggx_distribution(
            clamp(dot(near.lighting_normal, halfway), 0.0, 1.0),
            body_lighting.sun_roughness,
        );
        let geometric_attenuation = 1.0 / (1.0 + light_mask + view_mask);
        let sun_specular = distribution
            * geometric_attenuation / (4.0 * dot_nv + 0.1);
        reflected_radiance += sun_specular
            * surface.sun.x
            * light_color
            * light_strength
            * primary.shadow;
    }
    return reflected_radiance;
}

fn shade_local_lights(
    in: SurfaceVertexOutput,
    near: NearSurface,
    primary: PrimaryLightState,
    body_lighting: BodyLightingState,
    foam: FoamState,
    to_view: vec3<f32>,
    reflected_input: vec3<f32>,
    mode: u32,
) -> LocalLightingState {
    let has_local_lights = primary.point_start < primary.light_end;
    let local_sss_enabled = mode >= DEBUG_MODE_BEAUTY && has_local_lights;
    var local_crest_transmission = 0.0;
    if local_sss_enabled {
        let local_pinch = crest_sss(
            in.undisplaced_xz,
            u32(round(in.sample_data.x)),
            in.sample_data.y,
        );
        local_crest_transmission = clamp(
            (local_pinch - CREST_SSS_UNCOMPRESSED) / CREST_SSS_RANGE,
            0.0,
            1.0,
        );
    }
    let view_vertical = abs(to_view.y);
    let local_grazing = max(1.0 - view_vertical * view_vertical, 0.0);
    let foam_active = foam.visible_density > 0.0;
    var foam_normal = near.normal;
    if foam_active {
        let pixel_z = max(-primary.view_z, 0.0);
        let foam_delta = 0.25 * pixel_z / FOAM_PATTERN_RESOLUTION;
        let foam_x = surface_foam_mask(
            advected_world(in.undisplaced_xz),
            u32(round(in.sample_data.x)),
            in.sample_data.y,
            foam.white_density,
            vec2(foam_delta, 0.0),
        );
        let foam_z = surface_foam_mask(
            advected_world(in.undisplaced_xz),
            u32(round(in.sample_data.x)),
            in.sample_data.y,
            foam.white_density,
            vec2(0.0, foam_delta),
        );
        let foam_gradient = vec2(foam_x - foam.white_mask, foam_z - foam.white_mask);
        foam_normal = safe_normalize(
            near.normal + CREST_FOAM_NORMAL_STRENGTH
                * vec3(-foam_gradient.x, 0.0, -foam_gradient.y),
            near.normal,
        );
    }
    var body = body_lighting.body;
    var reflected_radiance = reflected_input;
    var local_foam_radiance = vec3(0.0);
    for (
        var local_index = primary.point_start;
        local_index < primary.spot_start;
        local_index += 1u
    ) {
        let light_id = clustering::get_clusterable_object_id(local_index);
        let sample = sample_local_light(
            light_id,
            false,
            in.world_position.xyz,
            near.normal,
            in.position.xy,
        );
        let contribution = local_light_contribution(
            sample,
            near.lighting_normal,
            to_view,
            in.sample_data.z,
            body_lighting.sun_roughness,
            local_grazing,
            local_crest_transmission,
            local_sss_enabled,
        );
        body += contribution.body;
        reflected_radiance += contribution.reflection;
        if foam_active {
            local_foam_radiance += local_foam_light(sample, foam_normal, to_view);
        }
    }
    for (
        var local_index = primary.spot_start;
        local_index < primary.light_end;
        local_index += 1u
    ) {
        let light_id = clustering::get_clusterable_object_id(local_index);
        let sample = sample_local_light(
            light_id,
            true,
            in.world_position.xyz,
            near.normal,
            in.position.xy,
        );
        let contribution = local_light_contribution(
            sample,
            near.lighting_normal,
            to_view,
            in.sample_data.z,
            body_lighting.sun_roughness,
            local_grazing,
            local_crest_transmission,
            local_sss_enabled,
        );
        body += contribution.body;
        reflected_radiance += contribution.reflection;
        if foam_active {
            local_foam_radiance += local_foam_light(sample, foam_normal, to_view);
        }
    }
    return LocalLightingState(
        body,
        reflected_radiance,
        local_foam_radiance,
        foam_normal,
    );
}

fn compose_water(
    primary: PrimaryLightState,
    body_lighting: BodyLightingState,
    local: LocalLightingState,
    foam: FoamState,
    to_view: vec3<f32>,
    far_water: vec3<f32>,
    far_tier: f32,
    mode: u32,
) -> vec4<f32> {
    // Foam is dielectric diffuse froth, not a second glossy water layer.
    // Godot's foam roughness therefore damps both environment and sun glints.
    let reflected_radiance = local.reflected * (1.0 - body_lighting.foam_roughness);

    // GodotOceanWaves roughness-damped Fresnel; Crest owns final composition.
    let reflection_weight = clamp(body_lighting.fresnel * surface.fresnel.z, 0.0, 1.0);
    if mode == DEBUG_MODE_REFLECTION_FRACTION {
        return vec4(vec3(reflection_weight), 1.0);
    }
    if mode == DEBUG_MODE_REFLECTION {
        return vec4(reflected_radiance * reflection_weight, 1.0);
    }
    var water = mix(local.body, reflected_radiance, reflection_weight);
    if foam.visible_density > 0.0 {
        let mask = CREST_FOAM_WHITE_COLOR.a * foam.white_mask;

        // Crest `OceanFoam.hlsl`: shipped 3D foam lighting. Bevy's scene
        // diffuse irradiance replaces Unity SH L0; no constant ambient term
        // enters the same pre-exposed radiance domain as SSS.
        var foam_light = CREST_FOAM_WHITE_COLOR.rgb * body_lighting.foam_ambient;
        if lights.n_directional_lights > 0u {
            let light = lights.directional_lights[0u];
            let light_direction = safe_normalize(
                light.direction_to_light,
                vec3(0.0, 1.0, 0.0),
            );
            // Stay in the established pre-exposed primary-light lane, but do
            // not let a below-horizon light illuminate wave-facing foam.
            let light_radiance = primary.radiance * max(light_direction.y, 0.0);
            let foam_ndl = max(dot(local.foam_normal, light_direction), 0.0);
            foam_light += CREST_FOAM_WHITE_COLOR.rgb
                * INV_PI * surface.foam.z * light_radiance * foam_ndl;
            let foam_reflection = reflect(-to_view, local.foam_normal);
            foam_light += pow(
                max(dot(foam_reflection, light_direction), 0.0),
                CREST_FOAM_SPECULAR_FALLOFF,
            ) * CREST_FOAM_SPECULAR_BOOST * light_radiance;
        }
        foam_light += local.foam_radiance;
        water = mix(water, foam_light, mask);
    }
    return vec4(mix(water, far_water, far_tier), 1.0);
}

@fragment
fn fragment(in: SurfaceVertexOutput) -> @location(0) vec4<f32> {
    // One shared tile set renders every scene: unclaimed texels discard
    // unless the Ocean resource is present, so localized scenes pay fill
    // only where their bodies (and the tiles themselves) exist.
    var slot = 0u;
    if field_params.info.x > 0.5 {
        slot = u32(sample_field_level(in.world_position.xz).y + 0.5);
    }
    let bounded = slot > 0u;
    if !bounded && field_params.info.y < 0.5 {
        discard;
    }
    let params = owning_body(slot);
    let surface_level = select(
        cascade_layout.bed_range.z,
        sample_field_level(in.undisplaced_xz).x,
        bounded,
    );
    begin_invocation(bounded, params);
    set_effective_time(globals.time);
    if bounded {
        // Bodies entirely beyond the far tier cull by extent-vs-distance.
        let distance_to_extent = length(
            params.extent.xy - view.world_position.xz,
        ) - params.extent.w;
        if distance_to_extent > surface.far_tier.y {
            discard;
        }
    }
    // River bodies clip to the baked bank distance. z is the SIGNED margin
    // in metres: negative is outside the channel; w is channel half-width.
    var fragment_river_flow = vec4(0.0, 0.0, 1024.0, 0.0);
    if bounded {
        fragment_river_flow = sample_field_flow(in.world_position.xz);
    }
    if bounded && params.flags.y > 0.5 && fragment_river_flow.z < 0.0 {
        discard;
    }
    set_fragment_river(fragment_river_flow);
    set_effective_flow(select(
        surface.advection.xy,
        fragment_river_flow.xy,
        bounded && params.flags.y > 0.5,
    ));
    // Fresh-water optics override the ocean profile when authored.
    let body_optics = bounded && params.optics_a.w > 0.5;
    set_body_optics(
        select(surface.fog_density.rgb, params.optics_a.rgb, body_optics),
        select(1.0, params.optics_b.x, body_optics),
    );
    // Discharge reads as roughness: faster narrows break up more, banks and
    // pools stay glassy. Bank fade eases the multiplier to zero at the edge.
    if bounded && params.flags.y > 0.5 {
        let speed = length(fragment_river_flow.xy);
        // Bank proximity over an 8 m band: ripples calm to the waterline.
        let bank_fade = clamp(fragment_river_flow.z / 8.0, 0.0, 1.0);
        set_river_ripple(bank_fade * clamp(0.8 + 0.75 * speed, 0.8, 2.6));
    } else {
        set_river_ripple(1.0);
    }
    let mode = u32(round(surface.debug.x));
    let surface_lod = u32(round(in.sample_data.x));
    let geometric_normal = safe_normalize(in.world_normal, vec3(0.0, 1.0, 0.0));
    let to_view = view_direction(in.world_position.xyz);
    let far_diagnostic = mode == DEBUG_MODE_FAR_TIER;
    var far_tier = select(
        0.0,
        far_tier_weight(in.base_world_position),
        mode == DEBUG_MODE_BEAUTY || far_diagnostic,
    );
    if far_diagnostic {
        return vec4(vec3(far_tier), 1.0);
    }
    var far_water = vec3(0.0);
    var far_water_depth = 0.0;
    if far_tier > 0.0 {
        // Shallow water keeps the depth-aware near optics. As the bed becomes
        // deep enough to disappear, the cheap far tier becomes fully active.
        far_water_depth = blended_water_depth(in.world_position.xz);
        far_tier *= deep_water_weight(far_water_depth);
    }
    if far_tier > 0.0 {
        far_water = far_field_water(
            in.world_position,
            surface_level,
            geometric_normal,
            to_view,
            in.sample_data.z,
            far_water_depth,
        );
        if far_tier >= 1.0 {
            // Fully far beauty skips detail, clusters, shadows, foam, sampled
            // SSS, local lights, and every transmission/camera-depth read.
            return vec4(far_water, 1.0);
        }
    }

    let near = resolve_near_surface(in, surface_lod, geometric_normal, far_tier, mode);
    let primary = resolve_primary_light(in, near.normal);
    let medium = sample_water_medium(in, surface_lod, near.lighting_normal, to_view, mode);
    if mode == DEBUG_MODE_SEA_FLOOR {
        let depth = clamp(medium.water_depth / surface.sea_floor.y, 0.0, 1.0);
        return vec4(1.0 - depth, 0.0, depth, 1.0);
    }
    if mode == DEBUG_MODE_LIGHT_RADIANCE {
        if lights.n_directional_lights == 0u {
            return vec4(0.0, 0.0, 0.0, 1.0);
        }
        let light_radiance = lights.directional_lights[0u].color.rgb * view.exposure;
        return vec4(light_radiance / 16.0, 1.0);
    }
    if mode == DEBUG_MODE_WAVE_HEIGHT {
        let height = clamp(0.5 + 0.5 * in.world_position.y, 0.0, 1.0);
        return vec4(vec3(height), 1.0);
    }
    if mode == DEBUG_MODE_FOAM {
        return vec4(vec3(medium.foam_density), 1.0);
    }

    let foam = prepare_surface_foam(
        in,
        surface_lod,
        medium.foam_density,
        near.lighting_distance,
    );
    let scatter = directional_scatter(
        in,
        surface_lod,
        near,
        primary,
        medium,
        to_view,
        mode,
    );
    // Deep-pool darkness: bodies scale the ocean scatter endpoint down so
    // colour comes from the bed through low-extinction water, not from a
    // turquoise volume endpoint.
    let scaled_scatter = scatter * invocation_scatter_scale();
    let transmission =
        resolve_transmission(in, near.normal, scaled_scatter, medium, foam, primary, mode);
    if transmission.handled {
        return transmission.output;
    }
    let body_lighting = shade_water_body(
        in,
        near,
        primary,
        medium,
        foam,
        to_view,
        transmission.body,
        mode,
    );
    let reflected = shade_environment_and_sun(
        in.world_position.xyz,
        surface_level,
        near,
        primary,
        body_lighting,
        to_view,
    );
    let local = shade_local_lights(
        in,
        near,
        primary,
        body_lighting,
        foam,
        to_view,
        reflected,
        mode,
    );
    return compose_water(
        primary,
        body_lighting,
        local,
        foam,
        to_view,
        far_water,
        far_tier,
        mode,
    );
}