In between bouts of festive over-eating I added support for blackbody emission to my fluid simulator and thought I'd describe what was involved.
Briefly, a blackbody is an idealised substance that gives off light when heated. Planck's formula describes the intensity of light per-wavelength with units W·sr-1·m-2·m-1 for a given temperature in Kelvins.
Radiance has units W·sr-1·m-2 so we need a way to convert the wavelength dependent power distribution given by Planck's formula to a radiance value in RGB that we can use in our shader / ray-tracer.
The typical way to do this is as follows:
- Integrate Planck's formula against the CIE XYZ colour matching functions (available as part of PBRT in 1nm increments)
- Convert from XYZ to linear sRGB (do not perform gamma correction yet)
- Render as normal
- Perform tone-mapping / gamma correction
We are throwing away spectral information by projecting into XYZ but a quick dimensional analysis shows that now we at least have the correct units (because the integration is with respect to dλ measured in meters the extra m-1 is removed).
I was going to write more about the colour conversion process, but I didn't want to add to the confusion out there by accidentally misusing terminology. Instead here are a couple of papers describing the conversion from Spectrum->RGB and RGB->Spectrum, questions about these come up all the time on various forums and I think these two papers do a good job of providing background and clarifying the process:
- Picture Perfect RGB Rendering Using Spectral Prefiltering and Sharp Color Primaries
- An RGB to Spectrum Conversion for Reflectances
And some more general colour space links:
- The CIE XYZ and xyY Color Spaces by Douglas Kerr (particularly good)
- SIGGRAPH 2010: Color Enhancement and Rendering in Film and Game Production
- Color Space FAQ
Here is a small sample of linear sRGB radiance values for different Blackbody temperatures:
1000K: 1.81e-02, 1.56e-04, 1.56e-04 2000K: 1.71e+03, 4.39e+02, 4.39e+02 4000K: 5.23e+05, 3.42e+05, 3.42e+05 8000K: 9.22e+06, 9.65e+06, 9.65e+06
It's clear from the range of values that we need some sort of exposure control and tone-mapping. I simply picked a temperature in the upper end of my range (around 3000K) and scaled intensities around it before applying Reinhard tone mapping and gamma correction. You can also perform more advanced mapping by taking into account the human visual system adaptation as described in Physically Based Modeling and Animation of Fire.
Again the hardest part was setting up the simulation parameters to get the look you want, here's one I spent at least 4 days tweaking:
Simulation time is ~30s a frame (10 substeps) on a 128^3 grid tracking temperature, fuel, smoke and velocity. Most of that time is spent in the tri-cubic interpolation during advection, I've been meaning to try MacCormack advection to see if it's a net win.
There are some pretty obvious artifacts due to the tri-linear interpolation on the GPU, that would be helped by a higher resolution grid or manually performing tri-cubic in the shader.
Inspired by Kevin Beason's work in progress videos I put together a collection of my own failed tests which I think are quite amusing: