rusting_engine 2.6.0

Vulkan 3D game engine with GPU-accelerated physics for massive physics-heavy scenes
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
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
#version 450
layout(location = 0) in vec3 v_normal;
layout(location = 1) in vec4 v_color;
layout(location = 2) in vec3 v_world_position;
layout(location = 3) flat in uvec3 v_alpha;
layout(location = 4) in vec2 v_uv;
layout(location = 5) in vec4 v_tangent;
layout(location = 6) flat in vec4 v_emissive;
layout(location = 7) flat in vec4 v_surface;
layout(location = 8) flat in vec4 v_transmission;
// Repeats of each terrain layer per unit of v_uv.
layout(location = 9) flat in vec4 v_splat_scale;
// Terrain: the material color, multiplied into the blended layers.
layout(location = 10) flat in vec3 v_tint;
// Water: x depth fade, y shore foam width, z ripple strength, w ripple
// length; v_splat_scale holds the deep color.
layout(location = 11) flat in vec4 v_water;
// Water crest foam, 0 to 1, from how far the vertex shader's waves pinch.
layout(location = 12) in float v_crest;
layout(location = 0) out vec4 f_color;
layout(set = 1, binding = 0) uniform sampler2D base_color_texture;
layout(set = 1, binding = 1) uniform sampler2D normal_texture;
layout(set = 1, binding = 2) uniform sampler2D metallic_roughness_texture;
layout(set = 1, binding = 3) uniform sampler2D occlusion_texture;
layout(set = 1, binding = 4) uniform sampler2D emissive_texture;
// Terrain layers: base color, and normal (rgb) with roughness (alpha), one
// array layer each. Bound for splat materials only.
layout(set = 1, binding = 5) uniform sampler2DArray terrain_colors;
layout(set = 1, binding = 6) uniform sampler2DArray terrain_normals;
const float PI = 3.14159265;
layout(push_constant) uniform Camera {
    mat4 view_projection;
    vec4 eye;
    vec4 ambient;
    vec4 ground_ambient;
    uvec4 light_info;
} camera;
struct Light {
    vec4 position_kind;
    vec4 direction_range;
    vec4 color_intensity;
    vec4 spot_angles;
    vec4 cookie;
};
layout(set = 0, binding = 2) readonly buffer Lights {
    Light data[];
} lights;
layout(set = 2, binding = 0) uniform sampler2DShadow shadow_map;
layout(set = 2, binding = 1) readonly buffer Shadow {
    vec4 environment;
    vec4 scene_color;
    vec4 viewport;
    vec4 probes[8];
    mat4 inverse_view_projection;
    vec4 fog;
    vec4 fog_shape;
    vec4 ambient_occlusion;
    mat4 cascades[6];
    vec4 cascade_far;
    vec4 cascade_forward;
    vec4 cascade_info;
    vec4 cluster_view;
    vec4 cluster_depth;
    vec4 fog_shafts;
    mat4 fog_volumes[8];
    vec4 fog_volume_colors[8];
    vec4 sky[4];
    vec4 decal_info;
    mat4 decals[16];
    vec4 decal_axes[16];
    vec4 decal_colors[16];
    // Roughness in x, painted when y is 1.
    vec4 decal_surfaces[16];
} shadow;
layout(set = 2, binding = 2) uniform sampler2D environment_map;
layout(set = 2, binding = 3) uniform sampler2D scene_color;
// Mip 0 of the depth pyramid: the opaque depth the reflection march tests.
layout(set = 2, binding = 4) uniform sampler2D scene_depth;
// Six faces per reflection probe; see PROBE_FACES.
layout(set = 2, binding = 5) uniform sampler2DArray probe_maps;
// Screen-space ambient occlusion per target pixel when
// shadow.ambient_occlusion.x is 1.
layout(set = 2, binding = 6) uniform sampler2D ambient_occlusion;
// Per light cluster an (offset, count) pair, then the light indices; see
// `build_light_clusters`.
layout(set = 2, binding = 7) readonly buffer Clusters {
    uint data[];
} clusters;
// Spot light cookie images; Light.cookie.w picks one. Matches
// MAX_LIGHT_COOKIES.
layout(set = 2, binding = 8) uniform sampler2D cookies[4];
// The cookie image in `slot` at `uv`. Constant indices only: the slot is
// not uniform across a draw.
vec3 cookie_at(int slot, vec2 uv) {
    if (slot == 0) return texture(cookies[0], uv).rgb;
    if (slot == 1) return texture(cookies[1], uv).rgb;
    if (slot == 2) return texture(cookies[2], uv).rgb;
    return texture(cookies[3], uv).rgb;
}
// Light bounced off nearby surfaces per target pixel when
// shadow.ambient_occlusion.z is 1.
layout(set = 2, binding = 10) uniform sampler2D indirect_light;
// Light probe grid: center and probe count (0 for none), half size and
// intensity, probes per axis, then six rows of irradiance over pi per probe
// (+X, -X, +Y, -Y, +Z, -Z), x fastest, then y, then z.
layout(set = 2, binding = 11) readonly buffer LightProbes {
    vec4 header[3];
    vec4 cubes[];
} light_probes;
// Baked lightmaps, laid out as `lightmap.rs` describes.
layout(set = 2, binding = 12) readonly buffer Lightmaps {
    vec4 data[];
} lightmaps;
// Decal images; decal_axes[i].w picks one. Matches MAX_DECAL_IMAGES.
layout(set = 2, binding = 9) uniform sampler2D decal_images[4];
vec4 decal_at(int slot, vec2 uv) {
    if (slot < 0) return vec4(1.0);
    if (slot == 0) return texture(decal_images[0], uv);
    if (slot == 1) return texture(decal_images[1], uv);
    if (slot == 2) return texture(decal_images[2], uv);
    return texture(decal_images[3], uv);
}
// `color` (and `roughness`, for decals that set one) with every decal whose
// box holds `position` painted over it in order, faded out on surfaces
// turned away from the projection.
vec3 paint_decals(vec3 color, inout float roughness, vec3 position,
    vec3 normal) {
    for (int i = 0; i < int(shadow.decal_info.x); ++i) {
        vec3 p = (shadow.decals[i] * vec4(position, 1.0)).xyz;
        if (any(greaterThan(abs(p), vec3(1.0)))) {
            continue;
        }
        float facing = dot(normal, shadow.decal_axes[i].xyz);
        // Image top along +Y; texture rows run down.
        vec4 paint = decal_at(int(floor(shadow.decal_axes[i].w + 0.5)),
            vec2(p.x, -p.y) * 0.5 + 0.5) * shadow.decal_colors[i];
        float coverage =
            clamp(paint.a, 0.0, 1.0) * smoothstep(0.1, 0.3, facing);
        color = mix(color, paint.rgb, coverage);
        roughness = mix(roughness, shadow.decal_surfaces[i].x,
            coverage * shadow.decal_surfaces[i].y);
    }
    return color;
}
#include "fog.glsl"
#include "surface.glsl"
// 0 opaque, 1 blended, 2 screen-space reflection overlay on opaque surfaces.
layout(constant_id = 0) const uint PASS = 0u;
// Matches SSR_MAX_ROUGHNESS.
const float SSR_MAX_ROUGHNESS = 0.5;
// Light arriving from `direction`: the equirectangular environment map at
// mip `lod` (image top is +Y), else the ambient hemisphere.
vec3 environment(vec3 direction, float lod) {
    if (shadow.environment.x > 0.5) {
        vec2 uv = vec2(
            atan(direction.z, direction.x) / (2.0 * PI) + 0.5,
            acos(clamp(direction.y, -1.0, 1.0)) / PI
        );
        // Keep the poles off the opposite edge a repeating sampler wraps to,
        // measured on the coarser of the two mips a trilinear lookup blends.
        float half_texel =
            0.5 / float(textureSize(environment_map, int(ceil(lod))).y);
        uv.y = clamp(uv.y, half_texel, 1.0 - half_texel);
        return textureLod(environment_map, uv, lod).rgb * shadow.environment.y;
    }
    return mix(
        camera.ground_ambient.rgb,
        camera.ambient.rgb,
        direction.y * 0.5 + 0.5
    );
}
const vec3 PROBE_FORWARD[6] = vec3[](
    vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0),
    vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0)
);
const vec3 PROBE_UP[6] = vec3[](
    vec3(0.0, 1.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0),
    vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 1.0, 0.0)
);
// Light arriving at `position` from `direction`: the environment at mip
// `lod`, overlaid by the reflection probes whose box holds the position.
// Each probe traces `direction` to its box wall and looks up the captured
// face toward that point; `rough` in 0..1 picks the probe mip.
vec3 surroundings(vec3 position, vec3 direction, float lod, float rough) {
    vec3 sky = environment(direction, lod);
    vec4 sum = vec4(0.0);
    for (int i = 0; i < int(shadow.environment.w); ++i) {
        vec3 center = shadow.probes[2 * i].xyz;
        vec3 extents = shadow.probes[2 * i + 1].xyz;
        vec3 local = position - center;
        vec3 inside = extents - abs(local);
        float margin = min(inside.x, min(inside.y, inside.z));
        if (margin <= 0.0) {
            continue;
        }
        vec3 toward = step(0.0, direction) * 2.0 - 1.0;
        vec3 wall = (extents - local * toward)
            / max(abs(direction), vec3(1e-5));
        vec3 d = local + direction * min(wall.x, min(wall.y, wall.z));
        vec3 a = abs(d);
        int face = a.x >= a.y && a.x >= a.z ? (d.x > 0.0 ? 0 : 1)
            : a.y >= a.z ? (d.y > 0.0 ? 2 : 3)
            : (d.z > 0.0 ? 4 : 5);
        vec3 forward = PROBE_FORWARD[face];
        vec3 up = PROBE_UP[face];
        vec2 uv = vec2(dot(d, cross(forward, up)), -dot(d, up))
            / dot(d, forward) * 0.5 + 0.5;
        vec3 light = textureLod(
            probe_maps,
            vec3(uv, float(6 * i + face)),
            rough * shadow.probes[2 * i + 1].w
        ).rgb * shadow.probes[2 * i].w;
        // Fades in over the outer tenth of the box.
        float weight = clamp(
            margin / (0.1 * min(extents.x, min(extents.y, extents.z))),
            0.0,
            1.0
        );
        sum += vec4(light * weight, weight);
    }
    return sum.w > 0.0 ? mix(sky, sum.rgb / sum.w, min(sum.w, 1.0)) : sky;
}
// Diffuse light at `position` toward `normal` from the light probe grid,
// blended trilinearly between the eight nearest probes, with its weight in
// w: 0 outside the grid, fading in over the outer tenth of the box.
vec4 probe_light(vec3 position, vec3 normal) {
    if (light_probes.header[0].w < 0.5) {
        return vec4(0.0);
    }
    vec3 extents = max(light_probes.header[1].xyz, vec3(1e-4));
    vec3 local = position - light_probes.header[0].xyz;
    vec3 inside = extents - abs(local);
    float margin = min(inside.x, min(inside.y, inside.z));
    if (margin <= 0.0) {
        return vec4(0.0);
    }
    ivec3 count = ivec3(light_probes.header[2].xyz);
    vec3 cell = (local / extents * 0.5 + 0.5) * vec3(count - 1);
    ivec3 low = clamp(ivec3(floor(cell)), ivec3(0), max(count - 2, ivec3(0)));
    vec3 t = clamp(cell - vec3(low), 0.0, 1.0);
    vec3 n2 = normal * normal;
    ivec3 face = ivec3(normal.x < 0.0 ? 1 : 0, normal.y < 0.0 ? 3 : 2,
        normal.z < 0.0 ? 5 : 4);
    vec3 sum = vec3(0.0);
    for (int corner = 0; corner < 8; ++corner) {
        ivec3 step_to = ivec3(corner & 1, (corner >> 1) & 1, (corner >> 2) & 1);
        ivec3 probe = min(low + step_to, count - 1);
        vec3 w = mix(1.0 - t, t, vec3(step_to));
        int base = 6 * ((probe.z * count.y + probe.y) * count.x + probe.x);
        sum += w.x * w.y * w.z * (n2.x * light_probes.cubes[base + face.x].rgb
            + n2.y * light_probes.cubes[base + face.y].rgb
            + n2.z * light_probes.cubes[base + face.z].rgb);
    }
    float weight = clamp(
        margin / (0.1 * min(extents.x, min(extents.y, extents.z))), 0.0, 1.0);
    return vec4(sum * light_probes.header[1].w, weight);
}
// Diffuse light baked into lightmap `slot` (plus one, 0 for none) at
// `position` toward `normal`, with its weight in w. Each axis reads the
// box face the normal points to, bilinearly, weighted by the squared
// normal in the object's axes.
vec4 lightmap_light(vec3 position, vec3 normal, uint slot) {
    if (slot == 0u || float(slot) > lightmaps.data[0].x) {
        return vec4(0.0);
    }
    uint header = 4u * slot - 3u;
    vec4 info = lightmaps.data[header + 3u];
    int res = int(info.x);
    if (res == 0) {
        return vec4(0.0);
    }
    vec3 local;
    vec3 axes;
    for (int a = 0; a < 3; ++a) {
        vec4 row = lightmaps.data[header + uint(a)];
        local[a] = dot(row, vec4(position, 1.0));
        axes[a] = dot(normal, normalize(row.xyz));
    }
    vec3 sum = vec3(0.0);
    for (int a = 0; a < 3; ++a) {
        int face = 2 * a + (axes[a] >= 0.0 ? 0 : 1);
        vec2 st = clamp(vec2(local[(a + 1) % 3], local[(a + 2) % 3]), 0.0,
            1.0) * float(res) - 0.5;
        ivec2 low = ivec2(floor(st));
        vec2 f = st - vec2(low);
        ivec2 a0 = clamp(low, 0, res - 1);
        ivec2 a1 = clamp(low + 1, 0, res - 1);
        int first = int(info.y) + face * res * res;
        vec3 texel = mix(
            mix(lightmaps.data[first + a0.y * res + a0.x].rgb,
                lightmaps.data[first + a0.y * res + a1.x].rgb, f.x),
            mix(lightmaps.data[first + a1.y * res + a0.x].rgb,
                lightmaps.data[first + a1.y * res + a1.x].rgb, f.x),
            f.y);
        sum += axes[a] * axes[a] * texel;
    }
    return vec4(sum / max(dot(axes, axes), 1e-4), 1.0);
}
// Target pixel of a point in normalized device coordinates.
vec2 screen_pixel(vec2 ndc) {
    return shadow.viewport.xy + (ndc * 0.5 + 0.5) * shadow.viewport.zw;
}
float scene_depth_at(vec2 ndc) {
    return texelFetch(scene_depth, ivec2(screen_pixel(ndc)), 0).r;
}
// Screen-space reflection: marches `direction` from the fragment through the
// opaque depth copy. Returns the opaque color copy where the ray crosses the
// depth surface, weighted down near the screen edge, at the end of the
// march, and toward SSR_MAX_ROUGHNESS; weight 0 on a miss.
// ponytail: linear march with growing steps and a binary refine; march the
// depth pyramid (Hi-Z) if long rays skip thin objects.
vec4 trace_reflection(vec3 direction, float roughness) {
    const int STEPS = 64;
    vec3 position = v_world_position;
    vec4 start = camera.view_projection * vec4(position, 1.0);
    float previous = start.z / start.w;
    float step_length = 0.02 * max(start.w, 1.0);
    for (int i = 0; i < STEPS; ++i) {
        vec3 next = position + direction * step_length;
        vec4 clip = camera.view_projection * vec4(next, 1.0);
        if (clip.w <= 0.0) {
            break;
        }
        vec3 ndc = clip.xyz / clip.w;
        if (any(greaterThan(abs(ndc.xy), vec2(1.0))) || ndc.z < 0.0
            || ndc.z > 1.0) {
            break;
        }
        float scene = scene_depth_at(ndc.xy);
        // A hit crosses the depth surface within this step; a ray that is
        // far behind it passed behind a foreground object and marches on.
        if (i > 0 && ndc.z > scene
            && ndc.z - scene <= 2.0 * (ndc.z - previous) + 1e-5) {
            vec3 low = position;
            vec3 high = next;
            for (int j = 0; j < 5; ++j) {
                vec3 middle = (low + high) * 0.5;
                vec4 c = camera.view_projection * vec4(middle, 1.0);
                if (c.z / c.w > scene_depth_at(c.xy / c.w)) {
                    high = middle;
                } else {
                    low = middle;
                }
            }
            vec4 c = camera.view_projection * vec4(high, 1.0);
            vec2 hit = clamp(c.xy / c.w, -1.0, 1.0);
            vec2 edge = 1.0 - smoothstep(0.8, 1.0, abs(hit));
            float weight = edge.x * edge.y
                * (1.0 - smoothstep(0.75, 1.0, float(i) / float(STEPS)))
                * (1.0 - smoothstep(0.5 * SSR_MAX_ROUGHNESS, SSR_MAX_ROUGHNESS,
                    roughness));
            vec3 color = textureLod(
                scene_color,
                screen_pixel(hit) / vec2(textureSize(scene_color, 0)),
                roughness * shadow.scene_color.y
            ).rgb;
            return vec4(color, weight);
        }
        previous = ndc.z;
        position = next;
        step_length *= 1.08;
    }
    return vec4(0.0);
}
float shadow_factor(vec3 surface_normal) {
    // Directional lights pick the first cascade that reaches this depth,
    // point lights the cube face along the major axis away from the light;
    // each view is one tile of the atlas.
    int count = int(shadow.cascade_info.x);
    int tiles = int(shadow.cascade_info.y);
    int cascade = 0;
    if (count == 0) {
        return 1.0;
    }
    if (shadow.cascade_info.z > 0.5) {
        vec3 away = v_world_position - shadow.cascade_forward.xyz;
        vec3 size = abs(away);
        if (size.x >= size.y && size.x >= size.z) {
            cascade = away.x > 0.0 ? 0 : 1;
        } else if (size.y >= size.z) {
            cascade = away.y > 0.0 ? 2 : 3;
        } else {
            cascade = away.z > 0.0 ? 4 : 5;
        }
    } else {
        float depth = dot(v_world_position, shadow.cascade_forward.xyz)
            - shadow.cascade_forward.w;
        while (cascade < count - 1 && depth > shadow.cascade_far[cascade]) {
            cascade += 1;
        }
        if (depth > shadow.cascade_far[count - 1]) {
            return 1.0;
        }
    }
    // One texel in the cascade's own 0..1 coordinates.
    vec2 texel = float(tiles) / vec2(textureSize(shadow_map, 0));
    // Normal offset: sample from two shadow texels off the surface. The PCF
    // taps reach one texel sideways, where a sloped surface's own depth is
    // nearer the light; without the offset it shadows itself in stripes.
    // Row 0 of the light matrix scales world units to clip x; dividing by
    // w (1 for the orthographic sun, the distance for a spot light) gives
    // the texel size at this surface.
    mat4 light = shadow.cascades[cascade];
    vec4 surface_clip = light * vec4(v_world_position, 1.0);
    if (surface_clip.w <= 0.0) {
        return 1.0;
    }
    float texel_world = 2.0 * texel.x * surface_clip.w
        / length(vec3(light[0][0], light[1][0], light[2][0]));
    vec3 position = v_world_position + surface_normal * 2.0 * texel_world;
    vec4 clip = light * vec4(position, 1.0);
    vec3 coords = clip.xyz / clip.w;
    if (coords.z > 1.0) {
        return 1.0;
    }
    vec2 uv = coords.xy * 0.5 + 0.5;
    vec2 tile = vec2(cascade % tiles, cascade / tiles);
    // Taps stay half a texel inside the tile, off its neighbours.
    #define SHADOW_TAP(offset) texture(shadow_map, vec3( \
        (tile + clamp(uv + (offset) * texel, texel * 0.5, 1.0 - texel * 0.5)) \
            / float(tiles), coords.z))
    if (shadow.ambient_occlusion.y > 0.5) {
        return SHADOW_TAP(vec2(0.0));
    }
    float lit = 0.0;
    for (int x = -1; x <= 1; ++x) {
        for (int y = -1; y <= 1; ++y) {
            lit += SHADOW_TAP(vec2(x, y));
        }
    }
    return lit / 9.0;
}
void main() {
    vec4 base_color = v_color * texture(base_color_texture, v_uv);
    // Terrain normal (rgb) and roughness (alpha), blended like the colors.
    vec4 terrain_bump = vec4(0.5, 0.5, 1.0, 1.0);
    // transmission.w is 1 for a terrain splat material, 2 for water.
    bool terrain = abs(v_transmission.w - 1.0) < 0.5;
    if (terrain) {
        // Terrain: blend the layer arrays by the vertex color's weights.
        // Each layer repeats with the sampler's wrap, so mips and
        // anisotropic filtering stay seamless; gradients are taken here,
        // outside the branch on the weight.
        vec4 weights = max(v_color, vec4(0.0));
        weights /= max(dot(weights, vec4(1.0)), 1e-4);
        vec2 dx = dFdx(v_uv);
        vec2 dy = dFdy(v_uv);
        base_color = vec4(0.0);
        if (v_surface.z > 0.5) {
            terrain_bump = vec4(0.0);
        }
        for (int layer = 0; layer < 4; ++layer) {
            if (weights[layer] <= 0.0) {
                continue;
            }
            float scale = v_splat_scale[layer];
            vec3 at = vec3(v_uv * scale, float(layer));
            base_color += weights[layer]
                * textureGrad(terrain_colors, at, dx * scale, dy * scale);
            if (v_surface.z > 0.5) {
                terrain_bump += weights[layer]
                    * textureGrad(terrain_normals, at, dx * scale, dy * scale);
            }
        }
        base_color = vec4(base_color.rgb * v_tint, 1.0);
    }
    // Flat shading's face normal, taken before any discard or branch:
    // derivatives are undefined once a pixel of the 2x2 quad has left, and
    // some drivers then return garbage.
    vec3 face = cross(dFdx(v_world_position), dFdy(v_world_position));
    vec3 normal = normalize(v_normal);
    // Flat shading: the triangle's face normal, turned to the side the
    // vertex normal points to. A sliver too thin to give one keeps the
    // vertex normal instead of normalizing zero into NaN.
    if (v_surface.w > 0.5 && dot(face, face) > 1e-20) {
        face = normalize(face);
        normal = dot(face, normal) < 0.0 ? -face : face;
    }
    if (v_surface.z > 0.5) {
        vec3 tangent = normalize(
            v_tangent.xyz - normal * dot(normal, v_tangent.xyz)
        );
        vec3 bitangent = cross(normal, tangent) * v_tangent.w;
        vec3 sampled = (terrain
            ? terrain_bump.xyz : texture(normal_texture, v_uv).xyz) * 2.0 - 1.0;
        normal = normalize(mat3(tangent, bitangent, normal) * sampled);
    }
    vec3 view_dir = camera.eye.w > 0.5
        ? normalize(camera.eye.xyz - v_world_position)
        : normalize(camera.eye.xyz);
    // Water: small scrolling waves on the normal, then color and shore foam
    // by the water between the surface and the opaque scene behind it.
    if (v_transmission.w > 1.5) {
        float time = camera.ambient.w;
        if (v_water.z > 0.0) {
            // Three crossing waves at the deep-water speed sqrt(g / k). They
            // fade out once a pixel spans half a wave, before they alias.
            float k = 6.2831853 / max(v_water.w, 0.01);
            vec2 slope = vec2(0.0);
            for (int i = 0; i < 3; ++i) {
                float angle = 0.4 + 2.1 * float(i);
                vec2 dir = vec2(cos(angle), sin(angle));
                float wave_k = k * (1.0 + 0.37 * float(i));
                float phase = wave_k * dot(dir, v_world_position.xz)
                    - sqrt(9.81 * wave_k) * time;
                slope += dir * cos(phase);
            }
            vec2 footprint = fwidth(v_world_position.xz);
            float fade = clamp(
                1.0 - 2.0 * max(footprint.x, footprint.y) / max(v_water.w, 0.01),
                0.0, 1.0);
            slope *= 0.15 * v_water.z * fade;
            normal = normalize(normal - vec3(slope.x, 0.0, slope.y));
        }
        base_color = mix(base_color, vec4(1.0), v_crest);
        // The opaque depth is copied before blended draws when some water
        // reads it; scene_color.w says it was.
        if (PASS == 1u && shadow.scene_color.w > 0.5
            && (v_water.x > 0.0 || v_water.y > 0.0)) {
            vec4 clip = camera.view_projection * vec4(v_world_position, 1.0);
            vec2 ndc = clip.xy / clip.w;
            vec4 behind = shadow.inverse_view_projection
                * vec4(ndc, scene_depth_at(ndc), 1.0);
            behind.xyz /= behind.w;
            if (v_water.x > 0.0) {
                // Light crosses more water the longer the view runs under
                // the surface; open water over the far plane is deep.
                float through = length(behind.xyz - v_world_position);
                base_color = mix(base_color, v_splat_scale,
                    clamp(through / v_water.x, 0.0, 1.0));
            }
            if (v_water.y > 0.0) {
                // Foam fills the band where the ground is close under the
                // surface and breaks up toward its edge in a slow pattern.
                float depth = max(v_world_position.y - behind.y, 0.0);
                vec2 p = v_world_position.xz * 3.0;
                float pattern = 0.25 * (sin(p.x + time * 0.7)
                    * sin(p.y * 1.3 - time * 0.5)
                    + sin((p.x + p.y) * 1.7 + time));
                float edge = 1.0 - depth / v_water.y;
                float foam = smoothstep(0.3, 0.6, edge + 0.4 * pattern);
                base_color = mix(base_color, vec4(1.0), foam);
            }
        }
    }
    // glTF packs roughness in green and metallic in blue.
    vec4 packed = texture(metallic_roughness_texture, v_uv);
    Surface s = Surface(base_color, normal, v_surface.x * packed.b,
        v_surface.y * packed.g * terrain_bump.a,
        v_emissive.rgb * texture(emissive_texture, v_uv).rgb);
    // camera.ambient.w carries the time.
    surface(SurfaceInput(v_uv, v_world_position, view_dir, v_color,
        camera.ambient.w), s);
    base_color = s.base_color;
    normal = normalize(s.normal);
    if (v_alpha.x == 1u && base_color.a < uintBitsToFloat(v_alpha.y)) {
        discard;
    }
    float alpha = v_alpha.x == 2u ? base_color.a : 1.0;
    base_color.rgb = paint_decals(base_color.rgb, s.roughness,
        v_world_position, normalize(v_normal));
    // Opacity 0 draws nothing: reflections are not scaled by alpha, so a
    // fully clear surface would otherwise still show a faint mirror.
    if (v_alpha.x == 2u && alpha <= 0.0 && v_transmission.x <= 0.0) {
        discard;
    }
    // Transmitted light comes from the opaque scene copy when the frame has
    // one; without it, transmission only makes the surface see-through.
    float transmission = v_transmission.x;
    bool refracts = transmission > 0.0 && shadow.scene_color.x > 0.5;
    alpha = refracts ? 1.0 : alpha * (1.0 - transmission);
    // light_info.z is the SceneDebugView: 1 unshaded, 2 normals.
    bool unlit = v_emissive.w > 0.5 && camera.light_info.z != 2u;
    if (unlit || camera.light_info.z == 1u) {
        // Fog is off in debug views.
        vec4 haze = fog_at(v_world_position);
        f_color = vec4(base_color.rgb * alpha * haze.a + haze.rgb * alpha, alpha);
        return;
    }
    if (camera.light_info.z == 2u) {
        f_color = vec4((normal * 0.5 + 0.5) * alpha, alpha);
        return;
    }
    float metallic = clamp(s.metallic, 0.0, 1.0);
    float roughness = clamp(s.roughness, 0.04, 1.0);
    float occlusion = texture(occlusion_texture, v_uv).r;
    // Blended surfaces are not in the depth the occlusion was traced from.
    if (PASS != 1u && shadow.ambient_occlusion.x > 0.5) {
        occlusion *= texelFetch(ambient_occlusion, ivec2(gl_FragCoord.xy), 0).r;
    }
    float n_dot_v = max(dot(normal, view_dir), 0.0001);
    vec3 f0 = mix(vec3(0.04), base_color.rgb, metallic);
    // Premultiplied by alpha; specular is not, see the blend pipeline.
    vec3 diffuse_color = base_color.rgb * (1.0 - metallic) * alpha
        * (refracts ? 1.0 - transmission : 1.0);
    float a2 = roughness * roughness * roughness * roughness;
    float k = (roughness + 1.0) * (roughness + 1.0) / 8.0;
    // Hemisphere environment: diffuse from the normal, specular from the
    // reflection direction with roughness-aware Fresnel.
    // ponytail: without an environment map, a two-color hemisphere. With
    // one, mip levels stand in for prefiltered radiance and irradiance.
    vec3 reflected = reflect(-view_dir, normal);
    vec3 env_fresnel = f0 + (max(vec3(1.0 - roughness), f0) - f0)
        * pow(1.0 - n_dot_v, 5.0);
    float last_mip = shadow.environment.z;
    vec3 reflection = surroundings(
        v_world_position, reflected, roughness * last_mip, roughness);
    // Surfaces drawn after the opaque copy see the scene in their
    // reflections. The overlay adds the difference over the opaque pass.
    vec4 traced = vec4(0.0);
    if (PASS != 0u && shadow.scene_color.z > 0.5
        && roughness < SSR_MAX_ROUGHNESS) {
        traced = trace_reflection(reflected, roughness);
    }
    // The overlay's difference is seen through the same fog as the
    // opaque surface under it.
    vec4 haze = fog_at(v_world_position);
    if (PASS == 2u) {
        f_color = vec4(
            (traced.rgb - reflection) * traced.a * env_fresnel * occlusion
                * haze.a,
            0.0
        );
        return;
    }
    reflection = mix(reflection, traced.rgb, traced.a);
    vec3 ambient = surroundings(
        v_world_position, normal, max(last_mip - 1.0, 0.0), 1.0);
    vec4 probed = probe_light(v_world_position, normal);
    ambient = mix(ambient, probed.rgb, probed.w);
    vec4 baked = lightmap_light(v_world_position, normal, v_alpha.z >> 8u);
    ambient = mix(ambient, baked.rgb, baked.w);
    vec3 result = (ambient * diffuse_color * (1.0 - env_fresnel)
        + reflection * env_fresnel)
        * occlusion;
    if (PASS != 1u && shadow.ambient_occlusion.z > 0.5) {
        result += texelFetch(indirect_light, ivec2(gl_FragCoord.xy), 0).rgb
            * diffuse_color * (1.0 - env_fresnel);
    }
    // Directional lights (the first light_info.w) light every pixel; point
    // and spot lights come from this pixel's cluster.
    vec2 cell = (gl_FragCoord.xy - shadow.viewport.xy) / shadow.viewport.zw;
    uvec2 tile = min(uvec2(max(cell, 0.0) * vec2(16.0, 9.0)), uvec2(15u, 8u));
    float view_depth = dot(v_world_position, shadow.cluster_view.xyz)
        - shadow.cluster_view.w;
    uint slice = view_depth <= shadow.cluster_depth.x ? 0u : min(
        uint(log(view_depth / shadow.cluster_depth.x)
            / shadow.cluster_depth.y * 24.0),
        23u
    );
    uint cluster = (slice * 9u + tile.y) * 16u + tile.x;
    uint cluster_first = clusters.data[2u * cluster];
    uint directional = camera.light_info.w;
    uint light_count = directional + clusters.data[2u * cluster + 1u];
    for (uint step = 0; step < light_count; ++step) {
        uint index = step < directional
            ? step
            : clusters.data[cluster_first + step - directional];
        Light light = lights.data[index];
        float kind = light.position_kind.w;
        vec3 to_light;
        float attenuation = 1.0;
        vec3 tint = vec3(1.0);
        // Sphere lights light specular from the point of the sphere
        // nearest the reflection ray, with the lobe widened to keep its
        // energy (Karis 2013, representative point).
        vec3 specular_dir;
        float lobe_energy = 1.0;
        if (kind < 0.5) {
            to_light = normalize(-light.direction_range.xyz);
            specular_dir = to_light;
        } else {
            vec3 delta = light.position_kind.xyz - v_world_position;
            float distance_to_light = length(delta);
            to_light = distance_to_light > 0.0001
                ? delta / distance_to_light
                : vec3(0.0, 1.0, 0.0);
            float range_fade = clamp(
                1.0 - distance_to_light / light.direction_range.w,
                0.0,
                1.0
            );
            attenuation = range_fade * range_fade;
            float source_radius = light.spot_angles.z;
            specular_dir = to_light;
            if (source_radius > 0.0) {
                vec3 to_ray = dot(delta, reflected) * reflected - delta;
                vec3 nearest = delta + to_ray
                    * clamp(source_radius / max(length(to_ray), 0.0001),
                        0.0, 1.0);
                specular_dir = normalize(nearest);
                float lobe = roughness * roughness;
                float widened = clamp(
                    lobe + source_radius / (2.0 * max(distance_to_light, 0.0001)),
                    0.0, 1.0);
                lobe_energy = (lobe / widened) * (lobe / widened);
            }
            if (kind > 1.5) {
                vec3 axis = normalize(light.direction_range.xyz);
                float cone = dot(-to_light, axis);
                attenuation *= smoothstep(
                    light.spot_angles.y,
                    light.spot_angles.x,
                    cone
                );
                if (light.cookie.w > -0.5 && cone > 0.0) {
                    // Projected like a slide: the outer cone spans the
                    // image, its top along the light's up.
                    vec3 right = light.cookie.xyz;
                    vec3 up = cross(right, axis);
                    vec2 spread = vec2(dot(-to_light, right),
                        dot(-to_light, up)) / (cone * light.spot_angles.w);
                    tint = cookie_at(int(light.cookie.w + 0.5),
                        vec2(0.5 + 0.5 * spread.x, 0.5 - 0.5 * spread.y));
                }
            }
        }
        // Clusters hold whole range spheres, so many lights here are out of
        // range or outside their cone; they would add exactly zero.
        if (attenuation <= 0.0) {
            continue;
        }
        if (index + 1u == camera.light_info.y && (v_alpha.z & 2u) != 0u) {
            attenuation *= shadow_factor(normalize(v_normal));
        }
        float n_dot_l = max(dot(normal, to_light), 0.0);
        if (n_dot_l <= 0.0) {
            continue;
        }
        // Cook-Torrance: GGX distribution, Smith-Schlick geometry,
        // Schlick Fresnel.
        vec3 half_dir = normalize(specular_dir + view_dir);
        float n_dot_h = max(dot(normal, half_dir), 0.0);
        float v_dot_h = max(dot(view_dir, half_dir), 0.0);
        float d = n_dot_h * n_dot_h * (a2 - 1.0) + 1.0;
        float distribution = a2 / (PI * d * d) * lobe_energy;
        float geometry = n_dot_v / (n_dot_v * (1.0 - k) + k)
            * n_dot_l / (n_dot_l * (1.0 - k) + k);
        vec3 fresnel = f0 + (1.0 - f0) * pow(1.0 - v_dot_h, 5.0);
        vec3 specular = distribution * geometry * fresnel
            / (4.0 * n_dot_v * n_dot_l + 0.0001);
        vec3 radiance = light.color_intensity.rgb
            * light.color_intensity.w * attenuation * tint;
        // Light intensity is scaled so a white Lambert surface facing a
        // unit light reflects 1, hence the PI on the specular lobe.
        result += ((1.0 - fresnel) * diffuse_color + specular * PI)
            * radiance * n_dot_l;
    }
    if (refracts) {
        // What is behind, seen along the ray bent at the surface after it
        // crosses `thickness`, tinted by the base color and blurred by
        // roughness.
        // ponytail: one refraction at the entry face, no exit face, and
        // blended objects behind are missing from the copy.
        vec3 refracted = refract(-view_dir, normal, 1.0 / v_transmission.y);
        vec4 clip = camera.view_projection
            * vec4(v_world_position + refracted * v_transmission.z, 1.0);
        vec2 pixel = shadow.viewport.xy
            + (clip.xy / clip.w * 0.5 + 0.5) * shadow.viewport.zw;
        vec3 behind = textureLod(
            scene_color,
            pixel / vec2(textureSize(scene_color, 0)),
            roughness * shadow.scene_color.y
        ).rgb;
        result += behind * base_color.rgb * transmission * (1.0 - env_fresnel);
    }
    result += s.emissive * alpha;
    // Premultiplied like the rest: blended surfaces add fog by coverage.
    // ponytail: refracted and screen-space reflected light comes from the
    // fogged opaque copy, so it is fogged twice over the stretch in front of
    // this surface; trace the fog per sample if dense fog shows it.
    f_color = vec4(result * haze.a + haze.rgb * alpha, alpha);
}