Fresnel Schlick’s approximation in Maya

fresnel_schlick_approximation

So recently I had the need to convert some Arnold materials to Vray. The aiStandard uses the Schlick’s approximation under the “reflectance at normal” attribute, similar to the Mental ray’s brdf control, the difference is that in Arnold you only have control over the 0 degree reflection, not 90 degrees slider or brdf curve control like in the Mia material. But, VrayMtl doesn’t have any other control for reflection falloff other than based on IOR, so having the background on the Fresnel formula implementation with remapValue node and sampler info, the Schlick’s approximation should be easy enough to implement and attach to a VrayMtl. In this post I am going to share with you the Schlick approximation formula in python and how to get around some issues.

Schlick’s Formula

The first step is to find the Schlick’s approximation formula and convert it to python. So the formula looks like this:

schlick formula

Right, so if you follow along the previous posts about Fresnel you have seen already that this will be really easy to implement, we already have the maths in python for the incident angles in radians, and all the other parts are just simple numbers, like the reflectance at normal incident that we want to input.

Translating the formula to Python

def SchlickAprox(reflt0):

	reflt0 = reflt0

	theta_deg = 0

	RefltResult = []

	while theta_deg <= 90:
		theta = math.radians(theta_deg)

		refVal = reflt0 + (float(1-reflt0)) * (1-math.cos(theta))**5

		RefltResult.append( round(refVal,6) )

		theta_deg += 1

	return RefltResult

So, we use the same approach of the complex IOR formula, this function will output the values into an array, from 0 to 90 degrees. The argument reflt0 is where we will input later the desired reflectance at normal.

VRay with Schlick Fresnel

The main reason I dived into the Schlick thing is that I wanted to convert some Arnold materials into VRayMtl’s. So, just like in the previous posts about Fresnel we need a function to draw the curve for us into the remapValue node and use a samplerInfo node to input the facing ratio.And again we use the Ramer-Douglas-Peucker algorithm to reduce the curve points. So here is the draw function for the Schlick’s curves:

def drawSchlick(normalReflt,VrayMtlNode):

	ref0Val = normalReflt

	# create remapValue node
	remapNode = cmds.shadingNode('remapValue',asUtility=1)
	schlickSInfo   = cmds.shadingNode('samplerInfo',asUtility=1)

	cmds.connectAttr(schlickSInfo+'.facingRatio', remapNode+'.inputValue',f=1)

	# Material connection

	cmds.connectAttr(remapNode+'.outValue', VrayMtlNode+'.reflectionColorAmount')

	# Calculate Fresnel Curve
	SchlickList = SchlickAprox(ref0Val)

	# 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()

	# Reduce curve points
	myline = zip(rawValues, SchlickList)
	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)

	# 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")

So, after this I started working on a Material Converter between Arnold and Vray, and this is the part where I am dealing with Arnold’s Schlick Fresnel:

			#Schlick fresnel
			else:
				print 'Schlick Fresnel'

				normalReflt = cmds.getAttr(Material[0]+'.Ksn')
				drawSchlick(normalReflt,VrayMtlNode)

				# reflections not traced under certain threshold (no reflection issue)
				cmds.setAttr(VrayMtlNode + '.cutoffThreshold', 0.009)

				cmds.setAttr(VrayMtlNode+'.useFresnel',0)

			cmds.select(ObjsWithMat)
			cmds.hyperShade( assign=VrayMtlNode )
			cmds.select(clear=1)

So, in this part of the Material Converter script I call the drawSchlick function with the input being the Arnold equivalent material.

VRay reflection threshold

When I was doing some tests on some dummy objects I run into this “strange” issue:

vray cutoff threshold

Both the dummy object and the Fresnel “measure” scene were giving me dark spots, I had no idea where that issue was coming from but after playing around with the VRayMtl I found that reducing the cuttoff threshold under the options to 0.009 (default is 0.01) would solve the issue. That’s why in the last code block I change that attribute on line 9.

Conclusion

So, there you have Schlick’s approximation in python that you can now use for any render engine that supports maya’s remapValue node, even on other 3D applications. Ok, I think I will give Fresnel some rest, hopefully we both learned something from this. Post you comments and let me know if you have a more simple approach to this, or maybe some hidden VRay feature to enable Schlick Fresnel? Thanks for reading.

Online Reference

Memo on Fresnel equations
Fresnel to Schlick approximation network
  • https://www.artstation.com/artist/joecyriac Joe Cyriac

    Hi, thanks for posting this. So do you run this script after picking one of the materials from the drop down menu of your previous script? Would this make adjusting the Fresnel curve easier?