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.