[Solved] Default material fallback for material schemes?

What it says on the tin: a place to discuss proposed new features.
Post Reply
User avatar
Noman
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 714
Joined: Mon Jan 31, 2005 7:21 pm
Location: Israel
x 2
Contact:

[Solved] Default material fallback for material schemes?

Post by Noman »

Hi

I didn't know wether to post this in Developer Talk or here, but I think its more of a debate than a bug, so I decided to post here.

The subject is materical schemes, more specifically, how objects without scheme-specific data deal with them.

Currently, in order for a material scheme to work properly, all renderables in the scene need to implement this scheme. Renderables that don't are rendered normally. Lets say I want to do something like a depth pass from the camera's POV. I would need for all of the objects to have a "Depth Render" scheme defined. This either means re-writing the depth definition (bad) or inheriting from a base interface (better).

This means that in order for the scheme to work properly, all materials have to inherit from a shared material.

Code: Select all

material BaseMaterial
{
	technique
	{
		//Original technique
	}

	technique DepthTechnique
	{
		scheme DepthRender
		//....
	}
}
This works, but the entire application has to be aware of the potential schemes that are in use. Programatically created materials, for example, now also have to be aware of this base materials. And what happens when I want to have more than one scheme? It quickly becomes annoying to maintain, and isn't modular in my opinion.

I would prefer something more along the lines of how shadows work. There is a default shadow caster / shadow receiver material that can be set globally, and materials that don't define custom behavior get it.

This model should be used with material schemes in my opinion. There should be a function along the lines of

Code: Select all

MaterialManager::setDefaultSchemeMaterial(const Ogre::String& schemeName, const Ogre::String& defaultMaterialName)
We would then be able to set a shared material (used material and not technique for capability fallback purposes) for a scheme, without the other objects having to know about it. They can still implement custom functionality by implementing a technique for this scheme, but there will be a more suitable fallback than their default rendering material.

What do you guys think?

I might implement this locally. If I do I'll post a wiki entry or something. But the question is, should this be in the main Ogre branch?
Last edited by Noman on Mon Apr 07, 2008 3:46 pm, edited 1 time in total.
User avatar
iloseall
Gremlin
Posts: 156
Joined: Sun Sep 14, 2003 3:54 am
Location: Beijing China
Contact:

Post by iloseall »

I like the idea.
In my project, i make a lot of "material scheme" for some target.
if has this, I can change a lot of scheme to global-default-setting.
User avatar
nullsquared
Old One
Posts: 3245
Joined: Tue Apr 24, 2007 8:23 pm
Location: NY, NY, USA
x 11

Re: Default material fallback for material schemes?

Post by nullsquared »

