2.5D relighting in compositing with Blender (using spherical harmonics)

Introduction

About a year ago, I was watching a great presentation at the Nuke Master Class by Roy Stelzer (2D TD at Double Negative) about “2.5D Relighting inside of Nuke”. There is 3 videos which you can download from The Foundry and also files which goes with. I recommend you to watch those videos especially if you are not familiar with relighting in post-production.

What Roy did, is using a paper from Siggraph 2001 called “An Efficient Representation For Irradiance Environment Maps“.

“An Efficient Representation For Irradiance Environment Maps”

This paper cover how to do Environment Lighting based on Angular Map (or light probes, or HDRI map, whatever you want to call it) but without using those map for computation. Odd you said ? Yes sure !
They develop a technique using Spherical Harmonics to simplify an HDR Angular Map to only 9 coefficients !! That means after their process, you only have to do a few multiplication with those 9 coefficients and then you are done !  Check out the comparison  :

left is a standard rendering, right is using the 9 parameters. Grace Cathedral Light Probe was used for this rendering http://www.debevec.org/Probes/

If I’m not mistaking, there is less than 1% of error between both rendering, and of course you can image that the second one goes much much faster :)

Please visit there website and presentation for more information about it :

What did Roy ?

In his demo, Roy shows the use of  the irradiance only of a Angular Map. This means he will get all the HDR Luma intensity of the map (no color variation). Once you have the 9 coefficient, the operation is pretty easy and so it compute very fast. The operation looks something like this :

color = c1 * L22 * (x2-y2) + c3 * L20 * z2 + c4 * L00 – c5*L20 + 2 * c1 * (L2_2 * xy + L21 * xz + L2_1 * yz) + 2 * c2 * (L11 * x + L1_1 * y + L10 * z)

Where c1, c2, c3, c4 and c5 are 5 constant number given in the paper. X, Y, Z are float numbers from your normal pass (X2 means x*x ; xy means x*y ; so on … ). All the L22, L20, …. variables are the 9 coefficients)

So as you can see, this is not a really complicated operation and it does compute really fast. Running this on each pixels would return a kind of light map (irradiance) which you can use to ADD to your original color map.

What did I do ?

As I did for the Lift/Gamma/Gain the first time, I tried to reproduce this formula with the “Math Node” of Blender. So for that matter I did use the EXR file (render passes) provided by Roy in his demo, and only kept the Normal and AO passes.

Blend File here

This is not as fast as it could be, the render time for a 1920×1080 is around 1.5 second (well for HDR environment lighting we have seen worst ^^). There is several reasons for this to be slow, but I’ll come to that later.
Note that for this example, I did use the Grace Cathedral Light Probe value and not Roy’s light probe.

I was kind of happy of the rendering though, but a bit disappointed to only get Luma value when environment maps have so much information about colors as well ! (you thought the previous example was a mess with nodes, wait for the following one ;) )
UPDATE : I was totally wrong :) !!! the only reason why I only get Luma (or actually greyscale) is because I used Math node. I thought it was able to do the operation on any kind of input, but actually it does it on only one composant. So the vector operation never happen :p. I just figure it was possible by trying this same formula in another shader language (pixel bender) and see color happening ^^ . So my bad, Color works too, and I’m not sure to know the difference between the vectors or the Matrices in this case, except using the formula with vectors is much faster ! (I’ll change the blend file later)

So I took a closer eye to the paper, and especially to the example they provide, and I found out that their filter wasn’t only generated coefficients, but also Matrices !!! This means you can do the operation with 9 coefficients to just get the irradiance of the environment or do a similar (but a bit more complex) operation with 3 4×4 matrices (red,green,blue) !
I guess the obvious reason Roy didn’t go for this solution was because the computation is more slower, and he didn’t really need it since he is doing a kind of 3 points lighting in his example.

As I said the math are a bit different ! Here is the formula by using the 3 matrices :

n = worldNormal;
color.red = dot(n , matriceRed);
color.green = dot(n , matriceGreen);
color.blue = dot(n , matriceBlue);

