CodeSnippits Easy RTT Materials Management

From PyWiki

Jump to: navigation, search

Managing Materials Easily for MRTs

Mrt materials.jpg

Often when doing fullscreen effects like depth of field or screen space ambient occlusion you'll want to quickly change all the materials in the scene to render a specific effect, for example to make a texture of the depth buffer or the normals of all objects. It can be a real pain to do this because it can mean either changing all your materials for all objects, or setting up complicated schemes which also have to be added to all materials.

Luckily, there is a simple way to do this using render targets, and the ogre.MaterialManager.Listener class. The listener has only one method - 'handleSchemeNotFound', which provides a very convenient way to temporarily switch all materials in the scene ( or just those being rendered to a specific target ) to whatever you want, without affecting the main scene, and without having to change all your material scripts. To do this, you have to set up at least one render target (though that could even be the main window if you wanted), and set it so that it renders using a specific material scheme. It doesn't really matter what this scheme is, as the listener will catch all the materials that don't implement that scheme and force them to render what you want instead.

Below is an example application that creates 2 additional render targets, and displays them both on the screen. One renders the depth buffer, and the other renders the object space normals in the scene. Both are controlled by shaders ( also below ), so you have complete control over how each target ( or even each object ) is rendered. Note, there are many ways to do this, but this is perhaps one of the most simple methods in OGRE, and its quite efficient both in framerate and the amount of time to implement.

MRT_Example.py

import sys
sys.path.insert(0,'..')
import PythonOgreConfig
import psyco
psyco.full()
 
import ogre.renderer.OGRE as ogre
import ogre.io.OIS as OIS
import SampleFramework as sf
 
# Simple frameListener that just enables a compositor for testing
class tListener(sf.FrameListener):
    def __init__(self, rw, cam, dcam):
        sf.FrameListener.__init__(self, rw, cam)
        self.it = 0
        self.vp = rw.getViewport(0)
        self.depthCam = dcam
 
    def frameStarted(self, evt):
        self.it += 1
        self.depthCam.setOrientation ( self.camera.getOrientation() )
        self.depthCam.setPosition (self.camera.getPosition())
        if self.it == 10:
            instance = ogre.CompositorManager.getSingleton().addCompositor(self.vp, 'Bloom', 0)
            ogre.CompositorManager.getSingleton().setCompositorEnabled(self.vp, 'Bloom', True)
        return sf.FrameListener.frameStarted(self, evt)
 
 
# class to handle material switching without having to modify scene materials individually
class MaterialSwitcher( ogre.MaterialManager.Listener ):
    def __init__(self):
        ogre.MaterialManager.Listener.__init__(self)
        depthMat = ogre.MaterialManager.getSingleton().getByName("BasicDepthWrite")
        depthMat.load()
        self.depthTechnique = depthMat.getBestTechnique()
 
        normMat = ogre.MaterialManager.getSingleton().getByName("BasicWorldNormalWrite")
        normMat.load()
        self.normalsTechnique = normMat.getBestTechnique()
 
 
    def handleSchemeNotFound(self, index, name, material, lod, rend):
        #print name + " " + str(index)
        if name == "DepthPass":
            #print "depth"
            return self.depthTechnique
        elif name == "NormalsPass":
            return self.normalsTechnique
 
# We need this attached to the depth target, otherwise we get problems with the compositor
# MaterialManager.Listener should NOT be running all the time - rather only when we're
# specifically rendering the target that needs it
class depthRenderListener(ogre.RenderTargetListener):
    def __init__(self, materialListener):
        ogre.RenderTargetListener.__init__(self)
        self.materialListener = materialListener
 
    def preRenderTargetUpdate(self, evt):
        ogre.MaterialManager.getSingleton().addListener( self.materialListener )
 
    def postRenderTargetUpdate(self, evt):
        ogre.MaterialManager.getSingleton().removeListener( self.materialListener )
 
 
