Per renderable transparency         Changing the material properties of a mesh without affecting other meshes using the same material

These are some classes to allow per renderable transparency, avoiding to change others that use the same materials. The intention is to solve the problem presented in this forum thread.

There are methods to retrieve the cloned material, and to traverse all the SubEntities material instances, as well as others to change blending type, for instance.

I've not tested them intensively, but no bugs or leaks were found (as long as you call destructors). If you find any, feel free to remove them.

The classes

MaterialInstance.h

#ifndef __MATERIALINSTANCE_H__
#define __MATERIALINSTANCE_H__

#include "Ogre.h"

using namespace Ogre;

/** An instance of a single material.
 * This class represents a single instance of a material. It's mainly 
 * an utility to allow a single Renderable to change its transparency without changing every 
 * other renderables' transparency which also use this material. 
 * It will create (and use) a copy of the original material only when needed. But it will keep 
 * the reference to the cloned material until it's no longer needed (to save re-clonation time).
 * Allows also changing the Renderable material through this class, so it's no need to destroy 
 * this instance and create a new one if the material changes.
 * @note Transparency is applied to all the passes in all the techniques.
 * @note Default blending method is alpha blending. Blending methods in the original material will be overridden. 
 * @note Modulative blending is not supported as it can't be done if some textures exist.
 * @author Kencho
 * @todo Check lighting enabled or not. Disabled lighting won't allow colour changing.
 * @todo Take care of shininess, specularity, and emissiveness.
 * @todo Add existing material recognising support (to allow existing transparency updating...).
 */
class MaterialInstance {
// Attributes =================================================================================
  public:
  protected:
    /** Reference to the original material.
     */
    MaterialPtr mOriginalMat;
    /** Reference to the copy material.
     */
    MaterialPtr mCopyMat;
    /** Keeps the current transparency value.
     */
    Real mCurrentTransparency;
    /** Current blending method.
     */
    SceneBlendType mSBT;
// Methods ====================================================================================
  public:
    /** Constructor. 
     * Initialises references and parameters.
     */
    MaterialInstance ();
    /** Destructor.
     * @note Destroys the copy material if needed.
     */
    ~MaterialInstance ();
    /** Sets the blending method to use to adjust transparency.
     * @param sbt The SceneBlendType desired.
     */
    void setSceneBlending (SceneBlendType sbt);
    /** Changes this instance transparency. 
     * @param transparency The new transparency. Values will be clamped to [0..1].
     * @note This changes transparency. A value of 0 means full opacity, while 1 means full 
     *       transparency (invisible)
     * @note If transparency equals 0, it will use the original material instead of the copy 
     *       (the copy is mandatory transparent, and thus might be slower than the original).
     */
    void setTransparency (Real transparency);
    /** Retrieves a shared pointer to its cloned material.
     * @return A MaterialPtr of the cloned material.
     */
    MaterialPtr getCopyMaterial ();
  protected:
    /** Initialises the reference to the original material.
     */
    virtual void initOriginalMaterial () = 0;
    /** Clones the original material.
     */
    void createCopyMaterial ();
    /** If exists, removes the copy material, and clears the reference to it.
     */
    void clearCopyMaterial ();
};

#endif // __MATERIALINSTANCE_H__

MaterialInstance.cpp

#include "MaterialInstance.h"

MaterialInstance::MaterialInstance () {
  mCurrentTransparency = 0.0f;

  mCopyMat.setNull ();
      
  mSBT = SBT_TRANSPARENT_ALPHA;
}

MaterialInstance::~MaterialInstance () {
  clearCopyMaterial ();
}

void MaterialInstance::setSceneBlending (SceneBlendType sbt) {
  mSBT = sbt;

  if (!mCopyMat.isNull ()) {
    Material::TechniqueIterator techniqueIt = mCopyMat->getTechniqueIterator ();
    while (techniqueIt.hasMoreElements ()) {
      Technique *t = techniqueIt.getNext ();
      Technique::PassIterator passIt = t->getPassIterator ();
      while (passIt.hasMoreElements ()) {
        passIt.getNext ()->setSceneBlending (mSBT);
      }
    }
  }
}

