Indirect lighting teaser

This week I’ve been busy with something rather unexciting: speeding up the light tree creation time. You might remember that, initially, I just implemented a O(n²) method in order to have a working prototype quickly; now I lowered that to O(nlogn) following advice from Miroslav Mikšík.

It is now possible to have several tens of thousands point lights without incurring in terrible tree creation times. This is particularly important when indirect lighting is added to the mix.

A simple way to add indirect illumination to renderers is to place several small lights on surfaces hit by direct lighting. This principle is used by a family of algorithms called instant radiosity, originally developed by Alexander Keller. A lot of research has gone into reducing the number of resulting lights, in optimizing their placement in the scene (so that, for instance, lights not influencing the final rendering get trimmed), and in overcoming some of the shortcomings of the algorithm, since it tends to suffer in some corner cases.

The final part of my Summer of Code project is indeed about creating a toy implementation of instant radiosity, in order to show a potential “killer application” for Lightcuts in Blender. I think it’s also important to have it in order to have a clearer idea on how to integrate Lightcuts from a UI point of view.

Ideally it would be nice to have just a button to press, without having to rework all the lighting differently, but I’m afraid that the user must have some awareness of the strengths and weaknesses of the algorithm in order to take full advantage of it. More on this in a later post.

Here are some very preliminary results from my initial experiments with virtual point light placement.

Textured area light with indirect lighting

Textured area light with indirect lighting - Tree creation time: 00:00.54 - Number of (point) lights: 5966 (0l + 0s + 5966o) - Average cut size: 155.22 - Shadow rays: 153.77 (2.58%)

Textured area light with direct lighting alone

Textured area light with direct lighting alone (despite the stamp) - Tree creation time: 00:00.26 - Number of (point) lights: 3102 (0l + 0s + 3102o) - Average cut size: 134.83 - Shadow rays: 132.35 (4.27%)

Test scene - with indirect lighting

Test scene - with indirect lighting - Tree creation time: 00:02.00 - Number of (point) lights: 18091 (0l + 0s + 18091o) (4900d + 13191i) - Requested error rate: 0.018 - Average cut size: 587.30 - Shadow rays: 582.20 (3.22%)

Test scene - direct only

Test scene - direct only - Tree creation time: 00:00.43 - Number of (point) lights: 4900 (0l + 0s + 4900o) - Requested error rate: 0.020 - Average cut size: 567.16 - Shadow rays: 562.23 (11.47%)

(Yes, I am aware of the banding problems; it’s one of the issues I have to address next week before the Gsoc is over.)

Lightcuts: week 9

Textured area lights turned out hugely popular, so now it’s hard to live up to expectations I guess!

This week’s work was indeed far less exciting: I worked on supporting more material features, as so far only lambertian diffuse and phong specular where supported. Now, all diffuse shaders are supported.

Every single shader must be specifically supported as we need to be able to answer this question: what’s the maximum value that a specific pixel, shaded this way, may assume over a bounding box of point lights? You don’t really need the exact maximum value, so a ridiculously large number would always be a “viable” answer. On the other hand, you want the bounding value to be as tight as possible, because if the error turns out to be very low, you may decide to skip all the lights further down in the light tree, thus saving rendering time.

I also added support for all falloff types (except custom curve falloffs). The default falloff type for Blender lamps is “inverse linear”, while only “inverse square” was supported so far: this could lead to very incorrect renderings, especially when testing existing scenes.

Now, for instance, this hangar scene (featuring a lot of dupliverted “inverse linear” point lights plus a couple of additional area lights) looks a bit better:

Tree creation time:     00:02.27 Number of (point) lights: 3841 (1476l + 0s + 2365o) Requested error rate: 0.020 Average cut size: 239.44 Shadow rays: 233.08 (6.07%)

Tree creation time: 00:02.27 Number of (point) lights: 3841 (1476l + 0s + 2365o) Requested error rate: 0.020 Average cut size: 239.44 Shadow rays: 233.08 (6.07%)

