The shader fragment feature was introduced into Maya a few years ago and it is very useful for creating and extending shader features of Maya. Shader fragments can be used for creating input or output for existing hardware shaders. I am going explain how it works with two samples in our devkit.
The first sample is VP2BlinnShader and it uses blinn stock shader as hardware shader. Another sample is fileTexture and it uses shader fragment to implement a fileTexture node.
Our goal is to create a VP2BlinnTextureShader that accepts an image as input for the blinn stock shader.
We can use MShaderInstance::addInputFragment and MShaderInstnace::addOutputFragment to add an input or output shader fragment.
First, we need to get parameters of blinn stock shader to find the the parameter we need. We can use MShaderInstance::parameterList and MShaderInstance::parameterType to get parameters and their types. To add a texture for blinn stock shader, diffuseColor is the one we need. It is float4. So, we are going to add our fragment shader as input of diffuseColor.
Then, we are going to create a fragment shader. We can reuse the shader fragment in the fileTexture
void initializeFragmentShaders() { static const MString sFragmentName("fileTexturePluginFragment"); static const char* sFragmentBody = "<fragment uiName=\"fileTexturePluginFragment\" name=\"fileTexturePluginFragment\" type=\"plumbing\" class=\"ShadeFragment\" version=\"1.0\">" " <description><![CDATA[Simple file texture fragment]]></description>" " <properties>" " <float2 name=\"uvCoord\" semantic=\"mayaUvCoordSemantic\" flags=\"varyingInputParam\" />" " <texture2 name=\"map\" />" " <sampler name=\"textureSampler\" />" " </properties>" " <values>" " </values>" " <outputs>" " <float4 name=\"output\" />" " </outputs>" " <implementation>" " <implementation render=\"OGSRenderer\" language=\"Cg\" lang_version=\"2.100000\">" " <function_name val=\"fileTexturePluginFragment\" />" " <source><![CDATA[" "float4 fileTexturePluginFragment(float2 uv, texture2D map, sampler2D mapSampler) \n" "{ \n" " uv -= floor(uv); \n" " uv.y = 1.0f - uv.y; \n" " float4 color = tex2D(mapSampler, uv); \n" " return color.rgba; \n" "} \n]]>" " </source>" " </implementation>" " <implementation render=\"OGSRenderer\" language=\"HLSL\" lang_version=\"11.000000\">" " <function_name val=\"fileTexturePluginFragment\" />" " <source><![CDATA[" "float4 fileTexturePluginFragment(float2 uv, Texture2D map, sampler mapSampler) \n" "{ \n" " uv -= floor(uv); \n" " uv.y = 1.0f - uv.y; \n" " float4 color = map.Sample(mapSampler, uv); \n" " return color.rgba; \n" "} \n]]>" " </source>" " </implementation>" " <implementation render=\"OGSRenderer\" language=\"GLSL\" lang_version=\"3.0\">" " <function_name val=\"fileTexturePluginFragment\" />" " <source><![CDATA[" "float4 fileTexturePluginFragment(vec2 uv, sampler2D mapSampler) \n" "{ \n" " uv -= floor(uv); \n" " uv.y = 1.0f - uv.y; \n" " vec4 color = texture(mapSampler, uv); \n" " return color.rgba; \n" "} \n]]>" " </source>" " </implementation>" " </implementation>" "</fragment>"; // Register fragments with the manager if needed // MHWRender::MRenderer* theRenderer = MHWRender::MRenderer::theRenderer(); if (theRenderer) { MHWRender::MFragmentManager* fragmentMgr = theRenderer->getFragmentManager(); if (fragmentMgr) { // Add fragments if needed bool fragAdded = fragmentMgr->hasFragment(sFragmentName); if (!fragAdded) { fragAdded = (sFragmentName == fragmentMgr->addShadeFragmentFromBuffer(sFragmentBody, false)); } if (fragAdded) { fFragmentName = sFragmentName; TRACE_API_CALLS("Added Fragment"); } } } }
The first two lines are creating a shader fragment called fileTexturePluginFragment, and defining the body of the fragment. In the fragment body, we define it as a ShadeFragment with three input parameters: uvCoords of float2, texture of map and textureSampler of sampler. diffuseColor is float4 parameter, so we define the output of our fragment as float4.
"<fragment uiName=\"fileTexturePluginFragment\" name=\"fileTexturePluginFragment\" type=\"plumbing\" class=\"ShadeFragment\" version=\"1.0\">" " <description><![CDATA[Simple file texture fragment]]></description>" " <properties>" " <float2 name=\"uvCoord\" semantic=\"mayaUvCoordSemantic\" flags=\"varyingInputParam\" />" " <texture2 name=\"map\" />" " <sampler name=\"textureSampler\" />" " </properties>" " <values>" " </values>" " <outputs>" " <float4 name=\"output\" />" " </outputs>"
For uvCoords, we can let Maya provide the system parameter as the input value. We can define it as mayaUvCoordSemantic and varyingInputParam. For more details about Maya semantics, please check "Varying Parameters and System Parameters" section in this document.
Then we are going to implement the shader using a different API. We can provide the same shader in different shader languages like GLSL, HLSL, Cg.
" <implementation render=\"OGSRenderer\" language=\"HLSL\" lang_version=\"11.000000\">" " <function_name val=\"fileTexturePluginFragment\" />" " <source><![CDATA[" "float4 fileTexturePluginFragment(float2 uv, Texture2D map, sampler mapSampler) \n" "{ \n" " uv -= floor(uv); \n" " uv.y = 1.0f - uv.y; \n" " float4 color = map.Sample(mapSampler, uv); \n" " return color.rgba; \n" "} \n]]>" " </source>" " </implementation>"
For example, the code above implements a dx shader for Maya default Renderer(OGSRenderer). The shader source code is in the source tag. It is a texture pass through.
Now, we need to register our shader fragment.
// Register fragments with the manager if needed MHWRender::MRenderer* theRenderer = MHWRender::MRenderer::theRenderer(); if (theRenderer) { MHWRender::MFragmentManager* fragmentMgr = theRenderer->getFragmentManager(); if (fragmentMgr) { // Add fragments if needed bool fragAdded = fragmentMgr->hasFragment(sFragmentName); if (!fragAdded) { fragAdded = (sFragmentName == fragmentMgr->addShadeFragmentFromBuffer(sFragmentBody, false)); } if (fragAdded) { fFragmentName = sFragmentName; TRACE_API_CALLS("Added Fragment"); } } }
We also need to create custom mapping for shader fragment parameters.
void createCustomMappings() { // Set up some mappings for the parameters on the file texture fragment, // there is no correspondence to attributes on the node for the texture // parameters. MHWRender::MAttributeParameterMapping mapMapping( "map", "", false, true); mappings.append(mapMapping); MHWRender::MAttributeParameterMapping textureSamplerMapping( "textureSampler", "", false, true); mappings.append(textureSamplerMapping); }
Next, we can make our shader fragment as input parameter for our stock shader in the MPxShaderOverride::createShaderInstance.
... // Create texture fragment shader initializeFragmentShaders(); createCustomMappings(); if (!fColorShaderInstance) { // We only need to add texture for color shader instance used by Texture enabled viewport. fColorShaderInstance = shaderMgr->getStockShader( MHWRender::MShaderManager::k3dBlinnShader ); // Make our shader fragment as input for diffuseColor of stock shader fColorShaderInstance->addInputFragment(fFragmentName, MString("output"), MString("diffuseColor")); }
Now, we have successfully added our shader fragment as input and we need to update it properly. Maya will call MPxShaderOverride::updateDevice during DG evaluation. So we need to create an update function and call it during the color shader mode.
if (fColorShaderInstance) { // Update shader to mark it as drawing with transparency or not. fColorShaderInstance->setIsTransparent( isTransparent() ); fColorShaderInstance->setParameter("specularColor", &fSpecular[0] ); // Update texture shader updateTextureShaderFragment(); }
updateTextureShaderFragment is almost the same as the fileTexture sample, except that we are going to generate a default texture when the file texture is not available.
... if (textureManager) { TRACE_API_CALLS("Accquire file texture"); TRACE_API_CALLS(fFileTexturePath); MHWRender::MTexture* texture = textureManager->acquireTexture(fFileTexturePath, name); if (texture) { assignTexture(texture, textureManager); // release our reference now that it is set on the shader textureManager->releaseTexture(texture); } else { MImage image; for (int i = 0; i < 4; ++i) { fDiffuseByte[i] = fDiffuse[i] * 255; } image.setPixels(fDiffuseByte, 1, 1); if (!fDefaultTexture) { const MString vp2BlinnShaderDummyTextureName = MString(""); MHWRender::MTextureDescription desc; desc.setToDefault2DTexture(); desc.fHeight = 1; desc.fWidth = 1; desc.fFormat = MHWRender::kR8G8B8A8_UNORM; fDefaultTexture = textureManager->acquireTexture(vp2BlinnShaderDummyTextureName, desc, fDiffuseByte, false); } else { MStatus status = fDefaultTexture->update(image, false); cerr << status << endl; } if (fDefaultTexture) { cerr << fDiffuse[0] << " " << fDiffuse[1] << " " << fDiffuse[2] << " " << fDiffuse[3] << endl; // It is cached by OGS, update to make sure texture has been updated. TRACE_API_CALLS("Accquired default texture"); assignTexture(fDefaultTexture, textureManager); } }... // Update texture void assignTexture(MHWRender::MTexture* texture, MHWRender::MTextureManager *textureManager) { MHWRender::MTextureAssignment textureAssignment; textureAssignment.texture = texture; TRACE_API_CALLS("Update texture parameter"); fFragmentTextureShader->setParameter(fResolvedMapName, textureAssignment); }
Most of the work has been done now and it will run in Maya. However, the texture is will likely not display properly, so there is one more step.
Remember the uvCoord semantic we defined in our shader fragment earlier? Although we have defined the semantic, we still need to ask Maya to provide these system parameters before it can be used. We can call MPxShaderOverride::addGeometryRequirement or MPxShaderOverride::addGeometryRequirements to tell Maya to provide these parameters. We should call them during the initialization.
MHWRender::MVertexBufferDescriptor positionDesc( empty, MHWRender::MGeometry::kPosition, MHWRender::MGeometry::kFloat, 3); MHWRender::MVertexBufferDescriptor normalDesc( empty, MHWRender::MGeometry::kNormal, MHWRender::MGeometry::kFloat, 3); MHWRender::MVertexBufferDescriptor textureDesc( empty, MHWRender::MGeometry::kTexture, MHWRender::MGeometry::kFloat, 2); addGeometryRequirement(positionDesc); addGeometryRequirement(normalDesc); addGeometryRequirement(textureDesc);
The vp2FileTextureFragmentShader is fully working now. Enjoy! :)
The source code is available on the github of ADN, please visit (source code link) to get it.
Comments
You can follow this conversation by subscribing to the comment feed for this post.