Ok so while this might look more simpler on paper, remember the matrices are 4×4 and if a dot product is quite simple, it is not the costless operation too :) . Here is what it looks like in Blender with “Math node” as well :


Blend File here
Again, due to the heavily use of Math node, I believe that 3 second is not too bad, but I’ll come back to that later. Also the node I use to rotate the normal pass, is using some math that might slow the render a bit too and not absolutely necessary

This shows that the technique is working pretty well, but probably not production ready as it is in Blender since we are missing a few thing to make this work smoothly.

What would we need in Blender for this to be more efficient ?

  • More “Input” nodes : this might be one of the reasons why the rendering is slow down a bit. Because actual “Input node” only works between 0-1, and the matrix number were between -n and n, I had to find a trick. For this I used the “Math node” for each number of a matrix. Setting it to “Add”, enter the value in the first input, and setting the second input to 0.0. So the output would be equal to the first input. I only did that because I couldn’t figure another way to have negative and positive values in Blender compositing in another way. But this also mean that the compositor tell blender to do an operation for each input, can’t say it’s much optimized :)
    • Input Value that goes between -n and n
    • Vector4 Input (right now you can only use Vector3, but you usually need to work with alpha)
    • Matrix Input (could even be useful to do some quick and dirty convolution)
  • Expression node : ok now, I’m dreaming for this one ! And this is IMO probably the main reason why this is so slow. I believe that each time I’m using a Math node, Blender does treat them individually. Which makes sense though, but it probably make a lot of exchange with inputs, outputs, inputs again, outputs again, ….
    I would believe that sending the entire operation at once to the CPU (or whatever) and getting it back at once would make things different and much faster (but I might be wrong on this one !?)
    Anyway the other reason for this node would be … well seriously, have you seen the mess with all the nodes ?!?
    So a simple field, even just interpreted by python would be great !!!!
  • Math with Vector : Maybe I did something wrong, but I couldn’t do a “dot product” between vectors, which is one of the reason why I have all those nodes. I’m doing the entire “dot product” by hand and this is heavy.
    I wish Math could be use with any kind of input. But again, maybe I’m doing something wrong here
  • Passes : we need more passes! For this example we need an Object Normal Pass rather than a World Normal Pass. Probably not to much to do though, the only problem I have with the Passes system today is that they are all hardcoded in Blender, which makes it complicated to create a custom one like you would have in Maya.
    I’d like to be able to assign a material overall to a define passes, but yet I would probably need to write shaders, which implicate the needs of writing shaders as well for the renderer. I guess OSL will fix that in a future if it gets implemented one day
  • Better support for EXR : beside this really annoying, flip node you have to add when working with other packages (I know flip node is nothing, but when working with 2K or 4K it is not the same deal, especially when the composite gets complex, you want to save any operation you could) , but I believe anyone is aware of this now, the other lack of EXR in Blender is the passes support. it doesn’t support custom passes coming from other package. Roy provided his Nuke script, with all the EXR file so you could play with it. But when I tried to load it in Blender, the input node couldn’t find all the passes inside, beside the usual color, Z (and maybe another one can’t remember exactly) it couldn’t find any. So I had to open the EXR in Djv, select the pass I wanted, and save it to another file as the RGB value. Really painful process

Hopefully someone hear me out there ^^

Share and Enjoy:
  • Twitter
  • Facebook
  • LinkedIn
  • StumbleUpon
  • Posterous
  • Tumblr
  • Digg
  • FriendFeed
  • del.icio.us
  • Netvibes
  • RSS
  • email
  • Print
  • PDF
  • Google Bookmarks
  • Blogplay
This entry was posted in CG/Animation, R&D and tagged , , , , , , , , , , , , , , , , , , , , , , , . Bookmark the permalink.