Hangar (False colour)

Hangar (False colour)

(Thanks Gillan for pointing me to this model).

◊ ◊ ◊

On the textured area light side, Italian Blenderhead Lusque has performed an interesting experiment. If you bake direct lighting coming from a light source (e.g. an area light) onto the walls of a Cornell-like box, and then you use the baked textures as textured area lights, you basically have a simple indirect lighting pass.

Here you can see a test, where the indirect lighting has been exaggerated on purpose:

I’ll post Lusque’s blend file here as soon as we sort the remaining issues out.

(Please note that this is just an experiment and that it has nothing to do with the “Instant Radiosity” scheme I would like to implement; I am also perfectly aware that it has limited applicability, but it’s still very clever in my opinion).

Textured area lights in Blender

One of my favourite pictures from the original Lightcuts paper is an office scene lit exclusively by a tv screen. You can do that with textured area lights.

In Blender you can already link a texture to a light, but the outcome is a sort of texture projection, which is useful if you want to fake effects like light passing through a tree without actually computing the visibility: that comes of course very handy for cutting rendering time, to have artistic control on shadowing, etc.

What I mean here with textured area lights, on the other hand, is actually modulating the color and intensity of the light throughout its area. This way you can obtain the effect of lighting coming from tv screens, large windows with varying lighting conditions, and even area lights with custom shapes. Here are some examples.

Textured area light

Area light with custom shape

Tv screen

In the lightcuts paper you had also the added contribution of indirect light. In this case I added some additional lighting using a faint environment map. The interesting point to note is that putting additional lighting has a negligible impact on rendering time, if not a beneficial effect! This is because completely occluded areas evaluate far more lights than bright areas, as a consequence of using a proportional error metric; thus, having those parts brighter allows for less evaluations.

Here’s a false colour rendering where the green areas show where the environment lighting is more influential and the bluish areas show where the area light is more influential. The red channel counts the number of lights used with respect to the maximum, so the image is white where it’s most occluded, as expected.

Textured area light (false colour)

GSoC: midterm evaluation

Apologies for not having updated my blog recently. There’s quite a lot of stuff I have yet to blog about and I’ll surely do that in the near future. This week is halfway through the Summer of Code and it’s midterm evaluation time. Having been scolded for the lack of coolness of my rendering tests during the last developer meeting, I took the time to grab some better looking model and arrange more interesting tests. You can find my midterm report, together with said renderings, on Blender’s wiki. A warm thank you to the Kino3D guys who helped a lot in the process.

By the way, as a SoCer I got to visit Google’s London office today. I was glad to have my Blender t-shirt handy for the circumstance. During the tour I’m pretty sure I saw a couple of guys editing a video using Blender on an enormous screen. Overall the visit was very inspiring and, you know, a free lunch is always welcome!

Lightcuts: oriented lights

These days I’ve been working on adding support for oriented lights. It’s mostly there, even though there are a couple of missing details and the current code is obviously slower than it could be.

Still, like in my previous post, I can’t resist posting another revisited preliminary test, now rendered with actual lightcuts code.

Indirect lighting

And the corresponding false colour image:

Indirect lighting scene (false colour)

The original test took almost an hour to render, at 400×300 OSA5. This one took ten minutes at 800×600. True, the images don’t look exactly the same, but that’s because I changed the falloff type of the virtual point lights to a more realistic inverse squared. Still, the speed improvement looks pretty good already!

Yes, I am perfectly aware that the shadow from the sun suffers from aliasing problems. This issue will be addressed somehow, so don’t worry about that.

Please note that the virtual point lights have been placed by a custom (and pretty hackish) Python script; generating them automatically is something that I definitely want to do in the future, but is not strictly speaking part of the current GSoC.

My plan in the near future is to complete the basic feature set in time for midterm evaluation — that is, adding support for specularity and for the other diffuse shaders available in Blender. This is holding back some other things I would like to do as soon as possible, such as releasing a couple of Blender scripts to render from environment maps and to do basic indirect lighting, and squashing the bug that is causing this video to flicker like crazy.

