Tutorial Intermedio 7 Renderizado a Textura (RTT)


external image help.gifCualquier problema que tengas durante el desarrollo del tutorial debes consultarlo en el foro de ayuda.

Introducción


Este tutorial te enseñará lo básico sobre el renderizado a texturas. Esta técnica es necesaria para una variedad de efectos especialmente en combinacion con los sombreadores, ej: Motion Blur.

La idea detras de renderizado a texturas RTT es bastante simple. En vez de enviar los datos de salida de renderizado a la escena de la ventana de renderizado, enviálo a una textura. Esta textura puede ser entonces una textura normal.

external image RTT_final.jpg

Puedes encontrar el código para este tutorial aquí.

El Código Inicial


Ajustando tu aplicacion IntermediateTutorial7 mira esto:
#ifndef __IntermediateTutorial7_h_
#define __IntermediateTutorial7_h_
 
#include "BaseApplication.h"
 
class IntermediateTutorial7 : public BaseApplication
{
public:
 IntermediateTutorial7(void);
 virtual ~IntermediateTutorial7(void);
 
protected:
 virtual void createScene(void);
 virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
 
 Ogre::MovablePlane* mPlane;
 Ogre::Entity* mPlaneEnt;
 Ogre::SceneNode* mPlaneNode;
};
 
#endif // #ifndef IntermediateTutorial7_h_
IntermediateTutorial7.cpp
#include "IntermediateTutorial7.h"
 
// -------------------------------------------------------------------------------------
IntermediateTutorial7::IntermediateTutorial7(void)
{
}
// -------------------------------------------------------------------------------------
IntermediateTutorial7::~IntermediateTutorial7(void)
{
}
 
// -------------------------------------------------------------------------------------
void IntermediateTutorial7::createScene(void)
{
 mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2f, 0.2f, 0.2f));
 
 Ogre::Light* light = mSceneMgr->createLight("MainLight");
 light->setPosition(20, 80, 50);
 
 mCamera->setPosition(60, 200, 70);
 mCamera->lookAt(0,0,0);
 
 Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create("PlaneMat", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
 Ogre::TextureUnitState* tuisTexture = mat->getTechnique(0)->getPass(0)->createTextureUnitState("grass_1024.jpg");
 
 mPlane = new Ogre::MovablePlane("Plane");
 mPlane->d = 0;
 mPlane->normal = Ogre::Vector3::UNIT_Y;
 
 
 Ogre::MeshManager::getSingleton().createPlane("PlaneMesh", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, *mPlane, 120, 120, 1, 1, true, 1, 1, 1, Ogre::Vector3::UNIT_Z);
 mPlaneEnt = mSceneMgr->createEntity("PlaneEntity", "PlaneMesh");
 mPlaneEnt->setMaterialName("PlaneMat");
 
 mPlaneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
 mPlaneNode->attachObject(mPlaneEnt);
}
 
bool IntermediateTutorial7::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
 mPlaneNode->yaw(Ogre::Radian(evt.timeSinceLastFrame));
 return BaseApplication::frameRenderingQueued(evt);
}
 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
 
#ifdef cplusplus
extern "C" {
#endif
 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
 INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
 int main(int argc, char *argv[])
#endif
{
 // Crea un objeto de aplicacion
 IntermediateTutorial7 app;
 
 try {
 app.go();
 } catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
 MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
 std::cerr << "An exception has occured: " <<
 e.getFullDescription().c_str() << std::endl;
#endif
 }
 
 return 0;
 }
 
#ifdef __cplusplus
}
#endif
Compila y ejecuta este programa antes de continuar. Deberías ver un plano simple rotando alrededor del eye Y.

external image RTT_initial.jpg

Renderizado a Texturas

Creando las texturas renderizadas


Lo primero de todo, necesitamos crear una textura.
Ogre::TexturePtr rtt_texture = Ogre::TextureManager::getSingleton().createManual("RttTex", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mWindow->getWidth(), mWindow->getHeight(), 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);
(Donde "mWindow" es el puntero a Ogre::RenderWindow)

El primer parametro para createManual() es el nombre de la textura, que es normalmente llamado RttTex. El segundo especifica el grupo de recursos, el tercero el tipo de textura (en nuestro caso una textura 2D), el cuarto y el quinto el width (ancho) y height (alto) de la textura. También tienes que pasar el número de mipmaps que quieres a la par que el formato de la textura y una bandera de usage. En referencia al formato de la textura: Hay muchos tipos diferentes, pero el más simple es PF_R8G8B8 que creará una textura 24bit RGB. Si necesitas un canal alfa, debería elegir mejor PF_R8G8B8A8.

