This is a follow up post from the first part on how to create custom fresnel curves in maya. In this second part I will break down the script that was added to the custom tools that you can download from creativecrash. This gives you the chance to create your own version and perhaps learn some python scripting.
Simplifying the network
If you remember from last post, we needed to create an extra remapValue node to compensate for the facingRatio “spike” curve. As you will see we no longer need that extra node because we can do it right inside our script when we remap the final fresnel curve, just like the reverse node, since we can use python to reverse the values.
Fresnel Formula in python
In order to calculate all the reflection curves we need to translate Fresnel’s formula to python, and there’s not much to explain here, just math calculations. In the end the function will return an array of values like so [0.05, 0.07,...,1.0], representing the reflection values from 0 to 90 degrees.
def IOR(n,k): theta_deg = 0 n = n k = k fresnel =  while theta_deg <= 90: theta = math.radians(theta_deg) a = math.sqrt((math.sqrt((n**2-k**2-(math.sin(theta))**2)**2 + ((4 * n**2) * k**2)) + (n**2 - k**2 - (math.sin(theta))**2))/2) b = math.sqrt((math.sqrt((n**2-k**2-(math.sin(theta))**2)**2 + ((4 * n**2) * k**2)) - (n**2 - k**2 - (math.sin(theta))**2))/2) Fs = (a**2+b**2-(2 * a * math.cos(theta))+(math.cos(theta))**2)/ \ (a**2+b**2+(2 * a * math.cos(theta))+(math.cos(theta))**2) Fp = Fs * ((a**2+b**2-(2 * a * math.sin(theta) * math.tan(theta))+(math.sin(theta))**2*(math.tan(theta))**2)/ \ (a**2+b**2+(2 * a * math.sin(theta) * math.tan(theta))+(math.sin(theta))**2*(math.tan(theta))**2)) R = (Fs + Fp)/2 fresnel.append(R) theta_deg += 1 return fresnel
I will be focusing right away in the main function, if you want to know how I created the UI you can have a look at the python script included in the download of the custom tools. It’s pretty simple, a few float fields and buttons.
def drawCurve(*args): nValue = cmds.floatField( 'nVal' , q=1,v=True) kValue = cmds.floatField( 'kVal' , q=1,v=True) if nValue > 0: # create remapValue node remapNode = cmds.shadingNode('remapValue',asUtility=1) # Calculate Fresnel Curve fresnelList = IOR( nValue, kValue )
So, we start by defining a function drawCurve, and we store the n and k values in 2 variables. Then we make a simple test, if n is not greater than 0 we will be showing a warning to change the default values. Then we need to create a remapValue node to draw the fresnel curves. Finally just call the IOR function with the given n and k values and store it on a variable called fresnelList.
# Compensate for non-linear facingRatio linearValues = [ float(i)/90 for i in range(91) ] rawValues = [ math.sin(linearValues[i]*90*math.pi/180) for i in range(91) ] rawValues.reverse()
If you remember from last post we had that non-linear facingRatio issue. Instead of creating an extra remap node to compensate for that, we calculate the facingRatio curve in python (rawValues) and then we reverse them so we don’t need to use a reverse node in maya.
Reducing curve points
So far we have everything that’s needed to calculate the reflection curves but we can’t feed 91 values into a remapValue since there’s a 50 points limit on that particular node. There’s also a good reason for having less points since I have noticed higher render times with remapValue nodes with too much points. So we need to simplify the curve, reduce the points but keep the “interpolation” so we don’t compromise accuracy.
The good thing is that there’s plenty of algorithms to do this job and we don’t need to reinvent the wheel. The one that we will be using is Ramer-Douglas-Peucker algorithm, and we are lucky since someone have already translated it into python. I will give you all the links/credits in the end of the post. So just grab these 4 functions and add them before our drawCurve function so we can use it later.
""" ----------------------- Ramer Douglas Algorithm ----------------------- """ def _vec2d_dist(p1, p2): return (p1 - p2)**2 + (p1 - p2)**2 def _vec2d_sub(p1, p2): return (p1-p2, p1-p2) def _vec2d_mult(p1, p2): return p1*p2 + p1*p2 def ramerdouglas(line, dist): if len(line) < 3: return line (begin, end) = (line, line[-1]) if line != line[-1] else (line, line[-2]) distSq =  for curr in line[1:-1]: tmp = ( _vec2d_dist(begin, curr) - _vec2d_mult(_vec2d_sub(end, begin), _vec2d_sub(curr, begin)) ** 2 / _vec2d_dist(begin, end)) distSq.append(tmp) maxdist = max(distSq) if maxdist < dist ** 2: return [begin, end] pos = distSq.index(maxdist) return (ramerdouglas(line[:pos + 2], dist) + ramerdouglas(line[pos + 1:], dist)[1:])
Back to the main function
Now let’s continue with the drawCurve function and use the the Ramer Douglas algorithm to simplify the reflection curve.
# just after the rawValues.reverse() # Reduce curve points myline = zip(rawValues, fresnelList) precisionVals = [0.00005,0.0001,0.0002,0.0003] simplified =  for i in precisionVals: if len(simplified) == 0 or len(simplified) > 50: simplified = ramerdouglas(myline, dist = i)
Here we zip the position(rawValues) and reflection value(fresnelList) in order to create a list of tuples, where each tuple is a 2D coordinate of the curve. Next we calculate the new simplified curve using 4 precision values until we get less than 50 points. You could probably experiment with this and even reduce more points. From the tests that I did it was working fine for all type of fresnel curves (simple/complex). We will deal with the interpolation later.
# Remove default values cmds.removeMultiInstance(remapNode+'.value', b=1) cmds.removeMultiInstance(remapNode+'.value', b=1) # Draw curve on remapValue Editor for i in simplified: currentSize = cmds.getAttr(remapNode +'.value',size=1) # First and last values with Linear interpolation if simplified.index(i) == 0 or simplified.index(i) == len(simplified)-1: cmds.setAttr( remapNode+'.value['+str( currentSize+1 )+']', i,i,1, type="double3") # Others with Spline interpolation else: cmds.setAttr( remapNode+'.value['+str( currentSize+1 )+']', i,i,3, type="double3") else: cmds.warning('N value must be greater than 0!')
Finally we create the fresnel curves. Start by removing the default values and we can then create the curve in the gradientEditor of the remapValue node feeding the position i and the value i. Here we also deal with interpolation, I found that the spline was the best but keeping the first and last point with linear would avoid any strange spline interpolation behavior. Hopefully the comments on the code are easy to understand what part of the code I am referring to.
Putting it all together
The script we just created creates an individual remapValue node for the given n and k values. For convenience I also build an user-friendly UI for metals where it automatically maps the remapValue nodes to your shader and has some presets. The function for the “triple” approach is the same but just doing it in a loop of 3 remapValue nodes for red, green and blue. You can easily add presets editing the code, and also create some if statements for any other type of shader besides the aiStandard, vrayMtl and mia material for instance.
There you have the script, play with it and customize it to fit your own workflows. This won’t make you perfect shaders, it’s just another tool that you have at your disposal. Give me your feedback in the comments below and if you have any suggestion about the code please let me know, I am not by any means a python expert, just a lot of stackoverflows