void MaterialInstance::setTransparency (Real transparency) {
  mCurrentTransparency = transparency;
  if (mCurrentTransparency > 0.0f) {
    if (mCurrentTransparency > 1.0f)
      mCurrentTransparency = 1.0f;
    
    if (mCopyMat.isNull ()) {
      createCopyMaterial ();
    }
      
    unsigned short i = 0, j;
    ColourValue sc, dc; // Source colur, destination colour
    Material::TechniqueIterator techniqueIt = mCopyMat->getTechniqueIterator ();
    while (techniqueIt.hasMoreElements ()) {
      Technique *t = techniqueIt.getNext ();
      Technique::PassIterator passIt = t->getPassIterator ();
      j = 0;
      while (passIt.hasMoreElements ()) {
        sc = mOriginalMat->getTechnique (i)->getPass (j)->getDiffuse ();

        switch (mSBT) {
          case SBT_ADD:
            dc = sc;
            dc.r -= sc.r * mCurrentTransparency;
            dc.g -= sc.g * mCurrentTransparency;
            dc.b -= sc.b * mCurrentTransparency;
            passIt.peekNext ()->setAmbient (ColourValue::Black);
            break;
          case SBT_TRANSPARENT_ALPHA:
          default:
            dc = sc;
            dc.a = sc.a * (1.0f - mCurrentTransparency);
            passIt.peekNext ()->setAmbient (mOriginalMat->getTechnique (i)->getPass (j)->getAmbient ());
            break;
        }
        passIt.peekNext ()->setDiffuse (dc);
        passIt.moveNext ();
            
        ++j;
      }
          
      ++i;
    }
  }
  else {
    mCurrentTransparency = 0.0f;
  }
}

MaterialPtr MaterialInstance::getCopyMaterial () {
  return mCopyMat;
}

void MaterialInstance::createCopyMaterial () {
  String name;
  // Avoid name collision
  do {
    name = mOriginalMat->getName () + StringConverter::toString (Math::UnitRandom ());
  } while (MaterialManager::getSingleton ().resourceExists (name));
          
  mCopyMat = mOriginalMat->clone (name);

  Material::TechniqueIterator techniqueIt = mCopyMat->getTechniqueIterator ();
  while (techniqueIt.hasMoreElements ()) {
    Technique *t = techniqueIt.getNext ();
    Technique::PassIterator passIt = t->getPassIterator ();
    while (passIt.hasMoreElements ()) {
      passIt.peekNext ()->setDepthWriteEnabled (false);
      passIt.peekNext ()->setSceneBlending (mSBT);
      passIt.moveNext ();
    }
  }
}

void MaterialInstance::clearCopyMaterial () {
  if (!mCopyMat.isNull ())
    MaterialManager::getSingleton ().remove (mCopyMat->getName ());
       
  mCopyMat.setNull ();
}

SubEntityMaterialInstance.h

#ifndef __SUBENTITYMATERIALINSTANCE_H__
#define __SUBENTITYMATERIALINSTANCE_H__

#include "MaterialInstance.h"

using namespace Ogre;

/** Specialisation of the MaterialInstance class for SubEntities.
 * @author Kencho.
 * @see MaterialInstance.
 */
class SubEntityMaterialInstance : public MaterialInstance {
// Attributes =================================================================================
  public:
  protected:
    /** Reference to the affected SubEntity.
     */
    SubEntity *mSubEntity;
// Methods ====================================================================================
  public:
    /** Constructor. 
     * Initialises references and parameters.
     * @param se The SubEntity this SubEntityMaterialInstance works on.
     */
    SubEntityMaterialInstance (SubEntity *se);
    /** Destructor.
     * @note Destroys the copy material if needed.
     */
    ~SubEntityMaterialInstance ();
    /** Changes this SubEntity material and does any needed operations to keep the previous
     * material instance parameters (transparency and such).
     * @param name Name of the new material.
     * @note This also changes the references SubEntity material, so there is no need to call 
     *       SubEntity::setMaterialName() if this method is called. Indeed it's recommended to 
     *       change it through this instance rather than changing it manually.
     */
    void setMaterialName (String name);
    /** Changes this instance transparency. 
     * @param transparency The new transparency. Values will be clamped to [0..1].
     * @note This changes transparency. A value of 0 means full opacity, while 1 means full 
     *       transparency (invisible)
     * @note If transparency equals 0, it will use the original material instead of the copy 
     *       (the copy is mandatory transparent, and thus might be slower than the original).
     * @see MaterialInstance::setTransparency().
     */
    void setTransparency (Real transparency);
  protected:
    /** Initialises the reference to the original material from the SubEntity's.
     * @see MaterialInstance::initOriginalMaterial().
     */
    void initOriginalMaterial ();
};

