LTC area lights (LTCGI)
A video screen, two light tubes and a tilted triangle light this room as real polygons. On polished tiles the screen reflects as a sharp picture, on rough tiles as a soft glow.
The problem
A point light has no size. Put one where a TV or a neon tube is and you get a round hot spot on the wall and a pinprick highlight on the floor, with nothing of the light's shape. The screen's picture doesn't reach the room at all, unless you add more lights and animate them by hand.
How it works
Linearly transformed cosines (LTC) make the integral over a polygon cheap. A clamped cosine over a polygon has an exact closed form: a sum over its edges. A 3 x 3 matrix, read from a lookup table indexed by roughness and view angle, warps the GGX highlight into that cosine. So the shader transforms the light's corners by the inverse matrix, clips the polygon to the horizon (up to 5 corners) and sums the edges.
Diffuse uses the same sum with no warp, only a rotation into the surface frame. A second table scales the specular result.
For a light with a picture, the shader projects the pixel onto the light plane to get a UV. Diffuse reads a heavily blurred copy of the picture. Specular picks a blur level from log base 3 of 333 x distance over area, measured after the warp, so far or rough reflections read blurrier copies.
The blurred copies are 3 levels made each frame with separable Gaussian passes: 32 samples, then 18, then 14 with mips. The first level also fills a border around the picture, so a reflection that lands just off the screen still gets colour.
What I hit
The repo ships two 64 x 64 table pairs and one 256 x 256 pair. The Controller prefab binds the 256 one, which matches the shader's table size constant, so I used it.
EXR rows start at the top, Unity UVs at the bottom. With the rows flipped, roughness 0 at a straight view gives a diagonal matrix.
The picture's corner UVs are written by the Unity editor, so I derived them from the corner order. A wrong guess mirrors the reflection, so I checked that the progress bar at the bottom of the screen shows up on the near edge of the mirror image.
My build
The light loop, the LTC evaluation, the horizon clipping, the edge integral, the UV projection and the 3 blur passes are ported from the shader code to GLSL in three.js. Four lights: a 3.2 x 1.8 m screen with a 1024 x 512 animated canvas, two 2.4 m tubes and a triangle. Floor tiles ramp from roughness 0.05 to 0.8 across 8 m.
The point light side is my own baseline: one point light per light at its centre, power = area x colour, GGX without Fresnel.
Trade-offs
Cost grows with lights x pixels, two integrals each. There are no shadows here: the repo's optional shadow map is not ported, so light passes through the pillars. The specular has no Fresnel term, which makes rough tiles a bit shiny.
When to use it
Screens, light strips, signs and windows whose shape should show in the lighting, a handful at a time.
Source and credits
Technique source: LTCGI by _pi_ (PiMaker), https://github.com/PiMaker/ltcgi, MIT license, commit f47b62146eb664dfc0ea987941187f2c3d5d39db. Ported to GLSL from its shader code: the light loop and LTC evaluation in LTCGI.cginc, the horizon clipping, edge integral and UV projection in LTCGI_functions.cginc, and the separable blur in Prefilter Blur (BlurPrefilter shader and the LOD1/LOD2/LOD3 materials). The 256 x 256 lookup tables (ltc_mat_hdr_inc, ltc_mag_hdr_inc) are taken from the repo, as bound in its Controller prefab.
Underlying method: "Real-Time Polygonal-Light Shading with Linearly Transformed Cosines" by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt, SIGGRAPH 2016.
Render framework: three.js by mrdoob and contributors, https://github.com/mrdoob/three.js, MIT license, r170. Used for drawing and render targets. It is not the source of the lighting technique.
My own code: the room, the four lights and their placement, the animated screen picture, the point light baseline used for comparison, the roughness sweep, camera moves, labels and video. No external models or assets.