Lightcuts: directional lights

I have just added support for directional lights on my branch. Using more than one “sun” light may sound weird, but that’s actually how you transform environment maps in a collection of point lights, as each pixel ideally represents an infinitely distant illumination source.

So I couldn’t resist going back to my preliminary tests, but this time running them with the actual code! Here’s Suzanne again under the Eucalyptus Grove light probe:

Eucalyptus Grove test

Please note that the AO image is there just to give a very rough feeling of how the two could compare — of course energy should be matched more carefully and parameters could be tuned better. Comparison with approximate AO would be useful as well.

Here’s another test using a hand-painted environment map:

Handpainted environment map test

In both tests the average cut size (the number of lights actually evaluated per pixel) is pretty low — see how dark the false color images appear. On the other hand the shadowing doesn’t look entirely convincing; still, it looks like a good starting point to me!

All images were rendered at OSA 5.

Lightcuts: week 2

A quick update from last week that I couldn’t find the time to finalize. I post it now as it discusses a couple of interesting issues. Btw, week numbering refers to the official GSoC timeframe. — UncleZeiv

The scene shown below — an ordinary Suzanne lit by 15000 colored lights — was sent by an early tester (thanks bullx) who found some noticeable artifacts when comparing the Lightcuts rendering to the plain one, despite following the instructions given in my previous post. It was indeed a bug that I finally found after a longish hunt. This is to say that early testing is indeed doable and welcome!

On the other hand, the reference 800×600 rendering using the traditional pipeline, according to the tester, took nothing less than 4 hours (it’s not displayed here). I have to admit, though, that this is not a fair comparison, since you can’t select “pure” single sampled shadows from the UI for the traditional pipeline — you are always forced to use the better quality, but slower, QMC code. [Note: I restored the possibility to select singled sampled lights from the UI in revision 15183.]

I don’t use that because in this context you don’t care if a single light is actually low quality or produces ugly hard shadows: on average the contribution from a single light will be fairly small and will be softened and corrected by the contribution from the other lights. Alas, this may not be the case if a small number of lights is significantly more intense than the average; that situation may require a special treatment.

Error comparison

Finally, I wanted to show an example of false color renderings, which are an invaluable tool to see at a glance how the algorithm is performing behind the scenes. Each color channel bears a different meaning — which is, by the way, liable to change frequently during development (it already has a couple of times).

Right now, the red channel is the most important and is proportional to the ratio between the number of evaluated lights and the maximum number allowed, that is, the maximum cut size. (If this figure is higher than the current number of lights, the latter is used).

As you can see from the picture, more lights get evaluated in darker regions: this may sound counterintuitive but is the direct result of using a relative error metric. The darker the region, the lower the absolute error allowed. This is particularly apparent in the second rendering, where a higher error threshold was selected.

The second picture gives also an idea of how image quality degrades by raising the allowed error.

Playing with a “working” prototype

The Google Summer Of Code has officially started on Monday. I’ve been doing a fair amount of coding  since my last post and it is now possible to see something almost meaningful on my branch.

I finally managed to have all pieces of the puzzle in place. My initial attempt to hook the lightcuts algorithm to the renderer through a “lights iterator” interface turned out to be ill conceived: what I really needed was a callback mechanism to let Blender Internal evaluate the contribution of a single light. In the end this will require more changes to the render engine, but overall this technique remains pretty non-intrusive.

Right now the technique supports omnidirectional lights (“Lamps” in Blender parlance), Lambert shading, only diffuse. Through the UI it’s possible to change the maximum allowed error and the cut size — that is, the maximum number of lights taken into account per sample.

Early tests show that the lightcuts version consistently outperforms the traditional rendering pipeline, even for fairly small amounts of lights. Results are not always correct though, especially when complex occlusions are present.