Ahora necesitamos conseguir el objetivo de renderizado para establecer algunos parametros y después también añadir un RenderTargetListener.
Ogre::RenderTexture *renderTexture = rtt_texture->getBuffer()->getRenderTarget();
 
renderTexture->addViewport(mCamera);
renderTexture->getViewport(0)->setClearEveryFrame(true);
renderTexture->getViewport(0)->setBackgroundColour(Ogre::ColourValue::Black);
renderTexture->getViewport(0)->setOverlaysEnabled(false);
Después de conseguir la textura renderizada, tenemos que añadir un puerto de vista. Este es el puerto de vista que contendrá la RenderTexture que se ha llenado. También necesitamos decirle al puerto de vista para que se limpie en cada frame, establecer el color de fondo a negro y llamar a desactivar todas las superposiciones para la textura, como no queremos tenerlas en ella.

Escribir la textura a un archivo


En este punto tenemos todo listo para hacer una primera comprobación: Sólo guardamos el contenido de nuestro RenderTexture creado en un archivo. Las RenderTextures son derivadas desde RenderTarget y asi tenemos lista la función para guardar el contenido de la textura en un archivo (también podemos hacerlo con RenderWindows). Pero antes de salvar el contenido a un archivo, debes actualizar tu RenderTexture. Puedes hacer esto manualmente via la función update() o llamar a la aplicación automáticamente para que actualice la RenderTexture llamando una vez a la función setAutoUpdate().
// De este modo
renderTexture->setAutoUpdated(true);
 // o de este modo
renderTexture->update();
renderTexture->writeContentsToFile("start.png");
Después de ejecutar la aplicación, encontraras un archivo .png en el directorio de tu .exe, que debería mostrar el contenido de tu pantalla (en nuestro caso el plano de textura).

Implementando una mini pantalla

General


Ahora, necesitaremos añadir una mini pantalla en la esquina inferior derecha de nuestra ventana de aplicación. Para hacer esto añadiremos un Rectangle2D a nuestra escena con lo que conseguiremos nuestra RenderTexture como textura. Nuestra escena se mostrará dos veces: una vez renderizada en la RenderWindow normalmente, y una segunda vez como la textura en nuestro Rectangle2D.

Usando este método también es posible implementar pantallas de TV (o CCTV cámaras, etc). Puedes mostrar otras partes de tu nivel poniendo una pantalla alli, creando una RenderTexture (como se describió en la primera sección) y mostrarla en la pantalla de TV.

Estableciendo el Rectángulo


Creando un Ogre::Rectangle2D de manera simple:
Ogre::Rectangle2D *mMiniScreen = new Ogre::Rectangle2D(true);
mMiniScreen->setCorners(0.5f, -0.5f, 1.0f, -1.0f);
mMiniScreen->setBoundingBox(Ogre::AxisAlignedBox(-100000.0f * Ogre::Vector3::UNIT_SCALE, 100000.0f * Ogre::Vector3::UNIT_SCALE));
En la primera línea, establecemos el parámetro a true para generar las coordenadas de la textura, lo que necesitamos después para mapear la textura en el rectángulo. En la segunda línea tenemos que especificar las esquinas del rectángulo. Left es -1.0 y right +1.0, top es +1.0 y bottom -1.0. Asi que nuestro rectángulo esta sólo en la parte baja de la esquina derecha de nuestra ventana de aplicación.
También damos al rectángulo un caja de límite real grande para prevenir que sea ocultado cuando no estamos mirando su nodo de escena. También puedes usar AxisAlignedBox::setInfinite(), en su lugar para establecer manualmente el ajuste de tamaño de la caja, pero tuvimos problemas con esto en el pasado, asi que los ajustes manuales con una caja enorme deberían ser el método más seguro.

Ahora que hemos creado el rectángulo, sólo necesitamos acoplarlo a un SceneNode que no debería ser nada nuevo para ti.
Ogre::SceneNode* miniScreenNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("MiniScreenNode");
miniScreenNode->attachObject(mMiniScreen);
Si ejecutas tu aplicación en esta etapa, verás un rectángulo blanco en la esquina inferior derecha de nuestra ventana de aplicación.

Creando un material desde cero


El siguiente paso es ahora mostrar el RenderTexture creado en la primera sección en este rectángulo. Por lo tanto tenemos que crear un material para él. Podemos hacer esto mediante un script de material o directamente por código mientras se ejecuta. Haremos el último método en este tutorial sólo para tener todo junto en un archivo.
Ogre::MaterialPtr renderMaterial = Ogre::MaterialManager::getSingleton().create("RttMat", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::Technique* matTechnique = renderMaterial->createTechnique();
matTechnique->createPass();
renderMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false);
renderMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RttTex");
En la primera línea creamos un material vacío y añadimos una técnica y una pasada en las siguientes dos líneas. Con la tercera línea desactivamos la iluminación para prevenir que nuestra mini pantalla se oscurezca con la textura actual. En la última línea hemos creado una nueva TextureUnitState pasando nuestra TRenderTexture creada desde la primera sección.

