Tutorial Básico 3 Terreno, Cielo, y Niebla


external image dl1773

Introducción del Tutorial


En este tutorial exploramos como manipular el terreno, el cielo, y la niebla en tus aplicaciones de Ogre. Después de este tutorial deberías comprender las diferencias entre Skyboxes (Cajas Celestes), Skyplanes (planos celestes) y Skydomes (Dominios celestes), y serás capaz de usarlos.
También conocerás la diferencia entre los diversos tipos de niebla, y como usarlos.

external image help.gifLos problemas que encuentres mientras trabajes en este tutorial deberías consultarlos en los Foros de Ayuda.

Prerrequisitos


  • Este tutorial asume que tienes conocimientos sobre programación en C++ y eres capaz de compilar e instalar una aplicación de Ogre.
  • Este tutorial también asume que has creado un proyecto usando el Tutorial Framework Ogre Wiki, manualmente, usando CMake o el AppWizard de Ogre - mirar Creando una aplicación para más instrucciones.
  • Este tutorial se compila sobre la versión anterior de los tutoriales básicos, asi que se asume que has trabajado con ellos.

Cuando mires el tutorial deberías ir lentamente añadiendo código a tu proyecto y mirando los resultados. Puedes ver el código fuente para ver el estado final del tutorial aquí. Si tienes problemas con el código, deberías comparar tu fuente del proyecto al resultado final.

Comenzando


Código


Modifica la cabecera de la clase Tutorial Básico 3 para que sea como esta:

cabecera BasicTutorial3
#include <Terrain/OgreTerrain.h>
#include <Terrain/OgreTerrainGroup.h>
class BasicTutorial3 : public BaseApplication
{
private:
 Ogre::TerrainGlobalOptions* mTerrainGlobals;
 Ogre::TerrainGroup* mTerrainGroup;
 bool mTerrainsImported;
 
 void defineTerrain(long x, long y);
 void initBlendMaps(Ogre::Terrain* terrain);
 void configureTerrainDefaults(Ogre::Light* light);
 
public:
 BasicTutorial3(void);
 virtual ~BasicTutorial3(void);
 
protected:
 virtual void createScene(void);
 virtual void createFrameListener(void);
 virtual void destroyScene(void);
 virtual bool frameRenderingQueued(const Ogre::FrameEvent& evt);
};
Asegúrate que la implementación del archivo de Tutorial Básico 3 se parece a esto:

implementacion BasicTutorial3
void BasicTutorial3::destroyScene(void)
{
}
//-------------------------------------------------------------------------------------
void getTerrainImage(bool flipX, bool flipY, Ogre::Image& img)
{
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::defineTerrain(long x, long y)
{
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::initBlendMaps(Ogre::Terrain* terrain)
{
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::configureTerrainDefaults(Ogre::Light* light)
{
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::createFrameListener(void)
{
 BaseApplication::createFrameListener();
}
//-------------------------------------------------------------------------------------
void BasicTutorial3::createScene(void)
{
}
//-------------------------------------------------------------------------------------
bool BasicTutorial3::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
 bool ret = BaseApplication::frameRenderingQueued(evt);
 return ret;
}

Entrada del Enlazador


Para ser capaz de compilar este código, necesitas enlazar el componente de Terreno de Ogre.
Añade "OgreTerrain.lib" para release y "OgreTerrain_d.lib" para depuración a tu entrada de librería de proyecto sobre Windows.

Pulsa para instrucciones

Terreno


En versiones anteriores de Ogre, tenías que usar el SceneManager de Terreno si querías introducir terrenos en tus aplicaciones de Ogre.
Nunca más, gracias al componente de Terreno de Ogre.
Desde Ogre 1.7 (Cthugha), hay tres componentes disponibles:
Terreno (Terrain), Paginado (Paging) y Propiedad (Property).

Los componentes de Terreno y Paginado tienen una relacion especial en la que el Terreno en Ogre está disponible para ser usado por el componente de paginado - sí, lo adivinastes: paginando.

Este tutorial te introducirá al componente de Terreno de Ogre, y dejará el paginado para un tutorial posterior.

Tratando con la cámara


Primero, vamos a modificar nuestro objeto Cámara para nuestro Terreno.
Poner este código en la funcion BasicTutorial3::createScene:
mCamera->setPosition(Ogre::Vector3(1683, 50, 2116));
 mCamera->lookAt(Ogre::Vector3(1963, 50, 1660));
 mCamera->setNearClipDistance(0.1);
 mCamera->setFarClipDistance(50000);
 if (mRoot->getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_INFINITE_FAR_PLANE))
 {
 mCamera->setFarClipDistance(0); // activar distancia infinita de recorte si se puede
 }
Que hace, detrás de los ajustes de posición y orientación de la cámara, se ajustan las distancias de recorte cercano y lejano.
Un terreno es frecuentemente bastante grande, y queremos que la cámara sea capaz de ver lejos en la distancia.
Si el sistema de renderizado lo soporta, hazla infinita.

Estableciendo una luz direccional y ambiental


El componente de Terreno usa una luz direccional para computar el mapa de luces del terreno, asi que vamos a poner una luz direccional dentro de nuestra escena:
Ogre::Vector3 lightdir(0.55, -0.3, 0.75);
 lightdir.normalise();
 
 Ogre::Light* light = mSceneMgr->createLight("tstLight");
 light->setType(Ogre::Light::LT_DIRECTIONAL);
 light->setDirection(lightdir);
 light->setDiffuseColour(Ogre::ColourValue::White);
 light->setSpecularColour(Ogre::ColourValue(0.4, 0.4, 0.4));
 
 mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));
