Desdinova Engine: Motore grafico 3D per rendering di ambienti outdoor in tempo reale (tesi di...

56
Desdinova Engine Motore grafico 3D per rendering di ambienti outdoor in tempo reale Daniele Ferla Matr: 617769 Università degli studi di Milano – Sede di crema Ultima modifica 24/03/2007

Transcript of Desdinova Engine: Motore grafico 3D per rendering di ambienti outdoor in tempo reale (tesi di...

Desdinova Engine

Motore grafico 3D per rendering di ambienti outdoor

in tempo reale

Daniele Ferla

Matr: 617769

Università degli studi di Milano – Sede di crema

Ultima modifica 24/03/2007

[Dedica e rigraziamenti]

Indice

Capitolo 1 Introduzione

1.1 Passato, presente e futuro dell’industria videoludica

1.2 Alla ricerca dei “frames per second”

1.3 Genesi e sviluppo del Desdinova Engine

1.4 Differenze tra Ambienti Outdoor e Ambienti Indoor

1.5 Sezioni del progetto

Capitolo 2 Teoria

2.1 Mesh

2.2 Coordinate locali

2.3 Coordinate mondo

2.4 Trasformazioni nello spazio

2.5 Proiezioni

2.6 Bounding volume

2.7 Procedure di Culling

2.8 Texture mapping

2.9 Illuminazione

2.10 Level Of Detail (LOD)

2.11 Vertex e Pixel Shaker

2.12 Post-processing

Capitolo 3 Architettura

3.1 La libreria

3.2 Struttura principale

3.3 Gestione delle scene

3.4 Passi di rendering

3.5 Illuminazione

3.6 Sistema di allocazione della memoria

3.7 Files di scripting esterni

3.8 Librerie esterne

Capitolo 4 Appendice

4.1 Elenco delle figure

4.2 Bibliografia

4.3 Web links utili e di interesse

Capitolo 1

Introduzione

1.1 Passato, presente e futuro dell’industria videoludica

L’industria dei videogiochi non iniziò semplicemente come qualcosa di stabilito a

tavolino o dettato dal mercato, piuttosto crebbe quando gruppi di persone

indipendenti incominciarono a pensare un modo diverso di concepire il

divertimento e l’intrattenimento ludico.

Già prima del 1900 possiamo far risalire la nascita dei primi gruppi specializzati

nell’arte dei giochi, soprattutto di carte e di dadi. Se in realtà volessimo dare una

data precisa della nascita del business videoludico bisogna riferirsi al decennio

compreso tra il 1970 e il 1980 conosciuto anche con il nome di “The Golden Age”.

Tale nome è da attribuirsi al fatto che in quel periodo iniziarono a fiorire le prime

compagnie interamente dedicate allo sviluppo dei videogiochi e degli elaboratori

per il divertimento.

Figura 1.1: Primo elaboratore dedicato al gioco del tennis in coppia

In quel momento infatti iniziarono a formarsi quelle che sarebbero poi diventate

la Atari, la Appe, la IBM, la Sega e, successivamente, Sony in occidente e la Taito,

la Nintendo e la Namco in oriente. Ma fu nel 1988 che la vera rivoluzione stava

per attuarsi: il rilascio della console NES sviluppata Nintendo con la relativa

creazione dei “miti” Mario Bros, Megamen e Zelda. L’anno successivo la Sega

dovette correre ai ripari proponendo la console Sega Master System come netta

contrapposizione allo strapotere del colosso giapponese.

La battaglia delle consoles da gioco era appena iniziata.

In epoca più recente il trend per lo sviluppo di consoles e videogiochi si è fatto più

acceso e nello stesso tempo più accessibile per gli utenti di ogni fascia di età

soprattutto perché i videogiochi ora hanno allargato il loro influsso anche verso i

Personal Computer che poco tempo prima era utilizzati esclusivamente per la

produttività. In questo contesto si sono quindi sviluppate nuove tecnologie e

nuovi approcci per lo sviluppo e la realizzazione di applicazioni grafiche sempre

più realistiche e efficaci. Nascono qui la Sony Playstation e la Microsoft XBox

senza dimenticare le consoles portatili Nintendo GameBoy (con le varie versioni

Color, Advance e DS) e PSP.

Seppur il mercato del videogioco sembra essere così diversificato e con molte

scelte, in realtà tale aspetto è ben più radicato e stabile anche grazie al fatto che

molti titoli vengono sviluppati per pc e consoles diverse in modo da rendere più

ricco e vendibile il catalogo che una multinazionale propone.

Per quanto riguarda il futuro le basi per le nuove tecnologie in parte sono già

state scritte e in parte devono ancora essere pensate. Quello che ci si aspetta è

una maggiore interattività, una maggiore resa grafica simile al fotorealismo e

soprattutto una maggiore accessibilità da parte degli utenti.

In questo contesto non si può non accennare ad un nuovo business che da poco si

è affacciato sul mercato e che sembra essere destinato a fiorire nei prossimi anni:

quello dei cellulari. Alcuni produttori di videogiochi ormai entrati nella storia,

primo tra tutti John Carmack (famoso per aver creato il mito di Doom, nonché

ideatore di nuove tecniche di rendering come il Carmack Reverse per le ombre in

tempo reale, il Surface Caching, il Binary Space Partition e le recenti

MegaTextures), si sono da poco interessati a tale ambito vedendolo come il

qualcosa su cui porre le fondamenta dell’intrattenimento portatile a 360 gradi alla

portata di chiunque.

Per ora non rimane che stare a vedere.

1.2 Alla ricerca dei “frames per second”

La grafica realizzata da un calcolatore, sia esso un PC oppure una consoles per

videogiochi, è da sempre stata in continua evoluzione e continuerà ad esserlo.

Quando si parla di grafica ci si riferisce molto spesso a quella in tempo reale (o

realtime) la quale consiste nella rapida successione di immagini in movimento

sullo schermo in modo da dare l’impressione di osservare una scena ripresa da

una telecamera posizionata in uno spazio definito.

La misura della velocità con cui si susseguono le immagini sullo schermo è

principalmente misurata in FPS (dall’inglese “frames per second”, fotogrammi per

secondo); più questo numero è basso e più le immagini risultano a scatti e

l’interattività è compromessa. Un numero accettabile di FPS si aggira intorno a 15

ma per non riscontrare problemi di visualizzazione è sempre consigliato non

scendere sotto i 30/33 FPS; superato il valore di 72 le differenze

nell’aggiornamento delle immagini iniziano ad essere impercettibili dall’occhio

umano. E’ da sottolineare come comunque il framerate considerato “accettabile”

rimane comunque un aspetto soggettivo che varia in base all’utente e anche in

base all’applicazione considerata.

Conoscendo tale concetto applicato alla grafica in tempo reale, chi produce

applicazioni grafiche si è sempre dovuto adoperare per ricercare il compromesso

tra interattività e aspetto finale, cosa non di poco conto per la maggior parte di

chi utilizza applicazioni grafiche. E’ in questa direzione che si è sviluppata

ultimamente una nuova frontiera: l’accelerazione hardware della grafica 3D.

Infatti, un elemento di traino dell’innovazione di questo settore è rappresentato

prima di tutti dai passi avanti fatti nello sviluppo dell’hardware.

Si è passati così dai VGAControllers dei primi anni novanta, al nuovo termine GPU

(Graphics Processing Unit) coniato dalla NVIDIA durante la distribuzione della

linea GeForce e della rispettiva Radeon della ATI. Attraverso questa nuova visione

si iniziava a cercare di diversificare il lavoro svolto dalla CPU da quello della GPU

rendendo più snello e performante il lavoro di entrambe. E solo intorno al 2001

che si è avuta la vera svolta: con la GeForce3 e GeForce4 della NVIDIA e la

Radeon 7500 della ATI che si può parlare finalmente di hardware programmabile

a tutti gli effetti. In questo modo si è reso possibile inviare al processore grafico

delle istruzioni compilate in piccoli frammenti di codice conosciuti come

VertexShader (o Vertex Program) e PixelShader (o Fragment Program). Negli anni

successivi la divisione tra CPU e GPU si è fatta sempre più insistente e

recentemente si sta cercando di rendere questo concetto sempre più astratto ed

applicabile anche ad altri aspetti (per esempio motori fisici e intelligenza

artificiale).

Queste nuove potenzialità messe a disposizione dall’hardware hanno invogliato i

produttori di applicazioni grafiche a sviluppare prodotti sempre più all’avanguardia

e sempre più realistici. Ovviamente tutto questo ha un costo che si ripercuote,

come detto, sulla visualizzazione a schermo dei frames per secondo. Di pari passo

allo sviluppo delle tecnologie vi è quindi anche lo sviluppo di nuove tecniche di

rendering e di nuove metodologie nella realizzazione di una struttura solida ma

efficace e di una applicazione sempre più complessa.

1.3 Genesi e sviluppo del Desdinova Engine

L’obiettivo di questa tesi è la realizzazione di una componente ad alto livello in

grado di gestire la visualizzazione a video di ambienti 3D esterni. Si è quindi

dovuto ricorrere alla creazione di quello che in gergo viene definito “motore

grafico” e cioè una serie di classi, funzioni e quant’altro possa essere utile ad un

programmatore che voglia avvicinarsi allo sviluppo di un videogioco e di una

applicazione grafica in generale, senza essere a conoscenza di tutte le regole e le

formule che ne derivano. Il motore quindi si occupa di tutte le metodologie

inerenti il rendering della scena e la sua gestione.

Figura 1.2: Logo ufficiale del Desdinova Engine (visualizzazione wireframe)

Il progetto del motore grafico denominato Desdinova Engine (per curiosità,

Desdinova è un nome puramente di fantasia) risale a qualche hanno fa,

precisamente nel maggio del 2000 quando venne iniziato il suo sviluppo in Visual

C++ tramite le librerie OpenGL, in quel periodo molto in auge e utilizzate da

parecchi produttori di grafica 3D. Con il passare del tempo però queste librerie

hanno perduto il loro “fascino” anche perché poco aggiornate e utilizzate solo in

ambiti specifici (rimangono comunque le librerie cross-platform più usate) e

quindi la realizzazione del motore grafico si è diretta verso l’utilizzo delle più

performanti, documentate e semplici Microsoft DirectX nella versione attuale 9.0c

Seppur a livello molto alto, le librerie della multinazionale di Redmond, non

offrono tutto il necessario per la realizzazione di un prodotto finito semplice ed

intuitivo ma, anzi, risultano essere “aperte” a nuove ottimizzazioni e utilizzi. Per

questo, dunque, in questa tesi è stata sviluppata una libreria ancora più ad alto

livello, avente come base le DirectX, ma finalizzate all’utilizzo più intuitivo e

semplice del programmatore quale utente finale.

1.4 Differenze tra Ambienti Outdoor e Ambienti Indoor

La nascita di software house incentrate sullo sviluppo di videogiochi ha fatto

crescere in sé anche il concetto di motore grafico adibito esclusivamente alla

realizzazione, in tutte le sue forme, del divertimento videoludico. Questa

prerogativa ha costretto i produttori a fare delle scelte in sede di realizzazione e a

dividere la realizzazione in due grandi categorie: motori grafici per “ambienti

outdoor” e motori grafici per “ambienti indoor”.

Con “ambiente outdoor” si intende tutto quello che può far parte di una

ambientazione naturale o comunque non ad opera dell’uomo; in questa categoria

quindi la fanno da padrone catene montuose, pianure, alberi, laghi e mari, cielo

(tramite skybox o altre tecniche), pianeti, spazio infinito e affetti grafici legati alla

luce (quali ad esempio lens flares, sole, nebulose, bagliori ecc).

Con “ambiente Indoor” si intende tutto quello che notoriamente è realizzato ad

opera dell’uomo; l’esempio più semplice sono le strutture abitative o qualsiasi tipo

di costruzione come case, baracche, carceri, stabilimenti, città o generalmente

ogni ambiente delimitato da pareti.

Questa grande differenza permette al produttore, e quindi al realizzatore, di

applicare diverse tecniche di rendering alle due categorie appena presentate.

Nella seguente tabella è possibile vedere come certe tecniche di rendering, o

algoritmi, possano essere o meno utilizzati quando si realizza un’applicazione in

ambienti esterni oppure interni.

Ambiente Indoor Ambiente Outdoor

Backface Culling Si Si

Frustrum Culling Si Si

Occlusioni Si Opzionale

LOD Opzionale Si

BSP Si No

Portali Si No

SceneGraph Opzionale Si

GeoMipmapping No Opzionale

Figura 1.3: Algoritmi di rendering applicabili agli “Ambienti Indoor” e “Ambienti outdoor” (per

maggiori dettagli vedere il Capitolo 2)

E’ bene specificare come esistano molte altre tecniche applicabili unicamente ad

una categoria o all’altra ma quello che più importa in questa sede è sapere come

tale differenza influisce sullo sviluppo del motore grafico stesso. Un altro aspetto

da tenere in considerazione è che il motore grafico viene realizzato in base ad uno

scopo preciso quale è il prodotto finale; se tale prodotto non necessita di un

particolare algoritmo (per motivi di scelta oppure di categoria) il motore grafico

può farne certamente a meno.

Oggigiorno comunque è difficile trovare un motore grafico adatto a tutti i generi di

videogiochi o comunque questo diventa possibile solo dopo una attenta riscrittura

oppure tramite dei plug-ins o MODs.

Per curiosità possiamo citare che il primo videogioco ad utilizzare una struttura

Indoor fu Wolfenstein3D della IDSoftware prodotto nel 1992 e che il primo gioco

che utilizzava la distinzione netta tra motore per interni e per esterni fu Descent3

realizzato nel 2000 dalla ParallaxSoftware (ma personalmente posso affermare

che non fu una scelta azzeccata seppur da lodare). Oggigiorno, grazie anche al

continuo sviluppo delle software house e anche grazie al mercato più esigente che

richiede maggior personale addetto, i motori grafici riescono ad includere

entrambe le due categorie di “ambienti indoor” e “ambienti outdoor” realizzando

così un grande impatto visivo; un esempio fra tutti il bellissimo “FarCry” della

Crytek ed il suo seguito “Crysis”.

Per quanto riguarda il Desdinova Engine si è optato per la realizzazione di un

motore grafico che rappresentasse ambienti esterni e, nella fattispecie, ambienti

di natura spaziale (spazio, asteroidi, pianeti, sistemi solari, astronavi ecc). Detto

questo è possibile capire come certe tecniche prettamente Outdoor non siano

nemmeno state prese in considerazione; un esempio tra tutti il GeoMipmapping

che invece risulta la scelta obbligata per chi vuole realizzare una applicazione che

gestisca terreni e catene montuose.

1.5 Sezioni del progetto

La seguente tesi è stata divisa in 3 sezioni principali.

La sezione “Teoria” contiene i principali concetti utilizzati nell’ambito della grafica

3D nonché la descrizione delle tecniche adottate nel motore grafico per il

rendering delle scene e delle utilità. Tali concetti sono da considerarsi

fondamentali per capire il lavoro svolto.

Si è optato di non fare riferimento a formule matematiche in modo specifico ma di

tenere il linguaggio ad un livello più semplice e comprensivo possibile. L’uso di

funzioni e termini matematici è stato dunque limitato al minimo indispensabile.

La sezione “Architettura” contiene la descrizione dettagliata dal motore grafico

nelle sue varie componenti nonché la rappresentazione della pipeline di rendering

e le scelte fatte per la miglior gestione possibile dell’applicazione. Si è optato per

non includere in questa sezione la descrizione delle singole classi e delle funzioni

perché renderebbe difficoltosa e noiosa la lettura.

La sezione “Appendice” contiene tutto quello a qui si è fatto riferimento durante la

stesura del seguente documento quali libri, immagini, links in rete. Questa

sezione non è meno importante delle altre e bisogna menzionare il fatto che in

questo ambito sono da considerarsi di ugual rilevanza sia la teoria riportata sui

manuali, sia i tutorials e i codici sorgenti trovati durante le innumerevoli ricerche

in rete. Un grazie va quindi agli autori dei libri ma soprattutto agli utenti, esperti

o alle prime armi, che hanno reso accessibile a tutti il loro lavoro e le loro

creazioni da cui è sempre possibile imparare.

Capitolo 2

Teoria

2.1 Mesh

Nello sviluppo di un’applicazione grafica 3D, l’elemento principale con cui bisogna

scontrarsi fin da subito è il punto o, chiamato comunemente, vertice. Il vertice è

definito tramite 3 valori x,y,z che ne localizzano la posizione in uno specifico

sistema di riferimento di base.

Una serie di tre vertici collegati tra loro, definiscono una faccia o poligono.

Qualsiasi entità visualizzata a video è rappresentata da una serie di facce aventi

delle proprietà comuni e dunque da una serie di vertici;

questa entità prende solitamente il nome di mesh o modello.

Esempi di mesh possono quindi essere una casa, una pianta, una astronave o di

un personaggio animato.

2.2 Coordinate Locali

Il sistema di coordinate locali o local space è dove viene definito il sistema di

riferimento della lista di facce del modello in riferimento. Questo sistema è molto

utile in quanto semplifica molto il processo di modellazione e di visualizzazione;

costruire un modello sul proprio sistema di coordinate locali è molto più semplice

che costruire tale modello direttamente nel mondo.

Per completezza le coordinate locali ci permettono di costruire il modello senza

sapere il suo rapporto con gli altri modelli inclusi nella scena che viene

visualizzata a video in quello stesso istante.

Figura 2.1: Sistema di coordinate locali

2.3 Coordinate Mondo

Una volta definito il sistema di coordinate locali riferite al modello dal visualizzare,

si può procedere definendo il sistema di coordinate mondo o world space. Tale

sistema è unico in tutta l’applicazione e ogni modello presente ne fa riferimento.

Tramite un processo chiamato world tranformation si procede a trasferire le

coordinate locali in coordinate mondo tramite delle trasformazioni lineari nello

spazio che definiscono la posizione la scalatura e la rotazione dei singoli modelli

all’interno del mondo, nonché il rapporto tra i modelli presenti.

Figura 2.2: Sistema di coordinate mondo

2.4 Trasformazioni nello spazio

La principale proprietà di una mesh è quella di essere localizzata spazialmente

tramite delle trasformazioni lineari che ne definiscono la posizione, la scalatura e

la rotazione sugli assi.

Le trasformazioni lineari sono generalmente rappresentate utilizzando

la convenzione matriciale, vettori e punti sono considerati come matrici di una

sola riga o una sola colonna a seconda della convenzione utilizzata. In tutto il

testo i vettori (e quindi anche i punti) saranno sempre rappresentati come matrici

ad una sola riga.

La cosa fondamentale da osservare è la necessità di rappresentare trasformazioni

in uno spazio a tre dimensioni utilizzando matrici 4x4. E’ sostanzialmente un

modo elegante per affrontare il problema di rappresentare anche le traslazioni

attraverso matrici.

Figura 2.3: Matrice associata ad un modello

Figura 2.4: Matrice di traslazione

Figura 2.5: matrice di scalatura

Figura 2.6: Matrici di rotazione (x, y, z)

Il vantaggio principale di questa rappresentazione è la possibilità di combinare gli

effetti di due o più matrici moltiplicandole tra loro. Questo significa che, per

ruotare e poi traslare un modello, non occorre moltiplicare ciascun vertice per due

matrici, basta moltiplicare le matrici di rotazione e di traslazione per produrre

un’altra matrice contenente i loro effetti. Questo processo prende il nome di

concatenamento delle matrici.

Figura 2.7: Concatenamento di metrici

Infine, la rappresentazione convenzionale di punti e dei vettori avverrà nelle così

dette coordinate omogenee. La quarta coordinata tradizionalmente indicata con la

lettera w, per i punti è uguale sempre ad uno, mentre per i vettori direzione è

uguale a zero.

2.5 Proiezioni

Le coordinate 3D (e quindi al singolo vertice) non bastano per poter

rappresentare a video la mesh in questione; tali coordinate devono essere

convertite in quelle dello schermo (quindi 2D).

Questa conversione dipende dalla posizione e dall’orientamento, della “telecamera

virtuale”, nello spazio 3d e dal tipo di proiezione desiderata.

Si ricorda che con il termine “telecamera virtuale” si intende lo strumento

attraverso il quale viene osservata la scena. L’utente quindi può specificare la

posizione e l’orientamento di tale telecamera nello spazio mondo specificando,

ovvero quella porzione di spazio che la telecamera può visualizzare sullo schermo

o comunque all’interno di un’immagine 2D.

Nel caso in cui si faccia uso di una proiezione prospettica, la forma del “view

frustrum” coincide con quella di un tronco di piramide a base rettangolare. La

telecamera viene posta sulla sommità di tale piramide, tagliata da due piani

infiniti che prendono il nome di “front clipping plane” (quello più vicino alla

telecamera) e “back clipping plane” (quello più lontano dalla telecamera).

Figura 2.8: View frustrum

2.6 Bounding Volume

Una volta che il modello è localizzato spazialmente nel sistema di coordinate

mondo, esso ne occupa una porzione ben definita o semplicemente occupa

volume.

Tale volume può essere rappresentato, nella maggior parte dei casi, tramite varie

semplici figure geometriche; rappresentare una mesh tramite una figura più

semplice è il metodo migliore nei casi in cui la velocità di calcolo è preferibile alla

accuratezza dei risultati. Quelle utilizzate nella realizzazione del seguente motore

grafico sono di due tipi: Bounding Spere e Bounding Box.

2.6.1 Bounding Sphere

La Bounding Sphere è la figura più semplice da analizzare perché composta

esclusivamente da due valori: il centro e il raggio. In questo modo il modello in

questione si riduce facilmente a questi due valori.

E’ da notare come questo tipo di rappresentazione risulta essere molto arbitraria

e dipendente dalla forma del modello in questione: se dovessimo visualizzare per

esempio un pianeta la Bounding Sphere calzerebbe a pennello, ma se dovessimo

rappresentare una persona in movimento tale figura occuperebbe più spazio

vuoto che altro.

2.6.2 Bounding Box

Il Bounding Box è una rappresentazione più accurata del modello in questione ma

comunque molto semplice perché definita da quattro valori: il centro, la

larghezza, l’altezza e la profondità oppure le coordinate dei due estremi del

parallelepipedo contenente il modello. Se questo tipo di figura ha gli assi allineati

al sistema di riferimento prende il nome di “Aligned Axes Bounding Box”

(sinteticamente AABB), se tale figura ha gli assi allineati al sistema di riferimento

locale, prende il nome di “Object Oriented Bounding Box” (sinteticamente OOBB).

Figura 2.9: Bounding Box (a sinistra) e Bounding Sphere (a destra)

applicate a un modello rappresentante un’astronave.

2.7 Procedure di Culling

Le procedure di culling sono tutte quelle operazioni che permettono alla

applicazione di escludere dalla fase di visualizzazione tutto ciò che non è visibile;

in questo modo è possibile alleggerire il carico di lavoro da parte del motore

grafico aumentandone la velocità.

Esistono due tecniche comunemente usate per questa procedura: Il Backface

Culling e il Frustrum Culling.

2.7.1 Backface Culling

Questa procedura si basa sul fatto che ogni poligono possiede due lati chiamati

rispettivamente “front side” e “back side”. In generale quest’ultimo lato non viene

mai visualizzato perché la maggioranza dei modelli in una scena hanno tutti un

volume chiuso (come per esempio un cubo, un cilindro, un personaggio animato

ecc.) e la telecamera non entrerà pratcamente mai all’interno di tali modelli.

Questa asserzione risulta essere importante perché se si permettesse alla

telecamera di entrare allora la seguente procedura di culling mostrerebbe degli

artefatti compromettendone il risultato.

Un poligono che ha il “front side” rivolto verso la telecamera prende il nome di

“front facing polygon” mentre quello che mostra il “back side” si chiama “back

facing polygon”; detto questo è possibile capire come un modello, mentre sta

mostrando le sue “front side”, esso nascondo implicitamente anche le “back side”

delle facce poste dietro le prime. Sapendo quali sono le facce retrostanti è

possibile eliminarle dalla visualizzazione garantendo quindi maggiori prestazioni.

Oggigiorno questa procedura è realizzata implicitamente, tramite la definizione di

un parametro opzionale, dalle librerie grafiche utilizzate.

2.7.2 Frustrum Culling

Questa procedura si basa sul fatto che tutti i modelli che non sono all’interno del

“view frustrum” non devono essere visualizzati. Per fare questo è necessario fare

riferimento alla “Bounding Sphere” associata al modello in questione e calcolare

se tale sfera (tramite il suo centro e il suo raggio) è all’interno dello spazio di

visualizzazione. In questo modo possono verificarsi 3 casi distinti:

La sfera è completamente interna al “view frustrum”:

il modello deve essere visualizzato a video.

La sfera è completamente esterna al “view frustrum”:

il modello non deve essere visualizzato a video.

La sfera è parzialmente contenuta nel “view frustrum”:

il modello deve essere diviso in due parti (detto “splitting”) e solo la parte

interna allo spazio deve essere visualizzata.

In questa sede è necessario puntualizzare che quest’ultimo caso non è stato

preso in considerazione nella realizzazione del motore grafico perché oneroso in

termine di calcolo e ininfluente ai fini della visualizzazione a video.

2.8 Texture mapping

Un modello collocato nello spazio non apparirebbe reale agli occhi dell’osservatore

se sopra di esso non fosse applicata una “texture”.

Il processo di “texturing” prende in esame una superficie e ne modifica l’aspetto

in ogni suo punto usando un’immagine, una funzione o qualsiasi altro insieme di

dati. Questa procedura risulta alquanto utile e semplice perché, invece di

rappresentare esattamente una parete di mattoni è possibile creare una semplice

superficie e applicare su di essa una immagine che ne rappresenta tali mattoni. In

realtà tale processo presenta i suoi inconvenienti soprattutto quando l’osservatore

si avvicina in modo rilevante alla superficie mostrandone il basso livello di

dettaglio dettato dalla qualità dell’immagine e soprattutto quando non sono

presenti fonti di luci in scena.

Il texture mapping si basa principalmente sul fatto di associare, durante la fase di

modellazione, coordinate nello spazio texture ai vertici della superficie. A questo

punto, durante il caricamento del modello in questione, non si fa altro che

caricare anche tali informazioni riguardanti le textures il cui accesso avviene come

lettura di un elemento in una matrice ed il campione prelevato prende il nome di

tassello (texel). In generale una, due o tre coordinate possono essere utilizzate

per accedere a texture ad una, due o tre dimensioni.

Il problema principale del texture mapping, come detto, è dato dal fatto che la

qualità dell’immagine applicata alla superficie deve essere il più dettagliata

possibile per poter essere credibile e non mostrare artefatti all’avvicinamento

dell’osservatore alla superficie; questo inconveniente introduce il concetto di Anti-

aliasing e di Mip-mapping spiegato di seguito.

2.8.1 Antialising e Mip-mapping

La prima soluzione consiste nell’eseguire un integrale esteso all’area del pixel

dopo che è stato proiettato nello spazio texture; essendo la forma del pixel non

nota sono dunque necessarie delle approssimazioni ottenibili tramite un filtraggio

dell’immagine. In particolare l’hardware può essere impostato in modo che

campioni due o più tasselli adiacenti e, di questi, ne esegue una interpolazione

lineare in modo da determinare il colore da assegnare.

Figura 2.10: Artefatto di aliasing applicato ad un triangolo (a sinistra) e effetto di anti-aliasing

correttivo (a destra)

La seconda soluzione, proposta da Williams nel 1983, si basa sul pre-calcolo di

copie della stessa immagine da applicare alla superficie ma a risoluzioni sempre

più basse. In questo modo, introducendo una nuova coordinata d (nota come

Livello di Dettaglio), è possibile determinare a runtime, tramite un’apposita

metrica, quale delle immagini a risoluzione differente caricare in qual momento.

Figura 2.11: Texture raffigurante un glow rappresentata

tramite 6 livelli di Mipmap

E’ da notare come l’Anti-aliasing e Mip-mapping non sono procedure mutuamente

esclusive ed, anzi, oggigiorno vengono comunemente usate entrambe nelle

maggiori applicazioni grafiche soprattutto perché non richiedono grande lavoro e i

risultati sono più che accettabili.

2.8.2. Bump-mapping

Il bump-mapping è un’estensione della tecnica di texture mapping.

E’ una tecnica di rendering, introdotta da Blinn nel 1978, che consente,

perturbando le normali, di simulare effetti di interazione della luce con irregolarità

locali ad una superficie. Codificando queste irregolarità all’interno di texture, il

bump-mapping simula l’aspetto irregolare di una superficie senza che questi

dettagli siano realmente aggiunti alla geometria.

Il rendering di superfici ad elevatissimo numero di poligoni è computazionalmente

troppo oneroso nel campo della grafica in tempo reale. Il bumpmapping è

generalmente una tecnica più efficiente perchè disaccoppia la descrizione degli

elementi di dettaglio, su piccola scala, della superficie, necessari per i calcoli di

illuminazione per-pixel, dalla descrizione a livello vertice della forma, su larga

scala, della superficie stessa, necessaria invece per l’esecuzione

efficiente di operazioni come le trasformazioni.

Il bump-mapping è una procedura divisa in due parti eseguite su tutti

i punti della superficie da renderizzare. Per prima cosa, si calcola, nel punto in

esame, la normale alla superficie perturbata. Successivamente si esegue

l’illuminazione usando la nuova normale così determinata.

Figura 2.12: Bump-mapping applicato ad una sfera (una texture raffigurante un muro, una

texture per le normali perturbatrici, una luce blu, una luce rossa)

2.9 Illuminazione

Possiamo considera l’illuminazione come il vero “biglietto da visita” di un motore

grafico perché è proprio questa che rendere realistico o meno un’applicazione; da

sempre quindi si è cercato di riprodurre gli effetti di rifrazione della luce anche in

ambito 3D e solo ultimamente, con le nuove tecniche, è stato possibile

raggiungere dettagli elevati come nella realtà.

Oltre al mero calcolo dell’illuminazione è necessario decidere la tecnica tramite il

quale mostrare effettivamente a video il risultato dell’interazione delle luci in

gioco in un determinato istante.

Oggigiorno la scelta ricade principalmente tra due tecniche avanti pregi e difetti

che sono necessariamente da prendere in considerazione soprattutto in base al

risultato finale che si vuole ottenere nonché all’utilizzo finale dell’applicazione che

si sta creando.

Queste tecniche prendono il nome di Multipass Rendering e Deferred Shading che

verranno spiegate brevemente di seguito.

2.9.1 Multipass Rendering

Il “Multipass Rendering” si basa su un semplice concetto: ogni modello in scena

viene renderizzato a video tante volte quante sono le luci che lo riguardano.

Tramite questa asserzione è possibile capire come una scena avente solo una luce

sarà facilmente renderizzabile rispetto ad una scena avente una decina di luci.

Inizialmente questa tecnica può sembrare pressoché inutile e inefficacie perché il

lavoro del programmatore è quello di visualizzare a video il maggior numero di

vertici e il più velocemente possibile e dunque questa tecnica va in contrasto con

questo.

Vantaggi:

1. Poca occupazione di memoria del frame-buffer

2. Il ciclo di rendering diventa lights-oriented facilitando operazioni comuni

3. Gestione delle ombre semplificata

Svantaggi:

1. Rendering multiplo di una stessa mesh in base alle luci che la influenza

2. Più luci sono presenti e più il numero di frames viene ridotto

3. Obbligata gestione del culling delle luci

4. E’ necessario fare una scelta nel gestire oggetti non influenzati da luci

2.9.2 Deferred Shading

Il “Deferred Shading” si basa sull’applicazione delle operazioni di shading alle

parti della scena effettivamente visibili in un determinato istante, evitando di

elaborare la geometria ripetutamente, come avviene nelle normali tecniche multi-

pass. La tecnica si basa sul differimento del processo di shading che viene

applicato nello spazio dell’immagine. A causa di tale differimento sarà necessario

rendere disponibili tutti i parametri richiesti per la computazione dello shading

nella fase successiva al rendering della scena.

Questa tecnica è composta principalmente da due fasi; la prima, chiamata

“Geometry phase” è l’unica dipendente dalla complessità della geometria della

scena; le successive fasi si basano sui risultati generati dall’elaborazione della

geometria effettuati durante la presente fase; ciò consente l’utilizzo di scene più

complesse, aumentandone in tal modo il realismo. E’ in questa fase che vengono

raccolti e immagazzinati i dati che serviranno per il calcolo dell’illuminazione della

scena all’interno dei cosiddetti Attribute buffers (o G-Buffers, Geometry buffers).

Essi sono implementati mediante texture di tipo “Render Target” opportunamente

dimensionate per far fronte alle esigenze di precisione dei dati in esse contenuti.

La seconda fase, chimata “Lighting phase” è senza dubbio la fase più importante

dell’algoritmo di rendering e consiste nell’applicazione dei metodi di shading sulla

base delle informazioni presenti nei buffer calcolati nella fase precedente.

Avendo a disposizione le informazioni di tutte le parti della scena visibili dalla

corrente posizione della telecamera, la complessità computazionale sarà legata

esclusivamente al numero di pixel coinvolti nel calcolo dell’illuminazione della

scena; i contributi di ciascuna luce presente nella scena vengono calcolati

singolarmente per poi essere combinati utilizzando la trasparenza.

Per ciascuna luce attiva è essenzialmente sufficiente renderizzare un quadrilatero

allineato con lo schermo e delle stesse dimensioni dello stesso, applicando la

tecnica di shading desiderata.

Vantaggi:

1. Calcolo delle luci solo nelle zone realmente interessate dalle luci

2. Le luci sono calcolate per-pixel

3. Luci con funzionalità di occlusione rendono semplice l’algoritmo

4. Calcolo delle shadows-maps semplice e poco costoso

Svantaggi:

1. Occupazione di memoria del frame-buffer eccessiva

2. Potenzialmente potrebbe ridurre il numero di frames

3. Calcolo delle equazione delle luci difficoltoso

4. Difficoltà nel gestire superfici trasparenti

5. Richiesta hardware onerosa

2.10 Level Of Detail (LOD)

Questa tecnica parte dal presupposto che più un modello è distante dalla camera

più il numero di vertici può essere ridotto pur mantenendo la forma e il volume di

quest’ultimo uguali all’originale; in questo modo l’osservatore avrà l’impressione

di vedere sempre lo stesso modello anche se esso, allontanandosi, ne perderà in

dettaglio.

Esistono principalmente due tecniche per realizzare questa procedura: creare in

fase di modellazione i vari livelli di dettaglio del modello oppure avvalersi delle

mesh-progressive.

La prima procedura consiste nello stabilire a priori, in fase di modellazione, quali e

quanti saranno i livelli di dettaglio che avrà il modello in questione (solitamente

variabile dai 6 ai 9); in fase di caricamento si avranno dunque in memoria

molteplici copie dello stesso modello aventi numero di vertici sempre inferiori.

Successivamente, durante la fase di rendering, si potrà calcolare la distanza del

modello dalla camera e caricare il modello appropriato. Il problema di questa

tecnica risiede nel fatto che durante il passaggio da un livello di dettaglio all’altro

potrebbe verificarsi l’effetto che l’osservatore percepisca facilmente la perdita di

dettaglio del modello.

Per ovviare a questo fastidioso inconveniente vengono in aiuto quelle che

comunemente vengono definite “mesh-progressive”.

Si tratta di una procedura in cui è possibile definire a runtime il numero di vertici

esatti che si vogliono visualizzare; in questo modo il passaggio da un livello

all’altro risulta pressoché impercettibile anche perchè il numero di livelli può

quindi essere molto più elevato. Le mesh-progressive hanno però un difetto in

termini di occupazione di memoria perché ne necessita in gran quantità,

proporzionalmente al numero di vertici della mesh in questione.

2.11 Vertex Shader e Pixel Shader

Negli ultimi anni si è assistito ad un notevole incremento di prestazioni da parte

delle schede grafiche 3D per PC, grazie soprattutto all’introduzione delle unità di

Transform&Lighting (in breve T&L), che si fanno carico dei pesanti calcoli

vettoriali necessari per le trasformazioni geometriche e per il calcolo

dell’illuminazione della scena, lasciando libera la CPU di dedicarsi ad altri compiti,

come ad esempio l’elaborazione del modello fisico e dell’intelligenza artificiale.

Tuttavia se da una parte le performance sono aumentate, dall’altra è diminuita la

libertà per il programmatore: le unità di T&L sono a funzioni fisse, cioè gli

algoritmi grafici sono stati scelti dai creatori del chip e memorizzati al suo interno:

non è possibile, per il programmatore, cambiarli. Per superare questo problema, i

produttori hanno dotato le schede di vere e proprie CPU programmabili,

denominate GPU (Graphic Processing Unit); questi programmi, eseguiti

direttamente dalla GPU, prendono il nome di “shaders”, dal verbo inglese to

shade, che significa ombreggiare; infatti il loro compito è proprio quello di

calcolare l’ombreggiatura e più in generale l’aspetto degli oggetti tridimensionali.

Comunemente gli shaders vengono distinti in in due categorie: Vertex Shader che

elaborano i vertici degli oggetti 3d, e Pixel Shaders che elaborano i pixel in cui gli

oggetti sono stati convertiti. Questi shaders possono essere scritti in diversi

linguaggi, sia di basso che di alto livello. Prima delle DirectX 9.0 questi programmi

erano scritti in un linguaggio simile all’assembler, con il rilascio delle DirectX 9.0,

avvenuto ad inizio 2003, oltre allo shaders assembler è stato introdotto un nuovo

linguaggio di alto livello, simile al C, chiamato High Level Shader Language

(HLSL). A tutt’oggi la versione degli shaders è arrivata alla 3.0 e con le DirectX 10

in uscita a breve verrà implementata anche la versione 4.0. I produttori di schede

video e Microsoft stanno pensando in futuro di uniformare le due categorie per

fornire al programmatore uno shader unico.

Per la realizzazione del motore grafico si è dunque fatto largo uso degli shaders

che permettono, come detto, una maggiore programmabilità dell’applicazione

nonché un realismo di immagine nettamente migliore.

In questo testo non si farà riferimento a comandi specifici dell’HLSL e si rimanda il

lettore alla documentazione delle Microsoft DirectX 9.0c SDK oppure alle

numerose guide on-line sull’argomento.

2.12 Post-processing

Capitolo 3

Architettura

3.1 La libreria

Oggigiorno pensare un motore grafico come un programma costituito da un

semplice file eseguibile è pressoché impossibile nonché poco producente; questo

perché si sta facendo largo il concetto di “modularità” e di “link statico e

dinamico”. Tramite queste tecniche è quindi possibile realizzare la propria

applicazione scorporata dal programma che lo utilizzerà venendosi così a creare

quella che in gergo viene definita “libreria”.

Il Desdinova Engine si è da sempre mosso in questa direzione mettendo quindi a

disposizione del suo utente finale una libreria contente tutte le classi e le funzioni

necessarie per la realizzazione di un’applicazione grafica di alto livello. In questo

modo l’utente dovrà conoscere esclusivamente la sintassi e le dichiarazioni del

motore grafico senza preoccuparsi, profondamente e sintatticamente, del

funzionamento delle DirectX il cui utilizzo resta invece in mano alla libreria.

Un fattore da non dimenticare è quello che affidare al programmatore un libreria

significa anche dotarlo di un qualcosa di dinamico in modo che, i successivi

aggiornamenti della stessa, non andranno ad influenzare drasticamente

l’applicazione che la utilizza e soprattutto che quest’ultima non vada ricompilata

nuovamente ad ogni nuova versione.

3.2 Struttura principale

Prima di iniziare è bene precisa come il Desdinova Engine sia utilizzabile

esclusivamente all’interno di un’applicazione grafica realizzata in Visual C++;

in realtà questo limite imposto sul tipo di linguaggio è di non poco conto se si

pensa a quanti linguaggi oggigiorno siano presenti sul mercato ma, tra tutti, non

vi è alcun dubbio che il Visual C++ sia quello più utilizzato per la realizzazione di

applicazioni grafiche veloci, stabili e performanti.

Il Desdinova Engine si presenta costituito principalmente da una serie di classi

istanziabili dal programmatore come oggetti statici o dinamici, e da una nutrita

serie di altri elementi, quali per esempio strutture, enumerazioni, macro, costanti

ecc, che lo rendono a tutti gli effetti un vero e proprio framework.

Di seguito vengono elencate le classi presenti, con una breve descrizione del loro

scopo, in modo da rendere l’idea delle numerose funzionalità che il motore grafico

mette a disposizione.

DECore: Classe principale utilizzata per la gestione di tutte le

funzionalità, a livello di core, del motore grafico;

DEUtility: Classe contenente svariate funzioni ritenute utili al fine di

un’ applicazione grafica come per esempio conversioni,

numeri casuali, interpolazioni, ecc;

DEModel: Classe per la gestione (caricamento, modifica,

visualizzazione ecc) dei modelli caricati da file;

DESkyBox: Classe per la creazione di uno sfondo presente in tutte le

direzioni dell’applicazione. Questa classe può creare:

skybox (cubo che racchiude tutta a scena, costituito da 6

facce rivolte verso l’interno con applicata su ognuna una

texture contigua di sfondo); skysphere (sfera che

racchiude tutta la scena, con applicata una texture sferica

di sfondo); skycylinder (cilindro che racchiude tutta la

scena, costituito da 3 parti con applicata su ognuna una

texture contigua di sfondo);

DELensFlare: Classe per la creazione dell’effetto grafico che si produce

quando una fonte luminosa viene ripresa da una

telecamera (utile per creare maggior realismo e

spettacolarità alla scena);

DEBillboard: Classe per la creazione di superfici 3D aventi come

proprietà quella di essere sempre rivolte verso la camera

(per esempio le particelle di un sistema particellare, effetti

di bagliore, fumo ecc);

DEParticleSystem:Classe per la creazione di sistemi particellari avanzati

(tramite l’utilizzo di variabili per il calcolo del vento,

della gravità, della forma dell’emettitore ecc);

DEScene: Classe per la creazione e la gestione delle scene da

includere nell’applicazione;

DEInput: Classe per la gestione degli input da tastiera,

joypad/joystick e mouse;

DEConsole: Classe per la creazione e la gestione del sistema di

console utile per richiamare funzioni (passate come

puntatori) a runtime, soprattutto per la fase di debug e di

testing;

DEPanel: Classe per la creazione e la gestione di tutti gli elementi

2D renderizzabili a video (per esempio menu, scritte,

HUD, ecc);

3.3 Gestione delle scene

3.3.1 Inizializzazione di una scena

L’elemento fondamentale del Desdinova Engine è senza dubbio “la scena”; nel

suo contesto la “scena” è dunque da considerarsi come quella entità in cui viene

inserito, e successivamente gestito, qualsiasi elemento grafico da visualizzare a

schermo, sia esso un modello, un sistema particellare, un suono oppure un effetto

grafico.

Più in generale è possibile immaginare una “scena” come quel gruppo di elementi

grafici che, per qualsivoglia motivo, hanno qualcosa in comune e che devono

essere utilizzati contemporaneamente nello stesso ambiente e che posseggono, a

grandi linee, le stesse proprietà.

Esempi di scene potrebbero essere: il menu iniziale in due dimensioni di una

applicazione grafica, la zona di rendering di un editor di modelli, la schermata

principale di gioco di un videogame ecc.

Innanzitutto è bene specificare che non esiste applicazione realizzata con il

Desdinova Engine che non abbia almeno un “scena”; la sua realizzazione infatti

deve essere pianificata a priori e durante la fase di sviluppo della propria

applicazione, definendone le proprietà e le relativa informazioni.

La “scena” ha principalmente una serie di proprietà indispensabili per la sua

creazione ed una serie di altre proprietà aggiuntive.

Le proprietà principali riguardano soprattutto i puntatori a funzione indispensabili

per le varie fasi di caricamento, rendering e rilascio della scena, le proprietà

fondamentali del motore fisico e alcuni effetti applicabili all’intera visualizzazione

a schermo. Di seguito sono riportate le variabili che definiscono la scena:

LPCSTR Scene_Name;

PFUNCSCENE_BOOL Scene_FuncPointer_Load;

PFUNCSCENE_BOOL Scene_FuncPointer_Input;

PFUNCSCENE_BOOL Scene_FuncPointer_Render3D;

PFUNCSCENE_BOOL Scene_FuncPointer_Render2D;

PFUNCSCENE_BOOL Scene_FuncPointer_Invalidate;

PFUNCSCENE_BOOL Scene_FuncPointer_Restore;

PFUNCSCENE_BOOL Scene_FuncPointer_Release;

ENUM_DE_SCENETYPE Scene_Physics_Type;

D3DXVECTOR3 Scene_Physics_TypeHash_Center;

D3DXVECTOR3 Scene_Physics_TypeHash_Extents;

int Scene_Physics_TypeHash_Depth;

int Scene_Physics_TypeQuadtree_Maxlevel;

int Scene_Physics_TypeQuadtree_Minlevel;

LPCSTR Scene_PostProcess_Filename;

LPCSTR Scene_CineEffect_Filename;

LPCSTR Scene_Cursor_Filename[50];

Oltre a queste proprietà è possibile specificare anche una serie di proprietà

aggiuntive che permettono al programmatore una maggiore interazione con la

scena e di definire meglio certe capacità specifiche della stessa.

Tali proprietà riguardano soprattutto la capacità di visualizzazione a tutto

schermo, la visualizzazione delle informazioni di debug (utili durante lo sviluppo),

le specifiche della prospettiva da applicare, la visualizzazione delle griglie, la

nebbia e altre proprietà fisiche utili per le interazioni tra i corpi rigidi presenti

nella scena.

Si riporta di seguito, esclusivamente per completezza e curiosità, un esempio

tipico di inizializzazione di una scena del motore grafico; è interessante notare

come le proprietà in questione siano tutte di facile utilizzo e organizzate in

strutture in base a quello a cui si riferiscono oltre, ovviamente, ad essere molto

esplicative:

//Scena

Scene1_Properties.Scene_Name = "Menu principale – Scena 1";

Scene1_Properties.Scene_FuncPointer_Load = Scene1_Load;

Scene1_Properties.Scene_FuncPointer_Input = Scene1_Input;

Scene1_Properties.Scene_FuncPointer_Render3D = Scene1_Render3D;

Scene1_Properties.Scene_FuncPointer_Render2D = Scene1_Render2D;

Scene1_Properties.Scene_FuncPointer_Invalidate = Scene1_Invalidate;

Scene1_Properties.Scene_FuncPointer_Restore = Scene1_Restore;

Scene1_Properties.Scene_FuncPointer_Release = Scene1_Release;

Scene1_Properties.Scene_Physics_Type = DE_SCENETYPE_SIMPLE;

Scene1_Properties.Scene_Physics_TypeHash_Center = D3DXVECTOR3(0,0,0);

Scene1_Properties.Scene_Physics_TypeHash_Extents = D3DXVECTOR3(0,0,0);

Scene1_Properties.Scene_Physics_TypeHash_Depth = 0;

Scene1_Properties.Scene_Physics_TypeQuadtree_Minlevel = 0;

Scene1_Properties.Scene_Physics_TypeQuadtree_Maxlevel = 0;

Scene1_Properties.Scene_PostProcess_Filename = "PostProcessEffects.fx";

Scene1_Properties.Scene_CineEffect_Filename = "Textures\\Panels\\Cine.bmp";

Scene1_Properties.Scene_Cursor_Filename[0] = "Textures\\Panels\\Cursor1.bmp";

Scene1_Properties.Scene_Cursor_Filename[1] = "Textures\\Panels\\Cursor2.bmp";

//Clear

Scene1_Clear.Clear_RectsCount = 0;

Scene1_Clear.Clear_Rects = NULL;

Scene1_Clear.Clear_ClearFlags = D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER;

Scene1_Clear.Clear_EnvironmentColor = D3DCOLOR_XRGB(161,161,161);

Scene1_Clear.Clear_Z = 1.0f;

Scene1_Clear.Clear_Stencil = 0;

//Debug

Scene1_Debug.Debug_ShowCenterAxes = true;

Scene1_Debug.Debug_ShowInfo = true;

Scene1_Debug.Debug_ShowGroundGrid = true;

Scene1_Debug.Debug_ShowSpatialGrid = false;

Scene1_Debug.Debug_ShowLights = false;

Scene1_Debug.Debug_FillMode = -1;

Scene1_Debug.Debug_UseLighting = -1;

Scene1_Debug.Debug_BoundingType = -1;

Scene1_Debug.Debug_ShowAxes = -1;

Scene1_Debug.Debug_UseFrustrumCulling = -1;

Scene1_Debug.Debug_UseLOD = -1;

//Prospettiva

Scene1_Perspective.Perspective_Angle = D3DX_PI/4;

Scene1_Perspective.Perspective_Near = 1.0f;

Scene1_Perspective.Perspective_Far = 3000.0f;

//Nebbia

Scene1_Fog.Fog_Enable = false;

Scene1_Fog.Fog_Type = D3DRS_FOGVERTEXMODE;

Scene1_Fog.Fog_Mode = D3DFOG_LINEAR;

Scene1_Fog.Fog_Start = 1.0f;

Scene1_Fog.Fog_End = Scene1_Perspective.Perspective_Far * 3;

Scene1_Fog.Fog_Color = D3DCOLOR_XRGB(0,255,0);

Scene1_Fog.Fog_UseRange = false;

//Griglie

Scene1_Grids.GridsProperties_SpatialGrid_DimensionCell = 1000;

Scene1_Grids.GridsProperties_SpatialGrid_PerSideCells = 10;

Scene1_Grids.GridsProperties_GroundGrid_DimensionCell = 1;

Scene1_Grids.GridsProperties_GroundGrid_PerSideCells = 40;

//Proprietà fisiche

Scene1_Physics.Scene_PhysicsProperties_Ground = true;

Scene1_Physics.Scene_PhysicsProperties_GroundNormals = D3DXVECTOR3(0, 1, 0);

Scene1_Physics.Scene_PhysicsProperties_StepSize = 0.1f;

Scene1_Physics.Scene_PhysicsProperties_NumIterations = 20;

Scene1_Physics.Scene_PhysicsProperties_Gravity = D3DXVECTOR3(0.0f,-9.8f, 0.0f);

Scene1_Physics.Scene_PhysicsProperties_CFM = 1e-5f;

Scene1_Physics.Scene_PhysicsProperties_ERP = 0.2f;

Scene1_Physics.Scene_PhysicsProperties_ContactMaxCorrectingVel = 0.9f;

Scene1_Physics.Scene_PhysicsProperties_ContactSurfaceLayer = 0.001f;

Scene1_Physics.Scene_PhysicsProperties_LinearThreshold = 0.01;

Scene1_Physics.Scene_PhysicsProperties_AngularThreshold = 0.01;

Scene1_Physics.Scene_PhysicsProperties_Steps = 10;

Scene1_Physics.Scene_PhysicsProperties_Time = 0;

Scene1_Physics.Scene_PhysicsProperties_Flag = 1;

3.3.2 Elementi fondamentali

Quando si definisce una scena è necessario associare ad essa delle funzioni che

verranno utilizzate durante la fase di runtime del motore grafico. Le parti

fondamentali di una scena sono 7, così suddivise:

Load()

Questa funzione viene eseguita durante il caricamento iniziale della stessa

ed in essa devono essere inserite tutte le inizializzazioni statiche degli

elementi che saranno presenti nella scena.

Input()

Questa funzione viene eseguita costantemente per la gestione degli input

(siano essi da tastiera, mouse, joypad ecc) che permettono all’utente di

comunicare con gli elementi presenti nella scena.

Render3D()

Questa funzione viene eseguita costantemente durante la fase di rendering

e contiene tutto ciò che il motore grafico deve renderizzare in tre

dimensioni e che possiede proprietà fisiche che ne influenzano la posizione.

Render2D()

Come la precedente, questa funzione viene eseguita costantemente e

contiene tutti gli elementi a due dimensioni da renderizzare.

Invalidate()

Questa funzione viene eseguita quando avviene un errore inaspettato che è

possibile risolvere scaricando e ricaricando le risorse in uso.

Restore()

Questa funzione segue la precedente, ristabilendo le proprietà che gli

oggetti presenti avevano prima dell’errore.

Release()

Questa funzione contiene tutto ciò che deve essere eseguito quando la

scena termina e viene dunque rilasciata; solitamente viene richiamata al

termine dell’applicazione ma è possibile farlo, magari per necessità di

memoria, anche durante la fase di runtime del motore grafico.

3.4 Passi di rendering

3.5 Illuminazione

3.6 Sistema di allocazione della memoria

Durante lo studio e la realizzazione del motore grafico si è dovuto

necessariamente affrontare il problema della allocazione della memoria; questo

per far fronte ad un problema noto delle DirectX le quali, al secondo caricamento

di una stessa risorsa, allocano nuovamente la memoria necessaria per tale

risorsa. Si capisce come questo sistema risulti praticamente inusabile quando si

devono caricare molte copie uguali di una stessa risorsa. Si è deciso quindi di

creare un sistema che permettesse il caricamento singolo e che alla richiesta dei

caricamenti successivi si facesse riferimento a alla prima senza venire

nuovamente caricata, evitando quindi il dispendio eccessivo di memoria occupata.

Le risorse principali del motore grafico influenzate da questo sistema sono le

seguenti:

Modelli

Meshes

Materiali

Textures

Il metodo utilizzato per il caricamento unico di tali risorse si basa sul fatto che

esse devono necessariamente essere caricate da disco tramite il proprio nome del

file. In questo modo una risorsa è associata univocamente a tale nome di file e

facilmente identificabile.

3.7 Suoni

Una qualsivoglia applicazione grafica, e più precisamente un videogioco, per dare

un senso di realismo non può limitarsi semplicemente alla grafica ma deve, in

qualche modo, coinvolgere il giocatore in una esperienza ancora maggiore di

divertimento.

Gli effetti sonori sono dunque una parte fondamentale se si vuole ottenere questo

tipo di risultato e anche il Desdinova Engine mette a disposizione una serie di

classi e funzioni utili per la riproduzione di qualsiasi suono collocato nella scena

(per maggiori informazioni sul tipo di libreria esterna utilizzata, fare riferimento al

paragrafo specifico, di seguito in questo capitolo).

3.8 Files di scripting esterni

E’ bene ricordare come, nell’ambito della realizzazione di un motore grafico, si

debba costantemente tenere in considerazione che l’utente finale (nel caso

specifico il programmatore della applicazione grafica) non deve essere vincolato,

nel limite del possibile, da nessuna restrizione soprattutto quando si tratta della

gestione delle configurazioni e delle variabili “esterne”.

Si è quindi costretti, se si vuole agire in questo senso, a dotare il programmatore

di strumenti esterni al motore grafico che rendano la fase di sviluppo, la fase di

esecuzione e la fase di test il più dinamiche possibile.

Il primo problema da affrontare quindi è la creazione di una serie di formati di file

caricabili da disco che costituiranno un vero e proprio strumento che il

programmatore dovrà conoscere per poter eseguire, dinamicamente, l’intera

scena e le parti più importanti di essa.

3.8.1 Considerazioni sui files esterni

E’ bene accennare al fatto che esistono svariati sistemi di scripting, tutti con le

loro potenzialità a debolezze che quindi ne favoriscono e pregiudicano l’utilizzo in

una o nell’altra categoria di applicazioni grafiche.

Durante lo sviluppo del Desdinova Engine si è subito presentata l’esigenza di

utilizzare un tale sistema di scripting per togliere gran parte di quella rigidità che

molti motori grafici posseggono. La scelta però non è ricaduta su librerie esterne

ma bensì sul semplice sistema di scripting integrato nelle DirectX che

normalmente viene utilizzato per il caricamento di VertexShaders e PixelShaders.

E’ bene chiarire in questa sede quanto tale sistema sia molto semplice e ristretto

rispetto a prodotti commerciali ma si è ritenuto comunque valido tale approccio

perché facilmente integrabile nel motore grafico.

Tale sistema di scripting si avvale di file esterni (solitamente con estensione .fx)

nei quali sono contenute le dichiarazioni e le assegnazioni di svariati tipi sintattici

dei più comuni linguaggi di programmazione nonché una gestione accurata di

vettori e stringhe; il concetto si basa semplicemente sul fatto che tale

dichiarazione di variabile e il suo rispettivo valore specificato, verranno caricate

da apposite funzioni che ne faranno il “parse” e restituiranno, a runtime, tale

valore. Si intuisce quindi come questo sistema sia incredibilmente semplice ma

nello stesso tempo potente e dinamico; in questo modo il codice sorgente

dell’applicazione non ha bisogno di essere ricompilato se i valori iniziali di una

variabile oppure di una costante devono essere, per necessità, cambiati.

3.8.2 Files dei Modelli (estensione .dem)

Il primo tipo di file necessario al motore grafico è quello utilizzato per la

definizione del modello e di tutte le sue proprietà. Tale file è strutturato

principalmente in quattro parti, utilizzate durante il caricamento del modello, che

verranno analizzate qui di seguito.

La prima parte è caratterizzata da tutte le informazioni che riguardano le

proprietà di visualizzazione del modello all’interno della scena (nome

identificativo, nome del file, nome del materiale, utilizzo di luci ecc):

string Model_Name

int Model_Lods

bool Model_Pickable

bool Model_ScreenAligned

bool Model_Cullable

bool Model_Trascurable

string Model_Material

string Model_Mesh

float3 Model_Scale

bool Model_UseDirectionalLight

bool Model_UsePointLight

bool Model_UseBump

bool Model_FillMode

bool Model_BoundingType

bool Model_ShowAxes

La parte successive riguarda invece le proprietà fisiche che il modello dovrà avere

(la massa e il tipo di classe utilizzata per la collisione):

float Model_Physics_Mass

int Model_Physics_Class

La terza parte invece definisce le proprietà che riguardano le textures da

applicare al modello (nome del file, colore, presenza di mipmaps); è da

specificare come questa sezione fa uso di un numero progressivo all’interno del

nome delle variabili, questo per poter identificare in modo univoco la singola

texture:

string Texture0_Filename

float4 Texture0_RGBA

bool Texture0_MipMaps

L’ultima parte riguarda invece le informazioni di creazione del modello utilizzate

unicamente come riferimento tra il programmatore e il creatore del modello

stesso (nome dell’autore, date, commenti e altre informazioni):

string Info_Author

string Info_Date

string Info_Revision

string Info_Comment

string Info_Other

3.8.3 Files dei Materiali (estensione .fx)

Una volta caricato il modello è necessario definire le proprietà che avrà il

materiale applicato su di esso; per fare questo è necessario quindi creare gli

shaders (pixel e vertex) necessari per il rendering.

Il file in questione è caratterizzato principalmente da tre sezioni che verranno

analizzate brevemente di seguito.

La prima sezione riguarda principalmente la dichiarazione dele variabili utilizzate

internamente dallo shader; è bene specificare come tali variabili non hanno

bisogno di un valore iniziale perché sono principalmente temporanee.

Le sezione successiva riguarda invece la dichiarazione e la definizione delle

variabili che descrivono, specificatamente, il materiale in questione; è in questa

sezione che è possibile impostare a piacimento i valori per la rifrazione della luce

sulla superficie del modello:

float4 ambientCol = { 0.6f, 0.6f, 0.6f, 0.6f };

float4 ambientMat = { 1.0f, 1.0f, 1.0f, 1.0f };

float4 diffuseMat = { 0.0f, 0.0f, 1.0f, 1.0f };

float4 emissiveMat = { 1.0f, 1.0f, 1.0f, 1.0f };

float shininess = 50.0f;

La terza parte riguarda principalmente l’implementazione dei VertexShader e

PixelShader; per la realizzazione di questa sezione si è pensato di crearla più

dinamica possibile e, a meno di particolari necessità, non è necessario modificarla

o conoscerla in dettaglio.

3.8.4 Files dei Sistemi Particellari (estensione .psy)

3.9 Librerie esterne

Sebbene con il termine “motore grafico” si indica solitamente tutto ciò che serve

per mostrare a video delle mesh collocate in uno spazio tridimensionale, si è

deciso di dotare il Desdinova Engine anche di qualcosa di più, qualcosa che gli

permettesse di creare in modo completo un’applicazione grafica di alto livello.

In base a questa prerogativa si sono implementate, attraverso librerie esterne di

terze parti, due importanti funzionalità che oggigiorno non possono mancare: il

motore fisico e i suoni.

3.9.1 Gestione del motore fisico tramite la libreria ODE

Innanzitutto è bene specificare quanto la scelta di utilizzare la libreria gratuita

OpenDynamicsEngine 0.5 (in breve ODE) sia stata davvero lunga e laboriosa;

questo è stato dettato soprattutto dal fatto che in rete ed in commercio esistono

parecchie librerie che gestiscono la fisica e tutto ciò che ne riguarda. E’ stato

quindi necessario un attento studio delle maggiori, e conosciute, alternative (tra

queste Ageia PhysX, Tokamak Physics, TrueAxis) basandosi principalmente sulla

facilità di implementazione ma anche, e soprattutto, sull’impatto che queste

hanno sul motore grafico e sulle sue prestazioni generali.

Una volta stabilita la scelta della libreria si è passati allo studio attento della sua

documentazione e degli esempi che vengono forniti con essa. A questo punto il

lavoro si è concentrato unicamente sulla implementazione di tale libreria

all’interno del motore grafico e specificatamente all’interno della gestione delle

scene. In questo modo l’utente è del tutto trasparente rispetto a quello che

riguarda la fisica e tutte le leggi matematiche che ne riguardano, ma deve solo

esclusivamente definire alcuni parametri generali di scena (gravità, attrito,

numero di iterazioni ecc.) e alcuni parametri riguardanti le mesh vere e proprie

(massa, tipo di collisione, ecc.) definiti quindi come dei corpi rigidi.

Il Desdinova Engine mette a disposizione dell’utente gran parte delle funzionalità

della libreria ODE tramite la struttura STRUCT_DE_SCENE_PHYSICSPROPERTIES

da impostare al momento della creazione della scena. Gli elementi principali di

tale struttura sono :

Scene_PhysicsProperties_Ground: Definisce la presenza di un piano di

collisione infinito collocato in 0,0,0; Scene_PhysicsProperties_GroundNormals: Definisce la normale del piano

infinito;

Scene_PhysicsProperties_StepSize; Scene_PhysicsProperties_NumIterations;

Scene_PhysicsProperties_Gravity: Valore della gravità (solitamento 9.8); Scene_PhysicsProperties_CFM:

Scene_PhysicsProperties_ERP: Valore di correzione applicato ad ogni step; Scene_PhysicsProperties_Flag;

Scene_PhysicsProperties_LinearThreshold; Scene_PhysicsProperties_AngularThreshold;

Scene_PhysicsProperties_Steps: Definisce l’accuratezza del calcolo fisico; Scene_PhysicsProperties_Time;

Scene_PhysicsProperties_ContactMaxCorrectingVel; Scene_PhysicsProperties_ContactSurfaceLayer;

[ Per uno studio approfondito del funzionamento della libreria si veda la

documentazione ondine della stessa ]

3.9.2 Gestione dei suoni tramite la libreria FMOD

La scelta della libreria utilizzata per la gestione dei suoni anch’essa non è stata

semplice perché, anche in questo caso, le alternative sono molte e tutte valide.

Inizialmente si era pensato di utilizzare, come lecito pensare, le DirectSound e le

DirectMusic che vengono fornite dalla Microsoft direttamente nelle DirectX;

sebbene tale alternativa fosse la più ovvia, si è deciso di focalizzare l’attenzione

su librerie più semplici ma nello stesso tempo complete. Dopo lo studio di

parecchie librerie (tra cui DirectSound e OpenAL) si è deciso di implementare nel

motore grafico l’ FMOD Ex SoundSystem 4.04 perché più performante e

soprattutto più intuitivo.

Questa libreria si avvale di un concetto molto semplice: la distinzione tra

“Samples” e “Streams”.

Nella prima categoria fanno parte tutti quei suoni che hanno lunghezza ridotta e

che devono essere riprodotti molte volte e ripetutamente; un esempio potrebbero

essere gli spari di una pistola, oppure l’accensione di una macchina. Tali suoni

vengono eseguiti a runtime al momento della chiamata.

Nella seconda categoria fanno parte invece tutti quei suoni che hanno una

lunghezza elevata e che vengono ripetuti pochissime volte all’interno

dell’applicazione; l’esempio più semplice è quello della colonna sonora o

comunque tutte le musiche che fanno da sottofondo ad una data scena. Tali suoni

vengono precaricati all’avvio dell’applicazione ed occupano un’elevata quantità di

memoria fissa (dipendente dalla lunghezza del file).

Un’altra prerogativa della libreria è la distinzione tra suoni 2D e suoni 3D. Nella

prima categoria ricadono tutti quei suoni che non hanno bisogno di effetti di

posizionamento e che generalmente vengono usati come effetti di interfaccia; un

esempio può essere il click del mouse alla pressione del pulsante “New Game”

oppure la voce fuori campo di un narratore.

Nella seconda categoria ricadono invece tutti i suoni che necessitano di una

collocazione spaziale tridimensionale in modo da essere percepiti dall’ascoltatore

dove realmente essi si trovano. Quando un suono 3D viene eseguito il suo

volume, la sua posizione e la sua reazione vengono stabiliti in base a quello che

viene definito “listener” (appunto “ascoltatore”). Solitamente tale entità viene

fatta riferire alla posizione corrente della telecamera che rappresenta gli occhi di

osserva, in questo modo non si è fatto altro che dotare tale telecamera di

“orecchie”. In base a questo principio, ad ogni suono 3D può essere applicato un

effetto sfruttando quella che ormai è conosciuta con il nome di tecnologia EAX e

DolbySourround.

Il Desdinova Engine mette a disposizione dell’utente gran parte delle funzionalità

della libreria FMOD tramite le seguenti classi di utilizzo immediato:

DESoundSystem: Classe utilizzata per inizializzare la libreria FMOD e per

impostare le proprietà 3D dell’ascoltatore, nonché per reperire alcune

informazioni utili sul sistema;

DESound: Classe utilizzata per la creazione e la riproduzione di un qualsiasi

suono previa distinzione tra “Sample” e “Stream”

DESoundChannel: Classe utilizzata per definire le proprietà di appartenenza

di un suono ad un canale.

DESoundChannelGroup: Classe utilizzata per creare e definire un gruppo di

canali che avranno proprietà comuni.

DESoundDSP: Classe utilizzata per definire gli effetti applicabili ad un canale

(per esempio effetti di echo, di chorus, di flanger ecc.)

[ Per uno studio approfondito del funzionamento della libreria si veda la

documentazione ondine della stessa ]

Capitolo 4

Appendice

4.1 Elenco delle figure

4.2 Bibliografia

[0] Microsoft Corporation. Microsoft DirectX 9.0c SDK (June Update)

Documentation, 2006.

[1] Daniel Sánchez-Crespo Dalmau. Core Techniques and Algorithms in Game Programming. New Riders Publishing, 2003.

[2] Frank Luna. Introduction to 3D Game Programming with DirectX 9.0.

Wordware Publishing, 2003.

[3] Michael Dawson. C++ Game Programming. Premiere Press, 2004.

[4] Cem Cebenoyan. Graphics Pipeline Performance, GPU Gems, volume 1.

Addison Wesley, 2004.

[5] Sebastien St-Laurent. The complete HSLS Reference. Paradoxal Press, 2005.

[6] Kelly Dempski and Emmanuel Viale. Advanced Lighting and

Materials with Shaders. Wordware Publishing, 2005.

[7] André Lamothe. Windows Game Programming Gurus. Sams, 1999.

[8] ATI Research. ShaderX: Vertex and Pixel Shader Tips and Tricks with

DirectX 9.

[9] Various Authors. Game Programming Gems Series (1 to 5). Charles River Media, 1996-2006

[10] Kasper Fauerby. Improved Collision detection and Response, 2003

[11] Michael Abrash. Ramblings in realtime: inside Quake. Blue’s News, 2000.

[12] Matthias Wloka. Batch batch, batch: what does it really mean? Game

developers presentation, Conference 2003.

[13] Brian Schwab. AI Game Engine Programming. Charles River Media, 2004.

[14] Ageia PhysX Documentation, 2006

[15] Firelight Technologies. FMOD Sound System Documentation, 2006

4.3 Web links di interesse

[1] DirectX Homepage www.microsoft.com/directx

[2] GameDev www.gamedev.net

[3] DevMaster www.devmaster.net

[4] GameProg Italia www.gameprog.it

[5] CodeSampler www.codesampler.com

[6] Ageia PhysX www.ageia.com

[7] FMOD Sound System www.fmod.org

[8] Equalmeans www.equalmeans.net/~chriss/EQM

[9] Gamasutra www.gamasutra.com

[10] Game Tutorials www.gametutorials.com

[11] Humus Homepage www.humus.ca

[12] UGProgramming www.ultimategameprogramming.com