Tutorial Intermedio 6 Decals Proyectivos


external image help.gifCualquier problema que encuentres mientras estas trabajando con este tutorial debes preguntarlo en el Foro de Ayuda.

Introducción


En este tutorial cubriremos como añadir decals proyectivos a un objeto en la escena. El texturizado proyectivo es útil cuando quieres hacer algo como un indicador de selección sobre la tierra, y ayudas visuales que se proyectan, o algun otro tipo de decal que se proyecte en algún sitio (pero que no sea permanente). Aqui hay un pantallazo del sitio que esta siendo proyectado sobre la cabeza de Ogre:

external image Decal_shot.png

Puedes encontrar el código para este tutorial aquí. Añade poco a poco el código según avances por el tutorial y mira los resultados.

Comenzando

Nuevas Texturas


Antes que comenzemos en este proyecto, necesitamos añadir dos nuevas imágenes que usaremos:
Decal.png
Decal_filter.png

El mejor lugar para poner esto es en la carpeta media/materials/textures (para la mayoría de la gente esto debería estar localizado en la carpeta OgreSDK). También notar que los caracteres estan en minúsculas.

El Código Inicial


Instalando el proyecto del Tutorial Intermedio 6 se parecerá a algo como esto:

IntermediateTutorial6.h
#ifndef IntermediateTutorial6_h_
#define IntermediateTutorial6_h_
 
#include "BaseApplication.h"
 
class IntermediateTutorial6 : public BaseApplication
{
public:
 IntermediateTutorial6(void);
 virtual ~IntermediateTutorial6(void);
 
protected:
 virtual void createScene(void);
 virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
 virtual void createProjector();
 virtual void makeMaterialRecieveDecal(const Ogre::String& matName);
 
 Ogre::SceneNode* mProjectorNode;
 Ogre::Frustum* mDecalFrustum;
 Ogre::Frustum* mFilterFrustum;
 float mAnim;
};
 
#endif // #ifndef IntermediateTutorial6_h_
IntermediateTutorial6.cpp
#include "IntermediateTutorial6.h"
// -------------------------------------------------------------------------------------
IntermediateTutorial6::IntermediateTutorial6(void)
{
}
// -------------------------------------------------------------------------------------
IntermediateTutorial6::~IntermediateTutorial6(void)
{
}
 
// -------------------------------------------------------------------------------------
void IntermediateTutorial6::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::Entity* ent;
 for (int i = 0; i < 6; i++)
 {
 Ogre::SceneNode* headNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
 ent = mSceneMgr->createEntity("head" + Ogre::StringConverter::toString(i), "ogrehead.mesh");
 headNode->attachObject(ent);
 
 Ogre::Radian angle(i + Ogre::Math::TWO_PI / 6);
 headNode->setPosition(75 * Ogre::Math::Cos(angle), 0, 75 * Ogre::Math::Sin(angle));
 }
}
 
bool IntermediateTutorial6::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
 return BaseApplication::frameRenderingQueued(evt);
}
 
void IntermediateTutorial6::createProjector()
{
}
 
void IntermediateTutorial6::makeMaterialRecieveDecal(const Ogre::String& matName)
{
}
 
#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
 IntermediateTutorial6 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 6 cabezas de Ogre.

Proyectando Decals

Frustums


Un frustrum representa una pirámide capada en lo cercana y el final, lo que representa el área visible de una proyección. Ogre usa esto para representar las cámaras (La Clase Camera deriva directamente desde la clase Frustum). En este tutorial, usaremos un frustum para proyectar el decal en las mallas en la escena.

