Variable FOLIAGE_GLSLConst
FOLIAGE_GLSL: "\n uniform vec3 uFoliageSummer;\n uniform vec3 uFoliageSpring;\n uniform vec3 uFoliageAutumn;\n uniform vec3 uFoliageBare;\n uniform float uFoliageGreenTemp;\n uniform float uFoliageBareTemp;\n uniform float uFoliageStrength;\n uniform float uFoliageSelect;\n uniform float uFoliageVariance;\n uniform float uFoliageWarming;\n uniform vec3 uBlossomColor;\n uniform vec3 uBlossomAlt;\n uniform float uBlossomShare;\n uniform float uBlossomStrength;\n\n /** 1 where a surface is living green, 0 where it is bark, rock or sand. */\n float foliageMask(vec3 base) {\n // Relative, not absolute: how far green leads the other two channels as a\n // fraction of itself, so the test survives a dark texture, a bright one, and\n // the gap between a linear-space stock material and the terrain shader's\n // own working space.\n float green = (base.g - max(base.r, base.b)) / max(base.g, 1e-4);\n return mix(1.0, smoothstep(0.02, 0.20, green), uFoliageSelect);\n }\n\n /** The bloom an individual plant carries, somewhere between the two colors. */\n vec3 blossomHue(float seed) {\n // A second hash off the same seed, so the tree that flowers pink is not\n // also the tree that turns reddest in autumn — one seed, uncorrelated uses.\n return mix(uBlossomColor, uBlossomAlt, smoothstep(0.3, 0.7, fract(seed * 91.3187)));\n }\n\n /** How thickly petals cover this plant right now, 0–1. */\n float blossomFall(float stage, float seed) {\n if (uBlossomStrength <= 0.0) return 0.0;\n\n // Spring only. Autumn passes through the identical temperatures, so\n // uFoliageWarming is the entire reason flowers don't open in October.\n float spring = smoothstep(0.55, 0.95, uFoliageWarming);\n\n // A bump through the middle of the turn: blossom comes to a half-bare tree\n // and is gone by the time it's in full leaf. Because it rides the turn and\n // not the calendar, the bloom climbs the map — valleys flower while the\n // hills above are still bare, exactly as the snowline retreats.\n float window = smoothstep(0.1, 0.42, stage) * (1.0 - smoothstep(0.5, 0.8, stage));\n\n // A tree either blooms or it doesn't; a wood half-flowering everywhere\n // reads as a color wash rather than as individual trees in bloom.\n float blooms = fract(seed * 43.7585) < uBlossomShare ? 1.0 : 0.0;\n\n return spring * window * blooms * uBlossomStrength;\n }\n\n /**\n * base — the surface's summer color, as authored\n * temp — season-adjusted temperature, 0–1 (climate texture's A channel)\n * seed — a stable per-instance/per-cell 0–1 hash, for hue variation\n */\n vec3 seasonalFoliage(vec3 base, float temp, float seed) {\n // 0 = full summer, 1 = bare. Runs \"backwards\" (high edge first) so falling\n // temperature drives the turn.\n float stage = 1.0 - smoothstep(uFoliageBareTemp, uFoliageGreenTemp, temp);\n if (stage <= 0.0) return base;\n\n float mask = foliageMask(base);\n if (mask <= 0.0) return base; // bark, rock, sand — nothing to turn\n\n // A wood where every tree turns the same gold reads as a single decal, so\n // push a share of them redder and darker by a per-instance hash. Applied to\n // the autumn color alone rather than to the blended mid — folded in later it\n // would muddy spring green and drag a pink blossom brown.\n vec3 autumn = mix(uFoliageAutumn, uFoliageAutumn * vec3(1.18, 0.76, 0.62), seed * uFoliageVariance);\n vec3 mid = mix(autumn, uFoliageSpring, uFoliageWarming);\n\n vec3 target = stage < 0.5\n ? mix(uFoliageSummer, mid, stage * 2.0)\n : mix(mid, uFoliageBare, stage * 2.0 - 1.0);\n\n // Clamped because a palette color with a near-black channel would otherwise\n // divide its way to a blowout.\n vec3 ratio = clamp(target / max(uFoliageSummer, vec3(1e-3)), vec3(0.0), vec3(8.0));\n vec3 turned = base * mix(vec3(1.0), ratio, uFoliageStrength * mask);\n\n // Blossom goes on as a layer over the canopy rather than as another stop in\n // the palette, because petals are a different thing from a leaf: they cover\n // the tree, they don't recolor it. It also happens to be the only way green\n // reaches pink or blue — those are a hue swing the ratio can't make without\n // running past its clamp.\n return mix(turned, blossomHue(seed), blossomFall(stage, seed) * mask);\n }\n" = ...
Shared seasonal foliage tint — the other half of what the climate texture is for. Snow is what settles on the world; this is what the world's own living surfaces do as the year turns.
Terrain and scatter both call
seasonalFoliage, for the same reason they sharesnowCoverage: a hillside and the trees standing on it have to turn together, and two copies of this curve would drift apart by a week of autumn.Why two inputs
tempis the cell's season-adjusted temperature (the climate texture's A channel), and it decides how far through the turn a cell is — so cold uplands go gold while the valley below is still green, and the tropics never turn at all. But temperature alone cannot tell spring from autumn: a cell at 0.4 is the same cell whether the year is warming or cooling.uFoliageWarming(seeseasonWarming) supplies that direction, and it is the only part of this that is global rather than per-cell.Why a ratio
The palette names four absolute colors, but they are applied as a multiplier against
uFoliageSummer— the surface's own as-authored green. That makes high summer exactly a no-op (summer / summer= 1) instead of a wash that flattens the material to a uniform color, and it carries every texel's own variation and shading through the turn rather than painting over it.Why it knows what a leaf is
A tree mesh is usually one material over trunk and canopy, and terrain is one shader over grass, rock and sand. Rather than demand a second material or a per-vertex mask,
foliageMaskreads the surface's own color: the green parts are the living parts. It is measured on the summer color, before any tint, so the mask is fixed for the year — an autumn-gold canopy doesn't stop counting as foliage halfway through October. SetuFoliageSelectto 0 for a mesh that is foliage all over (a bush) to skip the test entirely.