Building a Fresnel shader
From C4 Engine Wiki
Building a Fresnel shader
If you have never used the shader editor before: - read Using the Shader Editor first.
If you have used it, but still do not really know what you are doing: - read Implementing Overlay Blending first.
Once you have that covered, you should be fully prepared to continue reading this page.
The specific part of the Fresnel equations we are interested in here, describes how light travelling through a certain medium with a specific index of refraction (IOR) will be partially reflected and partially refracted when encountering another medium with a different IOR, and the way the angle between the incoming light, the surface and the observer affect the amount of specular reflection versus refraction or diffusion reaching the observer's eye. Perhaps the most well-known display of the fresnel effect in CG imagery is typically a shiny sphere that appears to have stronger reflections along the rim than in its centre, causing a distinct "glow" around its perimeter. The effect is also commonly used for various special effects, such as X-ray shaders or ghostly apparitions.
In the real world, you will most commonly notice the transition from refraction to reflection when looking in through an ordinary window. If you are standing on one side of a street somewhere, straight in front of an office building on the opposite side, it is quite easy to see into the rooms behind the glass in the windows. The angle between your viewplane and the glass in the windows is close to zero degrees, and that means the refraction (transmission, transparency) easily overpowers the reflection. However, if you cross the street and look at the same windows from an angle, close to 60 - 80 degrees, it will gradually become harder to see what it looks like inside, because in this case the reflections are much stronger than the refraction. The transparent glass finally appears to turn into a mirror as the viewing angle approaches 90 degrees. The relationship between the angle and the amount of reflection is exponential rather than linear.
This demonstration above will of course fail to some degree if the window in question is fitted with reflective film, that effectively turns it into a one-way mirror, but that's another story. Finally, a word of advice: make sure you do not study other peoples' windows for too long, or you might find yourself arrested or at least interrogated by a bunch of rather hostile guys in uniforms.
To sum up, a reasonable approximation of Fresnel reflection should tell us how much reflection to expect at a particular point of a certain surface depending on the viewing angle and the IOR of the mediums involved, one of which is usually air, and the other is the material we are looking at. A typical fresnel shader is not strictly limited to transparent materials, although that is what you might primarily associate with things that deal with IOR values. In fact, it is just as important for opaque materials, like metal or plastic, where it can be used to control the transition from diffuse (matte) reflection to specular (mirror like) reflection.
The Simplistic Approach
One very simple implementation is to take the Z component of the Tangent View Direction and invert it:
This is an economic solution that is quick to set up and does not add a lot of potentially expensive math operations to the shader code. There are a few drawbacks however: we can not easily adjust it much beyond simply multiplying it with something to increase or decrease the overall intensity; it does not take into account the IOR of any participating medium; and, most importantly, it is not really a model of Fresnel reflection at all.
The reason we specifically single out the Z component, is because that part of the tangent view vector can tells us in which direction the surface normal of a mesh is pointing relative to the position of the camera. This means that the Z component will have a value of 1.0 for any surface exactly parallel with the camera plane, and conversely a value of 0.0 for any surface facing 90 degrees away from the camera plane. By inverting this value, we get what we want - a value close to 1.0 at glancing angles, that drops off to 0.0 where the surface is parallel to the camera plane.
If you bypass the Invert node and reconnect the Tangent View Direction directly to the lighting output without a swizzle, you can see how the raw output of the node works on a basic sphere:
Limitations aside, the simple shader above - sometimes referred to as an "angle of incidence shader" - is quite versatile, and can be used to great effect in a lot of different materials, particularly if used as a control value in a linear interpolation set-up, which would then let you mix different colours or textures based on the angle of incidence. So, for many purposes, this type of Fresnel approximation may in fact be "good enough".
However, if we want a significantly improved approximation, we need to approach this task from a slightly more mathematical angle. The inverted Z tangent will be put to use as a small component of the improved version as well.
The Improved Approach
The equations we are going to use to get a better Fresnel approximation look like this:
(1) Rs = (n1 - n2)/(n1 + n2)
(2) F = Rs + (1 - Rs)(1 - Tv)^5
The first part, Rs, expresses the ratio of the IORs of the two mediums involved, where n1 represents the medium in which the ray of light starts, typically air, and n2 is the medium the light eventually hits, which would be the material we are interested in, perhaps composed of some sort of glass, liquid, plastic or metal.
The second part, F, calculates the final fresnel reflectivity based on the Rs and the Tv, which is our tangent view direction. As you can see, it is mainly the exponent (5) along with the difference between two IORs that affects the results. Thus, F is the value we ultimately want to get to, and n1, n2 together with the exponent will be the input variables that allow the user to tweak the final look.
This will give a much more accurate approximation than the angle of incidence shader above, but it is important to realize that we are still dealing with something highly simplified. For starters, we do not consider polarisation or absorption, neither do we take different wavelengths of light into account, and the list goes on. Then again, if we wanted full physical accuracy, we would have to forget about doing things in real-time anyway (and this tutorial would be much longer and not written by me). The goal is just a better approximation than the angle of incidence, but it is still just an approximation, and there are obviously many other ways to do it.
Building Rs (equation 1)
Open a new material in the shader editor, remove any nodes from the ambient and light tabs respectively, then start adding nodes in the ambient tab. To set up (n1 - n2)/(n1 + n2), we need two constant scalars, and one each of subtraction, division and addition. Connect them as shown below:
To avoid confusion in the next step, make sure you add comments in the n1 and n2 nodes, as well as "Rs" in the comment field for the division node, since that is the node holding the Rs value we want to use in the second equation.
Building F (equation 2)
To handle Rs + (1 - Rs)(1 - Tv)^5, it might be a good idea to split it up a little and take one bit at a time. Starting with (1 - Rs), just connect the Rs output to an invert node:
The (1 - Tv) means that we need a set of nodes exactly like the one we used in the angle of incidence shader above - a tangent view direction, swizzled to output Z only, and an invert node. They are not yet connected to anything else, so just put them alone somewhere beneath the previously added nodes:
Now, (1 - Tv) should also be raised to the power of 5, so we need a constant scalar and a power node:
The scalar in this case belongs to the user interface (of sorts), so it is a good idea to give it a comment and place it somewhere near the n1 and n2 to make them easy to group in a single, easy-to-find section rectangle.
To finish equation 2, the output from the Power node and the inverted Rs should be multiplied together, and the straight Rs should be added to the result of that multiplication. That calls for one multiply and one add node, like this:
That last Add node is the final Fresnel value, the coveted F - mark the node with a comment like "Fresnel Output". Your finished network, with an added rectangle to make it easy to find the user settings, should hopefully look like this:
There is one last thing to do before testing the results: running the fresnel output through a Saturate node, which simply clamps the input to a 0 - 1 range. The reason for this is that the raw output might have a tendency to produce hot pixels with a brightness well above 1.0, which usually causes aliasing and generally just looks bad. Adding a saturation node takes care of that, so go on and get one of those and connect the fresnel output to it. Then connect the sat node output to the lighting output so we can get a look at the result.
With the current parameter values:
- n1 = 1.0003 (the environment is filled with air)
- n2 = 1.333 (the material is made of water)
- Exponent = 5.0
which represents the fresnel reflectivity of the surface of a material with the same IOR as water (1.333) surrounded by air (1.0003), you should see something like this:
If, for example, you would instead be in an underwater environment looking at a material made of air (commonly believed to be a bubble in that case), the n1 and n2 values would need to switch places, i.e. the light would be moving through a medium with IOR=1.333 and hit an interface to another medium with IOR=1.0003. The difference is quite substantial.
As you can see in the preview with the initial values, the transition from white to black is very harsh, and the rim is extremely thin. While theoretically correct, it does not really work in a non-linear colorspace with a low dynamic range. The easiest way to make the whole thing softer and more display-friendly, is to decrease the exponent, maybe to a value as low as 1.5. While a change like that makes the result less correct, technically speaking, one should keep in mind that most C4 shaders are probably not intended for scientific visualisation, but mainly for entertainment purposes - which means that "if it looks right, it is right". If however a random physicist should complain about your environment map falloff, do not argue with him.
At any rate, our basic fresnel shader is up and running, and between the main parameters n1, n2 and the exponent we have sufficient control over the refraction indices of the mediums and the overall falloff rate respectively.
Using the shader
The typical case for the fresnel shader includes using it as a t-value in a linear interpolation node, and/or as a mask/multiplier combined with some other source of colour or texture, and/or as an input to the RGB-port of the environment output node, and/or anything else you can think of. In a transparent material, the white pixels of the fresnel output should be fully reflective and/or specular, and the black pixels should be fully refractive. In an opaque material, the white still maps to reflectivity/specularity, while the black maps to diffused light (i.e. the opposite of reflective/specular lighting).
More tutorials involving the application (and possible improvements) of this fresnel shader in various types of materials are coming soon, as indicated by the "See Also" section below. In the meantime, just do your own experiments with it.
A small collection of refraction indices for different materials in different categories. Note that there are several variables involved in defining the "true" IOR for any material - things like ambient temperature and the thickness of the material etcetera, can have a significant impact. For example, the IOR of gold, plated on some substrate of a lesser metal, may vary from as low as 0.14 to over 3.0 depending on the thickness. Thus, the values below, collected from various online sources, are just to be considered averaged estimates.
|Aluminium 1.44||Amber 1.546||Air 1.000293|
|Asphalt 1.635||Beryl 1.577||Carbon Dioxide 1.00045|
|Chalk 1.510||Crystal 2.00||Helium 1.00004|
|Copper 1.10||Ethanol 1.36||Hydrogen 1.00014|
|Gold 0.47||Glass 1.51714||Nitrogen 1.00029|
|Iron 1.51||Ice 1.309||Oxygen 1.00028|
|Lead 2.01||Quartz 1.544|
|Plastic 1.460||Water 1.3333|