Noman wrote:Lets say I want to do something like a depth pass from the camera's POV. I would need for all of the objects to have a "Depth Render" scheme defined. This either means re-writing the depth definition (bad) or inheriting from a base interface (better).
Depth-specific, it's very easy, there's no need to have a separate material scheme (assuming you want to fill the depth buffer; if you want to fill a colour buffer with depth, it's only slightly longer).

Code: Select all

void CustomSceneManager::_renderDepthBuffer(Ogre::Camera *c, Ogre::Viewport *vp) {
        Ogre::RenderSystem *rs = renderSystem;

        Ogre::Matrix4 viewmat = c->getViewMatrix(true);
        Ogre::Matrix4 projmat = c->getProjectionMatrixRS();

        rs->clearFrameBuffer(Ogre::FBT_DEPTH);
        _suppressRenderStateChanges(true); {
            _clearPass();

            rs->_setDepthBufferParams(true, true);
            rs->_setColourBufferWriteEnabled(false, false, false, false);
            rs->setLightingEnabled(false);

            rs->_setViewMatrix(viewmat);
            rs->_setProjectionMatrix(projmat);

            _renderScene(c, vp, false);
        } _suppressRenderStateChanges(false);
    }

All _clearPass does is clear the states on the render system, such as binded GPU programs/parameters (we don't want to accidentally use those when rendering).
User avatar
Noman
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 714
Joined: Mon Jan 31, 2005 7:21 pm
Location: Israel
x 2
Contact:

Post by Noman »

Hm. Thanks for the idea nullsquared.

However, this is more of a conceptual issue. Should I have to implement scene manager functionality for each global operation I want to perform?

I implemented a basic version of this. Its not 100% cleaned up yet, but its working.

Add this to MaterialManager.h :

Code: Select all

protected:
/// Scheme name -> default material. Never shrinks! Should be pretty static anyway
		typedef std::map<String, MaterialPtr> SchemeMaterialMap;
		//List of material scheme default materials
		SchemeMaterialMap mDefaultSchemeMaterials;
/// The default technique for the active scheme
		Technique* mActiveSchemeDefaultTechnique;

public:
/** Sets the name of the default material for a scheme. All objects rendered as part
			of this scheme that don't have a specific technique set for it will use it
		@par schemeName the name of the scheme to set a default material for
		@par defaultMaterialName the name of the default material to use
		*/
		void setDefaultSchemeMaterial(const Ogre::String& schemeName, 
			const Ogre::String& defaultMaterialName);
		
		/** Get the default technique set up for the current scheme
			@ret the active technique for the current scheme, null if none
		*/
		Technique* _getActiveSchemeDefaultTechnique() const;

/** Overridden from ResourceManager since we have to clean up scheme defaults too. */
		void removeAll(void);
And to MaterialManager.cpp :

Code: Select all

//-----------------------------------------------------------------------
	void MaterialManager::setDefaultSchemeMaterial(const Ogre::String& schemeName, 
		const Ogre::String& defaultMaterialName)
	{
		MaterialPtr material = static_cast<MaterialPtr>(getByName(defaultMaterialName));
		if (material.isNull())
		{
			return;
		}
		
		if (!material->isLoaded())
		{
			material->load();
		}
		mDefaultSchemeMaterials[schemeName] = material;
		if (mActiveSchemeName == schemeName)
		{
			//This is the currently active scheme, apply material as active technique.
			mActiveSchemeDefaultTechnique = material->getTechnique(0);
		}
	}
	//-----------------------------------------------------------------------
	Technique* MaterialManager::_getActiveSchemeDefaultTechnique() const
	{
		return mActiveSchemeDefaultTechnique;
	}
	//-----------------------------------------------------------------------
	void MaterialManager::removeAll( void )
	{
		for (SchemeMaterialMap::iterator it = mDefaultSchemeMaterials.begin();
			it != mDefaultSchemeMaterials.end(); it++)
		{
			it->second.setNull();
		}
		ResourceManager::removeAll();
	}
	//-----------------------------------------------------------------------
There are also some minor modifications that have to be made :

Code: Select all

void MaterialManager::setActiveScheme(const String& schemeName)
	{
		SchemeMap::iterator i = mSchemes.find(schemeName);
		if (i == mSchemes.end())
		{
			// Invalid scheme, use default
			mActiveSchemeName = DEFAULT_SCHEME_NAME;
			mActiveSchemeIndex = 0;
			mActiveSchemeDefaultTechnique = 0;
		}
		else
		{
			mActiveSchemeName = schemeName;
			mActiveSchemeIndex = i->second;
			SchemeMaterialMap::iterator matIt = mDefaultSchemeMaterials.find(schemeName);
			if (matIt != mDefaultSchemeMaterials.end())
			{
				mActiveSchemeDefaultTechnique = matIt->second->getTechnique(0);
			}
			else
			{
				mActiveSchemeDefaultTechnique = 0;
			}
		}

	}
And in Material::getBestTechnique :

Code: Select all

// scheme not found?
			if (si == mBestTechniquesBySchemeList.end())
			{
				// Check if a default material was set up for this scheme
				Ogre::Technique* defaultSchemeTechnique = 
					MaterialManager::getSingleton()._getActiveSchemeDefaultTechnique();
				if (defaultSchemeTechnique != 0)
				{
					return defaultSchemeTechnique;
				}

				// get the first item, will be 0 (the default) if default
				// scheme techniques exist, otherwise the earliest defined
				si = mBestTechniquesBySchemeList.begin();
			}
The material used for this should be a material with one technique (or more, for fallback issues), which will be the scheme default.

I'm not working against ogre head so i don't have a patch ready atm (nor has it been accepted). You are welcome to try this. Hopefully i didn't forget anything...
User avatar
Praetor
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 3335
Joined: Tue Jun 21, 2005 8:26 pm
Location: Rochester, New York, US
x 3
Contact:

Post by Praetor »

Similar to nullsquared's suggestion, you can override technique's used on a per-viewport basis. Meaning you can force rendering of all objects with a specific technique, like a depth pass. Check out the Depth of Field demo. It does exactly this.
User avatar
sinbad
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 19269
Joined: Sun Oct 06, 2002 11:19 pm
Location: Guernsey, Channel Islands
x 67
Contact:

Post by sinbad »

I do this kind of thing a lot to render depth/normal surfaces, selection buffer surfaces, or other things for a viewport - all I do is register is MaterialManager::Listener and implement the handleSchemeNotFound method. This is much more powerful & practical than having a default scheme material because a lot of the time you're going to want to merge in specifics from the original material (e.g. alpha maps & rejection settings - these are the most common in fact because your trees should still be alpha rejected). I use this technique to build myself a list of 'alternate' techniques which I can programmatically build from the originals for depth rendering or anything else for that matter, and then re-use as appropriate in subsequent frames.

Also importantly, it allows you to easily supply a different default for each scheme, or even each context of using a scheme, just by using a different listener (or changing its behaviour based on circumstances). I've used this a lot in practice and it works beautifully without ever messing with any of my existing materials; I can't think of a scenario where a stateful default would be better.

Code: Select all

			/** Called if a technique for a given scheme is not found within a material,
				allows the application to specify a Technique instance manually.
			@remarks
				Material schemes allow you to switch wholesale between families of 
				techniques on a material. However they require you to define those
				schemes on the materials up-front, which might not be possible or
				desirable for all materials, particular if, for example, you wanted
				a simple way to replace all materials with another using a scheme.
			@par
				This callback allows you to handle the case where a scheme is requested
				but the material doesn't have an entry for it. You can return a
				Technique pointer from this method to specify the material technique
				you'd like to be applied instead, which can be from another material
				entirely (and probably will be). Note that it is critical that you
				only return a Technique that is supported on this hardware; there are
				utility methods like Material::getBestTechnique to help you with this.
			@param schemeIndex The index of the scheme that was requested - all 
				schemes have a unique index when created that does not alter. 
			@param schemeName The friendly name of the scheme being requested
			@param originalMaterial The material that is being processed, that 
				didn't have a specific technique for this scheme
			@param lodIndex The material level-of-detail that was being asked for, 
				in case you need to use it to determine a technique.
			@param rend Pointer to the Renderable that is requesting this technique
				to be used, so this may influence your choice of Technique. May be
				null if the technique isn't being requested in that context.
			@returns A pointer to the technique to be used, or NULL if you wish to
				use the default technique for this material
			*/
			virtual Technique* handleSchemeNotFound(unsigned short schemeIndex, 
				const String& schemeName, Material* originalMaterial, unsigned short lodIndex, 
				const Renderable* rend) = 0;
User avatar
Noman
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 714
Joined: Mon Jan 31, 2005 7:21 pm
Location: Israel
x 2
Contact:

Post by Noman »

Ah!
The MaterialManager::Listener is what I was looking for. When was it introduced into ogre? I'm looking at 1.4.6 and I don't see it, but it is in my CVS ogre...
User avatar
sinbad
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 19269
Joined: Sun Oct 06, 2002 11:19 pm
Location: Guernsey, Channel Islands
x 67
Contact:

Post by sinbad »

Ah yes, this was a Shoggoth addition (ages ago now though so I forgot it wasn't in Eihort).
User avatar
Noman
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 714
Joined: Mon Jan 31, 2005 7:21 pm
Location: Israel
x 2
Contact:

Post by Noman »

Grrrrrrrrrr :D
I might make a local mini-merge, or just stick with the default scheme material for now. They are pretty similar.

Thanks!

By the way, nullsquared - Consider this scenario : I have a particle system which determines its particles' location in a vertex shader. How will you render their depth with your method? The method that I proposed (and sinbad showed a more general solution) allows objects to override a default behavior under a certain scheme.
Post Reply