# A simple application to test it all out
class RenderTargetExample( sf.Application ):
    def __init__(self):
        sf.Application.__init__(self)
 
    def _createScene(self):
        sm = self.sceneManager
        # This is the material listener - Note: it is controlled by a seperate
        # RenderTargetListener, not applied globally to all targets
        self.materialSwitchListener = MaterialSwitcher()
 
        # Create a simple test scene
        for i in range(10):
            e = self.sceneManager.createEntity("head" + str(i), "ogrehead.mesh")
            n = sm.getRootSceneNode().createChildSceneNode()
            n.attachObject( e )
            n.setPosition( ogre.Vector3( 0, 0, 100 * i) )
 
        for i in range(10):
            e = self.sceneManager.createEntity("robot" + str(i), "robot.mesh")
            n = sm.getRootSceneNode().createChildSceneNode()
            n.attachObject( e )
            n.setPosition( ogre.Vector3( 100, 0, 100 * i) )
 
        self.sceneManager.setAmbientLight(ogre.ColourValue(0.3, 0.3, 0.2)) 
        l = self.sceneManager.createLight("Light2") 
        dir_ = ogre.Vector3(-1,-1,0) 
        dir_.normalise() 
        l.setType(ogre.Light.LT_DIRECTIONAL) 
        l.setDirection(dir_) 
        l.setDiffuseColour(1, 1, 0.8) 
        l.setSpecularColour(1, 1, 1) 
        self.depthCam = self.sceneManager.createCamera("DepthCam")
        self.depthCam.setNearClipDistance(self.camera.getNearClipDistance())
        self.depthCam.setFarClipDistance(self.camera.getFarClipDistance())
        w = self.renderWindow.getViewport(0).getActualWidth() 
        h = self.renderWindow.getViewport(0).getActualHeight ()
        self.depthCam.setAspectRatio ( float(w)/float(h) )
        self.createRenderTargets()
        self.createRTTOverlays()
 
 
 
    def createRenderTargets(self):
 
        # depth pass    
        self.depthTargetListener = depthRenderListener( self.materialSwitchListener )
        texture = ogre.TextureManager.getSingleton().createManual("DepthPassTex", 
                                                                    ogre.ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME, 
                                                                    ogre.TEX_TYPE_2D, 
                                                                    self.viewport.getActualWidth(), 
                                                                    self.viewport.getActualHeight(), 
                                                                    0, ogre.PixelFormat.PF_R8G8B8, ogre.TU_RENDERTARGET)
 
        self.renderTexture = texture.getBuffer().getRenderTarget()
        self.renderTexture.setPriority(0)                                                         
        self.renderTexture.addViewport( self.depthCam )
        self.renderTexture.getViewport(0).setOverlaysEnabled(False)
        self.renderTexture.getViewport(0).setClearEveryFrame( True )
        self.renderTexture.addListener( self.depthTargetListener )
        self.renderTexture.getViewport(0).setMaterialScheme("DepthPass")
 
        # normals pass
        texture = ogre.TextureManager.getSingleton().createManual("NormalPassTex", 
                                                                    ogre.ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME, 
                                                                    ogre.TEX_TYPE_2D, 
                                                                    self.viewport.getActualWidth(), 
                                                                    self.viewport.getActualHeight(), 
                                                                    0, ogre.PixelFormat.PF_R8G8B8, ogre.TU_RENDERTARGET)
        self.normalTexture = texture.getBuffer().getRenderTarget()                                                           
        self.normalTexture.addViewport( self.depthCam )
        self.normalTexture.getViewport(0).setOverlaysEnabled(False)
        self.normalTexture.getViewport(0).setClearEveryFrame( True )
        self.normalTexture.addListener( self.depthTargetListener )
        self.normalTexture.getViewport(0).setMaterialScheme("NormalsPass")
 
    def createRTTOverlays(self):
        baseWhite = ogre.MaterialManager.getSingletonPtr().getByName("BaseWhite")
        DepthShadowTexture = baseWhite.clone("DepthDebugMaterial")
        textureUnit = DepthShadowTexture.getTechnique(0).getPass(0).createTextureUnitState()
 
        textureUnit.setTextureName("DepthPassTex")
 
        baseWhite = ogre.MaterialManager.getSingletonPtr().getByName("BaseWhite")
        DepthShadowTexture = baseWhite.clone("NormalDebugMaterial")
        textureUnit = DepthShadowTexture.getTechnique(0).getPass(0).createTextureUnitState()
 
        textureUnit.setTextureName("NormalPassTex")
 
 
        overlayManager = ogre.OverlayManager.getSingleton()
        # Create an overlay
        self.mDebugOverlay = overlayManager.create("OverlayName")
 
        # Create a panel
        panel = overlayManager.createOverlayElement("Panel", "PanelName0")
        panel.setMetricsMode(ogre.GMM_PIXELS)
        panel.setPosition(10, 10)
        panel.setDimensions(320, 240)
        panel.setMaterialName("DepthDebugMaterial") 
        self.mDebugOverlay.add2D(panel)
 
        panel = overlayManager.createOverlayElement("Panel", "PanelName1")
        panel.setMetricsMode(ogre.GMM_PIXELS)
        panel.setPosition(340, 10)
        panel.setDimensions(320, 240)
        panel.setMaterialName("NormalDebugMaterial") 
        self.mDebugOverlay.add2D(panel)
 
        self.mDebugOverlay.show()
 
    def _createFrameListener(self):
        self.fL = tListener(self.renderWindow, self.camera, self.depthCam)
        self.root.addFrameListener(self.fL)
 
 
 