También establecemos una luz de ambiente suave.

Configurando nuestro terreno


Primero, creamos un nuevo conjunto de opciones de terreno global:
mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
Entonces construimos nuestro objeto TerrainGroup, que esta manejado por nuestras instancias de Terreno:
mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(mSceneMgr, Ogre::Terrain::ALIGN_X_Z, 513, 12000.0f);
 mTerrainGroup->setFilenameConvention(Ogre::String("BasicTutorial3Terrain"), Ogre::String("dat"));
 mTerrainGroup->setOrigin(Ogre::Vector3::ZERO);
El constructor de la clase TerrainGroup coge nuestra instancia de SceneManager, la opción de alineacion del terreno, el tamaño del terreno y el tamaño del mundo del terreno como parámetros.
Entonces le decimos al TerrainGroup que nombre nos gustaría usar cuando salvemos nuestro terreno, usando la función setFilenameConvention.
Y finalmente establecemos el origen del grupo del terreno.

Ahora es tiempo de configurar nuestro terreno:
configureTerrainDefaults(light);
Trataremos con la función configureTerrainDefaults más tarde.
Nota que hemos pasado nuestra luz direccional a la función.

Y entonces definimos nuestros terrenos e indicamos a TerrainGroup que los cargue todos:
for (long x = 0; x <= 0; ++x)
 for (long y = 0; y <= 0; ++y)
 defineTerrain(x, y);
 
 // sincroniza la carga ya que queremos que todo se coloque cuando comencemos
 mTerrainGroup->loadAllTerrains(true);
Ya que sólo tenemos un terreno, sólo llamaremos a la función defineTerrain una vez.

Pero si tenemos múltiples, deberíamos hacerlo aquí.
Trataremos con la función defineTerrain más tarde.

Ahora, si sólo hemos importado nuetros terrenos, nos gustaría que nuestros mapas de mezcla sean calculados:
if (mTerrainsImported)
 {
 Ogre::TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator();
 while(ti.hasMoreElements())
 {
 Ogre::Terrain* t = ti.getNext()->instance;
 initBlendMaps(t);
 }
 }
El código se repite a través de los terrenos disponibles y llama a initBlendMaps en cada uno. La función initBlendMaps se explicará más tarde.

Ahora, Todo lo que está a la izquierda se limpiará después de la creacion inicial del terreno:
mTerrainGroup->freeTemporaryResources();

El createScene más o menos


Así es como nuestra función createScene debería quedar:
void BasicTutorial3::createScene(void)
{
 mCamera->setPosition(Ogre::Vector3(1683, 50, 2116));
 mCamera->lookAt(Ogre::Vector3(1963, 50, 1660));
 mCamera->setNearClipDistance(0.1);
 mCamera->setFarClipDistance(50000);
 
 if (mRoot->getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_INFINITE_FAR_PLANE))
 {
 mCamera->setFarClipDistance(0); // activar la distancia de recorte lejana a infinito si podemos
 }
 
 Ogre::MaterialManager::getSingleton().setDefaultTextureFiltering(Ogre::TFO_ANISOTROPIC);
 Ogre::MaterialManager::getSingleton().setDefaultAnisotropy(7);
 
 Ogre::Vector3 lightdir(0.55, -0.3, 0.75);
 lightdir.normalise();
 
 Ogre::Light* light = mSceneMgr->createLight("tstLight");
 light->setType(Ogre::Light::LT_DIRECTIONAL);
 light->setDirection(lightdir);
 light->setDiffuseColour(Ogre::ColourValue::White);
 light->setSpecularColour(Ogre::ColourValue(0.4, 0.4, 0.4));
 
 mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));
 
 mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
 
 mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(mSceneMgr, Ogre::Terrain::ALIGN_X_Z, 513, 12000.0f);
 mTerrainGroup->setFilenameConvention(Ogre::String("BasicTutorial3Terrain"), Ogre::String("dat"));
 mTerrainGroup->setOrigin(Ogre::Vector3::ZERO);
 
 configureTerrainDefaults(light);
 
 for (long x = 0; x <= 0; ++x)
 for (long y = 0; y <= 0; ++y)
 defineTerrain(x, y);
 
 // sincroniza la carga ya que queremos todo en su lugar cuando comencemos
 mTerrainGroup->loadAllTerrains(true);
 
 if (mTerrainsImported)
 {
 Ogre::TerrainGroup::TerrainIterator ti = mTerrainGroup->getTerrainIterator();
 while(ti.hasMoreElements())
 {
 Ogre::Terrain* t = ti.getNext()->instance;
 initBlendMaps(t);
 }
 }
 
 mTerrainGroup->freeTemporaryResources();
}
external image Tip_icon.pngNo intentar compilarlo ahora - no funcionará hasta que implementemos las funciones de utilidad del terreno.