#endif // __SUBENTITYMATERIALINSTANCE_H__

SubEntityMaterialInstance.cpp

#include "SubEntityMaterialInstance.h"

SubEntityMaterialInstance::SubEntityMaterialInstance (SubEntity *se) : MaterialInstance () {
  mSubEntity = se;
      
  initOriginalMaterial ();
}

SubEntityMaterialInstance::~SubEntityMaterialInstance () {
  // Reset to the original material
  mSubEntity->setMaterialName (mOriginalMat->getName ());
}

void SubEntityMaterialInstance::setMaterialName (String name) {
  clearCopyMaterial ();
      
  mSubEntity->setMaterialName (name);
      
  initOriginalMaterial ();
      
  setTransparency (mCurrentTransparency);
}

void SubEntityMaterialInstance::setTransparency (Real transparency) {
  MaterialInstance::setTransparency (transparency);
        
  if (mCurrentTransparency > 0.0f) {
    mSubEntity->setMaterialName (mCopyMat->getName ());
  }
  else {
    mSubEntity->setMaterialName (mOriginalMat->getName ());
  }
}

void SubEntityMaterialInstance::initOriginalMaterial () {
  mOriginalMat = MaterialManager::getSingleton ().getByName (mSubEntity->getMaterialName ());
}

EntityMaterialInstance.h

#ifndef __ENTITYMATERIALINSTANCE_H__
#define __ENTITYMATERIALINSTANCE_H__

#include "SubEntityMaterialInstance.h"

using namespace Ogre;

/** Iterator to traverse the SubEntityMaterialInstance's.
 * @author Kencho.
 */
typedef VectorIterator<std::vector<SubEntityMaterialInstance *> > SubEntityMaterialInstancesIterator;

/** An instance of a full Entity material.
 * This is like a hub for all the underlying SubEntities material instances.
 * It keeps a list of each SubEntity's material instance.
 * @see SubEntityMaterialInstance.
 * @author Kencho
 */
class EntityMaterialInstance {
// Attributes =================================================================================
  public:
  protected:
    /** List of SubEntities' material instances.
     */
    std::vector<SubEntityMaterialInstance *> mSEMIs;
    /** Keeps the current transparency value.
     * @see SubEntityMaterialInstance::mCurrentTransparency.
     */
    Real mCurrentTransparency;
// Methods ====================================================================================
  public:
    /** Constructor. 
     * Initialises the list of SubEntities' material instances.
     * @param e The entity this material instance will affect to.
     * @note This will create material instances for all the underlying SubEntities.
     */
    EntityMaterialInstance (Entity *e);
    /** Destructor.
     * Destroys all the underlying SubEntityMaterialInstances.
     */
    ~EntityMaterialInstance ();
    /** Assigns this material to all the SubEntities through their respective 
     * SubEntityMaterialInstances.
     * @param name Name of the new material for this entity (all of its SubEntities).
     * @see SubEntityMaterialInstance::setMaterialName().
     */
    void setMaterialName (String name);
    /** Sets the scene blending method for all the SubEntities.
     * @param sbt The desired SceneBlendType.
     * @see SubEntityMaterialInstance::setSceneBlending().
     */
    void setSceneBlending (SceneBlendType sbt);
    /** Changes the whole Entity transparency, through all the underlying SubEntityMaterialInstances.
     * @param transparency New transparency.
     * @see SubEntityMaterialInstance::setTransparency().
     */
    void setTransparency (Real transparency);
    /** Returns an iterator to traverse all the underlying MaterialInstances.
     * @return The SubEntityMaterialInstances iterator.
     */
    SubEntityMaterialInstancesIterator getSubEntityMaterialInstancesIterator ();
  protected:
};

