Niko's Project Corner

Global illumination

Description Global Illumination implementation
Languages C++
Tags Ren­der­ing
Con­ju­gate Gra­di­ent Method
Duration Spring 2010
Modified 13th July 2013

I im­ple­mented a sim­ple global il­lu­mi­na­tion al­go­rithm, which con­structs and solves the sparse ma­trix which de­scribes how much tiles re­flect light to each other. It sup­ported only gray-scale ren­der­ing and wouldn't scale up to big­ger sce­nes, but it was nev­er­the­less an in­ter­est­ing pro­ject and I learnt a lot about 3D ge­om­etry, lin­ear al­ge­bra and com­puter graph­ics. Graph­ics was drawn by a soft­ware ren­derer which uses only stan­dard SDL prim­itive draw calls.

When the pro­gram is started, scene data is read from a XML file and ob­ject sur­faces are gen­er­ated. Their spa­tial re­la­tion­ships are cal­cu­lated, and the n × n sym­met­ric ma­trix A is gen­er­ated, which en­codes the sur­face-to-sur­face re­flec­tion mag­ni­tudes. The global il­lu­mi­na­tion equa­tion sim­ply be­comes A x = b, where b is mostly filled with zeros, indicating that the number of inbound photos matches the number of absorbed and reflected photons. Light sources function the same way as normal surfaces, with the only difference that they only "reflect" photos, without receiving any. Light sources cause the only non-zero values in b.

The equa­tion A x = b is solved by having an initial estimate of uniform luminosity, and then iteratively refining it using conjugate gradient method. It is a very easy method to implement and suits well symmetric sparse matrices, but it may have slow convergence if the A is somewhat ill-conditioned. At some complex geometries it wasn't able to converge with a few iterations, but instead the luminosity seemed to fluctuate from region to region. These pre-calculated values are then used when the scene is rendered to the screen. Example results can been in figures 1 and 2.

Figure 1: Shad­ows are a nat­ural out­come from global il­lu­mi­na­tion.

In Fig­ure 1 ob­jects can be seen "cast­ing" a shadow on the wall be­hind them, and dark­ened cor­ners, prov­ing that the ge­om­etry was in­ter­preted cor­rectly. The block­iness of shad­ows is a typ­ical prob­lem in sim­ple tile-based global il­lu­mi­na­tion ren­der­ings, be­cause the lu­mi­nos­ity is forced to be uni­form across each tile, and thus gra­di­ents can­not be rep­re­sented. One so­lu­tion would be to use more tiles, but then cal­cu­la­tions would be a lot more time con­sum­ing, and it wouldn't solve the un­der­ly­ing is­sue. In fu­ture I might have a sec­ond look at this prob­lem, and use OpenGL for graph­ics with bi-lin­ear in­ter­po­la­tion. It would also do the ren­der­ing in color, and maybe even sup­port caus­tics and user-con­trolled light sources. Some of the cal­cu­la­tions could be done in CUDA, and the rest would be ex­ecuted in mul­ti­ple threads where pos­si­ble.

An other shadow phe­nomenon can be seen in Fig­ure 2, in which the shad­ows on the ceil­ing and floor are very dif­fer­ent. This oc­curs be­cause the light source is low on the floor, in which case the wall's cor­ner causes the shadow in the ceil­ing have a strong edge. In con­trast to this, the shadow on the floor is very soft. This oc­curs be­cause for pho­tons to hit the floor, they have to bounce from the ceil­ing and walls, mak­ing it pos­si­ble to reach each floor tile from mul­ti­ple paths, and the lu­mi­nos­ity de­creases grad­ually.

Figure 2: Since the light source is low on the floor, it is worth not­ing that the shadow on the ceil­ing is hard, where as the shadow on the floor is very soft.

Related blog posts: