bevy_flora 0.23.0

Parametric, stylized procedural trees and shrubs for Bevy that grow over time: 16 presets, bent and forking branches, CC0 bark and leaf scans, painterly and toon looks, deterministic per seed.
Documentation
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
// Shared by every flora shader: the wind, the flutter of a leaf, the surface of a leaf and the
// light that comes through it. The Rust side is `shading/mod.rs`.
//
// The wind follows Crysis' vegetation animation (GPU Gems 3, chapter 16): a main bend of the
// whole tree that grows with the square of the height and keeps every vertex's distance from the
// trunk, and per-leaf detail bending with smooth triangle waves of unrelated frequencies. The
// light through a leaf is Unreal's two-sided foliage model: the back side's wrapped N.L times a
// forward-scattering GGX lobe times the subsurface colour.
import bevy_pbr::render::{
    mesh_view_bindings as view_bindings,
    mesh_view_types,
    pbr_lighting::D_GGX,
    pbr_ambient::ambient_light,
    shadows::fetch_directional_shadow,
    mesh_types::MESH_FLAGS_SHADOW_RECEIVER_BIT,
    pbr_types::PbrInput,
};

struct FloraUniform {
    // 1 / trunk height, 0, 0, 0
    tree: vec4<f32>,
    // the species' sway, flutter, sway frequency, flutter frequency
    response: vec4<f32>,
    // translucency, roughness of the upper side, veins, underside
    leaf: vec4<f32>,
    // linear rgb of the light through a leaf, 0
    tint: vec4<f32>,
    // wood thinner than x metres is smooth, thicker than y metres wears the whole bark, 0, 0
    bark: vec4<f32>,
    // the bark scan's average linear rgb, 0
    bark_mean: vec4<f32>,
    // a turned leaf's colour multiplier (the autumn colour over the leaf colour), 1; zero for an
    // evergreen and for wood
    autumn: vec4<f32>,
    // a flower's colour multiplier (the blossom colour over the leaf colour), 1; zero for a
    // species without flowers and for wood
    blossom: vec4<f32>,
}

@group(constants::MATERIAL_BIND_GROUP) @binding(101) var<uniform> flora: FloraUniform;

// The surface's detail textures (bark): colour, normal, and the sampler both share. Bound only
// when the material has them (`FLORA_DETAIL`, `FloraKey::detail`).
@if(FLORA_DETAIL)
@group(constants::MATERIAL_BIND_GROUP) @binding(103) var detail_color: texture_2d<f32>;
@if(FLORA_DETAIL)
@group(constants::MATERIAL_BIND_GROUP) @binding(104) var detail_normal: texture_2d<f32>;
@if(FLORA_DETAIL)
@group(constants::MATERIAL_BIND_GROUP) @binding(105) var detail_sampler: sampler;

// The image of leaf veins (`FLORA_VENATION`, `FloraKey::venation`) and its own sampler.
@if(FLORA_VENATION)
@group(constants::MATERIAL_BIND_GROUP) @binding(106) var venation: texture_2d<f32>;
@if(FLORA_VENATION)
@group(constants::MATERIAL_BIND_GROUP) @binding(107) var venation_sampler: sampler;

// The weather, the same for every tree (`FloraWindBuffer`, `wind_vectors` in shading/mod.rs):
//   0: wind direction x, direction z, strength, speed
//   1: bend, flutter, gust strength, turbulence
//   2: gust frequency, flutter frequency, 0, 0
//   3: gust front origin x, origin z, direction x, direction z
//   4: gust front speed, width, strength, start time
//   5: the season (`FloraSeason`): how far the leaves have turned, the share fallen, snow, the
//      share of flowers out
//   6: the shakes (`TreeShakes`): the render time the last one ends (-1: none), 0, 0, 0
//   7 + 2i, 8 + 2i for i in 0..8: shake i's (at x, at z, radius, strength) and (direction x,
//      direction z, start time, duration); all zero for an empty slot
//   23, 24: the see-through fade (`SeeThroughTrees`): the fading camera's position and the
//      clear radius at the target; the target and 1 while a camera has one (all zero otherwise)
@group(constants::MATERIAL_BIND_GROUP) @binding(102) var<storage, read> flora_wind: array<vec4<f32>>;

// The see-through fade's slot (`see_through::SEE_THROUGH_SLOT`).
const SEE_THROUGH: u32 = 23u;

