SlideShare une entreprise Scribd logo
1  sur  56
Télécharger pour lire hors ligne
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 = "TexturesPanelsCine.bmp";
Scene1_Properties.Scene_Cursor_Filename[0] = "TexturesPanelsCursor1.bmp";
Scene1_Properties.Scene_Cursor_Filename[1] = "TexturesPanelsCursor2.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

Contenu connexe

Similaire à Desdinova Engine: Motore grafico 3D per rendering di ambienti outdoor in tempo reale (tesi di laurea)

La stampa 3D nella scuola: imparare creando
La stampa 3D nella scuola: imparare creandoLa stampa 3D nella scuola: imparare creando
La stampa 3D nella scuola: imparare creandoImpara digitale
 
Present kinect4 windows
Present kinect4 windowsPresent kinect4 windows
Present kinect4 windowsI3P
 
SVILUPPO DI VIDEOGIOCHI SU PIATTAFORMA ANDROID: TUTTA LA POTENZA SU DUE DIMEN...
SVILUPPO DI VIDEOGIOCHI SU PIATTAFORMA ANDROID: TUTTA LA POTENZA SU DUE DIMEN...SVILUPPO DI VIDEOGIOCHI SU PIATTAFORMA ANDROID: TUTTA LA POTENZA SU DUE DIMEN...
SVILUPPO DI VIDEOGIOCHI SU PIATTAFORMA ANDROID: TUTTA LA POTENZA SU DUE DIMEN...Danilo Riso
 
Quando il gioco si fa duro, i duri iniziano a giocare
Quando il gioco si fa duro, i duri iniziano a giocareQuando il gioco si fa duro, i duri iniziano a giocare
Quando il gioco si fa duro, i duri iniziano a giocareCherry Consulting by S.M.
 
Design of G.E.M.I.X.: Game Engine Movie Interaction eXperience
Design of G.E.M.I.X.: Game Engine Movie Interaction eXperienceDesign of G.E.M.I.X.: Game Engine Movie Interaction eXperience
Design of G.E.M.I.X.: Game Engine Movie Interaction eXperienceAntonio Notarangelo
 
Laura Rizzi 268916
Laura Rizzi 268916Laura Rizzi 268916
Laura Rizzi 268916asia90
 
Soluzioni per la produzione multimediale
Soluzioni per la produzione multimedialeSoluzioni per la produzione multimediale
Soluzioni per la produzione multimedialeGianluca Vaglio
 
Iuavcamp presentazione gianmarco ippino copia 2
Iuavcamp presentazione gianmarco ippino copia 2Iuavcamp presentazione gianmarco ippino copia 2
Iuavcamp presentazione gianmarco ippino copia 2gianmarcoippino
 
Iuavcamp presentazione gianmarco ippino
Iuavcamp presentazione gianmarco ippinoIuavcamp presentazione gianmarco ippino
Iuavcamp presentazione gianmarco ippinogianmarcoippino
 
Iuavcamp presentazione gianmarco ippino
Iuavcamp presentazione gianmarco ippinoIuavcamp presentazione gianmarco ippino
Iuavcamp presentazione gianmarco ippinogianmarcoippino
 
Vray - GianmarcoCisotto269256
Vray - GianmarcoCisotto269256Vray - GianmarcoCisotto269256
Vray - GianmarcoCisotto269256gianmarcoc
 
Una crescita in più direzioni per la stampa 3D
Una crescita in più direzioni per la stampa 3DUna crescita in più direzioni per la stampa 3D
Una crescita in più direzioni per la stampa 3DCherry Consulting by S.M.
 

Similaire à Desdinova Engine: Motore grafico 3D per rendering di ambienti outdoor in tempo reale (tesi di laurea) (20)

CINEMA 4D presentazione
CINEMA 4D presentazioneCINEMA 4D presentazione
CINEMA 4D presentazione
 
La stampa 3D nella scuola: imparare creando
La stampa 3D nella scuola: imparare creandoLa stampa 3D nella scuola: imparare creando
La stampa 3D nella scuola: imparare creando
 
Present kinect4 windows
Present kinect4 windowsPresent kinect4 windows
Present kinect4 windows
 
SVILUPPO DI VIDEOGIOCHI SU PIATTAFORMA ANDROID: TUTTA LA POTENZA SU DUE DIMEN...
SVILUPPO DI VIDEOGIOCHI SU PIATTAFORMA ANDROID: TUTTA LA POTENZA SU DUE DIMEN...SVILUPPO DI VIDEOGIOCHI SU PIATTAFORMA ANDROID: TUTTA LA POTENZA SU DUE DIMEN...
SVILUPPO DI VIDEOGIOCHI SU PIATTAFORMA ANDROID: TUTTA LA POTENZA SU DUE DIMEN...
 