configureTerrainDefaults (configurar los terrenos por defecto)


El componente de Terreno de Ogre es bastante configurable.
mTerrainGlobals es nuestra instancia de Ogre::TerrainGlobalOptions.

Encuentra la función configureTerrainDefaults y añade el siguiente código a ella:
 // Configuracion global
mTerrainGlobals->setMaxPixelError(8);
 // comprobando el mapa de composicion
mTerrainGlobals->setCompositeMapDistance(3000);
Primero, establecemos dos opciones globales: MaxPixelError y CompositeMapDistance.
MaxPixelError decide como de preciso es nuestro terreno. Un número bajo significa un terreno más preciso, a costa del rendimiento (por causa de la cantidad de vértices).
CompositeMapDistance decide como de lejos el terreno en Ogre renderizará el terreno mapeado de luz.
Siguiente, tratemos con el mapeado de luz (lightmapping), usando nuestra luz direccional:
 // Importante establecer esto para que asi el terreno sepa como usar los datos derivados (no-en tiempo real)
mTerrainGlobals->setLightMapDirection(light->getDerivedDirection());
mTerrainGlobals->setCompositeMapAmbient(mSceneMgr->getAmbientLight());
mTerrainGlobals->setCompositeMapDiffuse(light->getDiffuseColour());
Usa nuestra luz para establecer la dirección y color de difusión y establece el color de difusión para que coincida con nuestra luz ambiental de la scene manager.

Lo siguiente que hacemos es establecer algunos ajustes de los valores de ImportData:
// Configura los ajustes por defecto de import para si usamos imagenes importadas
Ogre::Terrain::ImportData& defaultimp = mTerrainGroup->getDefaultImportSettings();
defaultimp.terrainSize = 513;
defaultimp.worldSize = 12000.0f;
defaultimp.inputScale = 600;
defaultimp.minBatchSize = 33;
defaultimp.maxBatchSize = 65;
No hablaremos ahora de que y como usar estos valores, pero terrainSize y worldSize son establecidos para que coincidan con nuestros tamaños globales (los que digimos a nuestro TerrainGroup), e inputScale decide como la imagen heightmap se escala. Usamos una escala aquí porque las imágenes tienen un limite de precisión.

Un heightmap en crudo, por ejemplo, no es normalmente necesario para escalar porque los valores se guardan como un array de floats sin escalar.

El último trozo son nuestras texturas:
 // texturas
defaultimp.layerList.resize(3);
defaultimp.layerList[0].worldSize = 100;
defaultimp.layerList[0].textureNames.push_back("dirt_grayrocky_diffusespecular.dds");
defaultimp.layerList[0].textureNames.push_back("dirt_grayrocky_normalheight.dds");
defaultimp.layerList[1].worldSize = 30;
defaultimp.layerList[1].textureNames.push_back("grass_green-01_diffusespecular.dds");
defaultimp.layerList[1].textureNames.push_back("grass_green-01_normalheight.dds");
defaultimp.layerList[2].worldSize = 200;
defaultimp.layerList[2].textureNames.push_back("growth_weirdfungus-03_diffusespecular.dds");
defaultimp.layerList[2].textureNames.push_back("growth_weirdfungus-03_normalheight.dds");
Aqui establecemos el número de capas de textura de terreno a 3, llamando a defaultimp.layerList.resize(3)
Entonces inicializamos cada capa estableciendo el "worldSize" (tamaño del mundo) y especificando los nombres de las texturas.
"worldSize" decide como de grande cada conjunto de texturas sera. Un valor pequeño incrementará la resolución de la capa de textura renderizada.

El generador de material por defecto toma dos texturas por capa:

  1. diffuse_specular - textura difusa con un mapa especular en el canal alfa
  2. normal_height - mapa normal con un mapa de altura en el canal alfa

Mirar las Texturas de Terreno de Ogre si quieres saber como estas texturas están hechas.

