18-08-2015, 09:00 PM
Tutorial 12: Terrain Rendering
In questo tutorial mostreremo brevemente come utilizzare il render dei terreni di Irrlicht. Vedremo anche il funzionamento del selettore di triangoli per gestire le collisioni con i terreni.
Da notare che il Terrain Renderer di Irrlicht è basato sul lavoro dell'utente Spintz chiamato GeoMipMapSceneNode, a cui vanno i nostri ringraziamenti. L'utente DeusXL invece ha realizzato un nuovo, semplice ed elegante sistema per creare terreni di dimensioni molto più grandi tramite heightmaps di dimensioni ridotte, il terrain smoothing.
Nella parte iniziale non c'è niente di speciale. Includiamo i soliti file header necessari e creiamo un gestore die venti (event listener) per gestire la pressione del tasti: 'W' per passare alla modalità wireframe, il tasto 'P' per la modalità pointcloud ed il tasto 'D' per cambiare il materiale del terreno tra il 'solid' ed il 'detail mapped'.
La funzione main parte come in molti altri esempi Chiediamo all'utente quale renderer usare e lo avviamo. Stavolta però con la gestione avanzata dei parametri.
Prima, aggiungiamo le solite cose standard: Il logo di Irrlicht, un piccolo testo di aiuto, una camera comandata dall'utente, e disabilitiamo il cursore del mouse.
Ecco che poi arriva il nodo di scena che gestisce il terreno: lo aggiungiamo come faremmo con un qualsiasi altro nodo, cioè usando ISceneManager::addTerrainSceneNode(). L'unico parametro da usare è il file name della heightmap. Una heightmap è una semplice texture a scale di grigio (tipicamente sono sfumature che vanno dal nero al bianco corrispondenti allo 0 fino al 255 ndt1). Il terrain renderer legge la heightmap e con essa crea il terreno in 3D.
Per rendere il terreno più grande cambiamo il fattore di scala a (40, 4.4, 40) (notare la X e la Z sono le ampiezze planari mentre l'asse Y che indica la quota delle montagne è stata scalata molto di meno per non avere picchi troppo ripidi ndt). Non avendo nessuna luce dinamica nella scena, disabilitiamo ll'illuminazione sul terreno, impostiamo la texture del terreno terrain-texture.jpg come prima texture e la detailmap3.jpg come seconda texture per i dettagli. Alla fine andiamo ad indicare la scala delle texture rispetto al terreno stesso (in pratica come lo ricopre): la prima texture verrà ripetuta una sola volta su tutto il terreno (lo ricopre esattamente), mentre la seconda verrà ripetuta 20 volte (più piccola e riporta le asperità del terreno).
Per abilitare il terreno a ricevere le collisioni creiamo un triangle selector. Se volete sapere cosa fa un triangle selector, vi invito a dare un'occhiata al tutorial delle collisioni. Il terrain triangle selector lavora sul terreno. Per dimostrarlo creiamo un collision response animator ed agganciamolo alla camera, in questo modo la camera non potrà più attraversare la mesh del terreno.
Per accedere ad i dati di un terreno lo si può fare direttamente con il seguente codice.
Ora per permettere all'utente di cambiare la visualizzazione tra normale e wireframe, creiamo un'istanza del event receiver dichiarato prima e facciamolo conoscere ad Irrlicht. In più aggiungiamo una skybox (un cubo texturizzato con mapping sferico e con le facce rovesciate all'interno che segue la camera per l'effetto cielo e già usato negli esempi precedenti ndt) ed uno skydome (di solito una sfera o mezza sfera sempre con facce rovesciate internamente) e mostriamoli mutuamente tramite la pressione del tasto 'S'.
E' tutto, disegnamo.
Ora sapete come usare i terreni in Irrlicht.
1 ndt (nota del traduttore)
Versione in pdf scaricabile da QUI
In questo tutorial mostreremo brevemente come utilizzare il render dei terreni di Irrlicht. Vedremo anche il funzionamento del selettore di triangoli per gestire le collisioni con i terreni.
Da notare che il Terrain Renderer di Irrlicht è basato sul lavoro dell'utente Spintz chiamato GeoMipMapSceneNode, a cui vanno i nostri ringraziamenti. L'utente DeusXL invece ha realizzato un nuovo, semplice ed elegante sistema per creare terreni di dimensioni molto più grandi tramite heightmaps di dimensioni ridotte, il terrain smoothing.
Nella parte iniziale non c'è niente di speciale. Includiamo i soliti file header necessari e creiamo un gestore die venti (event listener) per gestire la pressione del tasti: 'W' per passare alla modalità wireframe, il tasto 'P' per la modalità pointcloud ed il tasto 'D' per cambiare il materiale del terreno tra il 'solid' ed il 'detail mapped'.
Codice PHP:
#include <irrlicht.h>
#include "driverChoice.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(scene::ISceneNode* terrain, scene::ISceneNode* skybox, scene::ISceneNode* skydome) :
Terrain(terrain), Skybox(skybox), Skydome(skydome), showBox(true), showDebug(false)
{
Skybox->setVisible(showBox);
Skydome->setVisible(!showBox);
}
bool OnEvent(const SEvent& event)
{
// check if user presses the key 'W' or 'D'
if (event.EventType == irr::EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown)
{
switch (event.KeyInput.Key)
{
case irr::KEY_KEY_W: // switch wire frame mode
Terrain->setMaterialFlag(video::EMF_WIREFRAME,
!Terrain->getMaterial(0).Wireframe);
Terrain->setMaterialFlag(video::EMF_POINTCLOUD, false);
return true;
case irr::KEY_KEY_P: // switch wire frame mode
Terrain->setMaterialFlag(video::EMF_POINTCLOUD,
!Terrain->getMaterial(0).PointCloud);
Terrain->setMaterialFlag(video::EMF_WIREFRAME, false);
return true;
case irr::KEY_KEY_D: // toggle detail map
Terrain->setMaterialType(
Terrain->getMaterial(0).MaterialType == video::EMT_SOLID ?
video::EMT_DETAIL_MAP : video::EMT_SOLID);
return true;
case irr::KEY_KEY_S: // toggle skies
showBox=!showBox;
Skybox->setVisible(showBox);
Skydome->setVisible(!showBox);
return true;
case irr::KEY_KEY_X: // toggle debug information
showDebug=!showDebug;
Terrain->setDebugDataVisible(showDebug?scene::EDS_BBOX_ALL:scene::EDS_OFF);
return true;
default:
break;
}
}
return false;
}
private:
scene::ISceneNode* Terrain;
scene::ISceneNode* Skybox;
scene::ISceneNode* Skydome;
bool showBox;
bool showDebug;
};
Codice PHP:
int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device with full flexibility over creation parameters
// you can add more parameters if desired, check irr::SIrrlichtCreationParameters
irr::SIrrlichtCreationParameters params;
params.DriverType=driverType;
params.WindowSize=core::dimension2d<u32>(640, 480);
IrrlichtDevice* device = createDeviceEx(params);
if (device == 0)
return 1; // could not create selected driver.
Codice PHP:
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
// add irrlicht logo
env->addImage(driver->getTexture("../../media/irrlichtlogo2.png"),
core::position2d<s32>(10,10));
//set other font
env->getSkin()->setFont(env->getFont("../../media/fontlucida.png"));
// add some help text
env->addStaticText(
L"Press 'W' to change wireframe mode\nPress 'D' to toggle detail map\nPress 'S' to toggle skybox/skydome",
core::rect<s32>(10,421,250,475), true, true, 0, -1, true);
// add camera
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0,100.0f,1.2f);
camera->setPosition(core::vector3df(2700*2,255*2,2600*2));
camera->setTarget(core::vector3df(2397*2,343*2,2700*2));
camera->setFarValue(42000.0f);
// disable mouse cursor
device->getCursorControl()->setVisible(false);
Per rendere il terreno più grande cambiamo il fattore di scala a (40, 4.4, 40) (notare la X e la Z sono le ampiezze planari mentre l'asse Y che indica la quota delle montagne è stata scalata molto di meno per non avere picchi troppo ripidi ndt). Non avendo nessuna luce dinamica nella scena, disabilitiamo ll'illuminazione sul terreno, impostiamo la texture del terreno terrain-texture.jpg come prima texture e la detailmap3.jpg come seconda texture per i dettagli. Alla fine andiamo ad indicare la scala delle texture rispetto al terreno stesso (in pratica come lo ricopre): la prima texture verrà ripetuta una sola volta su tutto il terreno (lo ricopre esattamente), mentre la seconda verrà ripetuta 20 volte (più piccola e riporta le asperità del terreno).
Codice PHP:
// add terrain scene node
scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode(
"../../media/terrain-heightmap.bmp",
0, // parent node
-1, // node id
core::vector3df(0.f, 0.f, 0.f), // position
core::vector3df(0.f, 0.f, 0.f), // rotation
core::vector3df(40.f, 4.4f, 40.f), // scale
video::SColor ( 255, 255, 255, 255 ), // vertexColor
5, // maxLOD
scene::ETPS_17, // patchSize
4 // smoothFactor
);
terrain->setMaterialFlag(video::EMF_LIGHTING, false);
terrain->setMaterialTexture(0,
driver->getTexture("../../media/terrain-texture.jpg"));
terrain->setMaterialTexture(1,
driver->getTexture("../../media/detailmap3.jpg"));
terrain->setMaterialType(video::EMT_DETAIL_MAP);
terrain->scaleTexture(1.0f, 20.0f);
Codice PHP:
// create triangle selector for the terrain
scene::ITriangleSelector* selector
= smgr->createTerrainTriangleSelector(terrain, 0);
terrain->setTriangleSelector(selector);
// create collision response animator and attach it to the camera
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(60,100,60),
core::vector3df(0,0,0),
core::vector3df(0,50,0));
selector->drop();
camera->addAnimator(anim);
anim->drop();
Codice PHP:
scene::CDynamicMeshBuffer* buffer = new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);
terrain->getMeshBufferForLOD(*buffer, 0);
video::S3DVertex2TCoords* data = (video::S3DVertex2TCoords*)buffer->getVertexBuffer().getData();
// Work on data or get the IndexBuffer with a similar call.
buffer->drop(); // When done drop the buffer again.
Codice PHP:
// create skybox and skydome
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
scene::ISceneNode* skybox=smgr->addSkyBoxSceneNode(
driver->getTexture("../../media/irrlicht2_up.jpg"),
driver->getTexture("../../media/irrlicht2_dn.jpg"),
driver->getTexture("../../media/irrlicht2_lf.jpg"),
driver->getTexture("../../media/irrlicht2_rt.jpg"),
driver->getTexture("../../media/irrlicht2_ft.jpg"),
driver->getTexture("../../media/irrlicht2_bk.jpg"));
scene::ISceneNode* skydome=smgr->addSkyDomeSceneNode(driver->getTexture("../../media/skydome.jpg"),16,8,0.95f,2.0f);
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
// create event receiver
MyEventReceiver receiver(terrain, skybox, skydome);
device->setEventReceiver(&receiver);
Codice PHP:
int lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, 0 );
smgr->drawAll();
env->drawAll();
driver->endScene();
// display frames per second in window title
int fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Terrain Renderer - Irrlicht Engine [";
str += driver->getName();
str += "] FPS:";
str += fps;
// Also print terrain height of current camera position
// We can use camera position because terrain is located at coordinate origin
str += " Height: ";
str += terrain->getHeight(camera->getAbsolutePosition().X,
camera->getAbsolutePosition().Z);
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}
1 ndt (nota del traduttore)
Versione in pdf scaricabile da QUI