Jump to content

How to create accurate Fresnel Curve (n+k) ?


sebkaine

Recommended Posts

Hi guys ,

 

i am curious on how to replicate this workflow in Houdini :

http://therenderblog.com/custom-fresnel-curves-in-maya/

http://therenderblog.com/custom-fresnel-curves-in-maya-part-2/

 

Basically the guy has created a Python script that draw a float spline that mimic a physical fresnel curves.

 

I would like to know what would be the straightforward way to do this in H.

So basically in your shader your enter N and K you press create and it build the correct ramp ...

 

It sound like a python scripting job does it ?

 

here is the maya cmds code :




"""
---------------
Fresnel Formula
---------------
"""

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



"""
-----------------------
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:])


"""
---------------------------
Draw Fresnel Curve | Single
---------------------------
"""


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 )


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



		# 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!')








def drawCurve2(*args):

	shaders = ['aiStandard','VRayMtl']

	if cmds.nodeType( cmds.ls(sl=1) ) not in shaders :
		cmds.warning('Select an aiStandard or a VRayMtl Material!')
		return False

	aiMetal = cmds.ls(sl=1)[0]

	redValues = [cmds.floatField( 'nValRED' , q=1,v=True),cmds.floatField( 'kValRED' , q=1,v=True)]
	greenValues = [cmds.floatField( 'nValGREEN' , q=1,v=True),cmds.floatField( 'kValGREEN' , q=1,v=True)]
	blueValues = [cmds.floatField( 'nValBLUE' , q=1,v=True),cmds.floatField( 'kValBLUE' , q=1,v=True)]

	allNnK = [redValues,greenValues,blueValues]

	if all(i > 0 for i in redValues) and all(i > 0 for i in greenValues) and all(i > 0 for i in blueValues):
	
		# create aditional nodes
		sInfo   = cmds.shadingNode('samplerInfo',asUtility=1)
		remapNodes = ['RED','GREEN','BLUE']


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

		finalRemapList = []

		for remap in remapNodes:
			remapNode = cmds.shadingNode('remapValue', asUtility=1,n='remap_'+str(remap)+'')
			finalRemapList.append(remapNode)

			fresnelList = IOR( allNnK[remapNodes.index(remap)][0] , allNnK[remapNodes.index(remap)][1] )


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

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

			
			# connect to network
			cmds.connectAttr(sInfo+'.facingRatio', remapNode+'.inputValue',f=1)


		# connect to Material
		if cmds.nodeType( aiMetal ) == 'aiStandard':
			cmds.connectAttr( finalRemapList[0]+'.outValue', aiMetal+'.KsColorR', f=1 )
			cmds.connectAttr( finalRemapList[1]+'.outValue', aiMetal+'.KsColorG', f=1 )
			cmds.connectAttr( finalRemapList[2]+'.outValue', aiMetal+'.KsColorB', f=1 )

		if cmds.nodeType( aiMetal ) == 'VRayMtl':
			cmds.connectAttr( finalRemapList[0]+'.outValue', aiMetal+'.reflectionColorR', f=1 )
			cmds.connectAttr( finalRemapList[1]+'.outValue', aiMetal+'.reflectionColorG', f=1 )
			cmds.connectAttr( finalRemapList[2]+'.outValue', aiMetal+'.reflectionColorB', f=1 )			

		# Affect Diffuse
		if cmds.nodeType( aiMetal ) == 'aiStandard' :

			if cmds.checkBox( 'checkDiff', q=1, v=1 ):
				cmds.setAttr(aiMetal+'.specularFresnel', 1)
				cmds.setAttr(aiMetal+'.Ksn', 1)
				cmds.setAttr(aiMetal+'.FresnelAffectDiff', 1)



	else:
		cmds.warning('N and K values must be greater than 0!')

	



def presetFresnel(*args):

	presets = [ ('Default',0,0,0,0,0,0),
				('Aluminium',1.55803,7.7124,0.84921,6.1648,0.72122,5.7556),
				('Gold',0.17009,3.1421,0.7062,2.0307,1.26175,1.8014),
				('Copper',0.21845,3.6370,1.12497,2.5834,1.15106,2.4926),
				('Silver',0.13928,4.1285,0.13,3.0094,0.13329,2.7028),
				('Chromium',3.1044,3.3274,2.85932,3.3221,2.54232,3.2521),
				('Platinum',2.37387,4.2454,2.00768,3.5017,1.90571,3.2893),
				('Nickel',2.01294,3.8059,1.69725,3.0186,1.65785,2.7993)
			  ]

	selected = cmds.optionMenu( 'opmenuIor',q=1, sl=1 )-1

	cmds.floatField( 'nValRED',e=1,v=presets[selected][1] )
	cmds.floatField( 'kValRED',e=1,v=presets[selected][2] )
	cmds.floatField( 'nValGREEN',e=1,v=presets[selected][3] )
	cmds.floatField( 'kValGREEN',e=1,v=presets[selected][4] )
	cmds.floatField( 'nValBLUE',e=1,v=presets[selected][5] )
	cmds.floatField( 'kValBLUE',e=1,v=presets[selected][6] )

Thanks for your time

 

Cheers

 

E

Edited by sebkaine
  • Like 1
Link to comment
Share on other sites

yes i think you're right miles, we don't need the extra step of generating the float ramp, implement the formula in VEX directly for metal would be the easiest way i guess ...

 

i'm gonna try to investigate the vex path. Do you know where i can find the original math formula that define ior from N and K ?

 

Thanks for your help !

Edited by sebkaine
Link to comment
Share on other sites

Excuse my ignorance but do you know how the native fresnel function approaches this curve or am I mixing multiple things together? I tried looking in the include files where most functions are defined but couldn't find it.

Does this also mean that whenever you start to make more complex materials that require such a custom curve the IOR value becomes useless and you have to fall back to a custom fresnel solution like the one posted here?

Link to comment
Share on other sites

well basically ior is define by this formula  ior = n + ki.

 

in 99% of render software we use ior = n and we omit ki.

 

for exemple for water ior = n = 1.333.

 

In the formula ior = n + ki

n is call the real part of the ior and it define how the medium modify the speed of incoming light ray and thus how the ray is bend when the ray is refracted.

k is call the imaginary or complex part  of the ior it only define how the medium aborb the light, and thus it is only useful for medium with high absorption

 

in real life we don't care about k , except for metal where having an ior that use n and k will give the most accurate result.

this site give you all the physical value of N and K for each Metal

http://refractiveindex.info/

 

and you just have to enter those value to get the exact fresnel curve to get accurate reflexion.

this concept of using a physical IOR with  n and K is a Maxwell concept , it was recently copy by RIS with their metal shader.

http://support.nextlimit.com/display/mxdocsv3/Index+of+Refraction+-+ND+and+K

https://renderman.pixar.com/resources/current/RenderMan/PxrLMMetal.html

 

Hope it clarify things a little !

 

Cheers

 

E

Edited by sebkaine
  • Like 1
Link to comment
Share on other sites

well basically ior is define by this formula  ior = n + ki.

 

in 99% of render software we use ior = n and we omit ki.

 

for exemple for water ior = n = 1.333.

 

In the formula ior = n + ki

n is call the real part of the ior and it define how the medium modify the speed of incoming light ray and thus how the ray is bend when the ray is refracted.

k is call the imaginary or complex part  of the ior it only define how the medium aborb the light, and thus it is only useful for medium with high absorption

 

in real life we don't care about k , except for metal where having an ior that use n and k will give the most accurate result.

this site give you all the physical value of N and K for each Metal

http://refractiveindex.info/

 

and you just have to enter those value to get the exact fresnel curve to get accurate reflexion.

this concept of using a physical IOR with  n and K is a Maxwell concept , it was recently copy by RIS with their metal shader.

http://support.nextlimit.com/display/mxdocsv3/Index+of+Refraction+-+ND+and+K

https://renderman.pixar.com/resources/current/RenderMan/PxrLMMetal.html

 

Hope it clarify things a little !

 

Cheers

 

E

 

Thanks for your explanation!

 

So does that mean that the default mantra surface shader is actually bad to use for metals and you should use some sort of custom fresnel function? Or does houdini already have this functionality hidden away somewhere?

I know there is a disney BRDF (or BSDF) in the BDSF bonanza thread here that also abandons the IOR values and just uses a constant value for F0. Does that mean they have some curve that fits most purposes baked in there?

 

In the PBR texture guide from substance designer (https://www.allegorithmic.com/pbr-guide) they seem to take IOR into account but just to get the base reflective value. Is this value (this is F0 right?) what you would normally put into the reflection intensity in the mantra shader?

 

Sorry for the large amount of questions, I find this quite interesting but I can't get it to "click" yet inside my head ;)

Link to comment
Share on other sites

well there is not one way of doing things the tools you enumarate have a different approach.

 

i don't like the mantra shader if find it

- overly complex

- very rigid

- messy

- confusing

But it's mainly a matter of taste. :)

 

But you can definitly build metal with it. when you just have an ior with only n you can simulate k by using an ior with very high value beetween 25 and 75 with physical fresnel on.

 

Complex ior define with n and k is not handle by default in H , or i have miss something strategic during my research !

 

Well for the intensity i use to balance thing only by using the IOR and the reflection color , and don't like the multiplicator on diffuse / reflection / refraction, because to respect energy conservation,

some operation are done in your back to correct the mistake you could have done. i prefer a shader that would prevent the user from making unrealistic choice :)

Edited by sebkaine
Link to comment
Share on other sites

  • 2 weeks later...

I have an implementation of the complex Fresnel in PhyShader. It's just a straightforward conversion of the Fresnel formula.

// n = etai/etat
// k - extinction coefficient
float
cfresnel(vector v, normal;
float n, k)
{
float u = dot(v, normal);
float n2 = n * n;
float k2 = k * k;
float u2 = u * u;
float nk4 = 4. * n2 * k2;
float n2minusk2 = n2 - k2;
float u2minus1 = u2 - 1.;
float tmp01 = n2minusk2 + u2minus1;
float tmp2 = tmp01 * tmp01;
float tmp2nk4 = tmp2 + nk4;
float tmp3 = tmp2nk4 + n2minusk2 + u2minus1;
float a2 = sqrt(tmp3) / 2.;
float tmp4 = tmp2nk4 - n2 + k2 - u2 + 1.;
float b2 = sqrt(tmp4) / 2.;
float a = sqrt(a2);
float aminusu = a - u;
float aplusu = a + u;
float F1 = (aminusu*aminusu + b2) / (aplusu*aplusu + b2) / 2.;
float u1 = 1. / u;
float aminusu1 = aminusu + u1;
float aplusu1 = aplusu - u1;
float F2 = (aplusu1*aplusu1 + b2) / (aminusu1*aminusu1 + b2) + 1.;
return F1 * F2;
}

 

I didn't any research on spectral version so far, but I think it's possible to make efficient solution based on measured data.

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...