Skip to main content

LIGHT_FRAG

Constant LIGHT_FRAG 

Source
pub const LIGHT_FRAG: &str = "// light.frag \u{2014} the screen-space light map, with shadows.\r\n//\r\n// Every pixel of the scene is lit by an ambient level plus each light in\r\n// the frame, and each light is shadowed by the matter between it and the\r\n// pixel. The matter is the occluder buffer the glyph pass wrote: coverage\r\n// for particles, nothing for floors and panel fills. Twenty-four samples\r\n// are taken along the line from the pixel to the light, and each one that\r\n// lands on matter takes a share of the light away.\r\n//\r\n// So a figure standing beside a brazier is lit on the side that faces it\r\n// and dark on the side that does not, and casts its shape across the floor\r\n// behind it, because the pixels back there have to look through the figure\r\n// to see the fire. Nobody wrote a shadow; the geometry did.\r\n#version 330 core\r\n\r\nin vec2 f_uv;\r\nuniform sampler2D u_occluder;\r\nuniform vec2  u_screen;\r\nuniform vec3  u_ambient;\r\nuniform int   u_count;\r\nconst int MAX_LIGHTS = 32;\r\n// (u, v, radius_px, casts_shadow)\r\nuniform vec4  u_light_pos[MAX_LIGHTS];\r\n// (r, g, b, intensity)\r\nuniform vec4  u_light_color[MAX_LIGHTS];\r\n// How much light one fully covered sample removes.\r\nuniform float u_shadow_density;\r\n\r\nout vec4 o_light;\r\n\r\nconst int STEPS = 24;\r\n\r\nvoid main() {\r\n    vec3 light = u_ambient;\r\n    for (int i = 0; i < MAX_LIGHTS; ++i) {\r\n        if (i >= u_count) break;\r\n        vec4 lp = u_light_pos[i];\r\n        vec4 lc = u_light_color[i];\r\n        vec2 d_px = (lp.xy - f_uv) * u_screen;\r\n        float dist = length(d_px);\r\n        float radius = max(lp.z, 1.0);\r\n        if (dist >= radius) continue;\r\n        // Inverse-square-ish with a smooth end at the radius.\r\n        float x = dist / radius;\r\n        float atten = (1.0 - x) * (1.0 - x) / (0.15 + x * x * 2.0);\r\n\r\n        float transmit = 1.0;\r\n        if (lp.w > 0.5 && u_shadow_density > 0.0) {\r\n            // Start a little way off the pixel itself, so a surface is not\r\n            // shadowed by its own coverage; stop a little short of the\r\n            // light, which sits inside its own emitter.\r\n            vec2 step_uv = (lp.xy - f_uv) / float(STEPS);\r\n            vec2 p = f_uv + step_uv * 1.5;\r\n            float density = u_shadow_density / float(STEPS);\r\n            for (int s = 1; s < STEPS - 1; ++s) {\r\n                float occ = texture(u_occluder, p).r;\r\n                transmit *= 1.0 - clamp(occ * density, 0.0, 1.0);\r\n                p += step_uv;\r\n                if (transmit < 0.02) break;\r\n            }\r\n        }\r\n        light += lc.rgb * lc.a * atten * transmit;\r\n    }\r\n    o_light = vec4(light, 1.0);\r\n}\r\n";
Expand description

The screen-space light map with shadows marched through the occluder buffer.