Let me clarify that although I am already receiving useful feedback from testers, there is a long list of limitations you have to be aware of before playing with my test builds. Here is a minimal and possibly incomplete checklist:

  • raytracing and shadowing must be enabled! This is not a current limitation: Lightcuts doesn’t make sense otherwise
  • all materials should be only diffuse, employing the Lambertian model (current limitation): disable specularity and/or set it to black
  • any other feature is not supported yet; in particular, ensure that the Bias toggle is turned off, or results could differ significantly
  • all lamps should be omnidirectional (“Lamp”), with Inverse Square falloff, and ray shadowing active
  • please ignore QMC settings: I am really using the old “single sample” lamps, not accessible from the UI anymore; fiddling with the settings could lead to inconsisten settings and eventually to crashes
  • it goes without saying: disable AO, SSS, whatever feature doesn’t look like a “plain” feature

I’ve also added a new render layer called “False Color“. It’s a visual debugging tool that shows per-pixel color-coded information. Right now, the red channel shows the ratio between used lights and maximum cut size: when it’s dark, it means that the algorithm took advantage of its metrics to aggressively save calculations; somewhat unintuitively, the most expensive parts to compute are the dark areas (in the actual picture, not in the false color one): having a relative error criterion means requiring a very accurate calculation precisely where the signal is weak, and that’s when the “max cut” limit is hit.

A sprouting tree

Focus on lowering algorithmic complexity and don’t perform other optimizations until their usefulness is demonstrated by thorough profiling: since I completely agree with this maxim, I feel obliged to explain why my latest lightcuts update seems to contradict it.

First of all, let’s recap: in order to select an optimal subset of the available lights per sample, a lightcuts implementation needs to maintain them organized into a binary tree. The leaves of this tree are the actual lights, while intermediate nodes represent clusters of lights. A cluster of lights is a sort of imaginary light whose position, intensity and orientation is somehow representative of all its children.

The original paper spends only a paragraph on tree building and related issues. The point is that building the light tree is trivial, but building it efficiently is far less trivial. Miroslav Mikšík, in his paper dealing with implementation issues regarding lightcuts, notes that a naive algorithm may have a O(n³) complexity; he devised a technique to reduce such complexity to O(nlogn).

Keep in mind that a robust implementation should be able to deal at least with several thousands of lights, and should allow venturing into the millions! This is one of those cases where complexity does matter.

Right now, though, I’ve implemented a simple O(n²) scheme, taking advantage of the nice heap mini-library available in Blender. I’ve spent some time, on the other hand, ensuring that a single memory allocation is required for the entire tree, instead of one for each node. This is possible because the number of nodes in the tree is predictable in advance. This reduces fragmentation, is cache friendly and, since it’s possible to use offsets instead of pointers, its memory footprint is the same on 64 bits machines instead of increasing because of the doubled pointer size.

Next, I want to move on to the other parts of the algorithm, in order to have a working prototype fairly soon. It probably won’t support a large number of lights, nor oriented lights (spot) which are probably going to be added later on, as they require special treatment. But at least it will be possible to validate the overall architecture of the project, and people will be able to start playing with it.

For this purpose, the current algorithm has both acceptable performance (I’m not going to debug with large number of lights at the beginning, anyway) and is easier to debug.

Use ack!

By misspelling awk during a search, I’ve come to know a very useful utility: ack. I wholeheartedly suggest to install it and use it as a drop in replacement for grep in most circumstances.

Basically, it’s a developer-friendly grep that, by default, ignores many annoying things that tend to get in the way nowadays, such as .svn folders. Other common options, such as recursiveness and “operate on all files in this directory” are assumed without the need of explicitly typing them. The output is structured, colored and highlighted in a meaningful and useful way — but it still plays well in a pipe, where it reverts to a more sober, grep-like output. It maintains a small (but extendable) database of filetypes, so that you can ask for specific categories of files to be included or not: for instance –noshell ignores .sh, .bash, .csh, .tcsh, .ksh, and .zsh files.

It’s also actively maintained.

It’s already an indispensable tool for me. Go grab it, you won’t regret it!