La primera cosa que haremos para crear el proyector es crear el frustum que representa y acoplarlo a un SceneNode. Busca el método createProjector y añade el siguiente código:
mDecalFrustum = new Ogre::Frustum();
mProjectorNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("DecalProjectorNode");
mProjectorNode->attachObject(mDecalFrustum);
mProjectorNode->setPosition(0,5,0);
Esto crea un proyector que crecerá el decal para que vaya más y más lejos, como funciona un proyector de cine. Si quieres crear un proyector que mantenga un tamaño constante y sombra de decal a cualquier distancia que queramos, deberías añadir el siguiente código (pero no lo haremos en este tutorial):
// No anyadir esto al proyecto
mDecalFrustum->setProjectionType(Ogre::PT_ORTHOGRAPHIC);
mDecalFrustum->setOrthoWindowHeight(100);
setOrthoWindowHeight() es usado junto con la relacion de aspecto para establecer el tamanyo de un frustum Ortografico.

Antes de continuar, toma nota de donde nuestro frustum esta proyectando el decal. En esta aplicación hay un anillo de cabezas de Ogre y el frustum se asienta en el centro de ellos (ligeramente por encima, 5 unidades), apuntando en la direccion -Z). Lo que significa que, eventualmente, cuando ejecutamos los decals de la aplicación se proyectarán de vuelta en las cabezas de Ogre.

Modificando el Material


Para que el decal se muestre sobre un objeto, el material que usa el objeto tiene que recibir el decal. Hacemos esto creando una nueva pasada que renderiza el decal en lo alto de la textura regular. El frustum determina la location (posición), size (tamaño), y shape (forma) del decal proyectado. En este demo modificaremos el material en si mismo para recibir el decal, pero para aplicaciones más reales, deberías crear un clon del material para modificarlo para que puedas intercambiar propiedades.

La primera cosa que haremos es conseguir el material y crear una nueva pasada para el material. Busca el makeMaterialReceiveDecal y añade el siguiente código:
Ogre::MaterialPtr mat = (Ogre::MaterialPtr)Ogre::MaterialManager::getSingleton().getByName(matName);
Ogre::Pass *pass = mat->getTechnique(0)->createPass();
Ahora que hemos creado nuestra pasada, necesitamos establecer el mezclado (blending) y la iluminación (lighting). Estamos añadiendo una nueva textura que debe ser mezclada apropiadamente con la textura actual del objeto. Para hacer esto estableceremos el mezclado de escena a transparencia alfa, y la profundidad de bias (bias depth) a 1 (así para que no haga transparencia en el decal). Finalmente necesitamos desactivar la iluminación para el material asi que siempre no tenga en cuenta la iluminación que la escena tenga. Si quieres que el decal de tu aplicación se vea afectado por la iluminación de la escena deberías no añadir la última llamada a función:
pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA);
pass->setDepthBias(1);
pass->setLightingEnabled(false);
Ahora que tenemos nuestra nueva pasada necesitamos crear un nuevo estado de unidad de textura usando nuestra imagen decal.png. La segunda llamada a función enciende el texturizado proyectivo y toma el frustum que hemos creado. Las dos llamas finales establecen el filtrado y los modos de direccionamiento:
Ogre::TextureUnitState *texState = pass->createTextureUnitState("decal.png");
texState->setProjectiveTexturing(true, mDecalFrustum);
texState->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
texState->setTextureFiltering(Ogre::FO_POINT, Ogre::FO_LINEAR, Ogre::FO_NONE);
Tenemos que establecer el modo de direccionamiento de la textura para que el decal no "entre en bucle" sobre si mismo sobre el objeto. Para las opciones de filtrado, tenemos que establecer la magnificación del objeto para usar un estándar linear, pero básicamente hemos apagado el filtrado para minimizar (FO_POINT), y apagado el mipmapping por completo. Esto preserva el borde del decal (que es transparente) de que se vea difuso dentro del resto de la textura cuando es minimizada. Si no hacemos eso, habrá un efecto indeseado.

Esto es todo lo que necesitas hacer para instalar el material.

Llamada a Funciones


