Starting from Maya 2017, the Maya team introduced a great improvement to the XGen feature, called “Interactive hair and fur grooming with XGen”. The improvement provides a full range of tools, including sculpting brushes, modifiers, and sculpting layers, dedicated to creating all styles of hair and fur.
Interactive grooming descriptions and modifiers are Maya-based nodes, and therefore they can be manipulated in the Node Editor. The cool thing is that these nodes are computed on your system's GPU instead of CPU, so your brush stokes can appear in real-time, providing you an interactive workflow that does not require preview generation. All of the interactive groom hair data is saved to Maya scene files without using any additional Ptex or XPD sidecar files. Also, you can save your grooms to Alembic-based cache files.
One thing you need to know is XGen Geometry Instancer descriptions (including default spline and groomable spline descriptions) are not compatible with interactive grooming tools or modifiers. But, Maya provides you a way to convert default spline descriptions to interactive grooming hairs.
Today, I am not going to talk in detail about this cool feature. If you are interested, please visit the XGen Interactive Grooming Help for more information.
Rather, I want to talk about how to support the XGen Interactive Grooming feature in any third party plug-ins, especially in a renderer like Arnold.
Before we talk about the API, I’d like to mention some MEL commands. The XGen interactive Grooming feature has many useful commands. Our engineering team is currently working on updating the XGen Tech Docs with these new commands. These commands would be quite helpful for TD’s who want to automate their tasks, or customize the pipeline. Our XGen development team is also actively working on them, if you have any problems or requirements, please feel free to contact us.
xgmSplineQuery is an important one, it provides many flags to help you get different values or results. Let’s take a look at them one by one.
· xgmSplineQuery
1. -listSplineDescriptions;
Lists all the descriptions, the result would be: description1 description2.
2. -listSplineDescriptions -long;
Lists the long name of all the descriptions, the result would be: |description1 |description2
3. -isSplineDescription descriptionNode;
Determines if the node is a spline description.
4. -splineCount descriptionNode;
Gets the spline count of the specified description.
5. -videoMemoryDedicated;
Gets the total available GPU memory.
6. -videoMemoryUsed;
Gets the GPU memory used by all the descriptions in scene.
7. -videoMemoryAvailable;
Gets the total GPU memory used by the selected description(s).
· xgmSplineSelect
1. – convertToFreeze
Freezes hairs in the selected description.
2. – replaceBySelectedFaces
Replaces currently bound faces with the selected faces.
· xgmSplineApplyRenderOverride descriptionNode
Lets you override the current hair density being rendered in Viewport with a different density at render time.
· xgmExportSplineDataInternal
Lets you export all the description data, which is stored in the Out Render Data attribute of the xgmSplineDescription node, to a specified file. Use the -output flag as follows:
xgmExportSplineDataInternal -output "D:/XGenAPI/test.txt" description1_Shape.outRenderData;
Now let’s take a look at the XGen API. You can find the SDK in your Maya installation folder. For example, in Maya 2017 this would be: \\Maya2017\plug-ins\xgen.
Here, you can see the header files, lib directories and files, but the most important one for third-party developers is the devkit folder. The devkit contains a couple of samples. Among them, the xgenSplineArnoldExtension, this is the one that I am going to talk about today. It demonstrates how to support the XGen interactive grooming new feature within Arnold renderer.
The new XGen interactive grooming feature also uses splines to represent each hair or fur, and the data for each spline is stored in the Out Render Data attribute of xgmSplineDescription node. You can use the following MEL command output the result.
xgmExportSplineDataInternal -output "D:/XGenAPI/test.txt" description1_Shape.outRenderData
How do you use the API to access the data of XGen interactive grooming feature? In general, there are 3 steps.
Step 1
You need to get the spline data. The XgFnSpline is the class to help you operate on all splines data. It’s located in the header file XgSplineApi.h. Class XgFnSpline can loads the data from xgmSplineDescription.outRenderData plug in Maya. Translators use Maya's MPxData::writeBinary() method to serialize the plug data into a BLOB. XgFnSpline can interpret the data without Maya.
Let’s take a look at the sample code to load the data for XgFnSpline:
MFnDagNode fnDagNode(m_dagPath); // Stream out the spline data std::string data; MPlug outPlug = fnDagNode.findPlug("outRenderData"); MObject outObj = outPlug.asMObject(); MPxData* outData = MFnPluginData(outObj).data(); if (outData) { std::ostringstream opaqueStrm; outData->writeBinary(opaqueStrm); data = opaqueStrm.str(); } // Load the sample for i-th motion step _splines.load(opaqueStrm, sampleSize, sampleTime)
Step 2
After the data is loaded into XgFnSpline object, you need to allocate memory for the data. You need to use XgItSpline to iterate with the XgFnSpline object _splines, which we got in Step 1, and then calculate the number of points, curves and samples. We can then allocate the memory for these buffers depending on your needs.
Here is the sample code to support Arnold renderer. There are some special requirements as mentioned inline. You can also handle that depending on your specific needs.
// Count the number of curves and the number of points // Arnold's b-spline requires two phantom points. unsigned int curveCount = 0; unsigned int pointCount = 0; unsigned int pointInterpoCount = 0; for (XgItSpline splineIt = _splines.iterator(); !splineIt.isDone(); splineIt.next()) { curveCount += splineIt.primitiveCount(); pointCount += splineIt.vertexCount(); pointInterpoCount += splineIt.vertexCount() + splineIt.primitiveCount() * 2; } // Get the number of motion samples const unsigned int steps = _splines.sampleCount(); // Allocate buffers for the curves _numPoints = AiArrayAllocate(curveCount, 1, AI_TYPE_UINT); _points = AiArrayAllocate(pointInterpoCount, steps, AI_TYPE_POINT); _radius = AiArrayAllocate(pointCount, 1, AI_TYPE_FLOAT); _uCoord = AiArrayAllocate(curveCount, 1, AI_TYPE_FLOAT); _vCoord = AiArrayAllocate(curveCount, 1, AI_TYPE_FLOAT); _wCoord = AiArrayAllocate(pointInterpoCount, 1, AI_TYPE_FLOAT); unsigned int* numPoints = reinterpret_cast(_numPoints->data); SgVec3f* points = reinterpret_cast (_points->data); float* radius = reinterpret_cast (_radius->data); float* uCoord = reinterpret_cast (_uCoord->data); float* vCoord = reinterpret_cast (_vCoord->data); float* wCoord = reinterpret_cast (_wCoord->data);
Step 3
Now, we come to the last step, and it’s also the most important one. Here, we will fill in these objects with the correct values we evaluate from XgFnSpline object. XGen provides an iterator called XgItSpline. It’s also located in XgSplineApi.h and provides the functionalities to help access the internal data of each spline. Some important methods are listed as follows, and should provide all the geometry information:
· unsigned int primitiveInfoStride() const;
Stride of the primitive info array,
[0]: Offset
[1]: Length
Offset and length determines the varying attribute location of each vertex in the varying array.
· unsigned int primitiveCount() const;
Return the number of primitives
· unsigned int vertexCount() const;
Return the number of vertices
· SgBox3f boundingBox(int motion = 0) const;
Return the bounding box
· SgBox3f motionBoundingBox() const;
Return the bounding box of motion vectors
· const unsigned int* primitiveInfos() const;
Primitive info. See primitiveInfoStride()
Varying (per-vertex) Attributes
· const SgVec3f* positions(int motion = 0) const;
Vertex positions
· const SgVec2f* texcoords( int motion = 0) const;
Texcoords from root to tip, U is 0.0, V ranges from 0.0 (root vertex) to 1.0 (tip vertex)
· const SgVec2f* patchUVs( int motion = 0) const;
Texcoords of the root vertex on the patch
· const float* width( int motion = 0) const;
Get width
With the information above, you can fetch whatever you need and fill into your data based on your requirement. Let’s take a look at the Arnold sample:
// Fill the array buffers for motion step 0 for (XgItSpline splineIt = _splines.iterator(); !splineIt.isDone(); splineIt.next()) { const unsigned int stride = splineIt.primitiveInfoStride(); const unsigned int primitiveCount = splineIt.primitiveCount(); const unsigned int* primitiveInfos = splineIt.primitiveInfos(); const SgVec3f* positions = splineIt.positions(0); const float* width = splineIt.width(); const SgVec2f* texcoords = splineIt.texcoords(); const SgVec2f* patchUVs = splineIt.patchUVs(); for (unsigned int p = 0; p < primitiveCount; p++) { const unsigned int offset = primitiveInfos[p * stride]; const unsigned int length = primitiveInfos[p * stride + 1]; // Number of points *numPoints++ = length + 2; // Texcoord using the patch UV from the root point *uCoord++ = patchUVs[offset][0]; *vCoord++ = patchUVs[offset][1]; // Add phantom points at the beginning *points++ = phantomFirst(&positions[offset], length); *wCoord++ = phantomFirst(&texcoords[offset], length)[1]; // Copy varying data for (unsigned int i = 0; i < length; i++) { *points++ = positions[offset + i]; *radius++ = width[offset + i] * 0.5f; *wCoord++ = texcoords[offset + i][1]; } // Add phantom points at the end *points++ = phantomLast(&positions[offset], length); *wCoord++ = phantomLast(&texcoords[offset], length)[1]; } // for each primitive } // for each primitive batch // Fill the array buffers for motion step > 0 for (unsigned int key = 1; key < steps; key++) { for (XgItSpline splineIt = _splines.iterator(); !splineIt.isDone(); splineIt.next()) { const unsigned int stride = splineIt.primitiveInfoStride(); const unsigned int primitiveCount = splineIt.primitiveCount(); const unsigned int* primitiveInfos = splineIt.primitiveInfos(); const SgVec3f* positions = splineIt.positions(key); for (unsigned int p = 0; p < primitiveCount; p++) { const unsigned int offset = primitiveInfos[p * stride]; const unsigned int length = primitiveInfos[p * stride + 1]; // Add phantom points at the beginning *points++ = phantomFirst(&positions[offset], length); // Copy varying data for (unsigned int i = 0; i < length; i++) { *points++ = positions[offset + i]; } // Add phantom points at the end *points++ = phantomLast(&positions[offset], length); } // for each primitive } // for each primitive batch } // for each motion step // Set the buffers to the curves node AiNodeSetArray(_curves, "num_points", _numPoints); AiNodeSetArray(_curves, "points", _points); AiNodeSetArray(_curves, "radius", _radius); AiNodeSetArray(_curves, "uparamcoord", _uCoord); AiNodeSetArray(_curves, "vparamcoord", _vCoord); AiNodeSetArray(_curves, "wparamcoord", _wCoord);
That’s how Arnold supports the XGen interactive grooming feature. You can also refer the gist for the core code. For more details, please refer the sample at "Maya 2017 installation folder\plug-ins\xgen\devkit\xgenSplineArnoldExtension\xgenSpline”.
If you have any questions or requests, please feel free to contact me. As I mentioned, our development team is still actively working on the feature, and we are happy to hear any feedback.
Comments