Nuestra función configureTerrainDefaults se parecerá a esta:
void BasicTutorial3::configureTerrainDefaults(Ogre::Light* light)
{
 // Configuracion global
mTerrainGlobals->setMaxPixelError(8);
 // Comprobar mapa de composicion
mTerrainGlobals->setCompositeMapDistance(3000);
 
 // Importante para establecer estos, asi el terreno sabra que usar para datos derivados (no-en tiempo real)
mTerrainGlobals->setLightMapDirection(light->getDerivedDirection());
mTerrainGlobals->setCompositeMapAmbient(mSceneMgr->getAmbientLight());
mTerrainGlobals->setCompositeMapDiffuse(light->getDiffuseColour());
 
 // Configuracion de los ajustes por defecto de import por si usas una imagen importada
Ogre::Terrain::ImportData& defaultimp = mTerrainGroup->getDefaultImportSettings();
defaultimp.terrainSize = 513;
defaultimp.worldSize = 12000.0f;
defaultimp.inputScale = 600;
defaultimp.minBatchSize = 33;
defaultimp.maxBatchSize = 65;
// texturas
defaultimp.layerList.resize(3);
defaultimp.layerList[0].worldSize = 100;
defaultimp.layerList[0].textureNames.push_back("dirt_grayrocky_diffusespecular.dds");
defaultimp.layerList[0].textureNames.push_back("dirt_grayrocky_normalheight.dds");
defaultimp.layerList[1].worldSize = 30;
defaultimp.layerList[1].textureNames.push_back("grass_green-01_diffusespecular.dds");
defaultimp.layerList[1].textureNames.push_back("grass_green-01_normalheight.dds");
defaultimp.layerList[2].worldSize = 200;
defaultimp.layerList[2].textureNames.push_back("growth_weirdfungus-03_diffusespecular.dds");
defaultimp.layerList[2].textureNames.push_back("growth_weirdfungus-03_normalheight.dds");
}

defineTerrain (definiendo el terreno)


Esta es nuestra función de definición del terreno:
void BasicTutorial3::defineTerrain(long x, long y)
{
 Ogre::String filename = mTerrainGroup->generateFilename(x, y);
 if (Ogre::ResourceGroupManager::getSingleton().resourceExists(mTerrainGroup->getResourceGroup(), filename))
 {
 mTerrainGroup->defineTerrain(x, y);
 }
 else
 {
 Ogre::Image img;
 getTerrainImage(x % 2 != 0, y % 2 != 0, img);
 mTerrainGroup->defineTerrain(x, y, &img);
 mTerrainsImported = true;
 }
}
Esta función es simple, pero clara:

Primero, pregunta a nuestro TerrainGroup que nombre de fichero usará para generar el terreno.
Entonces comprueba si hay un archivo por su nombre en nuestro grupo de recursos.
Si está, significa que ya hemos generado un archivo de datos de terreno binario, y no hay necesidad de importarlo de una imagen.
Si no se encuentra un archivo de datos, significa que tendremos que generar nuestro terreno, y cargaremos la imagen y usaremos esto para definirla.

La función usa una pequeña función de utilidad llamada getTerrainImage.

getTerrainImage


Ya que esta función es muy pequeña y solo se usa una vez, se encuentra en el archivo de implementación como una función local estática:
void getTerrainImage(bool flipX, bool flipY, Ogre::Image& img)
{
img.load("terrain.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
 if (flipX)
 img.flipAroundY();
 if (flipY)
 img.flipAroundX();
}
Este carga "terrain.png" desde nuestras localizaciones de recursos.

initBlendMaps


Esta es la función initBlendMaps:
void BasicTutorial3::initBlendMaps(Ogre::Terrain* terrain)
{
 Ogre::TerrainLayerBlendMap* blendMap0 = terrain->getLayerBlendMap(1);
 Ogre::TerrainLayerBlendMap* blendMap1 = terrain->getLayerBlendMap(2);
 Ogre::Real minHeight0 = 70;
 Ogre::Real fadeDist0 = 40;
 Ogre::Real minHeight1 = 70;
 Ogre::Real fadeDist1 = 15;
 float* pBlend1 = blendMap1->getBlendPointer();
 for (Ogre::uint16 y = 0; y < terrain->getLayerBlendMapSize(); ++y)
 {
 for (Ogre::uint16 x = 0; x < terrain->getLayerBlendMapSize(); ++x)
 {
 Ogre::Real tx, ty;
 
 blendMap0->convertImageToTerrainSpace(x, y, &tx, &ty);
 Ogre::Real height = terrain->getHeightAtTerrainPosition(tx, ty);
 Ogre::Real val = (height - minHeight0) / fadeDist0;
 val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
 
 val = (height - minHeight1) / fadeDist1;
 val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1);
 *pBlend1++ = val;
 }
 }
 blendMap0->dirty();
 blendMap1->dirty();
 blendMap0->update();
 blendMap1->update();
}
No queremos entrar en detalles de como funciona en este tutorial.
Sólo diremos que usa el alto del terreno para elevar las tres capas sobre el terreno.
Date cuenta del uso de getLayerBlendMap y getBlendPointer.

Compila y Ejecuta - parte 1


Da un paso adelante, compila y ejecuta nuestra aplicación del Tutorial Básico 3 ahora. Sonríe.

Deberías haber conseguido un bonito terreno renderizado.