// Discard a tree's fragment at `world` (pixel `frag_coord`) when it stands between the fading
// camera and its target, in a cone around the line of sight a third as wide at the camera as
// at the target: fully within 70% of the radius, dithered over the rest, and closing over the
// last tenth of the way so nothing behind the target is cut. Only the fading camera's own views
// cut: `view_position` is where the view being drawn looks from, so shadow maps and other
// cameras keep the whole tree. Compiled in only while a camera sees through (`FLORA_SEE_THROUGH`).
fn see_through(world: vec3<f32>, frag_coord: vec2<f32>, view_position: vec3<f32>) {
    let eye = flora_wind[SEE_THROUGH];
    // (`target` is a reserved word in WGSL.)
    let goal = flora_wind[SEE_THROUGH + 1u];
    if goal.w < 0.5 || any(abs(view_position - eye.xyz) > vec3<f32>(1.0e-3)) {
        return;
    }
    let line = goal.xyz - eye.xyz;
    let t = dot(world - eye.xyz, line) / max(dot(line, line), 1.0e-6);
    if t <= 0.0 || t >= 1.0 {
        return;
    }
    let radius = eye.w * mix(1.0 / 3.0, 1.0, t);
    let off = distance(world, eye.xyz + line * t);
    let open = (1.0 - smoothstep(0.7 * radius, radius, off)) * (1.0 - smoothstep(0.9, 1.0, t));
    if open > ordered_dither(frag_coord) {
        discard;
    }
}

// A 4x4 ordered-dither threshold in (0, 1) for the pixel at `frag_coord`.
fn ordered_dither(frag_coord: vec2<f32>) -> f32 {
    var bayer = array<f32, 16>(0.0, 8.0, 2.0, 10.0, 12.0, 4.0, 14.0, 6.0, 3.0, 11.0, 1.0, 9.0, 15.0, 7.0, 13.0, 5.0);
    let p = vec2<u32>(frag_coord) % vec2<u32>(4u);
    return (bayer[p.y * 4u + p.x] + 0.5) / 16.0;
}

// A leaf's own moment in the season, 0..1, from its phase (`sway.w`, one per leaf): it turns
// around `autumn` = its moment and falls once `leaf fall` passes it, so the first to turn fall
// first. Spread so that the season's early part is a few leaves, not none.
fn leaf_season_moment(sway: vec4<f32>) -> f32 {
    return fract(sway.w * 7.31 + 0.137);
}

// Whether a deciduous tree's leaf has fallen this season: its vertex is then put outside the
// clip volume, so the leaf draws nothing, in the main pass and in the shadow maps alike.
fn leaf_has_fallen(sway: vec4<f32>) -> bool {
    return flora.autumn.w > 0.5 && leaf_season_moment(sway) < flora_wind[5].y;
}

// Where a fallen leaf's vertices go: past the right edge of the clip volume, so all three
// corners of each of its triangles are clipped.
const FALLEN_LEAF_CLIP: vec4<f32> = vec4<f32>(2.0, 2.0, 0.5, 1.0);

// What a leaf's vertex colour is multiplied by this season: 1 in summer, the autumn colour over
// the leaf colour (`flora.autumn.rgb`) once it has turned, a short blend while it turns.
fn leaf_autumn_tint(sway: vec4<f32>) -> vec3<f32> {
    let moment = leaf_season_moment(sway) * 0.8;
    let turn = smoothstep(moment, moment + 0.2, flora_wind[5].x) * flora.autumn.w;
    return mix(vec3<f32>(1.0), flora.autumn.rgb, turn);
}

// A flower card's vertex: its colour's alpha is 0, a leaf's 1 (`FoliageParams::blossom`).
fn is_flower(color: vec4<f32>) -> bool {
    return color.a < 0.5;
}

// A flower's own moment in the blossom, 0..1, from its leaf's phase spread differently, so the
// flowers do not open in the order the leaves turn.
fn flower_moment(sway: vec4<f32>) -> f32 {
    return fract(sway.w * 3.71 + 0.53);
}

// Whether a leaf or a flower draws nothing this season (it is then put outside the clip
// volume): a fallen leaf, or a flower that is not out; flowers open once the blossom passes
// their moment and fall in the reverse order.
fn leaf_hidden(sway: vec4<f32>, color: vec4<f32>) -> bool {
    if is_flower(color) {
        return flower_moment(sway) >= flora_wind[5].w;
    }
    return leaf_has_fallen(sway);
}

