Custom Fresnel curves in Maya – Part 2

custom_fresnel_maya_python_part2

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

Fresnel Script

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[0] - p2[0])**2 + (p1[1] - p2[1])**2

def _vec2d_sub(p1, p2):
	return (p1[0]-p2[0], p1[1]-p2[1])

def _vec2d_mult(p1, p2):
	return p1[0]*p2[0] + p1[1]*p2[1]

def ramerdouglas(line, dist):

	if len(line) < 3:
		return line

	(begin, end) = (line[0], line[-1]) if line[0] != line[-1] else (line[0], 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[0]', b=1)
		cmds.removeMultiInstance(remapNode+'.value[1]', 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[0],i[1],1, type="double3")
			# Others with Spline interpolation
			else:
				cmds.setAttr( remapNode+'.value['+str( currentSize+1 )+']', i[0],i[1],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[0] and the value i[1]. 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

fresnel python ui

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 :D

External Links

Creating fresnel curves in maya – Part 1
Ramer Douglas Peucker algorithm
Download the scripts
  • Roman Lapaev

    super awesome, works really well too, it’s basically like having a curve in 3ds max :) )

    i wonder if you have any idea of how i can use RGB remap values and still have a texture map going to color in-between nodes?

    • therenderblog

      For simple ior’s you could map the remapValue node to the reflection ammount instead of the color. For complex/metals you can use only one of the remapValue nodes in the reflection ammount and map the reflection color with your texture. For colored metals you just need to somehow give the “true color” in the texture, gold for instance. But for things like aluminium and chrome, since there’s so little difference in the curves you can for sure use only 1 remap node, the one that has the higher reflection values. There’s some moving parts around this, not the perfect workflow.

      Not sure if this was what you were asking for. Let me know if it helps. Cheers

      • Roman Lapaev

        yeah figuring out how to drive texture wih 3 curves is a littlebit overkill, basically what i was thinking if there is way to drive texture hue/gamma with those 3 curves to “colorize it”, but pluging 1 curve plugging it into amount is more than enough!
        very very cool stuff, thank you for your effor sir!

  • Henrik D’oark

    Hey Man, I’m really happy, thrilling and forever grateful for you written/created this curve script for Maya! I’ve been suffering a long time trying to create those curves on hand at Maya…

    But your attitude is light at the end of tunnel for me. There is a donation button on this site? I need to reciprocate that feeling of joy with you.

    Man, I just can not sleep anymore! I need to play with it.
    Thank you very much!

    • therenderblog

      Thanks for that Henrik. :)

      Let me know later your experiments and if there’s something we can improve. If you want to learn a bit of python and support the website, I have a premium tutorial on how to create a viewportrender in maya with python. http://therenderblog.com/maya-viewport-render/

      Cheers

  • Sash

    One question. How to install it? I mean which folder i need to copy those PY files? Thanks!

    • therenderblog

      Just unzip the downloaded file and place the 3 scripts in your maya scrips folder, something like “C:UsersadminDocumentsmayascripts“. Place the icons inside your icons folder “C:UsersadminDocumentsmaya2014-x64prefsicons” .

  • Pingback: Maya Custom Tools Script Adds Custom Fresnel Curves | LESTERBANKS

  • http://jdbeals.com/ thebeals

    Thank you so much for this script. I have been learning a ton by going through the script this morning. It has been incredibly informative. One question for you though, how did you come to the N & K values for the rgb components in your metals presets? I’m having a hard time trying to match your values when I go to refractiveindex.info and put in the different wavelengths for each rgb component. Trying to figure out what I am doing wrong :) Thanks again!

    • therenderblog

      I use refractiveindex.info/legacy. Have a look at the previous post where I talk about that.

  • crltt

    mate, I will repeat myself again, you’re simply brilliant! I’ve bought the viewport render tutorial cause your work need to be supported and cause now I really would love to know more about python. The tutorial is obviously amazing as well, and combined with therenderblogtools that you give for free it change completely my lookdev pipe in maya. let me just bother you with a fast question, can you suggest an introduction book/blog/tutorial about python and maya? Thanks for everything

    • therenderblog

      Thanks for that, glad this is motivating you to learn more. About your question, this is where I started:

      https://vimeo.com/42848594

      Free and great course. Next start to experiment to build your own tools and when you have a question or something search, stackoverflow.com is your best friend ;) Enjoy

  • Janosch

    Great post, thanks for rounding up this concept and sharing your experience. I definitly will read your script and learn from it! I’m looking forward for more discussions like this.

  • therenderblog

    Thanks Grant, I think I missed your comment in the moderation panel, sorry about that. Actually you just remembered me an old maya topic, but back then I haven’t met python yet :D So yeh, after your course many people started to ask about it again, so I had some fun putting this together ;) btw, there’s also a C4D fresnel script out there on lesterbanks.

    Thank you for sharing therenderblog with your subscribers. keep up the amazing work :)

  • Ron Besseling

    i have bin struggeling for hours with this script to get it working with vray en the presets.. i really hope someone can help me , i am sure if you are a little more expirence with scripting its done in a few minutes.. every time i got an unexpect indent # or can`t find mtoa.utils,,, please help me with “converting” de Custom Fresnel tool so its working with de presets and vraymlt… Thanks in advantage a thousand times.

  • Jorge Miranda

    How can I install the custom fresnel tool only ? I dont have arnold and give errors ;S your blog rocks by the way !

  • http://www.pixel-reality.com/ Dennis Persson

    Hi, i’m trying to install this on maya 2015 – i do not have Arnold, but i want to use the custom curves with my Vray shaders, is it possible to install it and get it to work? – if you like – email me at – dennis(at)pixel-reality.com

    thanks!

  • therenderblog

    Hey guys, I will be updating the code to work with vray shaders too.

    • Ashton Woolley

      Hello!

      Great Tutorial!

      I was wondering if you have updated the code to work with Vray shaders yet? Or if you know the steps so I can do it manually that would be great as well!

      Thanks for your help!

    • therenderblog

      Sorry for the waiting guys, I just updated the script, redownload from creativecrash and now should work with vrayMtl. Thank you

      • crltt

        thanksthanksthanksthanksthanks

  • Gianluigi Bevilacqua

    This is awesome!!!

  • Florian von Behr

    Great tutorail. Thanks.
    Does anyone know how hard it would be to turn this into an OSL Shader for use with VRay 3.0?

    Cheers, Florian

  • gaia

    Great job , but the script does not work for me. I guess arnold must be installed to work because the error message is as follows:

    line 2 : No module named mtoa.utils #

    For Vray users , it can not work if arnold is not installed.

    it would be great to make a tool for vray that is not related to the script for arnold .

    in this regard, for wider use , it is possible that this to work with any input color ? ( carpaint color reflection , sss , maya blinn , etc …) .

    sorry if this is asking a lot , but I think that after such a very good job , it would be a shame to not use the tool thoroughly compatible with everything.

    thanks

    sorry for my approximative english.

  • gaia

    For works without install arnold for use fresnel curves for vray, remove line 2 import mtoa.utils in customTools_therenderblog.py

  • Biagio Del Sorbo

    Hello.

    First I want to thank you for writing these articles enlightening and very accurate, are helping me to improve my shader and consequently my render, my only question is the difference between the curves that are created by the your script and those that calculation of http://refractiveindex.info/; I’ll explain.

    Inserting in your script the values of “N” and “K” taken from the site, the curves that the script goes to create me inside the Remmap’s node seem reversed from those that returns the site.

    For exsemple, if I try to create a material that simulates the gold, the result does not look anywhere near to the gold. But if I reverse the direction of the curves of the three remap manually, the result is much closer to gold, so I want to understand where I’m wrong, perhaps inserting the values of “N” and “K”, or somewhere else.
    I hope I was clear;
    I am attaching a link to a picture to show you the difference between the curves generated by the site and those generated by the script.

    http://dl.dropboxusercontent.com/u/26882987/Comparative_script_therenderblog.jpg

    This is my mail biagiodelsorbo@gmail.com.

    Thank you very much
    Biagio Del Sorbo

    • therenderblog

      The curves are reversed on the remapNode on purpose, if you try to create a falloff curve manually you will see that the remapNode needs to be reversed. You can input a reverse node in between to have the visual feedback as the website, but there is no need for that.

  • bonespro

    When working in a linear workflow, do I have to add a gamma correct node after the remap nodes?

  • Marko Stravamir

    I will just repeat what other have said: Wow, you’re a genius! thank you so much.
    And will like to propose that we find someone who can make an arnold shader which will have inputs N,k and outputs values for reflection just like your setup. That sounds like a best solution right now. Who could do that? Lets lobby :D . I’m serious. I read somewhere that this year solidangle will be working on updating shaders in arnold. Can’t wait…

    Great work, thank you.

  • Sky

    Hello guys, it is really really inspiring article, your work is
    really admirable, I was thinking about these things a lot past few
    weeks. But few questions come to my mind…

    On sites such as
    refractiveindex.info, you have an option, at which wavelength you want
    the IOR curves of the material, different wavelengths give you different
    IOR curves, so what wavelength should I choose when creating a shader?

    And
    second question which comes to my mind is… When you create physically
    accurate (for example) gold, with physically accurate IoR curves…
    Well, they all start and end in nearly same place, they are only offset a
    bit in the centre… And we use this curves to drive our specular
    color… We get material with nearly pure white speculars such as
    chrome. Im not sure if im right but I think that metals dont have
    diffuse color, they only color the reflections, so… Why when we use
    physically correct IoRs we dont get gold with “goldish” color but…
    “white chrome”…? We cant control the specular color anymore, since we
    input our curves here… And if we dont want to destroy physicall
    correctness, we set our diffuse color to be black. So what is the
    solution to get “goldish color of gold” while maintaining physicall
    correctness?

    Sorry if my questions are confusing, Im confused A
    LOT by these things, have a nice day and thank you very much for
    potential explanation :)