Ahora que hemos construido las funciones, necesitamos llamarlas en el instalador del proyector y el material. Añade el siguiente código al final del método createScene:
createProjector();
for (unsigned int i = 0; i < ent->getNumSubEntities(); i++)
{
 makeMaterialReceiveDecal(ent->getSubEntity(i)->getMaterialName());
}
Nota que la variable ent ya tiene una de las entities cabeza de Ogre guardada en ella desde el buble anterior. Ya que todas las cabezas de ogre usan el mismo material, sólo necesitamos seleccionar uno de ellos de forma aleatoria para obtener los nombres de material.

Compila y ejecuta la aplicación, deberías ver unas pocas cabezas de ogre con un decal proyectado en ellas.

Poniendo en orden la Proyección trasera

Introducción


Como has notado probablemente cuando ejecutas la aplicación, hay dos decals que son proyectados. El primero es el proyectado en la dirección -Z, que es donde nuestro frustum esta apuntando, el otro es proyectando en la dirección +Z, hacia las cabezas de ogre detras del frustum que hemos creado, una correspondencia (invertida) del decal es proyectada por detras de él.

Esto no es lo que queremos obviamente. Para arreglarlo introduciremos un filtro que eliminará la proyección trasera.

Modificando el Proyector


Para filtrar la proyección trasera, necesitamos un nuevo frustum para filtrar los puntos en la dirección que deseemos filtrar. Añade el siguiente código al método createProjector.
mFilterFrustum = new Ogre::Frustum();
mFilterFrustum->setProjectionType(Ogre::PT_ORTHOGRAPHIC);
Ogre::SceneNode *filterNode = mProjectorNode->createChildSceneNode("DecalFilterNode");
filterNode->attachObject(mFilterFrustum);
filterNode->setOrientation(Ogre::Quaternion(Ogre::Degree(90),Ogre::Vector3::UNIT_Y));
Esto debería resultarnos familiar. La única diferencia es que hemos rotado el nodo 90 grados.

Modificando el Material


Ahora necesitamos añadir otro estado de textura a la pasada que hemos añadido al material.
Añadir lo siguiente a makeMaterialReceiveDecal:
texState = pass->createTextureUnitState("decal_filter.png");
texState->setProjectiveTexturing(true, mFilterFrustum);
texState->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP);
texState->setTextureFiltering(Ogre::TFO_NONE);
Todo esto nos debería resultar familiar. Nota que estamos usando la textura de filtro, el filtro frustrum, y que estamos apagando el filtrado. Compila y ejecuta la aplicación. Deberías ver sólo la proyección delantera de los decals.

Mostrando la Proyección

Rotación simple


Para mostrar la proyección, necesitaremos rotar el proyector. Para rotar el proyector, simplemente añade la siguiente línea de código al comienzo del método frameRenderingQueued:
mProjectorNode->rotate(Ogre::Vector3::UNIT_Y, Ogre::Degree(evt.timeSinceLastFrame * 10));
Compila y ejecuta la aplicación. Ahora veras el decal proyectado a través del círculo de las cabezas de ogre.

Compilar y ejecutar la aplicación.

Una nota final


Una de las últimas cosas a notar de los decals, si usas decals en tu aplicación, asegúrate de que los píxeles del borde externo de los decals son completamente transparentes (alfa zero).

Trabajando sobre eso

El anterior consejo es verdadero, pero hay un modo de sortearlo si tu textura no tiene bordes transparentes:
Como tu textura es en un formato que soporta canales alfa (ej: PNG), puedes establecer el Mode de direccionamiento de Textura a TAM_BORDER (border, si estas haciendo esto en un script) y establecer el Color de Borde de la Textura a (0,0,0,0). Esto causa que cualquier coordenada de textura fuera del rango 0,1 tiene el color de borde que especifiques, lo que es black (negro) con alfa 0. Así que esencialmente, sólo has añadido un borde transparente a tu textura.

Puedes buscar una implementación rápida de Decals Proyectivos.

Siguiente, Tutorial Intermedio 7 Renderizado a Textura (RTT)