Skip to main content
Multiple Render Targets         Setting up a Multiple Render Target and associated materials/shaders without using compositors.

A Multiple Render Target lets you render to more than one surface at the same time, which means the vertices only need to be transformed once, instead of once per surface. It's like doing an RTT (Render To Texture, Intermediate Tutorial 7) except you render to several textures instead of just one. This comes in handy for stuff like deferred shading where you render the exact same scene multiple times, only with different materials and onto different surfaces.

Normally MRTs are set up in Ogre using compositors. I don't know much about that, I don't use compositors and so I needed to set them up without them. I can't claim to understand the details either, but what follows is code that works, based on source found in the playpen.

The project renders two ogre heads sitting next to each other onto an MRT consisting of two float16 RGB textures. Each ogre head has a different material, so they're different colours. And the colours used on each rendered texture are different too. The user can press the "T" key to look at the rendered textures seperately.

MRT manual render surface 0 MRT manual render surface 1

Here's the complete project setup, including project solution and code, media, shaders and compiled binary. Only Release mode is setup for compiling, and you'll probably need to adjust the project lib and header directories to the location of your own Ogre headers and libs.
http://www.mediafire.com/file/4xzzidumjo2/MRTExp07_100703.zip
If the file is unavailable try PMing me at the Ogre forums, http://www.ogre3d.org/forums/ucp.php?i=pm&mode=compose&u=21383, I'm mkultra333.

The project is built on the Basic Ogre Framework

Ok, you need the materials for rendering to an MRT. Here's the two I use, and shaders to go with them. I call things A and B, indicating if they are intended for ogre head A or ogre head B.

Copy to clipboard
/////////////////////////////////////////////////////////////////// // vertex_program mrttestA_vs cg { source 0_mrttest.cg entry_point mrttestA_vs profiles arbvp1 vs_2_x default_params { param_named_auto wvp worldviewproj_matrix } } fragment_program mrttestA_ps cg { source 0_mrttest.cg entry_point mrttestA_ps profiles ps_2_x fp40 arbfp1 fp30 } material mrttestA { technique { scheme MRT pass { vertex_program_ref mrttestA_vs { } fragment_program_ref mrttestA_ps { } } } } /////////////////////////////////////////////////////////////////// // vertex_program mrttestB_vs cg { source 0_mrttest.cg entry_point mrttestB_vs profiles arbvp1 vs_2_x default_params { param_named_auto wvp worldviewproj_matrix } } fragment_program mrttestB_ps cg { source 0_mrttest.cg entry_point mrttestB_ps profiles ps_2_x fp40 arbfp1 fp30 } material mrttestB { technique { scheme MRT pass { vertex_program_ref mrttestB_vs { } fragment_program_ref mrttestB_ps { } } } }

Copy to clipboard
/////////////////////////////////////////////////////////////////////////////// // void mrttestA_vs( float4 InPos : POSITION, out float4 OutPos : POSITION, uniform float4x4 wvp ) { OutPos = mul(wvp, InPos); } void mrttestA_ps( float4 InPos : POSITION, out float4 Colour0 : COLOR0, out float4 Colour1 : COLOR1 ) { Colour0 = float4(1,0,0, 1); Colour1 = float4(0,1,0, 1); } /////////////////////////////////////////////////////////////////////////////// // void mrttestB_vs( float4 InPos : POSITION, out float4 OutPos : POSITION, uniform float4x4 wvp ) { OutPos = mul(wvp, InPos); } void mrttestB_ps( float4 InPos : POSITION, out float4 Colour0 : COLOR0, out float4 Colour1 : COLOR1 ) { Colour0 = float4(1,0,1, 1); Colour1 = float4(0,1,1, 1); }


Assuming you know shaders and materials, there isn't anything too unusual here, except notice that we have named the technique scheme "MRT" in both materials, and the pixel shaders output two colours, COLOR0 and COLOR1. The technique scheme name is needed to connect these materials to the MRT rendering, and the two output colours go to the two different rendered textures.

I assume you can set up your entities. Make sure they use the materials you've designed for your MRT. In my project above I manually set the two ogre heads to have mrttestA and mrttestB materials.

Here are the things I need for my MRT setup.