if __name__ == '__main__':
    try:
        application = RenderTargetExample()
        application.go()
    except ogre.OgreException, e:
        print e

Basic_Passes.material

// Depth
 
vertex_program basicDepthVP cg
{
    source basic_passes.cg
    entry_point basicDepthWrite_vp
    profiles vs_2_0 arbvp1
 
 
    default_params
    {
 
		param_named_auto wvp worldviewproj_matrix
		param_named minDepth float 10.0
		param_named maxDepth float 1000.0
 
    }
}
 
fragment_program basicDepthFP cg
{
    source basic_passes.cg
    entry_point basicDepthWrite_fp
    profiles ps_2_0 arbfp1
 
    default_params
    {
    }
}
 
material BasicDepthWrite
{
    technique
    {
        pass
        {
            vertex_program_ref basicDepthVP
            {
            }
 
            fragment_program_ref basicDepthFP
            {
            }
        }
    }
}
 
// Normals
 
vertex_program worldNormalsVP cg
{
    source basic_passes.cg
    entry_point worldNormalWrite_vp
    profiles vs_2_0 arbvp1
    default_params
    {
        param_named_auto wvp worldviewproj_matrix
        param_named_auto w world_matrix
    }
}
 
fragment_program worldNormalsFP cg
{
    source basic_passes.cg
    entry_point worldNormalWrite_fp
    profiles ps_2_0 arbfp1
    default_params
    {
    }
}
 
material BasicWorldNormalWrite
{
    technique
    {
        pass
        {
            vertex_program_ref worldNormalsVP
            {
            }
 
            fragment_program_ref worldNormalsFP
            {
            }
        }
    }
}


basic_passes.cg

// Write Depth to texture
 
void basicDepthWrite_vp (
    float4 pos: POSITION,
 
    out float4 oPos: POSITION,
    out float depth: TEXCOORD0,
 
    uniform float4x4 wvp,
    uniform float maxDepth,
    uniform float minDepth
    )
    {
        oPos = mul(wvp, pos);
        depth = oPos.z / maxDepth;
    }
 
float4 basicDepthWrite_fp (
    float depth: TEXCOORD0
    ) : COLOR
    {
        return float4( depth, depth, depth, 1.0 );
    }
 
 
// Write World Normal to texture
 
void worldNormalWrite_vp (
    float4 pos:POSITION,
    float3 n: NORMAL,
 
    out float4 oPos: POSITION,
    out float3 oNorm: TEXCOORD0,
 
    uniform float4x4 wvp,
    uniform float4x4 w)
    {
        oPos = mul(wvp, pos);
        oNorm = n;
    }
 
float4 worldNormalWrite_fp (
    float3 norm: TEXCOORD0) : COLOR
    {
        return float4 (norm.xyz, 1);
    }
Personal tools