// What a leaf's or a flower's vertex colour is multiplied by this season: a flower's is the
// blossom colour over the leaf colour (`flora.blossom.rgb`), a leaf's its autumn tint.
fn leaf_season_tint(sway: vec4<f32>, color: vec4<f32>) -> vec3<f32> {
    if is_flower(color) {
        return flora.blossom.rgb;
    }
    return leaf_autumn_tint(sway);
}

// Fresh snow's colour: bright and a little blue.
const SNOW_ALBEDO: vec3<f32> = vec3<f32>(0.80, 0.83, 0.88);

// The dusting of snow a sky-facing leaf or needle holds whichever way it faces, as a share of the
// full cover: a cold hint on a snowy conifer's green undersides. Tiny, because the mix is in
// linear light: on a spruce branch seen from below a share of 0.08 already lifts its mean colour
// from sRGB (39, 62, 50) to (67, 80, 70) and 0.2 to (89, 96, 87), a grey-olive that reads as
// paint, not snow. The snow itself lies on the sides that face up.
const LEAF_FROST: f32 = 0.03;

// How much snow lies on a surface whose world normal is `n` (unit): none until
// `FloraSeason::snow` rises, first on what faces straight up and, with more snow, on steeper
// faces too (all of it from `n.y` 0.45 at full snow). Not the normal the detail maps perturbed:
// the cover would break up into the bark's crevices and the leaf scans' veins.
fn snow_cover(n: vec3<f32>) -> f32 {
    let snow = flora_wind[5].z;
    let edge = 0.85 - 0.6 * snow;
    return snow * smoothstep(edge, edge + 0.2, n.y);
}

// How much of the falling snow reaches a leaf that sees `sky` of the sky (its baked crown
// visibility): none deep in the crown, all of it on the outside. Snow on an inner card is lit only
// by what little light gets in, and a white surface there renders charcoal, not white.
fn snow_shelter(sky: f32) -> f32 {
    return smoothstep(0.35, 0.85, sky);
}

// Lay this season's snow on a surface: `n` is the normal snow sees (the geometric normal for wood,
// `facing_normal` for a leaf card), `shelter` how much of the falling snow reaches it (a leaf's
// share of the sky; 1 for wood), `frost` the share of the snow that lies on it whichever way it
// faces (needles and leaves hold a dusting; wood 0). Its colour turns to snow's and it goes matte.
// Returns how much lies there, for what must fade under it (the light through a leaf). Without
// snow the surface is exactly as it was.
fn snow_on(p: ptr<function, PbrInput>, n: vec3<f32>, shelter: f32, frost: f32) -> f32 {
    let cover = max(snow_cover(n), frost * flora_wind[5].z) * shelter;
    let base = (*p).material.base_color;
    (*p).material.base_color = vec4<f32>(mix(base.rgb, SNOW_ALBEDO, cover), base.a);
    (*p).material.perceptual_roughness = mix((*p).material.perceptual_roughness, 0.9, cover);
    return cover;
}

// The world normal of the flat face being drawn, on the side the camera sees. Snow on a leaf card
// follows the card, not its crown-bent shading normal (which would whiten the top of the crown and
// nothing below it): a tier seen from above is white, seen from below green.
fn facing_normal(world_position: vec3<f32>, V: vec3<f32>) -> vec3<f32> {
    let n = normalize(cross(dpdx(world_position), dpdy(world_position)));
    return select(-n, n, dot(n, V) >= 0.0);
}

fn smooth_curve(x: vec4<f32>) -> vec4<f32> {
    return x * x * (3.0 - 2.0 * x);
}

fn triangle_wave(x: vec4<f32>) -> vec4<f32> {
    return abs(fract(x + 0.5) * 2.0 - 1.0);
}

fn smooth_triangle_wave(x: vec4<f32>) -> vec4<f32> {
    return smooth_curve(triangle_wave(x));
}

// How far the top of a tree leans at full strength, per metre of trunk height.
const MAIN_BEND: f32 = 0.04;
// How far a leaf's tip flutters at full strength, per metre of its length.
const FLUTTER: f32 = 0.25;

// A phase of its own for each tree, from where it stands, so a forest does not sway in step.
fn tree_phase(origin: vec3<f32>) -> f32 {
    return fract(dot(origin, vec3<f32>(0.0713, 0.0, 0.0531)));
}