#endif // __ENTITYMATERIALINSTANCE_H__

EntityMaterialInstance.cpp

#include "EntityMaterialInstance.h"

EntityMaterialInstance::EntityMaterialInstance (Entity *e) {
  for (unsigned int i = 0; i < e->getNumSubEntities (); i++) {
    mSEMIs.push_back (new SubEntityMaterialInstance (e->getSubEntity (i)));
  }
}

EntityMaterialInstance::~EntityMaterialInstance () {
  std::vector<SubEntityMaterialInstance *>::iterator it, iend;
  iend = mSEMIs.end ();
  for (it = mSEMIs.begin (); it != iend; ++it) {
    delete *it;
  }
}

void EntityMaterialInstance::setMaterialName (String name) {
  std::vector<SubEntityMaterialInstance *>::iterator it, iend;
  iend = mSEMIs.end ();
  for (it = mSEMIs.begin (); it != iend; ++it) {
    (*it)->setMaterialName (name);
  }
}

void EntityMaterialInstance::setSceneBlending (SceneBlendType sbt) {
  std::vector<SubEntityMaterialInstance *>::iterator it, iend;
  iend = mSEMIs.end ();
  for (it = mSEMIs.begin (); it != iend; ++it) {
    (*it)->setSceneBlending (sbt);
  }
}

void EntityMaterialInstance::setTransparency (Real transparency) {
  mCurrentTransparency = transparency;
  if (mCurrentTransparency > 1.0f)
    mCurrentTransparency = 1.0f;
  if (mCurrentTransparency < 0.0f)
    mCurrentTransparency = 0.0f;
        
  std::vector<SubEntityMaterialInstance *>::iterator it, iend;
  iend = mSEMIs.end ();
  for (it = mSEMIs.begin (); it != iend; ++it) {
    (*it)->setTransparency (mCurrentTransparency);
  }
}

SubEntityMaterialInstancesIterator EntityMaterialInstance::getSubEntityMaterialInstancesIterator () {
  return SubEntityMaterialInstancesIterator (mSEMIs.begin (), mSEMIs.end ());
}

Usage examples

Prerequisites

We'll demonstrate how to modify one Entity's transparency and one SubEntity's (included in EntityMaterialInstance.h) transparency.
So we add the header at first

#include "EntityMaterialInstance.h"

Creating the scene



We'll add a single Ogre head (hehehe)

Entity *ent = mSceneMgr->createEntity ("Ogre Head", "ogrehead.mesh");
SceneNode *node = mSceneMgr->getRootSceneNode ()->createChildSceneNode ("Ogre Head node");
node->attachObject (ent);

Whole entity transparency changes



First, we create our material transparency instance for this Entity.

EntityMaterialInstance *emi = new EntityMaterialInstance (ent);

and now we play with the instance's methods :P

Set whole Entity transparency to 50%

emi->setTransparency (0.5f);

Change whole Entity material to the env-mapped rusty steel

emi->setMaterialName ("Examples/EnvMappedRustySteel");
Change blending mode<BR>

The MaterialInstance is clever enough to modify the transparency requirements to fit the selected scene blending mode (alpha blending modifies alpha values, while additive blending fades the mesh to black, thus making it invisible)

  • Alpha blending



emi->setSceneBlending (SBT_TRANSPARENT_ALPHA);
  • Additive blending



emi->setSceneBlending (SBT_ADD);

Single subentity (eyes) transparency changes



Same as before. First we create our material transparency instance for a single SubEntity

SubEntityMaterialInstance *semi = new SubEntityMaterialInstance (ent->getSubEntity (0));

and then play with the methods :-)

Set SubEntity (eyes) transparency to 50%

semi->setTransparency (0.5f);

Change SubEntity (eyes) material to the env-mapped rusty steel

semi->setMaterialName ("Examples/EnvMappedRustySteel");

Future changes

  • Check if lighting is enabled or not. Disabled lighting won't allow colour changing.
  • Take care of shininess, specularity, and emissiveness. They affect blending types like additive one.
  • Add existing material recognising support (to allow existing transparency updating...). I'm afraid this won't be possible, but would be nice, so ideas are welcome :-)


Alias: Per_renderable_transparency