Sin embargo, podemos mejorar nuestra pequeña aplicación añadiéndole tres cosas:

  1. Un indicador de generación de Terreno
  2. Salvando el terreno
  3. Autolimpieza

Primero, necesitaremos añadir un nuevo miembro de datos a la clase BasicTutorial3:
OgreBites::Label* mInfoLabel;
Entonces necesitamos sobreescribir tres funciones en nuestra clase BasicTutorial3:
  1. frameRenderingQueued - muestra el proceso de generaciones de terreno y lo salva cuando este hecho.
  2. createFrameListener - construye la etiqueta de información
  3. destroyScene - autolimpieza

Así, sin más dilación:

frameRenderingQueued

bool BasicTutorial3::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
 bool ret = BaseApplication::frameRenderingQueued(evt);
 if (mTerrainGroup->isDerivedDataUpdateInProgress())
 {
 mTrayMgr->moveWidgetToTray(mInfoLabel, OgreBites::TL_TOP, 0);
 mInfoLabel->show();
 if (mTerrainsImported)
 {
 mInfoLabel->setCaption("Building terrain, please wait...");
 }
 else
 {
 mInfoLabel->setCaption("Updating textures, patience...");
 }
 }
 else
 {
 mTrayMgr->removeWidgetFromTray(mInfoLabel);
 mInfoLabel->hide();
 if (mTerrainsImported)
 {
 mTerrainGroup->saveAllTerrains(true);
 mTerrainsImported = false;
 }
 }
 
 return ret;
}
Primero preguntamos a nuestro TerrainGroup si estamos trabajando en la generación del terreno:isDerivedDataUpdateInProgress (la actualización de datos derivados está en proceso).
Si esta trabajando en ella, dejamos que muestre una etiqueta de información con el titulo correcto.

Si no esta trabajando en ello, comprueba si sólo hemos importados un nuevo terreno.
Si lo hicimos, entonces salva todos los terrenos, asi que podremos saltarnos el proceso de generación la proxima vez que ejecutemos nuestra aplicación.

createFrameListener


Hemos sobreescrito esta función porque queremos añadir una nueva etiqueta de información a nuestro OgreBites::SdkTrayManager 'mTrayMgr'.
Ya que la BaseApplication lo construyó en la función createFrameListener, no podemos añadirlo en nuestra función "createScene", porque la función createFrameListener se llama después de createScene...
Muy simple:
void BasicTutorial3::createFrameListener(void)
{
BaseApplication::createFrameListener();
mInfoLabel = mTrayMgr->createLabel(OgreBites::TL_TOP, "TInfo", "", 350);
}
Llama a BaseApplication::createFrameListener y entonces adéntrate en la etiqueta de información.

destroyScene


Aquí es donde debemos limpiarnos a nosotros mismos:
void BasicTutorial3::destroyScene(void)
{
OGRE_DELETE mTerrainGroup;
OGRE_DELETE mTerrainGlobals;
}
OGRE_NEW necesita de OGRE_DELETE

Compila y ejecuta - parte 2


Si compilas y ejecutas ahora, deberías ver una etiqueta de información diciéndote que el TerrainGroup está ocupado generando el terreno.
Cuando la etiqueta desaparece, el TerrainGroup salvará el terreno.

Probablemente notarás que no puedes salir del programa mientras la generación del terreno está en progreso.

Sin embargo, si ejecutas un programa una segunda vez ahora, no recibiras ningún mensaje por completo, y el comienzo será mucho más rápido, porque cargaremos el archivo de terreno generado directamente.

Ve más allá y mira dentro de OGRE_MEDIA/media.
Deberías ver un archivo "BasicTutorial3Terrain_00000000.dat" aquí...

Cielo


Ogre proporciona tres tipos diferentes de cielo: SkyBoxes (Cajas celestes), SkyDomes (Dominios celestes), y SkyPlanes (Planos celestes). Hablaremos de ellos en detalle.

Skyboxes (Cajas celestes)


Una SkyBox es básicamente un cubo gigante que rodea a todos los objetos en la escena. El mejor modo de describirlo es mostrarte como es. Añade esta línea al código de createScene:
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox");
Compila y ejecuta el programa. (Nota que la SkyBox esta granulada porque la textura actual tiene poca resolución; una SkyBox de mayor resolución se vería mucho mejor.) Hay varios parámetros útiles para las SkyBoxes que podremos activar cuando llamemos a setSkyBox. La primera opción es si esta o no activado el SkyBox. Si quieres que más tarde se desactive el SkyBox llama a "mSceneMgr->setSkyBox(false, "");". El segundo parametro es el script de material usado para la SkyBox.