Ahora podemos aplicar este material a nuestra mini pantalla.
mMiniScreen->setMaterial("RttMat");
Si ejecutas la aplicación ahora, veras un efecto no deseado: la mini pantalla tiene una minipantalla dentro de si! para solucionar esto, Ogre introduce el RenderTargetListener.

Uso del RenderTargetListener

General


En muchos casos necesitaras RTTs con sólo algunos objetos de escena en ellos. En nuestro caso necesitamos una textura que sólo contenga la salida de la ventana de aplicación sin la minipantalla, ya que esta textura esta intentando ser aplicada a nuestra mini pantalla. Tenemos que ocultar la mini pantalla cada vez antes de que la salida de la ventana sea guardada en nuestra RenderTexture. Y aqui es donde viene al rescate RenderTargetListener.
Este listener tiene dos importantes funciones: preRenderTargetUpdate() y postRenderTargetUpdate(). Como los nombres ya inducen, la primera funcion es llamada automaticamente por el listener antes de que el RenderTarget sea llenado (asi que podemos ocultar nuestra mini pantalla ahi) en cualquier caso la segunda función es llamada automáticamente después de que RenderTexture haya sido llenado (asi que podemos mostrar nuestra mini pantalla otra vez).

Implementando un RenderTargetListener


Implementar un RenderTargetListener es muy simple. Lo primero de todo tenemos que hacer que nuestra clase application derive desde RenderTargetListener para que sea su propio listener. Después de eso sólo tenemos que ocultar y mostrar nuestra mini pantalla con las funciones pre- y postRenderTargetUpdate(). Asi tu clase aplicación debería parecerse básicamente a esto:

IntermediateTutorial7.h
class IntermediateTutorial7 : public BaseApplication, public Ogre::RenderTargetListener
{
public:
IntermediateTutorial7(void);
virtual ~IntermediateTutorial7(void);
 
protected:
virtual void createScene(void);
virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
virtual void preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
virtual void postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt);
 
Ogre::MovablePlane* mPlane;
Ogre::Entity* mPlaneEnt;
Ogre::SceneNode* mPlaneNode;
 
// Esto deberia tomar el miembro createScene y traerlo aqui
Ogre::Rectangle2D* mMiniScreen;
};
IntermediateTutorial7.cpp
void IntermediateTutorial7::preRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
{
mMiniScreen->setVisible(false);
}
 
void IntermediateTutorial7::postRenderTargetUpdate(const Ogre::RenderTargetEvent& evt)
{
mMiniScreen->setVisible(true);
}
Ahora, el último paso que necesitamos hacer es añadir el listener al RenderTexture. Como la clase application esta derivada de RenderTargetListener podemos pasar este puntero como parámetro al final de la función createScene.
renderTexture->addListener(this);
Eso es todo. Ahora tenemos una mini pantalla simple en nuestra aplicación. Así de simple.

RTTs y sombreadores

Pasando un RTT y un sombreador


Como los RTTs son a menudo usados junto con los sombreadores, tenemos que saber como pasar el RenderTexture a uno. Ciertamente es muy simple.

El caso mas simple es, que no cambien nunca la textura para el sombreador durante la ejecucion. Si no lo necesitas, sólo tienes que decirle a tu script de material el nombre de la textura que crearas durante la ejecucion, en nuestro caso RttTex. Asi tu texture_unit en el material debería parecerse a esto:
texture_unit
{
texture RttTex
}
Si cambiaras la textura el sombreador debería usarse durante la ejecución, solo añade el siguiente código:
Ogre::MaterialPtr material = Ogre::MaterialManager::getSingleton().getByName("Sepia");
material->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("OtherRttTex");
Con la primera línea conseguimos un puntero al material (en este caso aqui un sombreador de material sepia) donde cambiamos el nombre de la textura en la segunda línea a la deseada.

Ahora, has establecido el nombre de la textura correcta en tu script de material con uno de estos modos puedes acceder a la textura en tu sombreador cg en la siguiente línea:
uniform sampler2D SceneSampler : register(s0)
Bien, esto es. Tu textura de la mini pantalla debería haberse pasado a tu sombreador y ej: se pareceria a esto:

external image RTT_final.jpg

Conclusión


Eso es todo lo básico que necesitas saber para comenzar con RTT. Ahora juega con ello un poco con este código y descubre un nuevo mundo de efectos gráficos.