// How strong a gust front is at `origin`: a bump of the front's strength, `width` metres deep,
// that travels along the front's direction. 0 where there is no front.
fn gust_front(origin: vec3<f32>, time: f32) -> f32 {
    let front = flora_wind[3];
    let front_shape = flora_wind[4];
    if front_shape.z <= 0.0 {
        return 0.0;
    }
    let along = dot(origin.xz - front.xy, front.zw) - front_shape.x * (time - front_shape.w);
    let u = along / front_shape.y;
    return front_shape.z * exp(-u * u);
}

// How fast a shaken tree swings, in swings a second for a tree 1 m tall: slower for taller trees
// (by the square root of the height, as a pendulum), 1.2 a second for a 6 m tree.
const SHAKE_SWINGS: f32 = 3.0;
// How hard a shake makes the leaves rustle, per unit of its strength, on the scale of the wind.
const SHAKE_RUSTLE: f32 = 0.8;

// What the shakes under way (`TreeShakes`) do to the tree standing at `origin`: (x, z) the lean
// they give it now, and how hard they make its leaves rustle. Each swings the tree first away
// from where it happened (or along its direction), then back and forth, dying away to nothing at
// its end; the nearer the tree, the harder. Nothing, and one comparison, once the last shake is
// over (the header in slot 6).
fn shake_at(origin: vec3<f32>, time: f32) -> vec3<f32> {
    if time > flora_wind[6].x {
        return vec3<f32>(0.0);
    }
    let swings = SHAKE_SWINGS * sqrt(max(flora.tree.x, 1.0e-4)) * (0.9 + 0.2 * tree_phase(origin));
    var lean = vec2<f32>(0.0);
    var rustle = 0.0;
    for (var i = 0u; i < 8u; i = i + 1u) {
        let place = flora_wind[7u + 2u * i];
        let how = flora_wind[8u + 2u * i];
        let age = time - how.z;
        if place.w <= 0.0 || age < 0.0 || age > how.w {
            continue;
        }
        let off = origin.xz - place.xy;
        let d = length(off);
        let near = 1.0 - smoothstep(0.0, place.z, d);
        if near <= 0.0 {
            continue;
        }
        var dir = how.xy;
        if dot(dir, dir) == 0.0 {
            dir = select(vec2<f32>(1.0, 0.0), off / d, d > 1.0e-4);
        }
        let fade = 1.0 - age / how.w;
        let amount = place.w * near * fade * fade;
        lean = lean + dir * (amount * sin(6.2831853 * swings * age));
        rustle = rustle + amount;
    }
    return vec3<f32>(lean, rustle);
}

// The leaf's movement in the tree's local space. `sway`: the leaf's direction scaled by how far
// the vertex is from where the leaf is attached (xyz) and the leaf's phase (w); `shake`:
// `shake_at(origin, time)`, which the caller works out once for both this and `wind_bend`.
fn flutter_offset(sway: vec4<f32>, origin: vec3<f32>, time: f32, shake: vec3<f32>) -> vec3<f32> {
    let wind = flora_wind[0];
    let motion = flora_wind[1];
    let rate = flora_wind[2];
    let t = time * wind.w * 2.0 * rate.y * flora.response.w + sway.w + tree_phase(origin);
    let w = smooth_triangle_wave(vec4<f32>(t * 1.975, t * 0.793, 0.0, 0.0));
    let strength = wind.z + gust_front(origin, time) + SHAKE_RUSTLE * shake.z;
    return sway.xyz * (FLUTTER * motion.y * flora.response.y * strength * (w.x + w.y - 1.0));
}

