// Shave and a Haircut // (c) 2019 Epic Games // US Patent 6720962 #include #include #include #include #include "shaveDebug.h" #include "shaveCheckObjectVisibility.h" #include "shaveConstant.h" #include "shaveGlobals.h" #include "shaveHairShape.h" #include "shaveMaya.h" #include "shaveRender.h" #include "shaveRenderer.h" #include "shaveSDKFUNCS.h" #include "shaveTextureStore.h" #include "shaveUtil.h" shaveRenderer::shaveRenderer() : mRendering(false) , mWarningGivenInvalidHairMode(false) , mWarningGivenInvalidInstanceMode(false) {} shaveRenderer::~shaveRenderer() {} void shaveRenderer::doShutter( const MTime& curTime, shaveConstant::ShutterState shutter ) { ENTER(); if ((mGeometryNodes.length() > 0) || (mNonGeometryNodes.length() > 0)) { float frame = (float)curTime.as(MTime::uiUnit()); shaveGlobals::getGlobals(); shaveMaya::getRenderGlobals(); // // We "render" geometry by having the shaveHairShape create its entire // mesh. As such, we want it in the scene before anything else // happens so that Maya will take it into account when doing // bounding box calculations for the render. // if (mGeometryNodes.length() > 0) shaveRender::geomRender(mGeometryNodes); MDagPath cameraPath = shaveRender::getRenderCamPath(); if (mNonGeometryNodes.length() > 0) { // // Create the texture lookup table for this frame. // // We only do this on shutterOpen, not shutterClose. The // reason for this is that Shave doesn't handle animation of // any of its parameters: it simply takes their value at // shutter close. So we only need to create the lookups once // per frame, even when motion blur is on. // // That would argue that we should create the lookup on // shutterClose, not shutterOpen. However, two things mitigate // against that. First, to properly support vertex shaders, we // need to have vertex-level colour info available at both // shutterOpen and shutterClose. Second, there is a bug in // Maya that if you sample a shading network on shutterClose, // it screws up other, unrelated textures in the scene. // // Okay, so the last thing to discuss is why we don't create // the lookup table in frameStart(). That's because there's a // bug in Mental Ray For Maya such that the frame number at // frameStart() is not correct during a batch render. So // instead we have to wait for the actual time change before we // can know which frame we are rendering. We no longer support // Mental Ray, but it seems safer to leave it as-is in case // any other renderers suffer from the same problem. // if ((shutter == shaveConstant::kShutterOpen) || (shutter == shaveConstant::kShutterBoth)) { #ifdef PER_NODE_TEXLOOKUP initTexInfoLookup2( mNonGeometryNodes, "", shaveRender::getFrameGlobals().verbose ); #else initTexInfoLookup( mNonGeometryNodes, "", shaveRender::getFrameGlobals().verbose ); #endif } shaveRender::buildHairStack(mNonGeometryNodes, shutter); } // // Call the overridden render() method. // render(frame, shutter, cameraPath); } LEAVE(); } void shaveRenderer::frameEnd(const shaveGlobals::Globals& g) { shaveRender::clearHairStack(mNonGeometryNodes); mGeometryNodes.clear(); mNonGeometryNodes.clear(); } void shaveRenderer::frameStart(const shaveGlobals::Globals& g) { SHAVEset_tile_limit((int)g.tileMemoryLimit, (int)g.transparencyDepth); // // Get the nodes to be rendered as geometry, and those to be rendered // not as geometry, into two separate lists. // mGeometryNodes.clear(); mNonGeometryNodes.clear(); getRenderableShaveNodesByRenderMode(&mGeometryNodes, &mNonGeometryNodes); // // Changes in animated attributes may have changed the list of // renderable shaveHairShapes. Let's update the flags telling us which // types of shaveHairShapes we have. // bool tempHaveHair; bool tempHaveInstances; shaveUtil::classifyShaveNodes(mGeometryNodes, tempHaveHair, tempHaveInstances); shaveUtil::classifyShaveNodes(mNonGeometryNodes, mHaveHair, mHaveInstances); mHaveHair = (mHaveHair || tempHaveHair); mHaveInstances = (mHaveInstances || tempHaveInstances); } // // Return the base render modes, without caring if we actually have any of // the relevant types of shaveHairShape. // shaveConstant::RenderMode shaveRenderer::getBaseRenderMode( bool* renderInstances ) const { shaveConstant::RenderMode hairRenderMode; hairRenderMode = normalRenderModeGlob; if (renderInstances) *renderInstances = enableInstanceGeometryGlob; // // If the specified render mode does not exist, then switch to buffer. // if (shaveRender::getRenderModeName(hairRenderMode) == "") { if (!mWarningGivenInvalidHairMode) { MGlobal::displayWarning( MString("Shave: Hair render mode ") + (double)hairRenderMode + " is invalid. Using " + shaveRender::getRenderModeName(shaveConstant::kBufferRender) + " render mode instead." ); mWarningGivenInvalidHairMode = true; } hairRenderMode = shaveConstant::kBufferRender; } return hairRenderMode; } shaveConstant::ShadowSource shaveRenderer::getBaseShadowSource() const { ENTER(); shaveConstant::ShadowSource hairShadowSource = shaveConstant::kNoShadows; shaveConstant::RenderMode hairRenderMode = getRenderMode(); if (doHairShadowsGlob) { switch (hairRenderMode) { case shaveConstant::kNoRender: break; case shaveConstant::kGeometryRender: hairShadowSource = shaveConstant::kMayaGeomShadows; break; default: if (shaveUseGeomShadowsGlob) hairShadowSource = shaveConstant::kMayaGeomShadows; else hairShadowSource = shaveConstant::kShaveShadows; break; } } // // Return the shadow sources to the caller. // RETURN(hairShadowSource); } // // Return the render modes applicable to the given array of shaveHairShapes. // shaveConstant::RenderMode shaveRenderer::getFilteredRenderMode( const MObjectArray& shaveHairShapes, bool* renderInstances ) const { // // Get the base mode. // shaveConstant::RenderMode hairRenderMode = getRenderMode(renderInstances); // // Do we have any hair? Any instances? // bool haveHair = false; bool haveInstances = false; shaveUtil::classifyShaveNodes(shaveHairShapes, haveHair, haveInstances); if (!haveInstances && renderInstances) *renderInstances = false; if (!haveHair) hairRenderMode = shaveConstant::kNoRender; return hairRenderMode; } void shaveRenderer::getGeomVisibility( bool& hairPrimaryVisibility, bool& hairSecondaryVisibility, bool& instancePrimaryVisibility, bool& instanceSecondaryVisibility ) { bool renderInstances; shaveConstant::RenderMode hairRenderMode; shaveConstant::ShadowSource hairShadowSource; hairRenderMode = getRenderMode(&renderInstances); hairShadowSource = getShadowSource(); switch (hairRenderMode) { case shaveConstant::kGeometryRender: hairPrimaryVisibility = true; hairSecondaryVisibility = true; break; case shaveConstant::kBufferRender: hairPrimaryVisibility = false; hairSecondaryVisibility = (hairShadowSource == shaveConstant::kMayaGeomShadows); break; case shaveConstant::kNoRender: default: hairPrimaryVisibility = false; hairSecondaryVisibility = false; break; } instancePrimaryVisibility = renderInstances; instanceSecondaryVisibility = renderInstances; } float shaveRenderer::getPixelAspect() const { return ((float)shaveMaya::imageHeight / (float)shaveMaya::imageWidth) * shaveMaya::deviceAspectRatio; } // Returns an array of those shaveHairShapes which are in an appropriate state // to be rendered (e.g. active, visible, etc). // void shaveRenderer::getRenderableShaveNodes(MObjectArray& shaveHairShapes) { shaveHairShapes.clear(); // Get the base render modes. // bool includeInstances; shaveConstant::RenderMode renderMode = getRenderMode(&includeInstances); const bool includeHair = (renderMode != shaveConstant::kNoRender); // If the render modes are excluding both hair and instances, then we're // done. // if (!includeHair && !includeInstances) return; // Run through all the shaveHairShapes in the scene and save the ones which // are renderable. // MItDependencyNodes iter; for (; !iter.isDone(); iter.next()) { MObject node = iter.item(); MFnDependencyNode nodeFn(node); if (nodeFn.typeId() != shaveHairShape::id) continue; shaveHairShape* nodePtr = (shaveHairShape*)nodeFn.userNode(); // // Skip any nodes whose render mode means that they're not being // rendered. // bool isInstanced = nodePtr->isInstanced(); if ((isInstanced && !includeInstances) || (!isInstanced && !includeHair)) { continue; } // Skip any nodes which are not visible. // // TODO: The user may have turned off visibility on the // hair node or its display node. The display node // only matters when we're displaying hair as geometry, // but it would be confusing to force our users to // switch back and forth between the hair and display // nodes, depending upon which render mode they were // using. To avoid that we go with a least common // denominator approach: both the hair and display // nodes must be visible for the hair to render. // MDagPath path; MDagPath::getAPathTo(node, path); if (areObjectAndParentsVisible(path, false, true)) { // Find the shaveHairShape's display node. // MObject displayNode = nodePtr->getDisplayShape(); if (displayNode.isNull()) continue; // Is the display node in a renderable layer? // MFnDagNode dagNodeFn(displayNode); MPlug plug = dagNodeFn.findPlug("layerRenderable"); bool layerIsRenderable; plug.getValue(layerIsRenderable); if (!layerIsRenderable) continue; // Get a DAG path to the display node. At the moment we don't // support instancing of shaveHairShapes, or their display // nodes, so any path will do. // dagNodeFn.getPath(path); // We only render shaveHairShapes whose display nodes are // visible. // // This method used to discard those shaveHairShapes whose // display nodes had their primary visibility off. However, if // someone turns off a node's primary visibility while leaving // its regular visibility on, that means that they still want // it to show up in shadows, reflections, refractions, etc. So // we really do still need to render it. Admittedly we might // not want it to be seen in a buffer render, but that's a // larger issue than we can resolve here with a simply binary // check of primary visibility. So now we ignore primary // visibility when checking display nodes. // if (areObjectAndParentsVisible(path, false, true)) shaveHairShapes.append(node); } } } // // Returns those shaveHairShapes which are in an appropriate state to be // rendered (e.g. active, visible, etc). The result is returned as two // arrays, one containing those shaveHairShapes which are to be rendered as // normal Maya geometry, and another containing those which require // rendering by Shave. (The latter includes geometry shaders using Shave // calls to generate render geometry.) // void shaveRenderer::getRenderableShaveNodesByRenderMode( MObjectArray* geomNodes, MObjectArray* nonGeomNodes ) { MObjectArray shaveHairShapes; getRenderableShaveNodes(shaveHairShapes); unsigned int i; for (i = 0; i < shaveHairShapes.length(); i++) { MFnDependencyNode nodeFn(shaveHairShapes[i]); shaveHairShape* nodePtr = (shaveHairShape*)nodeFn.userNode(); // // Ask the renderer-specific subclass whether this node should be // put onto the geomNodes list. // if (isGeomNode(nodePtr)) { if (geomNodes) geomNodes->append(shaveHairShapes[i]); } else { if (nonGeomNodes) nonGeomNodes->append(shaveHairShapes[i]); } } } shaveConstant::RenderMode shaveRenderer::getRenderMode(bool* renderInstances) const { // // If we're in the middle of a render, return the cached values. // if (mRendering) { if (renderInstances) *renderInstances = mRenderInstances; return mHairRenderMode; } return getBaseRenderMode(renderInstances); } shaveConstant::ShadowSource shaveRenderer::getShadowSource() const { // // If we're in the middle of a render, return the cached value. // if (mRendering) return mHairShadowSource; return getBaseShadowSource(); } bool shaveRenderer::needVertexColours() const { return false; } void shaveRenderer::renderEnd() { // // Clear the various warning flags so that the warnings will be issued // on the next render. // mWarningGivenInvalidHairMode = false; mWarningGivenInvalidInstanceMode = false; mRendering = false; } void shaveRenderer::renderInterrupted() {} void shaveRenderer::renderStart() { // // Cache the render and shadow modes. // MObjectArray shaveHairShapes; mHairRenderMode = getRenderMode(&mRenderInstances); mHairShadowSource = getShadowSource(); // // We have something of a problem here. We don't want to create, say, // a buffer render shader if there are only instances in the scene. // It's easy enough to check the list of shaveHairShapes in the scene, but // it's possible that there might be shaveHairShapes which are present but // not being rendered, because their 'active' flag is false, their // instance geom is invisible, etc. We can't take any of that into // account because the affect may be animated: thus an instance which // is invisible now might be visible on the next frame, so we have to // make sure that the shader for it is already in place. // // So we're stuck with just a static check of the nodes currently // available. If subsequently during a frame we find that a particular // type of shader is not needed we'll have to give it a flag telling it // to act as a no-op for that frame. // shaveUtil::getShaveNodes(shaveHairShapes); shaveUtil::classifyShaveNodes(shaveHairShapes, mHaveHair, mHaveInstances); if (!mHaveHair) { mHairRenderMode = shaveConstant::kNoRender; mHairShadowSource = shaveConstant::kNoShadows; } if (!mHaveInstances) mRenderInstances = false; // // This will trigger getRenderMode() and getShadowSource() to use the // cached values. // mRendering = true; }