So, we all know about fresnel reflections, objects reflect more at grazing angles. A few years ago we would use the facing ratio and a ramp to achieve that effect, and then reverse the result in the diffuse to keep somehow a physical based workflow. With more recent shaders and render engines we can drive the reflection with the index of refraction (ior or n) which works great for dielectric materials (plastic, wood, etc) but for metals the index of refraction is a bit more complex and very few render engines have gone through the trouble of writing those more complex reflection formulas. The question is: does it make any difference at all? Can’t we just tweek it until “looks good”? Sure we can, after all it’s all about art, making it look good, and realism is not always pleasing. So don’t take this too serious, but in the end you might learn something.
Simple vs complex Index of refraction
Dielectric materials have a simple ior, you can just search online for the value, feed your shader and the resulting “reflection falloff curve” will behave like the real thing.
What if you search for cooper or gold material? If you feed your shader with that number won’t work at all, you have 2 options, increase the ior to an arbitrary number like 8 or 15, or play with the reflection options that your shader allows, in this post I will be using the aiStandard shader, which gives you the option to play with the reflection at 0 degrees.
So you might have heard that metals have another variable called “k”, extinction coefficient (imaginary part of complex index of refraction), and this value is the key to create the real reflection falloff curves of metals. The “problem” is that arnold and many other render engines don’t have a k attribute, so what you can do is to use a few maya nodes to manually create the reflection curves to mimic the real thing and feed the reflection attribute with it.
Analysing reflection curves
Now let’s see what’s going on on those reflection curves to better understand the concept and make sure we are doing it correctly.
I have created a python script to generate a bunch of planes (91) so we can analyse the reflection curves, those planes are rotated from 0 to 90 degrees , and the last one is at 0 degrees since we can’t render a surface at 90º from the camera. We will render this scene from an orthographic camera with a not so common resolution like 1024px by 20px.
To analyse reflection we need a light, a consistent one and with no shadow casting, so we use a Skydome light with an 100% white color and no shadows. If you really want to get crazy you might want to change your AA filter, I found that for this, the “closest” filter is the best. Now you create a shader, get rid of any diffuse, and change the color and specular amount to 1/white, check fresnel and in the refraction attributes also check Fresnel use IOR. Let’s use a simple IOR first, a value of ~1.5 (plastic) for instance.
After rendering the scene, we get a gradient from black to white (remember that the last square is fully white to pretend that it’s the 90º one).
Now we jump into nuke, create a crop to node to get rid of any extra render useless space, and a sampler node to analyse the pixels. After sample the colors we get a curve and we need to compare it against something, in this case an online reference refractiveindex.info/legacy.
In this website choose the polycarbonate (common plastic) and set the wavelength around 0.65(red light). We get a refractive index (n) of 1.57869. If you scroll down you also have an important value R = 0.05036, which is the reflection at 0º degrees, if you compare it to the first square of the render, that’s exactly the number you get, so we are doing good so far.
Let’s now check all the curve shape and compare it to the graph we see on the website. Keep in mind that the curve will have slight bumps/imperfections to it, that’s normal. To be precise you can download a jpeg out of it, I have gone through the trouble of overlaying the images and compare. So here is the result:
That’s very close, the shader is doing a great job of rendering a true reflection curve for the given ior. Too bad we can’t get the same behavior for metals, so let’s see what we can do about it.
N and K
Right, since our render engines don’t have a k attribute we need to use some maya nodes do draw the curve manually with the same shape as our reference (refractiveindex.info/legacy). But that’s not good enough, we can get a bit more complex and write a python script with the fresnel reflection formulas that we can get online so the script can draw the curves for us.
After a bit of experiment and copy from here and there, we have a function named IOR() which takes 2 arguments, n and k, that we feed with the info from our reference, and the function will output an array with the reflection values for all the angles (0 to 90).
Now we can use those values to draw the reflection curve into a remapValue node, I won’t go into details about the script because that would be another entire post, I can do it for the next time if you guys are interested.
As you see in the interface I have an n and k inputs and it creates the fresnel curve with the remapValue node selected. The fresnel formula also works for simple ior’s, leaving the k value at 0.
The Shading network
We have the curve but we still need a way to calculate the facing angle of the object and for that we use the facingRatio attribute of the samplerInfo maya node. Also the facingRatio is 0/black at 90º and 1/white at 0º, so we need to reverse this because objects always reflect more at grazing angles. So connect the facingRatio to a reverse node, inputX for instance since it’s just a single value. And then we connect the outputX of the reverse node to our remapValue inputValue.
Finally we can connect to the outValue (not outColor) to the material. About the material, we dont need to calculate refletions now, we could feed this value to a simple shader like an aiUtility/surfaceShader and analyse the curves, it will work the same in the reflections. Make sure you connect the outValue to the r,g and b colors of the material.
Now let’s analyse the pixels in Nuke. For this example I used Aluminium values for a given wavelength (0.65 ~ red). At the first look it seems like everything is working great, the curve has the same shape, but if we overlay the images and compare, there’s a slight shift to the curves.
Why is that? The shape is there, but the values are shifted somehow, what’s going on really? Is it our fresnel python formula not working? That’s not the case really, there’s another problem in between, more precisely in the beginning, the facingRatio.
FacingRatio “spike” curve
So if we take our test scene and analyse the “raw input” of the facingRatio we see that we don’t get a straight curve, or a linear curve (nothing to do with any linear workflow issue). Without going into detail about how the facingRatio is calculated (look at the maya docs for that), we can’t really remap the values with the fresnel curve linearly if the “raw input” of the facingRatio is not linear. So to solve this and without knowing maths like me, what I found is that if we create a “reflected” curve of the facingRatio curve (see the image below) we can make it linear and then remap the values with the formula. So again I did that, just after the facingRatio connect a remapValue node to reflect/invert the curve, I used a python script to do that, but then saved out a preset for future use.
The correct curves
Finally, after our update to the shading network, we can compare the results against the reference image and we see that the results are accurate.
Still remember the simple ior reflection curve? With the custom fresnel curves the result is even closer.
For metals you will need to create 3 curves (3 remapValue nodes), for red, green and blue light and then feed the material reflection color with it, using the same workflow, but this time you have 3 inputs, for example cooper reflects more red light than green or blue, that’s why it looks more “redish”. As reference use for red: 0.65, green: 0.51 and blue: 0.475 in the wavelength input at the refractiveindex website. Then we calculate the reflectivity for all based on the n and k values. You can always create a script to automate the process.
I don’t know how many of you are still reading until here, but hopefully you have learned something even if you don’t care much about this topic. In the end with so many things happening like bumps, scratches, roughness, grading, etc, you really need to care only about the look and don’t get crazy with physical accuracy. Anyways, it’s always good to know a bit more and give it a try. If you ask me if I will use this in my daily workflow, I guess not, what if autodesk/render engines provide us with a decent node/shaders to deal with metal reflections? That would be appreciated, at least we could try without going into crazy shading networks and scripts.
Please let me know about your own workflows and if there’s something that I am missing here or there that could simplify the approach. Thanks for reading.