sebkaine Posted July 9, 2015 Share Posted July 9, 2015 (edited) 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 July 9, 2015 by sebkaine 1 Quote Link to comment Share on other sites More sharing options...
fathom Posted July 9, 2015 Share Posted July 9, 2015 if you're talking about having a fresnel that includes the complex component, it wouldn't be hard to code in vex. might be slowish, tho. not sure. Quote Link to comment Share on other sites More sharing options...
sebkaine Posted July 9, 2015 Author Share Posted July 9, 2015 (edited) 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 July 9, 2015 by sebkaine Quote Link to comment Share on other sites More sharing options...
sebkaine Posted July 9, 2015 Author Share Posted July 9, 2015 (edited) well i have make a quick test , and it does look to work fine ! i have try a quick gold shader and result are promising ! test_gold.hipnc Edited July 9, 2015 by sebkaine 1 Quote Link to comment Share on other sites More sharing options...
mestela Posted July 10, 2015 Share Posted July 10, 2015 Maybe this might be a better thing to implement? I've heard good things. http://jcgt.org/published/0003/04/03/ 2 Quote Link to comment Share on other sites More sharing options...
sebkaine Posted July 10, 2015 Author Share Posted July 10, 2015 thanks for the link Matt looks very interesting ! i will repost a new hip cause i was not using the correct angle value in the previous scene. i get pretty good stuff now and it looks quite fast. Quote Link to comment Share on other sites More sharing options...
sebkaine Posted July 11, 2015 Author Share Posted July 11, 2015 (edited) Here is a second test for gold it start to be pretty close to Maxwell ! I have corrected the facing ratio that was wrong in the previous version. physical_gold.hip Edited July 11, 2015 by sebkaine 2 Quote Link to comment Share on other sites More sharing options...
Jason Posted July 11, 2015 Share Posted July 11, 2015 Looking good sebkaine! Interesting thread, and well done. Quote Link to comment Share on other sites More sharing options...
Robert Posted July 15, 2015 Share Posted July 15, 2015 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? Quote Link to comment Share on other sites More sharing options...
sebkaine Posted July 15, 2015 Author Share Posted July 15, 2015 (edited) 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 July 15, 2015 by sebkaine 1 Quote Link to comment Share on other sites More sharing options...
Robert Posted July 15, 2015 Share Posted July 15, 2015 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 Quote Link to comment Share on other sites More sharing options...
sebkaine Posted July 15, 2015 Author Share Posted July 15, 2015 (edited) 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 July 15, 2015 by sebkaine Quote Link to comment Share on other sites More sharing options...
rsd Posted July 27, 2015 Share Posted July 27, 2015 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. 1 Quote Link to comment Share on other sites More sharing options...
sebkaine Posted July 27, 2015 Author Share Posted July 27, 2015 Thanks for sharing your code Roman ! Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.