Quando il gioco si fa duro, i duri iniziano a giocare
Quando il gioco si fa duro, i duri iniziano a giocareQuando il gioco si fa duro, i duri iniziano a giocare
Quando il gioco si fa duro, i duri iniziano a giocare
 
Scheda video word
Scheda video wordScheda video word
Scheda video word
 
Design of G.E.M.I.X.: Game Engine Movie Interaction eXperience
Design of G.E.M.I.X.: Game Engine Movie Interaction eXperienceDesign of G.E.M.I.X.: Game Engine Movie Interaction eXperience
Design of G.E.M.I.X.: Game Engine Movie Interaction eXperience
 
Cultural heritage, dalla digitalizzazione al web: nuovi strumenti e possibili...
Cultural heritage, dalla digitalizzazione al web: nuovi strumenti e possibili...Cultural heritage, dalla digitalizzazione al web: nuovi strumenti e possibili...
Cultural heritage, dalla digitalizzazione al web: nuovi strumenti e possibili...
 
Laura Rizzi 268916
Laura Rizzi 268916Laura Rizzi 268916
Laura Rizzi 268916
 
Xn Apdf
Xn ApdfXn Apdf
Xn Apdf
 
Io, Android
Io, AndroidIo, Android
Io, Android
 
this = that
this = that this = that
this = that
 
Soluzioni per la produzione multimediale
Soluzioni per la produzione multimedialeSoluzioni per la produzione multimediale
Soluzioni per la produzione multimediale
 
Iuavcamp presentazione gianmarco ippino copia 2
Iuavcamp presentazione gianmarco ippino copia 2Iuavcamp presentazione gianmarco ippino copia 2
Iuavcamp presentazione gianmarco ippino copia 2
 
Iuavcamp presentazione gianmarco ippino
Iuavcamp presentazione gianmarco ippinoIuavcamp presentazione gianmarco ippino
Iuavcamp presentazione gianmarco ippino
 
Iuavcamp presentazione gianmarco ippino
Iuavcamp presentazione gianmarco ippinoIuavcamp presentazione gianmarco ippino
Iuavcamp presentazione gianmarco ippino
 
Lezioni 2009
Lezioni 2009Lezioni 2009
Lezioni 2009
 
Vray - GianmarcoCisotto269256
Vray - GianmarcoCisotto269256Vray - GianmarcoCisotto269256
Vray - GianmarcoCisotto269256
 
Reportage Delphi Day 2012
Reportage Delphi Day 2012Reportage Delphi Day 2012
Reportage Delphi Day 2012
 
Una crescita in più direzioni per la stampa 3D
Una crescita in più direzioni per la stampa 3DUna crescita in più direzioni per la stampa 3D
Una crescita in più direzioni per la stampa 3D
 

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

  • 1. 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
  • 3. 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
  • 4. 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
  • 5. 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
  • 6. 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,
  • 7. 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.
  • 8. 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
  • 9. 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.
  • 10. 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
  • 11. livello, avente come base le DirectX, ma finalizzate all’utilizzo più intuitivo e semplice del programmatore quale utente finale.
  • 12. 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
  • 13. 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
  • 14. 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.
  • 15. 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.
  • 16. 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.
  • 17. 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
  • 18. 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
  • 19. 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
  • 20. 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.
  • 21. 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
  • 22. 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
  • 23. 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.
  • 24. 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.
  • 25. 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.
  • 26. 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.
  • 27. 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.
  • 28. 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
  • 29. 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)
  • 30. 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.
  • 31. 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).
  • 32. 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
  • 33. 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.
  • 34. 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
  • 35. 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
  • 36. 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.
  • 37. 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
  • 38. 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
  • 39. 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;
  • 40. 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;
  • 41. Scene1_Properties.Scene_PostProcess_Filename = "PostProcessEffects.fx"; Scene1_Properties.Scene_CineEffect_Filename = "TexturesPanelsCine.bmp"; Scene1_Properties.Scene_Cursor_Filename[0] = "TexturesPanelsCursor1.bmp"; Scene1_Properties.Scene_Cursor_Filename[1] = "TexturesPanelsCursor2.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;
  • 42. 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.
  • 43.  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.
  • 44. 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
  • 45. 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
  • 46. 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
  • 47. 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
  • 49. 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.
  • 50. 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
  • 51. 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
  • 52. 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
  • 53. 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 ]
  • 55. 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
  • 56. 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