Creare Videogiochi - Game Developer

Versione completa: Tutorial 12 - Terrain Rendering
Al momento stai visualizzando i contenuti in una versione ridotta. Visualizza la versione completa e formattata.
Tutorial 12: Terrain Rendering

[Immagine: 012shot.jpg]

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::ISceneNodeterrainscene::ISceneNodeskyboxscene::ISceneNodeskydome) :
        
Terrain(terrain), Skybox(skybox), Skydome(skydome), showBox(true), showDebug(false)
    {
        
Skybox->setVisible(showBox);
        
Skydome->setVisible(!showBox);
    }

    
bool OnEvent(const SEventevent)
    {
        
// 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_POINTCLOUDfalse);
                return 
true;
            case 
irr::KEY_KEY_P// switch wire frame mode
                
Terrain->setMaterialFlag(video::EMF_POINTCLOUD,
                        !
Terrain->getMaterial(0).PointCloud);
                
Terrain->setMaterialFlag(video::EMF_WIREFRAMEfalse);
                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::ISceneNodeTerrain;
    
scene::ISceneNodeSkybox;
    
scene::ISceneNodeSkydome;
    
bool showBox;
    
bool showDebug;
}; 
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.
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>(640480);
    
IrrlichtDevicedevice createDeviceEx(params);

    if (
device == 0)
        return 
1// could not create selected driver. 
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.
Codice PHP:
video::IVideoDriverdriver device->getVideoDriver();
    
scene::ISceneManagersmgr device->getSceneManager();
    
gui::IGUIEnvironmentenv device->getGUIEnvironment();

    
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BITtrue);

    
// 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), truetrue0, -1true);

    
// add camera
    
scene::ICameraSceneNodecamera =
        
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); 
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).
Codice PHP:
// add terrain scene node
    
scene::ITerrainSceneNodeterrain smgr->addTerrainSceneNode(
        
"../../media/terrain-heightmap.bmp",
        
0,                  // parent node
        
-1,                 // node id
        
core::vector3df(0.f0.f0.f),     // position
        
core::vector3df(0.f0.f0.f),     // rotation
        
core::vector3df(40.f4.4f40.f),  // scale
        
video::SColor 255255255255 ),   // vertexColor
        
5,                  // maxLOD
        
scene::ETPS_17,             // patchSize
        
4                   // smoothFactor
        
);

    
terrain->setMaterialFlag(video::EMF_LIGHTINGfalse);

    
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.0f20.0f); 
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.
Codice PHP:
// create triangle selector for the terrain 
    
scene::ITriangleSelectorselector
        
smgr->createTerrainTriangleSelector(terrain0);
    
terrain->setTriangleSelector(selector);

    
// create collision response animator and attach it to the camera
    
scene::ISceneNodeAnimatoranim smgr->createCollisionResponseAnimator(
        
selectorcameracore::vector3df(60,100,60),
        
core::vector3df(0,0,0),
        
core::vector3df(0,50,0));
    
selector->drop();
    
camera->addAnimator(anim);
    
anim->drop(); 
Per accedere ad i dati di un terreno lo si può fare direttamente con il seguente codice.
Codice PHP:
scene::CDynamicMeshBufferbuffer = new scene::CDynamicMeshBuffer(video::EVT_2TCOORDSvideo::EIT_16BIT);
    
terrain->getMeshBufferForLOD(*buffer0);
    
video::S3DVertex2TCoordsdata = (video::S3DVertex2TCoords*)buffer->getVertexBuffer().getData();
    
// Work on data or get the IndexBuffer with a similar call.
    
buffer->drop(); // When done drop the buffer again. 
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'.
Codice PHP:
// create skybox and skydome
    
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPSfalse);

    
scene::ISceneNodeskybox=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::ISceneNodeskydome=smgr->addSkyDomeSceneNode(driver->getTexture("../../media/skydome.jpg"),16,8,0.95f,2.0f);

    
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPStrue);

    
// create event receiver
    
MyEventReceiver receiver(terrainskyboxskydome);
    
device->setEventReceiver(&receiver); 
E' tutto, disegnamo.
Codice PHP:
int lastFPS = -1;

    while(
device->run())
    if (
device->isWindowActive())
    {
        
driver->beginScene(truetrue);

        
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;

Ora sapete come usare i terreni in Irrlicht.
1 ndt (nota del traduttore)

Versione in pdf scaricabile da QUI
Puntuale come sempre Tongue
Ben fatto, apena sono riuscito a settare codeblocks ci provo Tongue