10 Responses to 2.5D relighting in compositing with Blender (using spherical harmonics)

  1. Lukas Tönne says:

    This is very cool! I admire your patience with all these messy math nodes :D
    I have addressed some of the problems with math nodes you write about in my branch (particles-2010):
    * Socket types are more oriented at actual C types: “value” becomes “float”, in addition there are primitive int and bool types. Quaternions and matrices will follow. Special non-math types, such as strings and “operator” sockets are only really useful for the simulation nodes.
    * Most operations that are commonly defined for all fields, such as add, subtract and component-wise (!) multiply and divide work with generic nodes, which have an “adaptable” socket. That means the actual type of the node changes depending on what type of sockets it is linked to. When you plug in a vector, you will actually get a blue socket and real vector addition, etc.
    * Some vector operations such as dot- and cross products get their own nodes with specific inputs and outputs. The one-for-all math node is also replaced by one node type for each operation, which avoids having nodes with unused inputs, such as trigonometry nodes and other oddities. Imho it’s better to have one distinct functionality for a node than cramming somehow “similar” stuff into one. Selection could be made easier than by clicking through a menu though.
    * Implicit conversion between types is limited to scalars (floats, ints and boolean), other conversions need an explicit “compose”/”decompose” node, so no more wondering what actually happens internally (currently value->vector: each component = value, vector->value: value = average(!) of components)
    * will add a generic input node too, which adapts like the primitive math nodes.

    Cheers
    Lukas

    • François says:

      that’s good to know :)
      I think I have seen your video on Vimeo where inputs dynamically switch to the correct one. That’s very nice !
      I’m so waiting for this node stuff, but as I can see now, it looks like you are not just doing it for particles but for almost everything in Blender :p ? (mesh structure)
      Are you looking for an ICE like ?

  2. Pingback: Pixel Bender : ft-SSIBL shader for After Effects | François Tarlier's Blog > CG Artist, Matchmover, VFX, Open Source topics, Computer Vision, and geek stuff !

  3. carlos padial says:

    Thanks for diving into this stuff for blender… thats so cool.

    You guys are the masterclass!

  4. François says:

    Hum ! I should take a look at this ! I guess it could be better (at least faster) than using the matrices !

    What’s your opinion about an Expression node ?

  5. Matt says:

    It’s in the render – before render it prefilters the image and derives the coefficients, then at render time, adds light based on those. You can have colours with either really, it’s just an alternate form of the equation.

    Anyway, while I prefer to have this sort of thing in the render itself, I think there’s still room for some fun comp nodes like this, reflection maps, atmospheric mist, etc..

  6. François says:

    @Matt
    tell me about it!
    but so far world normal and expression node would make things much much easier though.
    I guess we’ll see after Durian :(
    The IBL was implemented in the Render right, wasn’t a compositing stuff ? So you were using an angular map as input, and generate the coefficient on the fly ? where you using matrices or coefficients ?
    Because as far as I understood it, you can’t have colors with the coefficient, only with the matrix right ?

    @3pointedit
    as says Matt, making a node of this math would be really easy and much much faster because we would use blender math functions and operation for processing pixels, without going back and forth with nodes !
    But still I would love a scripting node as you called it, because it would make this kind of prototyping much easier (trust me its a mess to do it with nodes like that :p ) and it would also keep Blender nodes clean and easier to develop.
    I mean we don’t always have to create a C node for each operation we want to do, sometimes a small script or even a group node is more than enough.
    But for sure, when we think it’s going to be heavily use, porting to C could be a good thing to

  7. @Francoisgfx Thanks for translating the Nuke Master class to Blender Class heh

  8. Matt says:

    This is the same technique as I used to do this IBL stuff: http://mke3.net/blender/devel/rendering/diffuse_sh/

    At the time, was considering making a compositing node for it too – would be very easy, pretty quick, and reasonably high quality with a bent normals pass exported too…

    Would be nice if someone would pay me to work on this stuff instead of fixing bugs eh..! :/

  9. 3pointedit says:

    Thanks for spending any time with Blender. I have really got a lot from your work. I see that scripted nodes may help, is there a way to compile node groups or wouldn’t that really sove the time issue?

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>