// `world` bent downwind with the tree. `local_y`: the vertex's height in the tree's own units,
// `scale_y`: the tree's scale along y, `shake`: `shake_at(origin, time)`.
fn wind_bend(world: vec3<f32>, local_y: f32, origin: vec3<f32>, scale_y: f32, time: f32, shake: vec3<f32>) -> vec3<f32> {
    let wind = flora_wind[0];
    let motion = flora_wind[1];
    let rate = flora_wind[2];
    let front = flora_wind[3];
    let bump = gust_front(origin, time);
    if wind.z <= 0.0 && bump <= 0.0 && shake.z <= 0.0 {
        return world;
    }
    let t = time * wind.w * rate.x * flora.response.z;
    let phase = tree_phase(origin);
    let g = smooth_triangle_wave(vec4<f32>(t * 0.193 + phase, t * 0.375 + 1.7 * phase, t * 0.27 + 2.3 * phase, 0.0));
    // 0.6 to 1.0 of the wind at the default gust strength of 0.4.
    let gust = 1.0 - motion.z + motion.z * 0.5 * (g.x + g.y);
    let down = wind.xy * (wind.z * gust);
    let across = vec2<f32>(-wind.y, wind.x) * (wind.z * motion.w * (2.0 * g.z - 1.0));
    let push = down + across + front.zw * bump;
    let h = max(local_y, 0.0) * flora.tree.x;
    // How far this vertex moves per unit of lean: the species' sway, more toward the top.
    let lean = MAIN_BEND * flora.response.x * (scale_y / max(flora.tree.x, 1.0e-4)) * h * h;
    let reach = lean * motion.x;
    let rel = world - origin;
    // A shake leans the tree whatever `FloraWind::bend` says: a still day has knocks too.
    let moved = rel + vec3<f32>(push.x, 0.0, push.y) * reach + vec3<f32>(shake.x, 0.0, shake.y) * lean;
    // Bend, do not stretch.
    return origin + moved * (length(rel) / max(length(moved), 1.0e-6));
}

fn sign_not_zero(v: vec2<f32>) -> vec2<f32> {
    return select(vec2<f32>(-1.0), vec2<f32>(1.0), v >= vec2<f32>(0.0));
}

// A unit vector as two numbers in -1..1: the octahedral mapping.
fn oct_encode(n: vec3<f32>) -> vec2<f32> {
    let p = n.xy / (abs(n.x) + abs(n.y) + abs(n.z));
    if n.z < 0.0 {
        return (1.0 - abs(p.yx)) * sign_not_zero(p);
    }
    return p;
}

fn oct_decode(e: vec2<f32>) -> vec3<f32> {
    var v = vec3<f32>(e.x, e.y, 1.0 - abs(e.x) - abs(e.y));
    if v.z < 0.0 {
        let folded = (1.0 - abs(v.yx)) * sign_not_zero(v.xy);
        v = vec3<f32>(folded.x, folded.y, v.z);
    }
    return normalize(v);
}