El tercer y cuarto parámetro para setSkyBox son muy importantes de comprender. El tercer parámetro establece la distancia a la que el SkyBox está de la cámara, y el cuarto parámetro establece si el SkyBox se dibuja antes que el resto de la escena. Así, que veamos que ocurre si cambiamos el parámetro de distancia de las 5000 unidades por defecto a algo más cercano:
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 10);
No ocurrió nada! Esto es porque el cuarto parámetro que controla si el SkyBox se dibuja primero o no está establecido a true por defecto. Si el SkyBox se dibuja primero, entonces todo se renderiza después (como nuetro terreno) que se dibujará sobre este, haciendo que el SkyBox siempre aparezca en el fondo. (Nota que no deberías establecer la distancia por delante de la distancia de recorte cercano de la cámara o no se mostrará!) No es actualmente deseable dibujar el SkyBox primero, porque la cosa completa se renderiza. Cuando lo dibujas lo último, sólo las porciones visibles serán dibujadas, lo cual proporcionará una mejora modesta de la velocidad. Así, que intentemos establecer nuestros ajustes para que el SkyBox se dibuje el último:
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 5000, false);
Otra vez, esto aparece como lo hizo antes, pero ahora las partes del SkyBox que no están visibles no serán renderizadas. hay una cosa con la que tienes que tener cuidado cuando uses esta técnica. Si estableces SkyBox para que este muy cerca, podría ser que eliminaras parte de la geometría de la escena. Por ejemplo, intenta esto:
mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 100, false);
Como vistes ahora , el terreno se recorta por el SkyBox. Definitivamente no lo queremos. Si usas SkyBoxes en tu aplicación tendrás que decidir como querer usarlos. El aumento de velocidad que conseguirás renderizando el SkyBox después del terreno es muy modesto, y tienes que tener cuidado de no obstruir tu geometría (a menos que sea lo que busques). Generalmente hablando, dejando todo como esta, el segundo parámetro por defecto es una elección muy segura.

SkyDomes (Dominios celestes)


Los SkyDomes son muy similares a las SkyBoxes, y puedes usarlos llamando a setSkyDome. Un cubo gigante se creará alrededor de la Cámara y se renderizará, pero la mayor diferencia es que la textura es "proyectada" sobre la SkyBox de una manera esférica. Todavía estarás usando un cubo, pero parecerá como si la textura estuviera enrollada alrededor de la superficie de una esfera. El primer esbozo de este método es que la parte inferior del cubo estará sin texturar, así que siempre necesitarás tener algún tipo de terreno que oculte la base.

La textura de ejemplo que Ogre proporciona para los SkyDomes te dejará ver esto claramente. Limpia el setSkyBox llamado desde la createScene y añadiendo este código en su lugar:
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8);
Cuando ejecutes esto, mueve la Cámara al centro de la zona muerta del terreno y mueve la Cámara así que se posicione cerca de la superficie del terreno (esto parece lo mejor). Después de mirar esto, pulsa el botón R para cambiar la vista de la malla. Como puedes ver, todavía miramos un cubo (sin la base), pero parece que si las nubes están enrolladas alrededor de la esfera en lo alto. (También nota que el movimiento de las nubes es propiedad del material "Examples/CloudySky", no de los SkyDomes en general.)

Los dos primeros parámetros de setSkyDome son igual que para setSkyBox, y puede apagar el SkyDome llamando a "mSceneMgr->setSkyDome(false, "");". El tercer parametro es la curvatura usada por el SkyDome. La referencia API sugiere usar valores entre 2 y 65; bajos para un mejor efecto de la distancia, pero valores mayores para menor distorsión y efecto más suave. Intenta ajustar el tercer parámetro de 2 a 65 y mira la diferencia. El efecto de distancia al que la API de referencia se refiere puede ser claramente visto en estos pantallazos. Esto esta ajustado a la curvatura de 2:

Este es el ajuste de curvatura a 64:

El cuarto parámetro es el número de veces que la textura es enlosada, la cual necesitarás ajustar dependiendo del tamaño de la textura. Asegúrate de notar que este parámetro es el valor real (punto flotante) y no un entero. Puedes enlosarlo 1.234 veces, que se verá bien en tu aplicacion. El quinto y sexto parámetros son la distancia y primer dibujado, respectivamente, lo que ya hemos cubierto en la sección SkyBox.

SkyPlanes (Planos celestes)