Copy to clipboard
Ogre::Rectangle2D* miniScreen_DfShTexture0; Ogre::SceneNode* miniScreenNode_DfShTexture0; Ogre::TexturePtr RTT_Texture_DfShTexture0 ; Ogre::RenderTexture* renderTexture_DfShTexture0 ; Ogre::MaterialPtr RTT_Mat_DfShTexture0 ; Ogre::Technique* RTT_Technique_DfShTexture0 ; Ogre::Rectangle2D* miniScreen_DfShTexture1; Ogre::SceneNode* miniScreenNode_DfShTexture1; Ogre::TexturePtr RTT_Texture_DfShTexture1 ; Ogre::RenderTexture* renderTexture_DfShTexture1 ; Ogre::MaterialPtr RTT_Mat_DfShTexture1 ; Ogre::Technique* RTT_Technique_DfShTexture1 ; /////////////////////////////////////////////////////////////////////////////// Ogre::MultiRenderTarget* m_mrttex; Ogre::Viewport *m_viewmrt ;


The top two groups are the textures we'll render to, plus rectangles and nodes so that we can look at them easily. the bottom group is our mrt and a vewport for it.

Here's the code that prepares the surfaces and mrt.

Copy to clipboard
// this function sets up the textures and mrt stuff void OgreFramework::setupResources() { MaterialManager::getSingleton().initialise() ; // I put the MRT textures in their own resource group so that they dont get destroyed if we need to destroy other resources. Ogre::ResourceGroupManager::getSingleton().createResourceGroup("RTTResources") ; Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); int DSWidth = m_pViewport->getActualWidth(); int DSHeight = m_pViewport->getActualHeight(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // set up the two textures used as the multiple render targets. // For each surface I also add a Rectangle2D, a material for that rectangle, and a node. // This is so that we can look at the individual surfaces easily after we've rendered the MRT. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// RTT_Texture_DfShTexture0 = Ogre::TextureManager::getSingleton().createManual("RttTex_DfShTexture0", "RTTResources", TEX_TYPE_2D, DSWidth, DSHeight, 0, PF_FLOAT16_RGB, TU_RENDERTARGET); renderTexture_DfShTexture0 = RTT_Texture_DfShTexture0->getBuffer()->getRenderTarget(); renderTexture_DfShTexture0->addViewport(m_pCamera); renderTexture_DfShTexture0->getViewport(0)->setClearEveryFrame(true, FBT_COLOUR|FBT_DEPTH); renderTexture_DfShTexture0->getViewport(0)->setBackgroundColour(ColourValue::Black); renderTexture_DfShTexture0->getViewport(0)->setOverlaysEnabled(false); RTT_Mat_DfShTexture0 = MaterialManager::getSingleton().create("RttMat_DfShTexture0", "RTTResources"); RTT_Technique_DfShTexture0 = RTT_Mat_DfShTexture0->createTechnique(); RTT_Technique_DfShTexture0->createPass(); TextureUnitState* tState_DfShTexture0 = RTT_Mat_DfShTexture0->getTechnique(0)->getPass(0)->createTextureUnitState("RttTex_DfShTexture0"); tState_DfShTexture0->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP) ; RTT_Mat_DfShTexture0->getTechnique(0)->getPass(0)->setLightingEnabled(false); RTT_Mat_DfShTexture0->getTechnique(0)->getPass(0)->setTextureFiltering(TFO_NONE) ; miniScreen_DfShTexture0 = new Ogre::Rectangle2D(true); miniScreen_DfShTexture0->setCorners(-1.0001, 1.0001, 1.0, -1.0); miniScreen_DfShTexture0->setBoundingBox(AxisAlignedBox(-100000.0*Vector3::UNIT_SCALE, 100000.0*Vector3::UNIT_SCALE)); miniScreenNode_DfShTexture0 = m_pSceneMgr->getRootSceneNode()->createChildSceneNode("MiniScreenNode_DfShTexture0"); miniScreenNode_DfShTexture0->attachObject(miniScreen_DfShTexture0); miniScreen_DfShTexture0->setMaterial("RttMat_DfShTexture0"); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// RTT_Texture_DfShTexture1 = Ogre::TextureManager::getSingleton().createManual("RttTex_DfShTexture1", "RTTResources", TEX_TYPE_2D, DSWidth, DSHeight, 0, PF_FLOAT16_RGB, TU_RENDERTARGET); renderTexture_DfShTexture1 = RTT_Texture_DfShTexture1->getBuffer()->getRenderTarget(); renderTexture_DfShTexture1->addViewport(m_pCamera); renderTexture_DfShTexture1->getViewport(0)->setClearEveryFrame(true, FBT_COLOUR|FBT_DEPTH); renderTexture_DfShTexture1->getViewport(0)->setBackgroundColour(ColourValue::Black); renderTexture_DfShTexture1->getViewport(0)->setOverlaysEnabled(false); RTT_Mat_DfShTexture1 = MaterialManager::getSingleton().create("RttMat_DfShTexture1", "RTTResources"); RTT_Technique_DfShTexture1 = RTT_Mat_DfShTexture1->createTechnique(); RTT_Technique_DfShTexture1->createPass(); TextureUnitState* tState_DfShTexture1 = RTT_Mat_DfShTexture1->getTechnique(0)->getPass(0)->createTextureUnitState("RttTex_DfShTexture1"); tState_DfShTexture1->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP) ; RTT_Mat_DfShTexture1->getTechnique(0)->getPass(0)->setLightingEnabled(false); RTT_Mat_DfShTexture1->getTechnique(0)->getPass(0)->setTextureFiltering(TFO_NONE) ; miniScreen_DfShTexture1 = new Ogre::Rectangle2D(true); miniScreen_DfShTexture1->setCorners(-1.0001, 1.0001, 1.0, -1.0); miniScreen_DfShTexture1->setBoundingBox(AxisAlignedBox(-100000.0*Vector3::UNIT_SCALE, 100000.0*Vector3::UNIT_SCALE)); miniScreenNode_DfShTexture1 = m_pSceneMgr->getRootSceneNode()->createChildSceneNode("MiniScreenNode_DfShTexture1"); miniScreenNode_DfShTexture1->attachObject(miniScreen_DfShTexture1); miniScreen_DfShTexture1->setMaterial("RttMat_DfShTexture1"); // Here's where we set up the MRT. m_mrttex = Ogre::Root::getSingleton().getRenderSystem()->createMultiRenderTarget("MRT"); renderTexture_DfShTexture0->setAutoUpdated(false) ; renderTexture_DfShTexture1->setAutoUpdated(false) ; m_mrttex->bindSurface(0, renderTexture_DfShTexture0); m_mrttex->bindSurface(1, renderTexture_DfShTexture1); m_mrttex->setAutoUpdated(true); m_viewmrt = m_mrttex->addViewport(m_pCamera); m_viewmrt->setMaterialScheme("MRT"); m_viewmrt->setClearEveryFrame(true); m_viewmrt->setOverlaysEnabled(false); m_viewmrt->setSkiesEnabled(false); m_viewmrt->setBackgroundColour(ColourValue(0,0,0,0)); // all done! }


