feat(render3d): a bole that does not quake, leaves out of the lens, a light with a reach

layer_flutter's mask was the vertex's height alone, so on a ten metre aspen every part of
the trunk above 1.2 m trembled with the leaves and the bole read as cloth. What separates
a leaf from a bole is distance from the model's centre line, not height; the mask is
radial now, and small plants keep the old one because a flower is all leaf.

NEAR_FADE dithers tree foliage out inside arm's length of the camera, in the depth
prepass, so the lit pass never sees those pixels and the equal-depth optimisation is
untouched. gl_Position.w is the view depth, so it costs one varying and no uniform.
Blades, cards and flowers are excluded - they live at the player's feet - and the shadow
pass keeps every leaf.

daylight_hand takes a reach in metres. The falloff was an inverse square windowed between
26 and 6 with both numbers hard-coded: two per cent of its own near field at ten metres,
so a torch lit your boots. It is a gentle power out to the reach now.

Measured in Maroon Lake, twice per side: the crown mask moves 1.7% of an aspen frame and
nothing at all in the meadow under it; the near fade moves 7.1% of a first-person frame
at a conifer and 0% where there is no foliage in the lens; the torch's reach lifts the lit
ground 20 to 45% and leaves the sky bit-identical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-20 00:05:22 +03:00
parent a88aecb993
commit 18d23ca20d
8 changed files with 97 additions and 8 deletions

View file

@ -63,6 +63,7 @@ uniform vec3 u_hand_pos; // a torch or flashlight in the hand
uniform vec3 u_hand_color;
uniform vec3 u_hand_dir;
uniform float u_hand_cone; // cos of the half-angle; <= -1: a point light
uniform float u_hand_reach; // metres to where it dies out entirely
vec3 skyIrradianceRaw(vec3 n) { return texture(u_irradiance, equirectUV(n)).rgb * u_ibl_scale; }
vec3 skyIrradiance(vec3 n) {
vec3 up = skyIrradianceRaw(vec3(n.x, max(n.y, 0.0), n.z));
@ -88,7 +89,13 @@ vec3 handLight(vec3 wpos, vec3 n, vec3 albedo) {
vec3 d = u_hand_pos - wpos;
float r2 = max(dot(d, d), 0.04);
vec3 l = d * inversesqrt(r2);
float att = smoothstep(26.0, 6.0, sqrt(r2)) / (0.5 + r2 * 0.35);
// A torch is not a point source in free air, and lighting it as one made it useless.
// The old curve was an inverse square windowed off between 26 and 6 metres: two per cent
// of its own near field by ten metres out, so carrying fire at night lit your boots and
// nothing else. This is a reach and a gentle power falloff instead - about nine tenths at
// a metre, half at half the reach, a tenth at nine tenths of it, nothing past it - which
// is a pool of light with a gradient in it rather than a hotspot with a cliff.
float att = pow(max(1.0 - sqrt(r2) / max(u_hand_reach, 0.001), 0.0), 1.25);
if (u_hand_cone > -1.0) {
float c = dot(-l, u_hand_dir);
att *= smoothstep(u_hand_cone, u_hand_cone + 0.12, c);