Los SkyPlanes son muy diferentes de los SkyBoxes y SkyDomes. En vez de un cubo para renderizar la textura del cielo, usaremos sólo un único plano. (Nota que todas las configuraciones de SkyPlane que necesitas estarán en el medio del terreno y cerca de la tierra.) Limpia todo el código de SkyDome desde createScene. La primera cosa que haremos será crear un plano, y apuntarle. el método setSkyPlane que se llamará para no tener un parámetro de distancia como SkyBox y SkyDome. En vez de esto, ese parámetro se ajustará a la variable "d" del Plano:
Ogre::Plane plane;
plane.d = 1000;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
Ahora que tenemos el plano definido, podemos crear el SkyPlane. Nota que el cuarto parámetro es el tamaño del SkyPlane (en este caso 1500x1500 unidades) y el quinto parámetro es cuantas veces enlosar la textura:
mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 1500, 75);
Compila y ejecuta el programa. Hay dos problemas con el SkyPlane que se crea aquí. Lo primero de todo, la textura que se usa es de demasiado baja resolución, y no se azuleja bien. Esto podría ser fijado creando una buena, y gran textura de cielo de alta resolución que se enlose bien. Sin embargo, el problema principal con esta técnica es que si miras al horizonte, puedes ver donde termina el SkyPlane. Incluso si tienes una buena textura, podría no verse bien del todo si miras hacia el horizonte. Este uso básico de SkyPlane es útil realmente cuando tengas muros altos (o montañas) todas alrededor del punto de vista. Usar un SkyPlane en esta situación se considerará menos costoso computacionalmente que crear unos SkyBox/SkyDome completos.

Afortunadamente, esto no es todo lo que podemos hacer con un SkyPlane. El sexto parámetro para renderizar el skyplane es el parámetro familiar "renderFirst" que ya ha sido cubierto en las secciones SkyBox y SkyDome. El parámetro séptimo te permite especificar la curvatura del SkyPlane, asi que no tendremos que usar más de un plano, pero usaremos una superficie curva en su lugar. También tenemos que establecer el número de segmentos x e y usados para crear el SkyPlane (nicialmente el SkyPlane fue un gran cuadrado, pero si queremos curvarlo necesitamos tener el plano hecho de pequeños cuadrados). El octavo y noveno parámetros de la función son el número de segmentos x e y, respectivamente:
mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 1500, 50, true, 1.5f, 150, 150);
Compila y ejecuta la aplicación. Ahora nuestro SkyPlane parece mucho mejor, piensa otra vez que el enlosado podría costarte algo de trabajo. Podrías también usar esto con el material de nube (cloud) en su lugar:
mSceneMgr->setSkyPlane(true, plane, "Examples/CloudySky", 1500, 40, true, 1.5f, 150, 150);
Compila y ejecuta la aplicación. El movimiento de las nubes y el modo en que se enlosa se ve peor que con SkyDome , especialmente cuando estás cerca del borde del Terreno y miras hacia el horizonte.

Otra nota, puedes limpiar el SkyPlane llamando a "mSceneMgr->setSkyPlane(false, Ogre::Plane(), "");"

¿Cuál usar?


Que cielo usar depende enteramente de tu aplicación. Si has visto todos, incluso en la direccion negativa de y, entonces realmente sólo tienes una elección real, será SkyBox. Si tienes un terreno, o algún tipo de suelo con bloques de vista negativa y dirección, entonces usa un SkyDome para darte unos resultados más realistas. Para áreas donde no puedes ver el horizonte (tales como en un valle rodeado por montañas por todos los lados, o en un patio interior de un castillo), un SkyPlane te dará un resultado muy bueno con pequeños costos de GPU. La principal razon de usar un SkyPlane, como veremos en la siguiente sección, es porque se pueden utilizar efectos de niebla.

Son sólo sugerencias. Para tu aplicación deberias esperimentar y usar lo que se vea mejor.

Niebla


Introducción a Niebla


La cosa más importante que debes saber sobre los ajustes de niebla es que no se puede crear una entidad de niebla en un espacio vacío como podrías imaginar que se pudiera hacer. En vez de eso, la niebla es un filtro aplicado a los objetos que estas mirando. Esto tiene algunas implicaciones interesantes, la más relevante de ellas es que cuando no estás mirando a nada (ej: cuando no estás mirando a ningún objeto), no verás niebla. De hecho, sólo verás el color del fondo del viewport. Así, el modo de que puedas ver correctamente la niebla, tenemos que establecer el fondo al color de niebla usado.

Hay dos tipos básicos de niebla: linear y exponencial. La niebla lineal es delgada, mientras que la niebla exponencial aumenta exponencialmente. Es fácil ver la diferencia, en los ejemplos.

Tipos de Niebla