This just needs to be called once as part of the program initialization.

I do manual rendering, so I need to explicitly render things myself rather than letting Ogre do it. Here's the function that does that.

Copy to clipboard
// this function updates the MRT then displays one of the selected textures on the main view screen. void OgreFramework::UpdateRenderTargets() { // this fireframe stuff probably isn't needed for this, but if you manually update things it becomes important for animation and stuff. m_pRoot->_fireFrameStarted() ; m_pRoot->_fireFrameRenderingQueued() ; // remove all the nodes (there'd usually be a rectangle2D sitting around from last frame) m_pSceneMgr->getRootSceneNode()->removeAllChildren() ; // put our two ogre heads into the scene m_pSceneMgr->getRootSceneNode()->addChild(m_pNodeA) ; m_pSceneMgr->getRootSceneNode()->addChild(m_pNodeB) ; // update the MRT. m_mrttex->update(true) ; // now our multiple targets have been rendered to, lets have a look at them. // remove the two ogre heads from the scene. m_pSceneMgr->getRootSceneNode()->removeAllChildren() ; // add one of the rectangle2ds to the scene for us to render on the main window. switch(m_nSelectedTex) { case 0: m_pSceneMgr->getRootSceneNode()->addChild(miniScreenNode_DfShTexture0) ; break ; case 1: m_pSceneMgr->getRootSceneNode()->addChild(miniScreenNode_DfShTexture1) ; break ; } // update the main window. m_pRenderWnd->update(true) ; // another bit of fireframe stuff that isn't strictly needed here, but you'll need it if you do animations. m_pRoot->_fireFrameEnded() ; }


And that's it. Not that difficult, though I admit I'd never have had a clue without the playpen code. (Thanks to Sinbad for pointing me to that code.)

The thread where this article can be discussed is here, http://www.ogre3d.org/forums/viewtopic.php?f=2&t=58732&p=395744#p395744. Odds are I won't be able to help with any really tricky technical questions, my understanding of this stuff is fairly shallow.