// The light a surface lets through from the far side of `N`: every directional light that shines
// on the back, scattered toward the viewer. `through`: the colour the light takes (the leaf's
// colour, its tint and its translucency). `visibility`: how much of the sky the leaf sees, for the
// ambient light that comes through. Point and spot lights do not shine through.
fn transmitted_light(
    P: vec4<f32>,
    world_normal: vec3<f32>,
    N: vec3<f32>,
    V: vec3<f32>,
    frag_coord: vec4<f32>,
    mesh_flags: u32,
    shadow_factor: f32,
    through: vec3<f32>,
    visibility: f32,
) -> vec3<f32> {
    let view_z = dot(vec4<f32>(
        view_bindings::view.view_from_world[0].z,
        view_bindings::view.view_from_world[1].z,
        view_bindings::view.view_from_world[2].z,
        view_bindings::view.view_from_world[3].z
    ), P);

    var sum = vec3<f32>(0.0);
    let n_directional_lights = view_bindings::lights.n_directional_lights;
    for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
        let light = &view_bindings::lights.directional_lights[i];
        let L = (*light).direction_to_light;
        let wrap = saturate((-dot(N, L) + 0.5) / 2.25);
        let scatter = D_GGX(0.6, saturate(-dot(V, L)));
        var shadow = 1.0;
        if (mesh_flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
                && ((*light).flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u {
            // The shadow of what is in front of the light, seen from the back of the leaf.
            shadow = fetch_directional_shadow(i, P, -world_normal, view_z, frag_coord.xy) * shadow_factor;
        }
        sum = sum + (*light).color.rgb * (wrap * scatter * shadow);
    }
    return sum * through
        + ambient_light(P, -N, -V, 1.0, through, vec3<f32>(0.0), 1.0, vec3<f32>(visibility));
}

// How much of a leaf's specular reflection to keep, seen along `V` (from the surface toward the
// camera): all of it unless the camera looks toward a directional light, none when it looks
// straight into it. A leaf card stands for a leaf, or a cluster of needles, whose many small
// surfaces would scatter the sun's glint; a flat card seen against the sun reflects all of it
// into the camera at once, and the crown's top turns white, as if it were snow or dead. Facing
// the sun, a crown glows green through its leaves (`transmitted_light`) instead. Lit from the
// side or from behind the camera nothing changes.
fn leaf_gloss_toward_light(V: vec3<f32>) -> f32 {
    var toward = 0.0;
    for (var i: u32 = 0u; i < view_bindings::lights.n_directional_lights; i = i + 1u) {
        toward = max(toward, -dot(V, view_bindings::lights.directional_lights[i].direction_to_light));
    }
    return 1.0 - smoothstep(0.4, 0.8, toward);
}

// The surface of a leaf card, in place: veins, hue of the underside, gloss, crown occlusion.
// `uv`: the card's own (0..1 across and from base to tip), `shade`: x is 1 on the upper side,
// y is how much of the sky the leaf sees. The veins are the image of `FLORA_VENATION` (grown by
// space colonisation, `venation.rs`), as strong as `flora.leaf.z` says; a card cut to a scanned
// leaf has its veins in the scan, and a material with no image draws none.
fn leaf_surface(p: ptr<function, PbrInput>, uv: vec2<f32>, shade: vec2<f32>) {
    var color = (*p).material.base_color.rgb;
@if(FLORA_VENATION)
    let vein = textureSample(venation, venation_sampler, uv).r * flora.leaf.z;
@if(FLORA_VENATION)
    color = mix(color, color * vec3<f32>(1.35, 1.3, 0.75), vein) * mix(0.92, 1.05, uv.y);

    // The lower side is paler and less saturated.
    let upper = shade.x > 0.5;
    if !upper {
        let luminance = dot(color, vec3<f32>(0.2126, 0.7152, 0.0722));
        color = mix(color, vec3<f32>(luminance), 0.25) * (1.0 + flora.leaf.w);
    }

    (*p).material.base_color = vec4<f32>(color, (*p).material.base_color.a);
    (*p).material.perceptual_roughness = select(min(flora.leaf.y + 0.3, 1.0), flora.leaf.y, upper);
    (*p).material.reflectance = (*p).material.reflectance * leaf_gloss_toward_light((*p).V);
    (*p).diffuse_occlusion = (*p).diffuse_occlusion * shade.y;
    (*p).specular_occlusion = (*p).specular_occlusion * shade.y;
}

// The light a leaf lets through, tinted by the species' leaves (`flora.tint`); a flower's petals
// let through their own colour.
fn leaf_transmission(p: PbrInput, visibility: f32, flower: bool) -> vec3<f32> {
    let tint = select(flora.tint.rgb, vec3<f32>(1.0), flower);
    return transmitted_light(
        p.world_position,
        p.world_normal,
        p.N,
        p.V,
        p.frag_coord,
        p.flags,
        p.directional_shadow_factor,
        p.material.base_color.rgb * tint * flora.leaf.x,
        visibility,
    );
}

// Toon lighting (`RenderStyle::Toon`, `FLORA_TOON` or the impostor's `params.y`), in place of
// `apply_pbr_lighting`: each directional light's N.L is cut into three bands (0.15 shaded, 0.55
// middle from N.L > 0, 1.0 lit from N.L > 0.5), times its colour, its shadow and the surface's
// colour, plus the ambient light, all on the surface's own (geometric, painterly) normal. Point and
// spot lights do not light a toon. Returns the exposed colour `apply_pbr_lighting` would.
const TOON_PI: f32 = 3.14159265358979;

fn toon_lighting(p: PbrInput) -> vec4<f32> {
    // The geometric normal, not the one the detail maps perturbed: the bands would break up into
    // the leaf scan's veins and the bark's crevices.
    let n = p.world_normal;
    let base = p.material.base_color;
    let view_z = dot(vec4<f32>(
        view_bindings::view.view_from_world[0].z,
        view_bindings::view.view_from_world[1].z,
        view_bindings::view.view_from_world[2].z,
        view_bindings::view.view_from_world[3].z
    ), p.world_position);

    var sum = vec3<f32>(0.0);
    let n_directional_lights = view_bindings::lights.n_directional_lights;
    for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
        let light = &view_bindings::lights.directional_lights[i];
        let n_dot_l = dot(n, (*light).direction_to_light);
        let band = select(select(0.15, 0.55, n_dot_l > 0.0), 1.0, n_dot_l > 0.5);
        var shadow = p.directional_shadow_factor;
        if (p.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
                && ((*light).flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u {
            shadow = shadow * fetch_directional_shadow(i, p.world_position, p.world_normal, view_z, p.frag_coord.xy);
        }
        sum = sum + (*light).color.rgb * (band * shadow);
    }
    let ambient = ambient_light(
        p.world_position, n, p.V, saturate(dot(n, p.V)), base.rgb, vec3<f32>(0.0), 1.0, p.diffuse_occlusion
    );
    return vec4<f32>(view_bindings::view.exposure * (base.rgb * sum / TOON_PI + ambient), base.a);
}

// The toon's ink: 1 where the surface faces the camera, darker (0.35) toward its silhouette.
fn toon_rim(N: vec3<f32>, V: vec3<f32>) -> f32 {
    return mix(0.35, 1.0, smoothstep(0.05, 0.3, abs(dot(N, V))));
}

// Schüler's cotangent frame ("Followup: Normal Mapping Without Precomputed Tangents", 2006): the
// tangent and bitangent of `uv` at `p` from screen-space derivatives (the meshes carry no
// tangents), scaled to the same length. Columns: T (along u), B (along v), N.
@if(FLORA_DETAIL)
fn cotangent_frame(n: vec3<f32>, p: vec3<f32>, uv: vec2<f32>) -> mat3x3<f32> {
    let dp1 = dpdx(p);
    let dp2 = dpdy(p);
    let duv1 = dpdx(uv);
    let duv2 = dpdy(uv);
    let dp2perp = cross(dp2, n);
    let dp1perp = cross(n, dp1);
    let t = dp2perp * duv1.x + dp1perp * duv2.x;
    let b = dp2perp * duv1.y + dp1perp * duv2.y;
    let inv_max = inverseSqrt(max(max(dot(t, t), dot(b, b)), 1.0e-30));
    return mat3x3<f32>(t * inv_max, b * inv_max, n);
}

// A scan's colour and normal map on a surface, in place, faded in by `fade` (0..1). The colour
// is a ratio to the scan's average, which the material's base colour has divided out so the
// surface keeps its colour: with no fade the surface is `plain` (the scan's average for bark)
// times the base colour, with all of it the scan's own colour. `uv`: the mesh's, in tiles of
// the scan (bark) or the atlas coordinates of a scanned leaf.
@if(FLORA_DETAIL)
fn detail_surface(p: ptr<function, PbrInput>, uv: vec2<f32>, fade: f32, plain: vec3<f32>) {
    // Derivatives and samples before any branch.
    let n = (*p).N;
    let frame = cotangent_frame(n, (*p).world_position.xyz, uv);
    let albedo = textureSample(detail_color, detail_sampler, uv).rgb;
    let tangent_normal = textureSample(detail_normal, detail_sampler, uv).xyz * 2.0 - 1.0;
    // The map is OpenGL's (green points up the image) and v runs down it, so B is flipped.
    let bumped = frame * vec3<f32>(tangent_normal.x, -tangent_normal.y, tangent_normal.z);
    let bumped_length = length(bumped);
    let perturbed = select(n, bumped / max(bumped_length, 1.0e-6), bumped_length > 1.0e-4);
    // The relief fades out toward the silhouette: there the bumped normal turns away from the
    // camera and the light, and the crevices turn to black speckle.
    let facing = smoothstep(0.05, 0.4, dot(n, (*p).V));
    (*p).N = normalize(mix(n, perturbed, fade * facing));
    let base = (*p).material.base_color;
    (*p).material.base_color = vec4<f32>(base.rgb * mix(plain, albedo, fade), base.a);
}

// The bark of a piece of wood, in place, faded in with the wood's radius: smooth below
// `flora.bark.x` metres, the whole scan above `flora.bark.y`. `uv`: the mesh's, in tiles of the
// scan; `radius`: the vertex's, in metres.
@if(FLORA_DETAIL)
fn bark_surface(p: ptr<function, PbrInput>, uv: vec2<f32>, radius: f32) {
    let fade = smoothstep(flora.bark.x, max(flora.bark.y, flora.bark.x + 1.0e-4), radius);
    detail_surface(p, uv, fade, flora.bark_mean.rgb);
}

// A leaf card cut to a scanned leaf wears the scan whole.
@if(FLORA_DETAIL)
fn leaf_detail(p: ptr<function, PbrInput>, uv: vec2<f32>) {
    detail_surface(p, uv, 1.0, vec3<f32>(1.0));
}