El primer tipo de niebla que estudiaremos será el linear, es la niebla más sencilla de comprender. La primera cosa que vamos a hacer después de que llamemos a setWorldGeometry es establecer el color del fondo del viewport. Podemos hacer esto sobreescribiendo la función createViewport (como lo hicimos en el último tutorial), pero a veces necesitamos establecerlo sin recrear el viewport cada vez. así es como lo haremos:
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
Podría usar la función miembro getNumViewports para conseguir el número de Viewports e iterar sobre ellos si tienes más de uno, aunque es un caso raro (sabemos que sólo tenemos uno), podemos conseguir sólo el viewport directamente. Una vez que hemos establecido el color del fondo, podemos crear la niebla:
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 50, 500);
El primer parámetro de la función setFog es el tipo de niebla (en este caso, linear). El segundo parámetro de setFog es el color de la niebla que estamos usando (en este caso un gris muy, muy claro o "WhiteSmoke" para C#). El tercer parámetro no se usa en la niebla linear. Los parámetros cuarto y quinto especifican el rango donde la niebla cambia. En este caso tenemos que establecer el punto de comienzo de la niebla para que sea 50 y el punto final en 500. Esto significa que desde 0 a 50 unidades enfrente de la camara, no hay niebla. Desde 50 a 500 unidades de la camara, la niebla cambia de una manera linear. Desde las 500 unidades en adelante, no veras otra cosa que niebla. Compila y ejecuta la aplicación.

Otro tipo de niebla que podemos usar es la exponencial. En vez de establecer los límites de comienzo y parada para la niebla, estableceremos la densidad de la niebla (el cuarto y el quinto parámetros no se usan). Reemplaza la anterior llamada a setFog con esto:
mSceneMgr->setFog(Ogre::FOG_EXP, fadeColour, 0.005);
Compila y ejecuta la aplicación

Esto crea una apariencia diferente para la niebla que es generada. Hay también otra función de niebla exponencial que es más severa que la anterior (ej: la niebla es mucho mas gruesa por cada unidad que se mueve desde la cámara comparada a la primera función de niebla). Nota que hay más niebla por densidad cuando usas FOG_EXP2. Reemplaza la anterior llamada a setFog con esto:
mSceneMgr->setFog(Ogre::FOG_EXP2, fadeColour, 0.003);
Compila y ejecuta la aplicación otra vez. La niebla es intercambiable entre tres funciones que proporciona Ogre. Deberías experimentar con los tres tipos de funciones de niebla y mirar cual se ve mejor en tu aplicación.

Niebla y Cielo


Puedes tener problemas cuando intentes ejecutar la niebla con los SkyBox y SkyDome. Ya que los SkyDomes y SkyBoxes son solo cubos, usarlos con la niebla es problemático ya que la niebla funciona de una manera esférica. Limpia los contendios del metodo createScene. Si hemos elegido SkyDome y los parámetros de niebla, podemos ver el problema directamente:
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 10, 1200);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
mSceneMgr->setSkyDome(true, "Examples/CloudySky", 5, 8, 500);
Compila y ejecuta la aplicación. Si mueves la camara alrededor, verás diferentes porciones de SkyDome a través de la niebla dependiendo de que partes del SkyDome estás mirando (nota que el azul viene por los costados , pero no por el medio):


Esto no es lo que queremos. Otra opción es usar en su lugar un SkyPlane. Reemplaza el código en createScene con esto:
Ogre::ColourValue fadeColour(0.9, 0.9, 0.9);
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 10, 1200);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
Ogre::Plane plane;
plane.d = 100;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
mSceneMgr->setSkyPlane(true, plane, "Examples/CloudySky", 500, 20, true, 0.5, 150, 150);
Esto parece correcto. Si miramos más allá podremos ver el cielo (lo cual en este caso es lo que ocurre en la realidad si la niebla es sólo derecha), pero no se encamina de maneras extrañas. Da igual si usas curvatura o no, esto resulte nuestro problema de que el usuario sea capaz de ver el horizonte donde el SkyPlane no se ve bien.

Hay un modo de que la niebla no afecte al cielo por completo, pero requiere de modificar el script de material para la textura del cielo. Esta fuera del objetivo de este tutorial, pero para futuras referencias este parámetro desactiva la niebla para un material.

Niebla como Oscuridad


No querrás usar el cielo todas las veces que uses la niebla, porque si la niebla es lo bastante espesa no verás el cielo. El truco con la niebla que describimos a continuación nos permite realizar una argucia gráfica muy útil en algunos casos. En vez de establecer la niebla a un color brillante, establezcamosla a un muy oscuro y veamos que sucede (nota que hemos establecido SkyPlane para que sea este a 10 unidades desde la cámara, lo que es anterior al establecimiento de la niebla):
Ogre::ColourValue fadeColour(0.1, 0.1, 0.1);
mWindow->getViewport(0)->setBackgroundColour(fadeColour);
mSceneMgr->setFog(Ogre::FOG_LINEAR, fadeColour, 0.0, 10, 150);
Ogre::Plane plane;
plane.d = 10;
plane.normal = Ogre::Vector3::NEGATIVE_UNIT_Y;
mSceneMgr->setSkyPlane(true, plane, "Examples/SpaceSkyPlane", 100, 45, true, 0.5, 150, 150);
Compila y ejecuta la aplicación. Esto es lo que conseguiremos:

No es tan terrible. De acuerdo, una vez que seas capaz, deberiás usar apropiadamente la iluminación en vez de este truco, pero muestra la flexibilidad de la niebla, y algunas cosas interesantes que puedes hacer con el motor. Usando la niebla negra también podremos hacer un efecto cegador o de oscuridad. Si estás escribiendo un juego que use la vista en primera persona.

Siguiente, Tutorial Básico 4 Manejadores de Frame (FrameListeners) y Entrada sin búfer