Tuesday, 11 April 2017

Texture Based Deformer

I recently thought about Maya's in built texture deformation tools and became interested in having a stab at making a deformer that could produce the equivalent of 'Texture To Geometry' and 'Displacement To Polygons' but in real time.
Having created a number of deformers in the past I was aware of some of the required pre requisites. However one thing that was a mystery was how I would sample colours from a UV position using the API.Some digging around suggested three options that might be suitable.
The first is MImage::readFromTextureNode. This method can be called to pull an image in ready to have pixels read. However MImage deals predominantly with specific image files. I wanted to base this deformer around a procedural texture for easy customisation.
The second, MRenderUtil::sampleShadingNetwork initially looked promising but after further investigation I was concerned that it would update very slowly as it took such things as  shading and lighting into account.
The third was MDynamicsUtil::evalDynamics2dTexture. This proved the most likely candidate, especially due to its compatibility with procedural textures so I set about building a basic one shot Python implementation to test the logic. I have included it in this post so others can try it out.
With a few fits and starts I succeeded in creating a solution that I deemed worthy to convert into a C++ node. The speed increase was phenomenal especially as we are dealing with potentially vast numbers of vertices. The deformer is able to hit over 300000 triangles on my machine without to much of an issue. In the included video I show it performing deformation on a mesh a little under 200000 triangles.

Texture Deformer from SBGrover on Vimeo.

The python code below assumes you have created a sphere ('pSphere1'), added some kind of procedural texture via a lambert ('lambert2') and is using uv's from 'map1'.

   
 import maya.OpenMaya as om  
 import maya.OpenMayaFX as omfx  
   
 obj_sel = om.MSelectionList()  
 obj_sel.add('pSphere1')  
 obj_dag = om.MDagPath()  
 obj_sel.getDagPath(0, obj_dag)  
 itr = om.MItMeshVertex(obj_dag)  
   
 length = itr.count()  
 scaler = 1  
   
 #declare arrays  
 vtx_pos_array = om.MPointArray()  
 vtx_nor_array = om.MVectorArray()  
 uv_array = om.MIntArray()  
 uColArray = om.MDoubleArray()  
 vColArray = om.MDoubleArray()  
   
 #set array lengths  
 vtx_pos_array.setLength(length)  
 vtx_nor_array.setLength(length)  
 uColArray.setLength(length)  
 vColArray.setLength(length)  
   
 #declare vars for itr  
 global_count = 0  
 v_pos = om.MPoint()  
 n_vec = om.MVector()  
   
 #horrible MScriptUtil shenanigans  
 uv_list = [0, 0]  
 uv_util = om.MScriptUtil()  
 uv_util.createFromList(uv_list, 2)  
 uv = uv_util.asFloat2Ptr()  
   
 #iterate to get uv positions  
 while not itr.isDone():  
      itr.getUV(uv, 'map1')  
      v_pos =      itr.position(om.MSpace.kWorld)  
      vtx_pos_array.set(v_pos, global_count)  
      itr.getNormal(n_vec, om.MSpace.kWorld)  
      vtx_nor_array.set(n_vec, global_count)       
      u = uv_util.getFloat2ArrayItem(uv, 0, 0)  
      v = uv_util.getFloat2ArrayItem(uv, 0, 1)       
      uColArray.set(u, global_count)  
      vColArray.set(v, global_count)  
      global_count += 1  
      itr.next()  
   
 #get color attribute from node        
 imgObj = om.MObject()  
 sel = om.MSelectionList()  
 om.MGlobal.getSelectionListByName('lambert2', sel)  
 sel.getDependNode(0, imgObj)  
 fnThisNode = om.MFnDependencyNode(imgObj)  
 attr = fnThisNode.attribute( "color" )  
   
 #set up output arrays for avalDynamics2dTexture  
 outColours = om.MVectorArray()  
 outAlphas = om.MDoubleArray()  
   
 #do it!  
 omfx.MDynamicsUtil.evalDynamics2dTexture(imgObj, attr, uColArray, vColArray, outColours, outAlphas)  
        
 itr.reset()  
 global_count = 0  
   
 #iterate over the mesh to deform it  
 while not itr.isDone():  
      point = vtx_pos_array[global_count]  
      normal = vtx_nor_array[global_count]  
      colour = outColours[global_count]  
      col_avg = ((colour.x + colour.y + colour.z) /3)  
      pos = om.MPoint(point.x + (normal.x * col_avg * scaler), point.y + (normal.y * col_avg * scaler), point.z + (normal.z * col_avg * scaler))  
      itr.setPosition(pos, om.MSpace.kWorld)  
      global_count += 1  
      itr.next()       

0 comments:

Post a Comment