Monday, 22 October 2012 15:39

Tutorial: Absorption in Cycles

Written by  Gottfried Hofmann
The new ray length node in Blender 2.64 allows to fake absorption even without a volumetric shader. This tutorial shows you how to create your own absorption shader and make it re-usable.

Have you ever seen a complex Cycles shader that uses some strange math nodes to create cool features you'd like to play with? Ever tried to get your head around it and ended up with a headache? This tutorial shows you how you can create a Cycles shader node-tree from a physical formula. In the end you'll be able to understand other peoples setups a lot better and be able to create your own re-usable shaders from physical formulas you can find in textbooks or on the internet.

Dim lights

Download the training Blendfile including the finished shader here!

Further reading: An approximate SSS-setup using the ray length-node (thx blazraidr) and a set of node-groups that are based on real-world absorption coefficients (thx Broadstu).

Additional Info

  • CC-BY 3.0: This tutorial and blendfiles are released under the Creative Commons License CC-BY 3.0 Unported

34 comments

  • Comment Link Chris Couderc Tuesday, 05 March 2013 10:38 posted by Chris Couderc

    Hello Gottfried,

    Thank you for your tutorials in general and this one in particular. I've just started a blog in french, and tries to make a fire light (on a candle).

    You node helped me to change material and color of the borders of the light. A mix shader node with yellow translucent and red transparent, a fader made with an adaptation of your special group node (1.5 instead of "e" and some others ajustments) and the light goes on.

    You can see the result at : http://virtualdesign.fr/archive/2013-03-05/bougies-de-photophore

    Don't hesitate to post a comment if you could.

    Best regards.

  • Comment Link bnemer Sunday, 13 January 2013 20:31 posted by bnemer

    Hi Gottfried, thanks for this amazing tutorial !

    Just having a problem here : when I move far from my object, it gets darker, as if Ray Length becomes bigger. Is it just me, or am I missing something ?

    Cheers,

    bnemer

  • Comment Link Lafarge Eric Wednesday, 19 December 2012 15:22 posted by Lafarge Eric

    Bravo et mille merci.

  • Comment Link zz Saturday, 08 December 2012 20:26 posted by zz

    amazing video, i didn't expected that was possible...
    i would like more vids like that !

    Thanks Gottfried

  • Comment Link Gottfried Hofmann Saturday, 08 December 2012 11:43 posted by Gottfried Hofmann

    Thanks Matt for the lengthy answer. I got now what was so odd - it's that you have the (inverse) color in the exponent (it's that was I misunderstood for I0).
    The reason why I find it odd to put the color you want (the artistic choice) into the exponent is the following:

    Except for corner cases, the color you picked will never occur in your material, no matter how thick or thin it is. That's due to the non-linear relationship.

    Here's one example: Consider you chose the color 0.5, 0.1 and 0.9. And consider that the absorption constant is set to 1. I plotted the results for that in the following image and added a cube to the bottom which has the color chosen.

    http://www.pasteall.org/pic/41515

    At a depth of 1, the blue channel becomes roughly 0.9 - that is the original value. Yet the red channel is at 0.6 instead of 0.5 and the green channel is at 0.4 instead of 0.1.

    What you get is a color that is somewhat looking like the color you have chosen at any depth but that is always off. It's not even converging.

    I've modified the absorption shader in a way that you can set a depth (in Blender units) and a color. It will set the absorption constant in a way that you get the chosen color at the chosen depth. You can also set it for each color channel individually:

    http://www.pasteall.org/blend/17970

    This way you get a balance of physical accury and artistic choice.

    Realy phyisical accuracy is afaik only guaranteed by chosing the absorption constant of a measured fact table. And even then it would not be physically accurate as absorption is not limited to three wavelengths. One would need a shader that reads a distribution function of measurements and calculates absorption based on the wavelength. Maybe an OSL-shader...

  • Comment Link Tobias Ebbers Saturday, 24 November 2012 21:59 posted by Tobias Ebbers

    Hallo Blender Mentoren !

    Wie zu Ende des Videos erwähnt habe ich mich mal an einer erweiterung der Glas Node versucht. Bin zwar kein absoluter Experte was Blender und Compositing betrifft, aber vllt. ist das Node Setup für einige Hilfreich.

    Die Schachfiguren sind echt schicke Modelle, perfekt um Materialien auszuprobieren !!!! Ich hoffe das gibt keine Probleme wenn man sie für solche Experimente "missbrauch". Danke dafür!

    Bild 1:
    http://s14.directupload.net/file/d/3084/bz2huwg4_png.htm

    Bild 2:
    http://s7.directupload.net/file/d/3084/o3r2vm5c_png.htm

    Node Setup:
    http://s14.directupload.net/file/d/3084/z3zdgr6t_jpg.htm

    Macht weiter so gute Arbeit ! Eure Videos sind echt super lehrreich.
    Ein paar "Feuerfliegen" sind auch mit eingebaut :)))

  • Comment Link HomerS Friday, 02 November 2012 11:24 posted by HomerS

    Awesome...useful.Thanks a lot of, Gottfried Hofmann !!!

  • Comment Link dahray Wednesday, 31 October 2012 01:07 posted by dahray

    With what can be done with the ray length node. What need have we for a true sss shader.

  • Comment Link John Sunday, 28 October 2012 06:17 posted by John

    Fantastic, and now I'm just thinking out loud, can this technique be used to render a flame as well? i.e. do you think information from the Light path node can be used to achieve a photorealistic flame effect, like a blue/yellowish hotter center part of a flame that gradually goes to a more orange tone at the colder edge?

  • Comment Link Matt Sunday, 28 October 2012 02:43 posted by Matt

    Hi.,

    I think in order to understand this better, you need to understand what Beer's law means, and how it's derived - not just as a formula on a website.

    Beer's law is actually very simple - it's a formula relating the ability of a medium to absorb light, with the distance that the light travels in it. Say you have a material/medium such as water. Light can be transmitted through it, but when light hits some of the molecules floating around, there's a chance that the light will get absorbed.

    Let's say for example that for a given unit of distance that a ray of light can pass through the volume, the medium will absorb 40% of that light, leaving 60% remaining. So:
    * after unit 1, there will be 60% of that original light energy remaining
    * after unit 2, there will be 60% of that previous amount remaining, which equals 36% of the original light energy.
    * after unit 3 there will be 22% of the original energy
    * after unit 4 there will be 13%
    and so on… The proportion of light that the medium absorbs per unit (let's call it an absorption factor) is a property of the material - some materials will absorb a higher proportion of incoming light than others.

    If you plot these values on a graph, it follows an exponential curve. The steepness of the curve is is due to the absorption factor, i.e. the exponent. There's nothing inherent in the material that knows anything about exponential functions, the formula is just a convenient way to represent this repeated multiplication of a proportion less than 100% over and over again.

    Now if you want to render a volume of medium that absorbs light, you could design the algorithm the same way. The renderer knows how much light is incoming into the volume, so it could shoot a ray 1 unit into the medium, and calculate the amount of light energy remaining (eg. by multiplying by 60%). Then it could shoot that ray 1 unit further in, and calculate the amount of light remaining (multiply the previous amount of light energy by 60% again), and then shoot the ray another unit, and another unit, and another unit, multiplying the current light energy by that absorption factor each time.

    This is almost exactly what happens inside a ray marching volume renderer. If you're rendering something like smoke, where the material properties like density are changing over the space of your volume, you need to take it in small steps and see how much light has been absorbed inside that distance that the light has travelled in that step.

    For volumes like water or glass, we can assume that the medium has constant (homogenous) material properties throughout, and so rather than stepping through it, we can use the simpler representation with the exponential function to find the aggregate amount of light that passes through the entire distance, since we know that constant absorption factor will hold true all the way along the light ray length.

    So, what about colours? Take it back to that original definition of what happens in the material - instead of the material absorbing eg. 40% of the incoming light for every wavelength, it absorbs different proportions of incoming light per wavelength. So for example like water, it may absorb more of the incoming red light than green or blue, leaving mostly blue light to be transmitted through the volume (to your eye). Since this absorption factor is different for each r/g/b wavelength, to represent it using beer's law, we now need 3 exponential curves, since the shape and slope of each curve is dependent on that absorption factor. So one curve represents the amount of red light that is transmitted, another represents the amount of blue light, etc. The important thing to remember is that the material just reduces light by a proportion per unit distance - the exponential curves are then derived as a way of representing that relationship.

    In most renderers (like cycles in this case), light transport is decoupled from shading. So the renderer will say: "Hey brdf, there's is X amount of light incoming at Y angle. Please tell me how much light will then leave the material, and at what angle will it leave?" How that brdf will respond is dependent on what sort of surface it is representing (eg. diffuse/glossy/transmission), and the inputs provided. This is a black box - it's entirely up the the shader to determine how much light comes out of it, based on the information it has available.

    In the case of cycles' transmission btdf, it has an input which is by default rgb 1,1,1. While that value looks like a 'colour', it's really more of a proportion/multiplier to say how much light energy per wavelength leaves the btdf, vs how much enters. So by default (1,1,1) this means that while the shader can change the direction that light leaves relative to how it enters (due to refraction), the amount of light that is transmitted will be equal to how much enters.

    If you want to represent an absorbing medium, as well as the output light direction, the btdf needs to tell the renderer how what (less than 1) proportion of r/g/b light remains after being transmitted, and the way to calculate that proportion per r/g/b wavelength is with an exponential curve per r/g/b wavelength.

  • Comment Link Matt Sunday, 28 October 2012 02:40 posted by Matt

    Hi, I'll address this first, since this is incorrect:
    "but your node-setup computes Intensity = exp(-dist*Cfac*Intensity0)"

    No it doesn't - the 'original intensity of light' aka intensityO as far as a shader is concerned is always 1, and you don't really need to consider that. Shaders just deal with proportions of outgoing light relative to incoming - it's up to the render to calculate a final value.

    I think what you may have mistaken in my setup is that 'absorption' constant value node. That's basically just a multiplier on the absorption factor per wavelength, which can be used to control the strength of the effect - the physical basis for it is controlling the density of the absorbing medium.

    I'll explain how beer's law works in the next post.

  • Comment Link Gottfried Hofmann Saturday, 27 October 2012 17:44 posted by Gottfried Hofmann

    Hey Matt,

    the formula in the tutorial is exp(-dist*Cfac) - all the multiplications to the left of the exponential function in the nodes are evaluated before the exponentialization takes place, thus the factor is in the exponent.

    Regarding my question of your setup - with intensity I mean the intensity of light at a given wave length before and after it has passed through the medium. Cycles afaik the change in intesity with transparent materials by changing the color.
    Check the wikipedia page I refer in the tutorial (http://en.wikipedia.org/wiki/Beer%E2%80%93Lambert_law).

    There Extinction = exp(-dist*Cfac) with Extintion being defined by Intensity/Inensity0 (the latter being the original intensity) which leads to:

    Intensity = Intensity0*exp(-dist*Cfac)

    but your node-setup computes

    Intensity = exp(-dist*Cfac*Intensity0)

    which produces results from white through the absorption color(s) towards black which is good but it seems to me like you're changing the relationship this way. I could not find a better way, though.

    Try to set the input color to 60FE6A in your example - you'll need to set the absorption constant to a high value (for example 200) to get it darker in that case. But maybe that is the correct behaviour.

    My setup can also be used to absorp towards a certain color by setting the input color to white and the absorption color to any color you want but it indeed has the shortcoming that the absorption will not become purely black for thick materials/high absorption coefficient in that case but said color. You can get a more real effect by using a linear color ramp from white to the color of choice to black (which still is not physically accurate).

  • Comment Link Matt Friday, 26 October 2012 23:36 posted by Matt

    Hi Gottfired,

    What do you mean about 'intensity'? As in 'the colour you are trying to emulate'? I guess the important thing to think about is that physically there's no such thing as a colour of a material - it's all just light bouncing around. So the 'colour' that you introduce into the setup should be thought of more as a scaling factor for the wavelengths of light that are transmitted/reflected.

    So say you had a formula that was: Cfac * exp(-dist) . That's representing the same as the tutorial setup - the exponential light propagation is calculated once, and then multiplied once against a scaling factor. The way I see it, physically, that would be like a coloured paint layer over the top of some grey absorbing glass. Light would pass into that thin outer coloured layer, then all light except a few wavelengths would be filtered out at the surface, and as it progresses through the medium, the remaining light would then be absorbed exponentially, equally for all wavelengths.

    I don't even know if such a material can exist in real life, but either way its probably not what people are thinking of when they want to render most coloured absorbing materials like glass or water.

    As for ramps, yes, you could try to emulate something like that, but it becomes a lot more complicated - you then also have to estimate an 'end distance' to map the ramp to, guessing when the difference in light penetration becomes minimal, and you'll almost certainly get an unrealistic result. The other thing to think about too is that when you're using global illumination, the result of your primary shading can and does influence lighting elsewhere. So if you're not careful with things like energy conservation, you can get into strange situations where you're generating weird indirect lighting from your material. This isn't much of an issue for this setup since it's mostly removing light, not reflecting it - just something to keep in mind.

    Of course if you want to do something non-physical for any particular artistic reason that's totally fine as well! But usually if you're rendering something like water, or coloured glass, you actually want it to look like that, so it's better to stay within the realms of physical possibility, and use other means such as lighting design for artistic expression.

    cheers

  • Comment Link Gottfried Hofmann Friday, 26 October 2012 17:03 posted by Gottfried Hofmann

    Hey Matt,

    you are right about using seperate color channels for more realistic results. I started the shader from a version that used 3 channels and reduced it to one for the sake of simplicity since it still produces decent results in my opinion but is way better suited for a tutorial like this.

    I found that one can emulate the "different absorption at different depths" by using a color ramp instead of a mix node at the end.

    One question about your setup: Why did you put the color channels into the exponent? It looks odd to me since all formulas I came accross have the intensity outside of the exponent.

  • Comment Link Matt Friday, 26 October 2012 00:35 posted by Matt

    I think I should try and explain this better, since I'm not sure if people understand the reason why it's important to do it with separate channels.

    The physical basis for coloured absorption is that not only do materials absorb light, but various materials absorb different wavelengths of light at different amounts*. This is why when you're underwater everything looks tinted blue- most of the other wavelengths of light have been filtered out by the water, leaving only blue light reaching your eye.

    So maybe for each unit of distance that light passes through a medium it may absorb 50% of the red light, 30% of the green light and 20% of the blue light. As the light travels further, the relative proportions of each colour changes. So at the start, you have 100% red, 100% green, 100% blue. Then after one unit of distance, there's 50% red, 70% green, 80% blue. Then after another unit, there's 25% red, 49% green, 64% blue. And so on.

    If you look at the relative proportions of coloured light at each step along the way, it's changing - which means the hue of the coloured tints are changing too. It may start white, pass through a bit that looks more cyan, then end up at a deeper indigo. It's not just a simple gradient from one colour to another. But that's what the above setup does - it uses a single gradient of values to blend between two colours, which is not physically based.

    Here's an example graphically - it's not exactly accurate, but I think it can give you the picture of the difference between modifying channels individually or not.

    This image represents the method in the tutorial above - it calculates a single gradient of intensity (similar to applying that curve function), then uses that to blend between two colours:
    http://www.pasteall.org/pic/39531

    And here, it applies separate curves to each r/g/b channel. Notice the range of values is so much richer and more lifelike:
    http://www.pasteall.org/pic/39530

    You can also try this by taking an image and applying a single gamma to it, vs separating each channel and applying individual gammas to each.


    Anyway, hope this helps - if people are going to be making reusable nodes to drop in their setups, it would be better to make it a correct one :)

    * For the purposes of this I'm just referring to R/G/B channels as wavelengths. It's not entirely correct, but it's a very reasonable approximation when we're talking rendering like this.

  • Comment Link John D Smith Thursday, 25 October 2012 23:38 posted by John D Smith

    Great stuff Matteb! That was one of the posts that really got me on track. That setup got me closer, more of the time, than anything else I've seen. Like I said below, and like Gottfried mentioned here, Translucency still has some mystery in it for most of us!

  • Comment Link John D Smith Thursday, 25 October 2012 23:29 posted by John D Smith

    Awesome info! I've spent a lot of time learning this stuff and learned about 95 % of it, especially using the Translucency Node, but I just did it without really deciphering Beer's Law. Thanks for the knowledge! I knew you couldn't mess too much with a few of the values, but now I know exactly which ones not to touch. And I feel ya on not trying to explain the Translucency Node just yet! lol Once I got a pretty decent handle on it, I was pretty daunted by the idea of trying to explain it to anyone else. It's seems like hard work for each and every material, or object, to get satisfactory results on things like realistic translucent, colored rubber which has such obvious visual properties with an Un-obvious string of Node settings. I've resorted to hacking node connections! haha. Anyway great tut, thanks for filling in the blanks and I think another tut would be fantastic. To learn more about the real-world principles available for use in Cycles and how we can use them is pretty awesome.

  • Comment Link Matt Thursday, 25 October 2012 22:35 posted by Matt

    You may like to try modifying each colour channel independently, that way you'll get a more realistic colour effect. Since it's a non-linear relationship between colour channels (wavelengths) I don't think you can fully capture it using just a single absorption value to blend between colours.

    I made this file a few months ago: http://blenderartists.org/forum/showthread.php?216113-Brecht-s-easter-egg-surprise-Modernizing-shading-and-rendering&p=2113051&highlight=#post2113051

  • Comment Link remibug Thursday, 25 October 2012 18:34 posted by remibug

    Very very cool tutorial. Thx a lot !

  • Comment Link Gottfried Hofmann Thursday, 25 October 2012 10:25 posted by Gottfried Hofmann

    Hey Broadstu, I've added your link to the description!

    Hey Todd, thx for the review, I'll try to be more clear next time. The initial scene just shows how Cycles is handling light transport through transparent parts, no absorption happening there.
    The graph is supposed to be overlayed with the surface so you see how the light intensity is reduced while the light is traveling through the object.

Leave a comment