1. POLITECNICO DI TORINO
Facoltà di Ingegneria
Corso di Laurea in Ingegneria Informatica
TESI DI LAUREA
Motore tridimensionale per ambienti virtuali distribuiti
Relatori
Prof. Paolo Montuschi
Prof. Raffaele Meo
Ing. Andrea Sanna
Candidato
Francesco E. Carucci
Ottobre 2000
4. Premessa
Premessa
La creazione di mondi tridimensionali ha assunto una grande importanza negli ul-
timi anni; in particolare un ampio spettro di applicazioni consiste nella costruzione di
scenari virtuali multiutente dove gli avatar possano interagire e/o collaborare (video-
giochi, realtà virtuale, visualizzazione e progettazione collaborativa, applicazioni mul-
timediali). I ricercatori e gli sviluppatori mirano a rendere mondi sempre più comples-
si e dettagliati, che possano essere “visitati” da un gran numero di utenti. Nuovi e più
sofisticati strumenti sono messi a disposizione dagli attori virtuali per interagire sia
con i mondi virtuali sia con gli altri avatar. Il mercato delle applicazioni grafiche sta,
inoltre, subendo una notevole espansione grazie alla definitiva affermazione del setto-
re videoludico: il videogioco è diventato un prodotto di massa e la sua enorme diffu-
sione sta spingendo alla progettazione e commercializzazione di hardware dedicato
alla grafica tridimensionale sempre più potente e ricco di funzionalità. Il software in
grado di sfruttare l’hardware odierno è ancora sviluppato con criteri non ingegneristici
e poco flessibili, che porta, a volte, all’uscita sul mercato di prodotti poco portabili e
mantenibili. Il videogioco moderno, tuttavia, ha raggiunto un livello di qualità e di cu-
ra dei particolari nei mondi tridimensionali tale da richiedere lunghi tempi di sviluppo
e costi di produzione elevati; ne sono validi esempi giochi famosi ormai entrati
nell’immaginario collettivo quali Quake III prodotto dalla ID software e Tribes pro-
dotto dalla Dynamix.
Il progetto PEUCK (Pixel ‘Em Up Construction Kit) si colloca in questo scenario e
mira a fornire strumenti semplici da usare, flessibili, portabili per lo sviluppo di mondi
tridimensionali realistici e multiutente. L’obiettivo è creare uno strato software alta-
mente ottimizzato fra l’hardware e l’utente, tale da permettere al progettista di con-
centrare i propri sforzi sul lato “artistico” del progetto, relegando le problematiche
tecniche e realizzative, alla base della realizzazione di un’applicazione in tempo reale,
allo strato software intermedio. Il PEUCK può essere considerato alla stregua di un
Sistema Operativo che virtualizza e gestisce le risorse di sistema (oggetti nella scena,
effetti sonori, luci, connessioni di rete) fornendo un insieme di “chiamate di sistema”
che possono essere usate dall’utente per realizzare la propria applicazione.
Il PEUCK fornisce le primitive per la gestione di ambienti sia esterni sia interni or-
ganizzati in diversi mondi visibili attraverso delle telecamere
Nell’ottica di realizzare una piattaforma modulare e semplice da estendere, il
PEUCK è stato interamente progettato e realizzato usando estensivamente tecniche di
progettazione e programmazione ad oggetti (OOD, OOP); l’utente “vede” ogni entità
del Sistema Operativo (oggetti, suoni, luci, connessioni) come una classe estendibile a
piacere. L’efficienza della piattaforma software è garantita adottando tecniche di pro-
grammazione a basso livello nelle parti di codice più critiche dal punto di vista presta-
zionale (“inner loops”).
I
5. Premessa
Il progetto nasce nel settembre del 1998 e si è sviluppato secondo un modello di
programmazione prototipale: è stato dapprima programmato un prototipo
dell’ambiente, che permettesse di valutare le problematiche da affrontare e fornisse
un’idea delle specifiche di progetto. Il prototipo è stato successivamente usato come
base per la programmazione effettiva del motore e tenuto presente durante lo sviluppo
della gerarchia di classi. Il progetto è stato, quindi, portato avanti secondo un modello
evolutivo, riformulando le specifiche in base all’analisi dell’evoluzione dell’hardware
e delle esigenze del mercato videoludico, primo riferimento e applicazione di questa
tesi.
Questo progetto è stato usato per la produzione di due applicazioni che girano su
normali PC dotati di una scheda grafica che acceleri le normali funzioni tridimensio-
nali, dimostrando la sua utilizzabilità in prodotti commerciali. La prima applicazione
consiste nella simulazione di un’aula virtuale in cui un professore è in grado di tenere
una lezione a diversi studenti collegati da postazioni remote in un ambiente tridimen-
sionale; un server gestisce la propagazione dell’ambiente ai diversi client e lo scambio
di messaggi dimostrando l’usabilità di questo lavoro anche in applicazioni che preve-
dano ambienti virtuali distribuiti. L’applicazione è stata testata con un massimo di 6
studenti virtuali collegati in una rete locale. La seconda applicazione consiste nella
navigazione di una città composta da un totale di 35 quartieri e 700 palazzi, per una
scena composta da circa 140k poligoni. Il motore tridimensionale permette la naviga-
zione in tempo reale all’interno della città dimostrando una buona adattabilità ad am-
bienti densi di oggetti e con un alto numero di poligoni, ambienti che saranno
nell’immediato futuro sempre più frequenti nelle applicazioni videoludiche.
Il presente lavoro è diviso in cinque sezioni: Introduzione, Architettura, Esempi
d’uso e risultati, Note tecniche e Conclusioni.
La prima sezione, Introduzione, introduce le problematiche relative alla produzione
di grafica in tempo reale descrivendo i concetti di “interattività” e “realismo”
nell’ambito delle applicazioni grafiche. Sono qui forniti al lettore gli strumenti per
comprendere le basi per giudicare la bontà di un motore in grado di generare immagi-
ni “abbastanza realistiche” in tempo reale, di ambienti dei quali è fornita una descri-
zione geometrica.
La seconda sezione che occupa quasi interamente questo scritto, descrive
l’architettura del PEUCK: dopo una descrizione generale dell’architettura, sono pre-
sentati uno dopo l’altro gli strati dai quali il progetto è composto a partire dallo strato
piu’ prossimo all’ambiente operativo ospite (Operative Layer), proseguendo con lo
strato di astrazione della piattaforma (PAL), per concludere, infine, con il cuore del
progetto che risiede nello strato indipendente dalla piattaforma dove sono realizzati i
servizi offerti direttamente all’utente organizzati in una gerarchia di classi. Una breve
descrizione dello strato applicativo, conclude la seconda sezione.
II
6. Premessa
La terza sezione descrive due esempi d’uso del PEUCK con alcuni stralci di codice
che permettano di valutare l’interfaccia a disposizione dell’utente e immagini che mo-
strano i risultati visivi. I risultati forniscono un’analisi delle prestazioni velocistiche
del motore di rendering tridimensionale.
La quarta sezione descrive l’hardware e l’ambiente di sviluppo usati durante la
scrittura del codice sorgente e il testing delle applicazioni.
Nella quinta sezione, sono stilate le conclusioni e descritti gli obiettivi raggiunti da
questo lavoro con particolare enfasi nel descrivere i possibili usi in ambiti professio-
nali quali la produzione di titoli per l’intrattenimento. Sono, inoltre, descritti gli svi-
luppi del progetto con l’elenco dei servizi che il motore non è ancora in grado di for-
nire, ma che sono fondamentali per il suo uso professionale e che saranno realizzati
nei mesi futuri dal team di sviluppo responsabile del progetto.
III
7. Ringraziamenti
Ringrazio i miei genitori per il sostegno fornitomi fin qui.
Ringrazio l’ing. Sanna per la sua disponibilità e per la sua pazienza.
Ringrazio Sebastiano Mandalà per l’aiuto nella progettazione e nella realizzazione
del codice.
8. Indice
Indice
1. INTRODUZIONE...................................................................................1
2. ARCHITETTURA..................................................................................4
Descrizione dell’architettura......................................................................4
Piattaforma operativa (Operative layer) ..................................................6
Platform Abstraction Layer (PAL) ...........................................................7
Kernel.......................................................................................................7
Driver .......................................................................................................9
Driver di Rendering............................................................................10
PEUCK.......................................................................................................14
Il Peuck Object Mode (POM) ...............................................................14
Persistenza degli oggetti ........................................................................16
PEUCK3D..............................................................................................18
Trasformazioni, viewport e telecamera ..............................................18
Trasformazioni..............................................................................18
Viewport e proiezioni ...................................................................19
Telecamera....................................................................................23
Realizzazione ................................................................................25
Modello d’illuminazione ....................................................................32
Interpretazione matematica...........................................................32
Realizzazione ................................................................................34
Texture mapping.................................................................................39
Texture mapping.................................................................................39
Introduzione..................................................................................39
Realizzazione ................................................................................41
Bump mapping ...................................................................................46
Introduzione..................................................................................46
Interpretazione matematica...........................................................47
Realizzazione ................................................................................52
Mesh e oggetti ....................................................................................55
Mesh, Parti e VertexBuffer...........................................................56
Oggetti ..........................................................................................63
Strato applicazioni ............................... Errore. Il segnalibro non è definito.
I
10. Introduzione
1. Introduzione
La produzione di un’immagine generata dal calcolatore consiste in un processo
formato da diversi passi:
• Il designer dell’applicazione specifica gli oggetti che compongono la scena, le
loro forme, le posizioni, gli orientamenti e il colore (oppure la texture, concet-
to che sarà definito in seguito) delle superifici.
• Sceglie, quindi, la posizione dell’osservatore e la direzione di vista. Il motore
tridimensionale trasforma, quindi, i punti dell’immagine per creare una proie-
zione prospettica della scena
• A questo punto del processo, è eseguito uno o più algoritmi in cascata che de-
terminano quali parti della scena sono visibili all’osservatore. Questo è ciò che
si chiama in letteratura “hidden surface removing”, ovvero rimozione delle su-
perfici nascoste.
• Gli oggetti della scena sono resi realistici attraverso la simulazione del proces-
so fisico di illuminazione usando modelli più o meno realistici. Il designer de-
finisce le sorgenti luminose, la loro posizione, l’intensità e il colore. La luce
emanata da una superficie è la combinazione della luce che arriva direttamente
dalle sorgenti luminose e riflessa dalla superficie stessa, luce dell’ambiente
che simula il contributo uniforme di tutte le sorgenti luminose, la luce generata
dalla superficie stessa.
• L’immagine è, quindi, disegnata dal motore di rendering che calcola per ogni
punto dell’immgine il colore visto dal punto di vista dell’osservatore.
Il compito più importante di un motore di rendering tridimensionale è la simula-
zione del processo di illuminazione. Due algoritmi per l’illuminazione delle scene so-
no al momento i più popolari, ray tracing e radiosity. Questi due metodi usano ap-
procci opposti al problema.
Il ray tracing è stato introdotto nel 1979 da Turner Whitted dei Bell Laboratories.
L’idea principale consiste nel tracciare un raggio che attraversi la scena dal punto di
vista dell’osservatore verso ogni pixel dello schermo. Se il raggio colpisce una super-
ficie, l’algoritmo genera una serie di raggi riflessi e rifratti che, a turno, sono tracciati
per conoscere la loro eventuale intersezione con altre superfici. Il colore finale e
l’intensità di ogni pixel sono calcolati sommando i contributi forniti da ogni raggio
generato. Il ray tracing produce immagini realistiche simulando accuratamente super-
1
11. Introduzione
fici riflettenti e metalliche ed è, per sua natura, dipendente dalla posizione
dell’osservatore; questo significa che l’intero calcolo deve essere ripetuto quando la
posizione dell’osservatore si modifica.
Il radiosity, metodo sviluppato nel 1984, e’, al contrario, indipendente dalla punto
di vista: data una scena statica, il calcolo deve essere effettuato una sola volta. Una
volta che l’illuminazione globale della scena è stata determinata, è semplice creare
una serie di immagini movendo il punto di vista dell’osservatore. Radiosità usa il
principio di conservazione dell’energia per determinare l’intensità della luce per ogni
superficie: per ogni superficie della scena è scritta un’equazione che calcola il contri-
buto della luce come la somma di dei contributi di tutte le restanti superfice. Sebbene
questo metodo possa sembrare ideale per la simulazione dell’illuminazione in un am-
biente virtuale, il suo principale difetto risiede nell’enorme numero di calcoli necessa-
rio nel ricalcolo qualora la scena non fosse statica, numero di calcoli ancora non alla
portata degli odierni calcolatori.
Entrambi i metodi sono quindi inadatti al giorno d’oggi per la resa grafica di am-
bienti virtuali tridimensionali. Sebbene in futuro si possa ipotizzare che la potenza di
calcolo delle macchine commercializzate a livello “consumer” sarà sufficiente per
l’esecuzione in tempi ragionevoli di questi due algoritmi, altri modelli di illuminazio-
ne, descritti nel seguito del lavoro, sono oggi usati per il rendering in tempo reale.
Il rendering in tempo reale, infatti, consiste nel generare rapidamente immagini sul
monitor di un computer: un’immagine appare sullo schermo, l’utente interagisce con
essa e reagisce, i suoi comandi influiscono su ciò che è visualizzato successivamente.
Il ciclo di reazione e resa dell’immagine avviene ad un ritmo abbastanza rapido tale
che l’utente non possa notare le singole immagini, ma sia immerso in un processo di-
namico che appare a lui come un’animazione.
La velocità alla quale le immagini sono visualizzate sullo schermo è misurata in
“frame per secondo” (fps) oppure Hertz (Hz). Alla velocità di un frame per secondo,
c’è poca sensazione di interattività con il mondo visualizzato e l’utente è consapevole
dell’arrivo di ogni singola immagine. A circa 6 frame per secondo, l’utente inizia a
percepire una certa sensazione di interattività con l’ambiente. Un’applicazione che
riesca a visualizzare 15 frame per secondo può già iniziare ad essere considerata
“un’applicazione grafica real time”; l’utente riesce a focalizzare la propria attenzione
sull’azione e sulla reazione. Esiste un limite oltre il quale le differenze di interattività
si fanno talmente scarse da non essere più notabili: il punto di fusione delle immagini,
è mediamente per l’uomo attorno ai 30 frame per secondo. Sebbene questo limite teo-
rico sia sufficiente a fornire l’illusione di un’animazione continua, oltre il 60/70 frame
per secondo, l’utente percepisce un’animazione talmente fluida che la sua interazione
con il mondo diventa naturale e realistica.
L’interattività non è il solo criterio con il quale si giudica un’applicazione grafica
in tempo reale: se così fosse, qualunque applicazione che rispondesse rapidamente ai
2
12. Introduzione
comandi dell’utente e disegnasse qualcosa sullo schermo potrebbe essere giudicata
positivamente. Un’applicazione grafica in tempo reale, per essere usabile e immersi-
va, deve produrre immagini dotate di un certo “realismo”, che implica ricorrere alla
resa di immagini tridimensionali il più possibile fedeli alla realtà. E’ necessario in
porre l’attenzione sul compromesso che si cerca di ottenere in questo campo:
un’immagine fotorealistica nell’accezione classica non e’, con la tecnologia attuale,
ottenibile in tempo reale: ciò che si cerca di ottenere è una buona approssimazione di
fotorealismo, che, con l’ausilio dell’animazione, possa ragionevolmente ingannare
l’utente.
L’interattività e un certo senso di tridimensionalità dello spazio sono condizioni
sufficienti per un’applicazione grafica in tempo reale, ma un terzo elemento sta rapi-
damente diventando parte importante in questa definizione: l’accelerazione hardware
della grafica tridimensionale. Sebbene hardware dedicato per la grafica tridimensiona-
le è disponibile nelle workstation professionali da parecchi anni, è recente l’ingresso
di acceleratori a livello “consumer”. Con il rapido avanzamento del mercato; adattato-
ri in grado di accelerare la resa di primitive tridimensionali stanno diventando acces-
sori comuni nei PC di fascia bassa. Sebbene un acceleratore tridimensionale non sia
assolutamente necessario per la generazione di grafica in tempo reale, esso velocizza
le operazione e permette una resa visiva migliore a un livello tale da rendere il suo
supporto necessario per una qualsiasi applicazione grafica in tempo reale odierna.
Il motore tridimensionale qui presentato si propone come una API in grado di for-
nire primitive ad alto livello per la programmazione di applicazioni grafiche in tempo
reale e pone massima attenzione sulla generazione di immagini il più possibile reali-
stiche ad un livello “interattivo”, facendo largo uso delle possibilità offerte dai mo-
derni acceleratori grafici tridimensionali. Durante lo sviluppo è stata, quindi, posta
particolare attenzione nel delegare quando possibile all’acceleratore grafico la mag-
gior parte delle operazione così da ottenere due scopi: un maggiore frame rate e una
minore occupazione della cpu, così da lasciarla il più possibile libera di eseguire altri
task quali possono essere ad esempio la simulazione di leggi fisiche oppure
l’esecuzione di algoritmi di intelligenza artificiale che forniscano maggior realismo
all’ambiente virtuale.
3
13. Architettura
2. Architettura
Figura 1: L'architettura del PEUCK
Descrizione dell’architettura
L’architettura di sistema del PEUCK è mostrata in Fig. 1. Ogni modulo apparte-
nente ad un determinato livello, può usare i servizi messi a disposizione dai moduli
appartenenti al medesimo livello oppure ai livelli inferiori, in un’architettura comune
a quella usata tipicamente nella progettazione dei sistemi operativi, che permette
un’organizzazione dei servizi coerente e semplice da mantenere.
Il module Peuck3D, che si occupa della resa di un ambiente tridimensionale, usa i
servizi forniti dal modulo Peuck2D (per visualizzare un’interfaccia utente bidimen-
sionale ad esempio) al suo stesso livello e l’API messa a disposizione dal driver di
Rendering situato ad un livello inferiore (PAL). L’uso di oggetti appartenenti ad un
modulo per la visita di mondi VRML sarebbe illegittimo. Ad ogni modulo, quindi,
non è permesso l’uso di servizi forniti da un livello superiore.
L’organizzazione a strati permette, inoltre, una netta separazione del S.O. dai det-
tagli della piattaforma sulla quale è eseguito, incapsulati nel PAL. Il Kernel si farà ca-
rico di fornire agli strati superiori i servizi di base comuni ad ogni piattaforma quali
l’allocazione della memoria, la gestione dei moduli, il multithreading e la sincronizza-
4
14. Architettura
zione, l’accesso alle periferiche di memorizzazione di massa. La struttura a driver
permette una semplice estendibilità dell’architettura fornendo API per la gestione del-
le schede grafiche accelerate, delle periferiche di input quali tastiera, mouse, joystick,
dei diversi protocolli di rete (TCP/IP, NetBEUI). Lo strato intermedio che realizza le
classi base del PEUCK risulta, quindi, portabile e risente in minima parte del cambio
di piattaforma. E’ possibile ipotizzare a questo livello la realizzazione del PEUCK an-
che su piattaforme non “standard” quali Win32/Macintosh, ma su hardware dedicato
come può essere una consolle per videogiochi.
5
15. Architettura
Piattaforma operativa (Operative layer)
Il primo livello in cui si articola l’architettura del PEUCK è lo strato operativo di-
pendente dalla piattaforma. Allo stato attuale del progetto, l’unica piattaforma
hardware supportata è la famiglia di processori Intel e compatibili (PentiumII, Pen-
tiumIII, AMD e Cyrix), con sistema operativo Win32 (Windows 9x, Windows
NT/2000) oppure BeOS.
Il primo strato riguarda l’hardware specifico e/o il sistema operativo sottostante sul
quale il progetto si appoggia: questo non è, quindi, oggetto del nostro lavoro, ma si in-
tende fornito “così com’è) dal produttore della piattaforma.
Le risorse messe a disposizione dal primo strato sono strettamente dipendenti dalla
piattaforma in oggetto e possono variare al variare della piattaforma stessa: è possibi-
le, ad esempio, avere hardware avanzato per la gestione di grafica tridimensionale
come doversi appoggiare ad una realizzazione completamente software del disegno
delle primitive. Ancora ci si può trovare di fronte ad una piattaforma che non realizzi
nativamente un’architettura multithreading: sarà compito dello strato superiore di a-
strazione dell’hardware fornire una sua realizzazione adeguata.
Le risorse sono, infatti, “virtualizzate” dagli strati superiori e fornite in un contesto
coerente e invariante alla piattaforma; in quest’ottica, l’utente non è direttamente a
contatto con l’hardware e la migrazione della propria applicazione da una piattaforma
all’altra può essere eseguita con maggiore semplicità, riducendo i tempi di sviluppo di
un progetto multimediale multipiattaforma.
6
16. Architettura
Platform Abstraction Layer (PAL)
Anche il PAL (Platform Abstraction Layer) dipende strettamente dalla piattaforma,
in quanto al suo interno è situato il Kernel che fornisce i servizi base del PEUCK: al-
locazione della memoria, gestione dei task, primitive di sincronizzazione, file system,
caricamento e gestione di moduli dinamici. Al contrario della piattaforma operativa, il
PAL è oggetto del nostro lavoro, in quanto il suo compito è quello di virtualizzare
l’ambiente di lavoro e di fornire una piattaforma virtuale alla quale gli strati superiori
possano appoggiarsi per fornire i propri servizi.
Oltre al Kernel, il PAL contiene moduli a caricamento dinamico (driver) per
l’accesso alle periferiche comuni in applicazioni di grafica in tempo reale: parliamo
del driver di rendering per l’accesso alle schede grafiche acceleratici (in caso di loro
assenza, fornirà una loro realizzazione software), il driver di input per l’accesso a pe-
riferiche quali tastiera, mouse, joystick, il driver sonoro per l’accesso ad eventuali
schede di riproduzione sonora, driver di rete per l’accesso a schede di rete attraverso
vari protocolli.
Il driver di rendering, ad esempio, fornisce agli strati superiori un set di chiamate
per la manipolazione di primitive bidimensionali e tridimensionali (vertici, triangoli),
per la loro trasformazione e proiezione, per la gestione di texture e proprietà dei mate-
riali. I driver dornite correntemente, supportano sia la libreria di “rastering” OpenGL
di Silicon Graphic (in versione generica o con particolare supporto per l’etensioni
proprietarie NVIDIA) sia la librearia Direct3D fornita assieme al pacchetto DirectX di
Microsoft.
Kernel
Il Kernel si occupa di astrarre le risorse tipiche di un sistema operativo e di fornire
agli strati superiori i servizi ottimizzati per il genere d’applicazione richiesto: grafica
in tempo reale. Il Kernel è strattamente dipendente dalla piattaforma e, per ragioni
d’efficienza d’esecuzione, è scritto in C puro.
Si divide in quattro sezioni principali: inizializzazione e logging, gestione della
memoria, multithreading e sincronizzazione, gestione dei moduli.
int _KERNEL_API kernelInit (struct EnvStruct *env, char *log_file)
int _KERNEL_API kernelShutdown ()
void _KERNEL_API kernelRegisterLogger (LOG_PROC loggerProc)
void _KERNEL_API kernelLog (char *msg)
7
17. Architettura
Al momento del bootstrap del PEUCK (a cura dell’hardware oppure di un modulo
software lanciato dal Sistema Operativo ospite), il Kernel è inizializzato mediante una
chiamata alla funzione kernelInit; è possibile registrare una propria funzione che ver-
rà richiamata ogni qual volta sarà richiesto al kernel il “logging” di una stringa me-
diante una chiamata a kernelLog.
void* _KERNEL_API kernelMemAlloc (dword size)
void* _KERNEL_API kernelMemRealloc (void *blk, dword size)
void _KERNEL_API kernelMemFree (void *blk)
La gestione della memoria del Kernel si divide in base al tipo di compilazione: in
modalità Debug, l’allocatore di memoria controlla e tiene traccia di ogni singola ope-
razione di allocazione e disallocazione alla ricerca di possibili blocchi di memoria la-
sciati non deallocati; al momento della deallocazione di un blocco di memoria, il
“mxemory tracker” controlla eventuali scritture al di fuori della zona di memoria as-
segnata per fornire maggior sicurezza all’utente.
int _KERNEL_API kernelStartThread (char name[], THREAD_PROC thread, int priority,
void *data)
int _KERNEL_API kernelStopThread (int thread)
int _KERNEL_API kernelTerminateThread (int thread)
int _KERNEL_API kernelSuspendThread (int thread)
int _KERNEL_API kernelResumeThread (int thread)
int _KERNEL_API kernelChangeThreadPriority (int thread, int priority)
int _KERNEL_API kernelSleep (int ms)
int _KERNEL_API kernelCreateSemaphore (int count)
int _KERNEL_API kernelDestroySemaphore (int semaphore)
int _KERNEL_API kernelAcquireSemaphore (int semaphore)
int _KERNEL_API kernelReleaseSemaphore (int semaphore, int count)
int _KERNEL_API kernelCreateTimer ()
int _KERNEL_API kernelDestroyTimer (int timer)
int _KERNEL_API kernelStartTimer (int timer, int ms)
int _KERNEL_API kernelStopTimer (int timer)
int _KERNEL_API kernelSetTimerResolution (int micros)
int _KERNEL_API kernelWaitForThread (int thread)
int _KERNEL_API kernelWaitForTimer (int timer)
Il Kernel fornisce un meccanismo di programmazione concorrente attraverso
l’astrazione del concetto di thread, fornendo agli strati superiori le primitive per la
creazione, distruzione, esecuzione e gestione di porzioni di codice concorrente. Il
Kernel fornisce il semaforo come unica primitiva di sincronizzazione nativa: sarà
compito degli strati superiori definire meccanismi di sincronizzazione più complessi.
8
18. Architettura
Le applicazioni grafiche/multimediali necessitano sovente di timer sui quali sin-
cronizzare le operazioni: il Kernel fornisce le funzioni per la gestione di un massimo
di quattro timer contemporanei.
int _KERNEL_API kernelLoadModule (char module[], struct ModuleInfoStruct *info)
int _KERNEL_API kernelUnloadModule (int module)
int _KERNEL_API kernelGetModuleProc (int module, char name[], void **proc)
Sono forniti, inoltre, i meccanismi per la gestione di moduli caricabili dinamica-
mente; i driver di gestione delle periferiche sono, ad esempio, un particolare di tipo di
modulo dinamico.
Driver
Il driver generico è un modulo a caricamento dinamico per la gestione di periferi-
che quali la scheda grafica acceleratrice, le periferiche di input, la scheda di produzio-
ne sonora, la scheda di rete.
Un driver è tipicamente realizzato come un elenco di funzioni; fornisce, quindi, un
API non ad oggetti per l’astrazione del periferico gestito. E’ suddiviso in due parti di-
stinte: che si occupano della sua inizializzione, distruzione e dall’API di gestione ve-
ra e propria del periferico.
_PEXPORT int _INTERFACE moduleGetInfo (struct ModuleInfoStruct *info)
_PEXPORT int _INTERFACE moduleInit (struct KernelAPIStruct *kernel)
_PEXPORT int _INTERFACE moduleShutdown ()
_PEXPORT int _INTERFACE buildAPI (struct APIStruct *api)
L’interfaccia esportata è comune e si occupa di fornire informazioni sul modulo (e
quindi sul driver del quale è un tipo particolare), inizializzarlo, rimuoverlo dalla me-
moria e costruire l’API specifica.
Questo è un esempio delle informazioni fornite da un driver di rendering:
struct opengl {
quot;OpenGL driverquot;,
quot;OpenGL driver under Win32quot;,
quot;Francesco E. Carucciquot;,
0x0090,
1
}
L’interfaccia specifica di ogni driver è richiesta mediante una chiamata a buildAPI
e varia con il variare del tipo di driver.
9
19. Architettura
Driver di Rendering
Il driver di rendering espone agli strati superiori un’API generica per la trasforma-
zione, l’illumanizione e il disegno di primitive tridimensionali. Crea, inoltre, il conte-
sto di visualizzazione adatto alla risoluzione e alla profondità di colore richieste
dall’utente.
La libreria di rendering astratta, si basa sul modello della macchina a stati:
un’insieme di variabili impostabili dall’esterno, stabilisce lo stato di rendering corren-
te sul quale si baseranno le operazioni di disegno delle primitive; alcuni esempi di va-
riabili di stato sono il materiale corrente, che contiene le informazioni usate durante la
fase di illuminazione, il buffer di vertici corrente, la texture di dettaglio corrente, il
colore della luce ambiente, il colore di sfondo. Altri stati contengono le informazioni
che riguardano particolari modalità di disegno quali il “disegno a fil di ferro”.
L’interfaccia del driver di rendering generico presenta per prime le funzioni per la
sua inizializzione.
int _DRIVER_API renInit (struct PeuckContext const *context)
int _DRIVER_API renStart (struct HostWinStruct *window, int w, int h, int bpp, bool full-
screen)
int _DRIVER_API renStop (int arena)
int _DRIVER_API renSetCurrent (int arena)
int _DRIVER_API renShutdown ()
L’applicazione inizializza il driver di rendering fornendo a renInit il contesto cor-
rente all’interno del quale il PEUCK è in esecuzione. La struttura dati PeuckContext
contiene informazioni specifiche dell’ambiente operativo ed è inizializzata durante il
bootstrap del PEUCK; in ambiente Win32, ad esempio, la struttura contiene
un’istanza dell’applicazione che ospita il Sistema Operativo:
struct PeuckContext {
HINSTANCE hInstance; // application process instance
};
L’apertura di un contesto di rendering (chiamato arena di rendering) avviene me-
diante una chiamata a renStart, fornendo una struttura riservata di nome HostWin-
Struct, il cui contenuto dipende dal particolare tipo di ambiente operativo e può esse-
re l’identificativo di una finestra sistema, come di un particolare monitor per applica-
zioni a tutto schermo (specificate inoltre dall’appisito parametro fullscreen). La di-
mensione dell’arena di rendering e la profondità di colore desiderata, completano le
informazioni necessarie all’inizializzazione dell’arena di rendering, la cui chiusura è
richiesta invocando renStop. L’arena di rendering oggetto del disegno è selezionata
mediante una chiamata a renSetCurrent. La fine delle operazioni e la distruzione del-
le strutture interne al driver di rendering è segnalato da renShutDown.
10
20. Architettura
int _DRIVER_API renSetCurrentViewport (struct ViewportStruct *viewport)
int _DRIVER_API renSetCurrentCamera (float camera[])
int _DRIVER_API renClear ()
int _DRIVER_API renBeginScene ()
int _DRIVER_API renEndScene ()
int _DRIVER_API renSwap ()
Dopo aver informato il driver dell’inizio del disegno delle primitive che compon-
gono una scena con renBeginScene, l’applicazione riempie lo schermo con un colore
predefinito mediante renClear.
I passi per disegnare una scena iniziano con la selezione di una viewport corrente
(renSetCurrentViewport) per la visualizzazione. Una viewport è definita da una
porzione bidimensionale sullo schermo caratterizzata dalla posizione, dalla dimensio-
ne, da un livello di trasparenza che permetta di visualizzare viewport sovrapposte e da
una matrice di proiezione, che indichi il tipo di visualizzazione richiesto (prospettico,
ortogonale). La funzione renSetCurrentCamera seleziona la telecamera mediante la
quale le successive primitive devono essere disegnate. La scena si chiude con una
chiamata a renEndScene.
Al fine di mantenere un’immagine visualizzata stabile ed evitare le distorsioni do-
vute all’effetto della rintraccia verticale del monitor, il driver di rendering disegna la
scena su un porzione di memoria video non correntemente visualizzata. Al termine del
disegno di un “frame di animazione”, il driver è forzato a visualizzare la zona conte-
nente la nuova scena chiamando la funzione renSwap. L’uso della tecnica del “dou-
ble buffering” non può essere disabilitato.
int _DRIVER_API renUseMaterial (struct MaterialStruct *material)
int _DRIVER_API renSetAmbientLight (float color[])
int _DRIVER_API renUseLight (int pos, struct Light3DStruct *light)
int _DRIVER_API renBlockVertexBuffer (struct VertexBufferStruct *buffer)
int _DRIVER_API renUnblockVertexBuffer (struct VertexBufferStruct *buffer)
int _DRIVER_API renUseVertexBuffer (struct VertexBufferStruct *buffer)
int _DRIVER_API renTransformVertexBuffer (float matrix[], int stage)
Il materiale corrente (renUseMaterial) descrive le proprietà di diffusione, specula-
rità e emissione delle primitive secondo l’equazione di Blinn per l’illuminazione che
sarà descritta in seguito. Un massimo di 8 luci e di una luce ambiente possono essere
rese operative correntemente mediante renUseLight e renSetAmbientLight.
Il disegno delle primitive si basa sulla selezione di un insieme di vertici corrente: le
informazioni riguardanti un vertice comprendono le sue coordinate nello spazio
dell’oggetto in cui è definito, la base ortonormale che ne definisce uno spazio,
l’insieme di coordinate nello spazio delle texture ed eventualmente un insieme di pesi
11
21. Architettura
da usare in caso di trattamento delle deformazioni durante animazioni scheletriche.
Gli insiemi di vertici (Vertex Buffer) si dividono in due categorie: vertici dinamici e
statici. Le caratteristiche di un insieme di vertici dinamici possono essere modificate
durante l’esecuzione per ottenere effetti quali morphing e ritassellazione di superfici
curve. Un insieme di vertici statico, al contrario, non può essere modificato durante
l’esecuzione e viene indicato come tale mediante la chiamata alla funzione renBlo-
ckVertexBuffer, che suggerisce al driver la possibilità di effettuare ottimizzazioni
particolari in presenza di un buffer statico. La versione corrente del driver di rende-
ring, ad esempio, copia i dati relativi ad un insieme di vertici statici all’interno della
memoria video della scheda grafica, in modo da minimizzare il traffico dalla memoria
di sistema e velocizzare enormemente il disegno delle primitive.
int _DRIVER_API renDownloadTexture (struct TextureStruct *texture, int compress)
int _DRIVER_API renUnloadTexture (struct TextureStruct *texture)
int _DRIVER_API renUseTexture (struct TextureStruct *texture, float *matrix, int stage)
Per aumentare il realismo delle scene, una tecnica comune consiste nell’associare
alle primitive un’immagine (detta texture) che descriva i dettagli fini della superficie.
Una texture è, solitamente, una bitmap mappata dallo spazio delle texture allo spazio
dell’oggetto (da qui il termine texture map); ogni vertice contiene le coordinate nello
spazio delle texture alle quali l’applicazione può associare una matrice di trasforma-
zione. Il driver di rendering richiede che ogni texture sia trasferita all’hardware prima
del suo uso (renDownloadTexture); la texture può eventualmente essere compressa
all’interno della memoria video (se l’operazione è supportata dall’hardware) con lieve
perdita di dettaglio visivo, al fine di ottimizzare l’occupazione di memoria e la banda
passante verso la scheda grafica. Le texture tendono, infatti, ad occupare una notevole
quantità di memoria soprattutto se altamente dettagliate. La texture corrente è specifi-
cata dal comando renUseTexture.
int _DRIVER_API renDrawTriangles (word v[], int count)
int _DRIVER_API renDrawTriangleStripe(word v[], int count)
Il driver di rendering supporta il disegno di primitive composte da un’insieme di
triangoli (renDrawTriangles) oppure da una striscia (renDrawTriangleStripe). I
triangoli sono specificati come indici ai vertici presenti nel VertexBuffer corrente.
Non è supportato il disegno di soli punti, linee, strisce di linee oppure ventagli di
triangoli (GR_TRIANGLE_FAN).
12
23. Architettura
PEUCK
Il Peuck Object Mode (POM)
Il Peuck Object Model rappresenta il cuore della gerarchia di classi dell’intero Si-
stema Operativo; dichiara, infatti, la classe base della gerarchia dalla quale eredita o-
gni classe dell’ambiente. Realizza, inoltre, un sistema di riconoscimento dei tipi a
tempo di esecuzione (RTTI), mediante una gerarchia di metaclassi; ad ogni classe del-
la gerarchia è possibile associare una metaclasse che contiene le informazioni che la
riguardano (nome, dimensione, codice identificativo, puntatore alla metaclasse asso-
ciata alla classe padre).
PClass
PClass (char name[], int size, int code, void *parent, void *func)
BaseClass* createObjectPtr () const
BaseClass& createObject () const
bool isDerivedFrom (PClass const& aClass) const
char m_name [21]
int m_size
int m_code
PClass* m_parent
PClass* m_next
BaseClass* (*m_creator )()
Ogni oggetto che vive all’interno dell’ambiente, ha associato un puntatore (unico
per tutti gli oggetti istanza di una classe) alla propria metaclasse di appartenenza. A
partire da una metaclasse, l’applicazione può creare un nuovo oggetto (vuoto) istanza
della particolare classe oppure conoscere la lista delle metaclassi associate alle classe
parenti.
La gerarchia di classi esportata dal POM e’ mostrata in figura. La classe base
dell’intera gerarchia del Sistema Operativo è BaseClass. Il suo compito è fornire gli
strumenti base di un qualsiasi oggetto dell’ambiente: è possibile conoscere la validità
dell’oggetto mediante una chiamata al metodo isValid oppure conoscerne il tipo con
un istruzione del tipo:
a.isA(_pclass(A));
La macro _pclass restituisce la metaclasse associata alla classe richiesta.
L’ultimo compito della classe base è fornire i metodi standard per l’allocazione e la
disallocazione di un oggetto (new and delete), che useranno le funzione del kernel per
la gestione della memoria di sistema.
14
24. Architettura
BaseClass
BaseClass ()
BaseClass* getThis ()
bool isValid () const
PClass& getParentClass () const
const char* getClassName () const
int getClassCode () const
bool isA (PClass const& aClass) const
void* _PCDECL operator new (size_t size)
void _PCDECL operator delete (void *ptr)
void* _PCDECL operator new[] (size_t)
void _PCDECL operator delete[] (void*)
Le macro _DECLARE_CLASS e _IMPLEMENT_CLASS aggiungono una
classe al sistema di riconoscimento del tipo a tempo d’esecuzione.
Il meccanismo di persistenza degli oggetti del Sistema Operativo si basa sulla clas-
se Persistent (ereditata da BaseClass), che esporta i metodi riguardanti
l’immagazzinamento e il recupero dell’oggetto (load and store).
L’ultima classe nella gerarchia del Peuck Object Module è String che fornisce una
classe generale per la gestione delle stringhe (concatenazione, copia, ricerca) attraver-
so il conteggio delle referenze. Due stringhe uguali condividono la medesima zona di
memoria: il conflitto è risolto ricorrendo ad una variabile che conta il numero di strin-
ghe che si riferiscono alla medesima copia. La class String si appoggia all’unica clas-
se esterna alla gerarchia all’interno dell’ambiente: RefCount.
Il POM fornisce, inoltre, i puntatori intelligenti (smart pointer): dichiarando un
puntatore intelligente ad una classe ereditata da RefCount, esso, pur comportandosi
come un comune puntatore, si occupa autonomamente del conteggio delle referenze
agli oggetti e della loro deallocazione in caso di cessato utilizzo.
15
25. Architettura
Persistenza degli oggetti
Il PEUCK propone un sistema di persistenza degli oggetti alternativo e proprieta-
rio, basandosi sui servizi offerti dal Peuck Object Model, in particolare sul sistema di
riconoscimento del tipo a tempo d’esecuzione. La classe cardine alla base della persi-
stenza è PeuckStream, che realizza un generico flusso d’oggetti bidirezionale: la
classe FileStream ad esempio realizza in concreto il flusso d’oggetti verso un file
memorizzato sui supporti di massa dell’ambiente ospite.
PStream
void init ()
void shutdown ()
void registerClass (POM::PClass *aClass)
void unregisterClass (POM::PClass *aClass)
const POM::PClass* findClass (int code)
const POM::PClass* findClass (char name[])
Al momento del bootstrap del PEUCK, il sistema di persistenza degli oggetti è ini-
zializzato (init) e ogni classe della gerarchia è registrata automaticamente attraverso
un codice univoco universale. L’applicazione può registrare nuove classi aggiungendo
un codice univoco alla macro _IMPLEMENT_CLASS.
Il metodo findClass ricerca una metaclasse all’interno della gerarchia di classi a
partire dal codice univoco oppure da una stringa contenente il nome con la quale la
classe è stata registrata nel sistema.
void store (const POM::Persistent *obj)
void store (const POM::Persistent& obj)
POM::Persistent* load (void *arg = 0)
L’applicazione memorizza un oggetto all’interno del flusso mediante il metodo
store e lo recupera mediante il metodo load.
Il metodo store riconosce al momento della sua esecuzione il tipo dell’oggetto da
memorizzare, scrive nel flusso il codice univoco e invoca il metodo ereditato da Per-
sistent per la scrittura del contenuto dell’oggetto all’interno del flusso.
Le operazioni compiute dal metodo load, durante il caricamento sono leggermente
più complicate; dal flusso di dati è estratto il codice che identifica univocamente una
classe all’interno della gerarchia. Il codice è usato dal metodo statico findClass per
cercare all’interno della gerarchia di classi la metaclasse corrispondente; la metaclasse
16
26. Architettura
è in grado di creare istanze “vuote” della classe alla quale corrisponde: all’oggetto
“vuoto” è chiesto di “riempirsi” con i restanti dati estratti dal flusso.
Ogni classe i cui oggetti debbano essere persistenti è responsabile della scrittura e
dell’estrazione dei suoi dati dal flusso scrivendo il codice dei metodi store e load ere-
ditati da Persistent e si appoggia, per svolgere il suo compito, ai metodi di scrittura e
lettura generici di PeuckStream.
void write (byte *data, int len)
void write (void *data, int len)
void write (int x)
void write (byte b)
void write (char c)
void write (long l)
void write (bool b)
void write (float f)
void read (byte *data, int len)
void read (void *data, int len)
int readInt ()
byte readByte ()
char readChar ()
long readLong ()
bool readBool ()
float readFloat ()
La classe PeuckStream fornisce i metodi per la scrittura e la lettura dei tipi di dati
standard, sui quali appoggiarsi per la gestione di strutture dati complesse.
17
27. Architettura
PEUCK3D
Trasformazioni, viewport e telecamera
Trasformazioni
Le trasformazioni assumono notevole importanza nella grafica realizzata al compu-
ter e sono parte integrante del processo di generazione di un’immagine. Se all’interno
di una scena sono, ad esempio, presenti due oggetti di forma identica ma in posizione
diversa, solo uno dei due necessita di essere costruito, il secondo è ottenuto sempli-
cemente copiando il primo e applicando una trasformazione per portarlo nella giusta
posizione. Operazioni quali la traslazione, la rotazione, lo scolamento sono dette tra-
sformazioni geometriche.
Una trasformazione geometrica è una funzione f che lavora su punti. La notazione
P = f(P) implica che l’applicazione di f al punto P porta al punto trasformato P*. Dal
*
momento che le trasformazioni trattate in computer grafica sono chiamate geometri-
che, devono avere un’interpretazione geometrica: non tutte le trasformazioni sono
quindi utili. Le funzioni utili geometricamente devono soddisfare le due seguenti pro-
prietà.
Una generica funzione f mappa il suo dominio D in C. Se ogni punto di C
ha un punto corrispondente in D, allora la funzione mappa l’intero dominio
di in C. La funzione è detta suriettiva
Una funzione arbitraria può mappare due punti distinti x e y sullo stesso
punto. Una funzione iniettiva soddisfa la proprietà x != y -> f(x) != f(y). E’
sensato richiedere che una trasformazione geometrica sia iniettiva, in quan-
to è possibile costruire la trasformazione inversa di un dato punto P* facen-
te parte dall’immagine trasformata di un solo punto.
Definizione. Una trasformazione geometrica è una funzione contemporaneamente
suriettiva e iniettiva, il cui dominio e condominio sono punti.
La combinazione di trasformazioni è un’operazione fondamentale e si riduce alla
composizione di funzioni. Se due funzioni f e g rappresentano due trasformazioni, la
loro composizione g o f rappresenta il prodotto (combinazione, concatenazione) delle
due trasformazioni. E’ possibile scrivere la trasformazione composta nella seguente
forma: P* = g(f(P)).
18
28. Architettura
Un esempio importante di concatenazione di trasformazioni geometriche sono le
trasformazioni lineari che mappano il punto P = (x, y, z) nel punto P* = (x*, y*, z*),
dove:
x* = a11x + a12y + a13z + a14
y* = a21x + a22y + a23z + a24 [1]
z* = a31x + a32y + a33z + a34
Questo tipo di trasformazione è anche detto affine.
I termini ai4 rappresentano le quantità che sono sommate alle coordinate trasforma-
te e rappresentanto semplicemente la traslazione di P* lungo gli assi coordinati. Il si-
stema precedente può essere semplificato in:
x* = a11x + a12y + a13z
y* = a21x + a22y + a23z [2]
z* = a31x + a32y + a33z
Se la matrice dei coefficienti 3x3 del sistema di equazioni è non singolare o, equi-
valentemente, se il determinante della matrice dei coefficienti è diverso da zero, allora
il sistema è invertibile e può essere espresso nella forma
x = b11x*+ b12y*+ b13z*
y = b21x*+ b22y*+ b23z* [3]
z = b31x*+ b32y*+ b33z*
dove i coefficienti bij sono espressi in termini dei coefficienti aij.
Questa proprietà tornerà utile nella gestione della telecamera.
Maggiori dettagli sulle trasformazioni possono essere trovati in [2].
Viewport e proiezioni
Data una scena della quale mediamente solo una parte può essere visualizzata, è
possibile pensare allo schermo come ad una finestra sull’immagine. Questa visione
introduce il problema della trasformazione delle coordinate. Si chiami la scena mondo
(world). Un punto in coordinate mondo (x, y, z) per essere visualizzato all’interno di
una finestra, che chiameremo viewport d’ora in poi, deve essere proiettato e trasfor-
mato in coordinate viewport. Una viewport può, inoltre, coprire solo una porzione del-
lo schermo; il punto deve quindi essere ulteriormente scalato e traslato dalle coordina-
te viewport alle coordinate schermo.
19
29. Architettura
Una proiezione è, in generale, una trasformazione da uno spazio1 con dimensione n
a uno spazio con dimensione n-1. Le proiezioni sono classificate in lineari e non linea-
ri. I due principali tipi di proiezioni lineari solo la parallela e prospettica. Il PEUCK
supporta nativamente matrici solo proiettive.
La proiezione prospettica è importante in quanto è il modo in cui l’uomo osserva
gli oggetti nella vita reale. Questo tipo di proiezione è ottenuta posizionando
l’osservatore nel centro di proiezione. Sappiamo dall’esperienza quotidiana che la
proiezione prospettica ha le seguenti tre proprietà:
• Maggiore è la distanza dell’oggetto dall’osservatore, più esso appare picco-
lo
• Tutte le linee dell’oggetto parallele alla linea di vista dell’osservatore ap-
paiono convergere a ciò che è chiamato punto di fuga.
• La quantità di prospettiva vista da un osservatore dipende dalla distanza fra
l’osservatore e l’oggetto; un osservatore vicino vede più prospettiva, ovvero
una differenza di dimensioni maggiore fra la porzione dell’oggetto più vici-
na e piu’ lontana.
Assumiamo che l’osservatore sia posizionato lungo l’asse z negativo a distanza k
dall’origine e guarda nella direzione dell’asse z positivo. Assumiamo, inoltre, che il
piano di proiezione (lo schermo sul quale l’immagine bidimensionale sarà disegnata)
sia il piano xy. Sia P = (x, y, z) un punto tridimensionale e P*=(x*, y*) il punto tra-
sformato sul piano di proiezione. La regola della proiezione prospettica afferma che
per ottenere P*, è necessario tirare una linea dal punto P al centro di projezione
(l’osservatore); il punto in cui questa linea interseca il piano di proiezione è P*.
x* x
=
z+k
k
[4]
*
y y
=
z+k
k
perciò
1
Sebbene le denominazioni “spazio del mondo”, “spazio dell’oggetto” e “spazio della telecamera”
non siano formalmente corrette, saranno usate all’interno di questo lavoro per questione di chiarezza
perché comunemente accettate. Sia chiaro che esiste un solo spazio tridimensionale e diversi sistemi di
rifelrimento (mondo, oggetto, telecamera).
20
30. Architettura
x
x* = [5]
(z + k) +1
y
y* =
(z + k) +1
Possiamo ora provare le tre proprietà della proiezione prospettica precedentemente
citate:
a) Quando l’oggetto è lontano, l’equazione produce piccoli valori per x* e y*.
lim x* = 0, lim y* = 0.
z->infinito z->infinito
Percio’ l’oggetto appare più piccolo all’osservatore.
b) Sia data una linea parallela all’asse z (la linea di vista). Tutti i punti su questa
linea hanno le stesse coordinate x e y e differiscono solo per la z. Quando que-
sta linea è estesa all’infinito, la sua proiezione sullo schermo si avvicina al pun-
to (0, 0) senza che le coordinate x e y possano influire. Questo mostra che tutte
le linee parallele all’asse z hanno una proiezione che converge all’origine.
L’origine è quindi il punto di fuga di queste linee.
c) Scegliendo due punti P1=(x1, y1, z1) e P2=(x1, y1, z2) sull’oggetto (con le stesse
coordinate x e y) e considerando le loro proiezioni P1*=(x1*, y1*,) e P2*=(x1*,
y1*,), il rapporto x1*/x2* è così calcolato:
x1
z / k + 1 z2 / k + 1 d 2
*
x
=1 = =
1
[6]
x2 z1 / k + 1 d 1
*
x 2
z2 / k + 1
Quando l’oggetto e l’osservatore si allontanano l’uno dall’altro, sia d1 sia d2
crescono, portando il rapporto x1*/x2* vicino a 1. I due punti proiettati si avvici-
nano, quindi, sullo schermo, il che sta a significare che l’oggetto è proiettato
sullo schermo con minore prospettiva.
21
31. Architettura
La matrice di trasformazione (non affine) che genera la trasformazione prospettica
è:
1 0
0 0
0 1 0 0
0 r
0 1
0 1
0 0
dove r = 1 / k.
Si assuma, ora, che la finestra attraverso la quale il mondo proiettato è visualizzato,
occupi il rettangolo (Wxl, Wxr, Wyb, Wyt) mentre la viewport occupi sullo schermo il
rettangolo (Vxl, Vxr, Vyb,Vtr). Per trasformare il punto proiettato (Xw, Yw) nel punto di
coordinate schermo (Xs, Ys) si procede attraverso i seguenti tre passi:
1. Si calcola la distanza fra il punto e la l’angolo in basso a sinistra della fine-
stra: Xw – Wxl e Yw – Wyb.
2. Si scala le distanze della dimensione relativa dell viewport
V −V
A= xr x1 ( X w − Wx1 )
Wxr −Wx1
[7]
V −V
B= yt yb (Yw − Wyb )
Wyt −W yb
3. Si aggiungono le coordinate dell’angolo in basso a sinistra della viewport
Xs = A + Wxl [8]
Ys = B + Vyb
Da questo si ricava la matrice che trasforma i punti dalle coordinate della vie-
wport alle coordinate dello schermo:
a 0 0
( X s , Ys ,1)= ( X w , Yw ,1) 0 c 0 [9]
m n 1
22
32. Architettura
Dove a, c, m, n sono coefficienti calcolati una sola volta al momento di inizia-
lizzare la viewport.
Telecamera
E’ stato fino ad ora assunto che i punti fossero trasformati in un sistema di coordi-
nate statico. Una telecamera, al contrario, introduce una trasformarmazione del siste-
ma di coordinate piuttosto che dei singoli punti. Si consideri, ad esempio, una sempli-
ce traslazione e un punto P trasformato in un punto P* traslandolo di m e n unità ri-
spettivamente lungo gli assi x e y. La trasformazione può essere invertita in uno dei
seguenti due modi:
1. Si supponga che la trasformazione originale sia P* = PT, dove
1 0 0
T= 0 1 0
m n 0
è semplice ricavare la matrice di trasformazione che porta il punto P* indietro
al punto P
1 0 0
S= 0 1 0
− m − n 0
è altrettanto semplice dimostrare che S è la matrice inversa di T.
2. La trasformazione può essere invertita traslando il sistema di coordinate nella
direzione inversa (di –m e –n unità) usando una (ancora sconosciuta) matrice di
trasformazione M.
Dal momento che entrambi i metodi producono il medesimo risultato, si può con-
cludere che M = S = T-1. La trasformazione degli assi coordinati è perciò effettutata
dalla matrice che è l’inversa della matrice che trasforma i punti. Questò è vero per o-
gni trasformazione affine, non solo per le traslazioni.
23
33. Architettura
E’ quindi possibile introdurre, inoltre, un orientamento della telecamera che defini-
sce il cambio di sistema di riferimento dallo spazio del mondo allo spazio della tele-
camera successivamente proiettato nello spazio della viewport e, infine, dello schermo
per la visualizzazione, sicuri che l’operazione possa sempre essere invertita per torna-
re dallo spazio della telecamera allo spazio del mondo.
24
34. Architettura
Realizzazione
Una scena è divisa in uno o più mondi, contenenti gli oggetti da visualizzare e vi-
sualizzati attraverso una o più viewport. Una viewport consiste in una finestra sullo
schermo che visualizza uno e un solo mondo attraverso una delle telecamere associa-
te. La viewport si occupa di creare e gestire la matrice di proiezione e i parametri ad
essa associati.
Figura 3. Diagramma di Viewport
Viewport
Viewport ()
Viewport (float x, float y, float w, float h, int mode =
0)
virtual ~Viewport ()
void setPosition (float x, float y)
virtual void setDimension (float w, float h)
virtual void setAspectRatio (float ratio)
void setAlpha (float alpha)
void setOverlapped (int type)
float x () const
float y () const
float width () const
float height () const
int priority () const
float aspectRatio () const
float invAspectRatio () const
float screenXUnit () const
float screenYUnit () const
MATH::Point3D project (MATH::Point3D& point) const
const ViewportStruct* data () const
virtual void makeCurrent () = 0
virtual void render () = 0
25
35. Architettura
La classe Viewport astrae il concetto di finestra generica attraverso la quale osser-
vare una scena che può essere bidimensionale o tridimensionale. Al momento della
costruzione della viewport, si specificano la sua posizione e la sua dimensione sullo
schermo in percentuale. Una viewport posizionata, ad esempio, in (0.1, 0.2) su uno
schermo di dimensione 800 x 600 pixel, si troverà a 80 pixel dal bordo sinistro e 120
pixel dal bordo in alto.
I metodi setPosition() e setDimension() modificano la posizione e la dimensione
della finestra durante l’esecuzione. SetAspectRatio() stabilisce il rapporto desiderato
fra x e y; l’aspect ratio è automaticamente calcolato dalla viewport come rapporto fra
la sua larghezza e la sua altezza: per mantenere un aspetto dell’immagine uniforme
anche su viewport di rapporto differente, l’utente può specificare un proprio valore
differente da quello calcolato in automatico. Si immagini di avere, ad esempio, una
viewport di larghezza 100 e altezza 200, il cui aspect ratio calcolato automaticamente
è 0.5, su uno schermo 800x600 (aspect ratio 0.75); l’utente può forzare l’aspect ratio
della viewport a 0.75 per mantenere una visualizzazione non deformata (cerchi non
visualizzati come ellissi ad esempio).
Il metodo setOverlapped() informa la viewport che essa può sovrapporsi ad altre
viewport; può, inoltre, essere associata con setAlpha() una trasparenza globale per ef-
fetti quali una barra di stato semi trasparente per la visualizzazione di informazioni
particolari o una mappa sovrimposta alla scena.
I metodi x(), y(), width(), height(), priority(), aspectRatio(), invAspectRatio(),
screenXUnit(), screenYUnit() permettono l’accesso ai campi interni della viewport. I
metodi screenXUnit() e screenYUnit(), in particolare, restituiscono il fattore di con-
versione dall’unità di misura della viewport all’unità di misura dello schermo. Il me-
todo project() applica la matrice di proiezione ad un punto tridimensionale effettuan-
do la proiezione dallo spazio della telecamera allo spazio della viewport. L’ultimo
metodo di accesso data() restituisce un puntatore costante alla struttura che contiene i
dati della viewport (ViewportStruct): i dati contenuti all’interno della struttura pos-
sono essere letti, ma non modificati dall’esterno della classe Viewport.
ViewportStruct
float x
float y
float w
float h
float hither
float yon
float alpha
26
36. Architettura
float projection [4][4]
int type
Dove x, y, w e h sono i valori della posizione e della dimensionae della viewport
validi per una finestra generica, i campi hither e yon rappresentano la distanza dei
piani di clipping perpendicolare al piano di proiezione che definiscono i valori in z
minimo e massimo degli oggetti visualizzabili; questi valori hanno, quindi, senso solo
per viewport associate a scene tridimensionali. La matrice di proiezione dallo spazioe
della telecamera allo spazio della viewport è contenuta nel campo projection.
Gli ultimi due metodi astratti della classe Viewport definiscono l’interfaccia d’uso:
makeCurrent() informa il driver che la viewport è la finestra corrente in cui disegna-
re gli oggetti, il metodo render() disegna il contenuto della viewport.
Viewport3D
Viewport3D ()
Viewport3D (float x, float y, float w, float h, int mode = 0)
virtual ~Viewport3D ()
void setZLimits (float minz, float maxz)
virtual void makeCurrent ()
virtual void render ()
void useCamera (Camera& camera)
Camera& camera () const
void addClipPlane (PEUCK::MATH::Plane3D& plane)
void disableClipPlane (int nplane)
virtual void setWorld (World3D& world)
World3D const& world () const
float viewingDistance () const
float invViewingDistance () const
La classe Viewport3D estende il concetto di viewport per supportare la visione di
mondi tridimensionali: ogni viewport tridimensionale può avere associato un solo
mondo tridimensionale attraverso la chiamata al metodo setWorld(); il metodo use-
Camera() specifica la telecamera attraverso la quale il mondo deve essere visualizza-
to.
Il metodo setZLimitz() specifica la distanza dei piani di clipping più vicino e più
lontano che, assieme ai restanti 4 piani calcolati dai valori della dimensione e dal fov
(field of view) specificato dalla telecamera corrente, definiscono il tronco di piramide
27
37. Architettura
che delimita la porzione di spazio visibile. E’ possibile specificare fino ad altri sei
piani di clipping supplementari invocando il metodo addClipPlane(); l’uso di questo
metodo è, tuttavia, sconsigliato in quanto introduce notevoli rallentamenti nella fase di
rendering delle primitive portanto ad un sensibile decadimento delle prestazioni.
Gli ultimi due metodi restituiscono l’informazione riguardante la distanza del pun-
to di vista dal piano di proiezione e il suo inverso (viewingDistance() e invViewin-
gDistance()).
Figura 4. Gerarchia della telecamera
La classe astratta Camera realizza il concetto di telecamera che, per essere usata,
deve necessariamente essere inclusa all’interno di un mondo tridimensionale da visua-
lizzare (classe World3D). L'utente segnala alla Viewport3D corrente, la telecamera
tramite la quale il mondo assegnato alla viewport è visualizzato. Il fov (field of view)
della telecamera definisce, assieme ai campi della classe Viewport3D, il viewing fru-
stum tridimensionale, ovvero la porzione di spazio da visualizzare. Il fov rappresenta
geometricamente l'angolo d’apertura della camera ed è specificato in gradi.
Camera
Camera ()
Virtual ~Camera ()
virtual void evaluateGlobalMatrix () = 0
virtual void setFoV (float angle)
virtual void setFoV (int angle)
Float tfov ()
Float fov ()
float focus ()
virtual Vector3D const& position () const
virtual Matrix const& matrix () const
28
38. Architettura
Il calcolo effettivo della matrice di trasformazione è effettuato al momento
dell’invocazione del metodo evaluateGlobalMatrix() che del quale le classi derivate
forniranno un realizzazione concreta.
Il metodo setFovAngle() specifica il valore espresso in gradi del field of view tra-
mite un numero reale oppure un numero intero, valore che può essere recuperato in-
vocando i metodi tfov() (tangente del fov) e fov() (fov espresso in gradi). Il metodo
focus() restituisce un valore direttamente proporzionale alla distanza del punto di vista
dal piano di proiezione che rappresenta l’unità di misura del mondo.
Il metodo position() restituisce la posizione della telecamera espresso nello spazio
del mondo. L’ultimo metodo matrix() restutisce la matrice di trasformazione dallo
spazio del mondo allo spazio della telecamera.
StandardCamera
StandardCamera ()
~StandardCamera ()
void translate (float x, float y = 0.0f, float z = 0.0f)
void setPosition (float x, float y = 0.0f, float z = 0.0f)
void setPosition (Point3D const& p)
void setRotation (float x, float y = 0.0f, float z = 0.0f)
void rotateX (int rx)
void rotateY (int ry)
void rotateZ (int rz)
void rotateX (float rx)
void rotateY (float ry)
void rotateZ (float rz)
void lookAt (Vector3D const& eye, Vector3D const& up
= Vector3D(0.0f, 1.0f, 0.0f))
void lookAt (OBJECT3D::Object3D const& obj,
Vector3D const& up = Vector3D(0.0f, 1.0f, 0.0f))
void unlockLookAt ()
virtual void evaluateGlobalMatrix ()
virtual Matrix const& matrix () const
virtual Vector3D const& position () const
Moveable const& moveable () const
La StandardCamera è la realizzazione concreta della telecamera fornita dal
PEUCK che usa le informazioni di movimento fornite da un’istanza della classe Mo-
veable.
29
39. Architettura
La posizione e la rotazione iniziale della telecamera possono essere modificati in-
vocando i metodi setPosition() e setRotation(); traslate() e i metodi relativi alla ro-
tazione attorno agli assi, al contrario, specificano un valore dipendente dalla posizione
o dagli angoli di rotazione precedenti.
I metodi lookAt() e unlockLookAt() dirigono l’inquadratura della telecamera ver-
so un punto specifico nello spazio del mondo che può essere specificato indipenden-
temente come un vettore oppure un oggetto.
L’ultimo metodo moveable() restituisce un handle costante all’oggetto responsabi-
le dell’orientamento della telecamera.
Figura 5. Gerarchia di class Moveable.
La classe Moveable gestisce il movimento e l’orientamento delle entità del
PEUCK: può essere considerata una classe che astrae il concetto di matrice affine ge-
nerica di passaggio da un sistema di riferimento ad un altro. E’ usata, ad esempio, per
gestire il cambio di coordinate dallo spazio dell’oggetto allo spazio del mondo (nel
caso degli oggetti) o dallo spazio del mondo. L’orientamento durante il cambio di ba-
se è gestito mediante l’uso del metodo degli angoli euleriano (classe EulerianOrien-
table) o dei quaternioni (classe QuatOrientable).
Moveable
Moveable ()
Moveable (Vector3D& position, Matrix& matrix)
Moveable (Matrix& matrix)
Moveable (Vector3D& position)
30
40. Architettura
virtual ~Moveable ()
Matrix const& self () const
Vector3D const& position () const
Matrix const& matrix () const
Vector3D const& worldPosition () const
virtual void useHierarchicalMatrix ()
virtual void setRotation (int x, int y, int z)
virtual void setRotation (float x, float y, float z)
virtual void setPosition (float x, float y, float z)
virtual void setPosition (Point3D const& p)
virtual void translate (float x, float y, float z)
virtual void rotateX (float rx)
virtual void rotateY (float ry)
virtual void rotateZ (float rz)
virtual void rotateX (int rx)
virtual void rotateY (int ry)
virtual void rotateZ (int rz)
virtual void lookAt (Vector3D const& eye, Vector3D const& up)
virtual void unLockLookAt ()
virtual bool nextPosition ()
virtual void updatePosition ()
virtual void updateRotation ()
virtual bool buildMatrix ()
virtual bool buildHierarchicalMatrix ()
virtual void buildHierarchicalMatrix (Matrix& parent)
Molti metodi (per lo spostamento, la rotazione e il calcolo delle matrici) sono ana-
loghi ai metodi incontrati nell’analisi della classe StandardCamera in quanto usati
“per delegazione” ogni qual volta è necessario costruire matrici di cambio base a se-
guito di rototraslazioni.
La classe Moveable è in grado di gestire concatenazioni di cambiamenti di basi ge-
rararchiche attraverso i metodi useHierarchicalMatrix() e buildHierarchicalMa-
trix() utile, ad esempio, nella realizzazione delle animazioni gerarchiche di un ogget-
to.
31
41. Architettura
Modello d’illuminazione
Interpretazione matematica
Il PEUCK usa un’illuminazione semplificata rispetto a modelli realistici quali il
Ray Tracing e il Radiosity, che basano il calcolo sulle proprietà fisiche
dell’interazione fra luce e materiali. Gli algoritmi “foto realistici” sono, al livello at-
tuale della tecnologia, troppo onerosi perché siano realizzati in applicazioni grafiche
in tempo reale, perché alla loro base è un’enorme quanitità di calcoli.
Il modello usato calcola l’illuminazione al livello del singolo vertice: per ogni ver-
tice presente nella scena, è valutata l’equazione del modello di Phong
I = Ia + Id + Is [10]
L’intensità della luce “vista” da ogni vertice è data dal contributo di tre componen-
ti: componente ambientale, componente diffusa, componente speculare.
La componente ambientale è propria di ogni oggetto a prescindere dalle luci pre-
senti nella scena; si tratta di un’approssimazione dell’intensità luminosa presente
nell’ambiente senza una particolare collocazione spaziale. E’ usata per dare agli og-
getti un’illuminazione minima, priva di sfumature, costante e, per questo, poco reali-
stica.
La componente ambientale si valuta mediante la seguente espressione:
Ia = KaLa [11]
Dove Ka è il coefficiente ambientale specifico del materiale associato al vertice e
indica la quantità di luce ambiente non assorbita (e quindi visibile) dall’oggetto. La è
l’intensità della luce ambientale costante per tutti i vertici presenti nella scena.
Gli oggetti sottoposti alla sola componente ambientali ricevono un’illuminazione
uniforme lungo le loro superfici. La componente diffusa simula l’illuminazione for-
nita da una sorgente luminosa puntiforme; l’intensità luminosa sull’oggetto varia in
dipendenza della direzione d’incidenza della sorgente luminosa. L’intensità della
componente diffusa, anche detta riflessione Lambertiana, appare costane da ogni an-
golo visuale perché riflette la luce con eguale intensità in tutte le direzioni. Per una
data superificie, la luminosità dipende solo dall’angolo Φ fra la direzione della luce L
e la direzione della normale N.
32
42. Architettura
La componente diffusa si valuta per ogni luce presente nella scena mediante la se-
guente espressione:
I d = K d Ld ( N o L) [12]
Dove Kd è il coefficiente di diffusione specifico del materiale, che rappresenta la
quantità di luce diffusa non assorbita dalla superficie. Ld rappresenta l’intensità della
luce. N è il vettore normale alla superficie che, in generale, varia da vertice a vertice.
L è il vettore direzione che porta dalla luce al vertice: il valore di questo vettore è co-
stante in ogni vertice per luci poste all’infinito (luci direzionali), mentre varia per luci
non all’infinito.
La componente speculare può essere osservata su qualunque superficie riflettente
e simula il riflesso della sorgente luminosa sull’oggetto. L’intensità della componente
varia al variare della posizione dell’osservatore ed è, quindi, dipendente dalla posizio-
ne del punto di vista.
La forma generale dell’espressione che valuta la componente speculare è:
I s = K s Ls ( R o L) ns [13]
Ks è l’analogo coefficiente che si trova nella componente ambientale e diffusa.
Ls è l’intensita speculare della sorgente luminosa (o colore speculare della luce):
per avere effetti realistici, Ls e Ld sono tipicamente uguali per ogni luce presente nella
scena. R è il vettore simmetrico della direzione V del punto di vista secondo la nor-
male N alla superficie. L’esponente ns dipende dal materiale associato alla superficie
(e, quindi, al vertice che ne fa parte) e varia tipicamente da 1 a 256. Piu’ ns è grande,
più il riflesso della luce appare piccolo; per ns sufficientemente grande, il firlesso ri-
sulta evidente solo osservando la superficie in direzione simmetrica di L rispetto alla
normale e per piccoli scostamente da tale direzione.
Una versione semplificata del calcolo della componente speculare che non conside-
ra il vettore R è:
I s = K s Ls ( H o N ) ns [14]
33
43. Architettura
Dove H è il vettore a metà angolo definito da:
L +V
H= [15]
L +V
V è il vettore normalizzato che porta dal vertice al punto di vista. Sebbene questa
versione della componente speculare si semplificata e veloce da calcolare, quindi più
adatta alla grafuca in tempo reale, risulta fornire risultati visivi più realistici ed è usata
dal presente motore tridimensionale sia in versione sw sia quando il calcolo
dell’illuminazione è delegato alle schede che supportano il protocollo T&L (Tran-
sformation and Lightning).
Le due componenti diffusa e speculare devono essere calcolate per ogni sorgente
luminosa e sommate assieme per valutare tutta la componente di colore locale al ver-
tice.
Realizzazione
Figura 6. Gerarchia di classi delle luci
In figura 5 è mostrata la gerarchia di classi che realizza il sistema di illuminazione
base per vertici del PEUCK. La classe padre astratta Light3D contiene al suo interno
una struttura privata (Light3DStruct) che raccoglie le informazioni comuni quali colo-
ri (diffuso, speculare), posizione, direzione, coefficienti di attenuazione: ogni tipo di
luce è libero di usare i soli campi necessari.
34
44. Architettura
Light3DStruct
float position [4]
float direction [4]
float diffuse [4]
float specular [4]
float spot_direction [4]
float spot_cutoff
float spot_exponent
float attenuation0
float attenuation1
float attenuation2
int type
La classe Light3D espone, inoltre, l’interfaccia comune ad ogni tipo di luce e i me-
todi di gestione base di una luce in un ambiente tridimensionale.
Light3D
Light3D ()
Light3D (MATH::Color const& color)
~Light3D ()
void setPosition (Vector3D const& position)
void setDiffuseColor (Color const& color)
void setSpecularColor (Color const& color)
Vector3D position () const
Vector3D direction () const
Vector3D position (Matrix& matrix) const
Vector3D direction (Matrix& matrix) const
Color diffuse () const
Color specular () const
void use (int index)
virtual void transform (Matrix& matrix) = 0
I primi metodi permettono di accedere ai parametri base di una luce quali la posi-
zione, la direzione e il colore sia in lettura sia in scrittura. E’ possibile specificare due
colori differenti per la componente diffusiva della luce e per la componente speculare:
sebbene questi due colori saranno nella maggior parte dei casi uguali, è data la possi-
bilità di specificare valori diversi per simulare effetti particolari. Non esiste, tuttavia,
35
45. Architettura
la certezza che il driver di rendering supporti questa differenziazione; in tal caso le
specifiche progettazione del driver di rendering affermano che il colore speculare sia
ignorato.
Il metodo use informa il PEUCK dell’intenzione di usare la luce all’interno della
scena. Il motore tridimensionale è in grado di usare un massimo di 8 luci contempora-
neamente attive all’interno della scena. Un mondo può contenere, quindi, un numero
virtualmente illimitato di luci, ma solo otto di queste possono essere attive contempo-
raneamente e quindi fornire illuminazione per vertici agli oggetti della scena. L’indice
specificato dal metodo use informa quale degli otto disponibili sarà occupato dalla lu-
ce corrente: una luce eventualmente già assegnata allo stesso indice sarà disattivata.
L’ultimo metodo applica alla luce una matrice di trasformazione aggiornandone di
conseguenza i vettori posizione e direzione. Può essere usato per muovere la luce at-
traverso diversi sistemi di riferimento oppure per ancorare la stessa ad un oggetto par-
ticolare, come ad esempio una torcia in mano ad un Avatar.
DirectionalLight3D
DirectionalLight3D ()
DirectionalLight3D (Vector3D const& direction, Color
const& color, int type)
virtual ~DirectionalLight3D ()
virtual Moveable& moveable ()
void rotateX (int rx)
void rotateY (int ry)
void rotateZ (int rz)
void rotateX (float rx)
void rotateY (float ry)
void rotateZ (float rz)
virtual void transform (Matrix& camera)
Il primo tipo è direzionale e simula una luce con sorgente posta all’infinito; la sola
direzione è quindi il dato caratterizzante. Con DirectionalLight3D è possibile simula-
re la luce proveniente, ad esempio, dal sole in un ambiente esterno. L’utente può ruo-
tare il vettore direzione della luce secondo gli assi di un valore espresso con un nume-
ro reale oppure intero (versione ottimizzata). Il metodo moveable() restutisce un og-
getto di tipo Moveable associato alla luce che ne descrive il tipo di orientamento.
36
46. Architettura
OmniLight3D
OmniLight3D ()
OmniLight3D (Vector3D const& position, Color const&
color, int type = PEUCK3D_MOV_MOVEABLE)
virtual ~OmniLight3D ()
void translate (float x, float y, float z)
Matrix const& evaluateGlobalMatrix ()
void transform (Matrix& cameraMatrix)
Una luce di tipo OmniLight3D simula una sorgente luminosa puntiforme che irra-
dia uniformemente in tutte le direzioni, senza attenuazione dovuta alla distanza del
vertice da illuminare dalla sorgente luminosa. La posizione della luce è considerata
prendendo il mondo come sistema di riferimento e il metodo translate() le applica
una semplice traslazione.
PointLight3D
PointLight3D ()
PointLight3D (Vector3D const& position, Color const& color, int
type)
virtual ~PointLight3D ()
void setAttenuation (float constant = 1.0f, float linear = 0.0f, float quadratic
= 0.0f)
La classe PointLight3D fornisce una luce puntiform che irradia uniformemente in
tutte le direzione, ma permette, invoando il metodo setAttenuation(), di specificare
l’attenuazione dell’intensità luminosa al variare della distanza dei vertici illuminati
secondo la seguente formula,
1
A= [16]
c + ld + qd 2
dove A è il valore dell’attenuazione dell’intensità luminosa, c è il coefficiente co-
stante, l il coefficiente lineare e q il coefficiente quadratico.
37
47. Architettura
SpotLight3D
SpotLight3D ()
SpotLight3D (Vector3D const& position, Color const& color, int type)
virtual ~SpotLight3D ()
void setDirection (Vector3D const& direction)
void setCutoff (float cutoff)
void setExponent (float exp)
La classe SpotLight fornisce una luce puntiforme che irradia in una zona di spazio
definita da un cono la cui direzione è indicata mediante il metodo setDirection(),
l’ampiezza mediante il metodo setCutoff(). L’ultimo metodo setExponent() indica
l’esponente da associare alla legge quadratica che regola il valore dell’attenuazione
della luce al variare della posizione del vertice all’interno del cono illuminato:
l’attenuazione è minima se il vertice giace al centro del cono, infinita se giace
all’estremità del cono di luce.
38
48. Architettura
Texture mapping
Introduzione
Alla ricerca d’immagini realistiche, la principale critica verso i primi sintetizzatori
d’immagini digitali è stata l’eccessiva “piattezza” delle superfici, che non mostravano
alcun tipo di particolare, rugosità: il realismo richiede complessità oppure, al limite,
un’apparenza di complessità. Il “Texture mapping” è una tecnica relativamente effi-
ciente per creare l’apparenza della complessità senza aumentare la complessità geo-
metrica degli oggetti.
Texture mapping è il processo che mappa una funzione sopra una superficie tridi-
mensionale; il dominio della funzione può essere mono, bi o tridimensionale e può es-
sere rappresentato sia da un vettore di valori sia da una funzione matematica. Il moto-
re tridimensionale all’interno del Sistema Operativo supporta attualmente solo texture
bidimensionali.
L’immagine sorgente è mappata su una superficie di un oggetto tridimensionale,
che è successivamente mappato sull’immagine destinazione (lo schermo) attraverso la
proiezione visiva. Lo spazio delle texture è solitamente indirizzato dalla coppia (u, v),
lo spazio dell’oggetto dalla terna (x0, y0, z0) e lo spazio dello schermo dalla coppia (x,
y).
Nella pratica, il texture mapping è fatto usando uno dei due seguenti metodi:
1. E’ data una superficie P(x, y) da mappare con una texture. L’intera superficie
deve essere esaminata e ad ogni pixel assegnato un valore a partire dalla te-
xture da mappare. La superficie è tipicamente più grande o più piccola della
mappa, che comporta una soluzione non banale al problema. La superficie è
attraversanta variando i parametri x e y indipendentemente da 0 a 1 usando
una funzione che mappi ogni coppia (x, y) in una coppia (u, v) all’interno
della texture (dove 0 < u < 1 e 0 < v < 1).
2. Sono date diverse superfici e l’intera scena è illuminata usando il metodo del
raytracing (v. pag XXX). Piuttosto che calcolare separatamente ogni superfi-
cie, si segue un raggio di luce dal punto di vista all’osservatore attraverso la
sceno, raggio che potrebbe colpire una superficie P(x, y) nel punto P = (x, y,
z). Per mappare la texture, si cerca la coppia (x, y) che corrisponde al punto
di intersezione e la si mappa alla coppia di indici (u, v) nella texture.
Come precedentemente analizzato, dal punto di vista del rendering in tempo reale
il raytracing non è ancora praticabile con la potenza delle macchine a disposizione del
mercato consumer: la prima strada è quindi quella percorsa anche dal PEUCK.
39
49. Architettura
I possibili usi del texture mapping sono molteplici; alcuni parametri delle superfici
tipicamente simulati sono:
• Il colore della superficie
• La riflessione speculare
• La perturbazione della normale (bump mapping)
• La trasparenza
• Le ombre
Un particolare uso del texture mapping si trova nel simulare la riflessione
dell’ambiente sugli oggetti (environment mapping), dove un’immagine rappresentante
l’ambiente, in cui un determinato oggetto è situato, è associata ad una sfera oppure ad
un cubo che circonda la scena.
Il processo che mappa una funzione dallo spazio della texture allo spazio
dell’oggetto è generalmente suddiviso in due fasi: dapprima la superficie è parame-
trizzata di modo da associare lo spazio della texture allo spazio dell’oggetto (genera-
zione delle coordinate uv), seguito dalla normale trasformazione e proiezione delle
primitive dallo spazio dell’oggetto allo spazio dello schermo.
La parametrizzazione è solitamente naturale per le superfici definite mediante una
funzione matematica, ad esempio superfici curve di Bezier, ma meno naturale per al-
tre superfici quali triangoli, poligoni e insiemi di primitive standard (strisce di poligo-
ni), che sono generalmente definie in maniera implicita. Un triangolo, ad esempio, è
mappato semplicemente associando ad ogni vertice una coppia (o più coppie) di coor-
dinate (u,v) che rappresentano la sua posizione all’interno dello spazio della texture,
posizione che, nell’attuale generazone di schede acceleratrici, è interpolata lungo la
primitiva al livello del singolo pixel.
Dopo il calcolo della parametrizzazione e la proiezione delle primitive, l’immagine
deve essere ricampionata sulla griglia di pixel dello schermo: questo processo è chia-
mato filtraggio della texture o, più comunemente, filtering. Il più semplice metodo di
filtraggio disponibile trova il pixel all’interno della texture più vicino al punto deside-
rato. Questo metodo porta ad artefatti molto visibili nelle immagini che hanno la tipi-
ca apparenza “blocchettosa”. Un metodo più preciso consiste nell’associare al pixel
sullo schermo il valore ricavato dalla media pesata dei pixel della texture piu’ vicini al
punto considerato.
Sebbene fornisca ottimi risultati visivi, l’ultimo metodo di filtraggio considerato è
estremamente lento anche se eseguito in hardware e non applicabile alla generazione
di immagini in tempo reale. Per velocizzare il processo, si preferisce, quindi, prefiltra-
re la funzione da mappare creando una sua rappresentazione piramidale; a ogni livello
della piramide è associata una versione prefiltrata da usare per il calcolo del pixel sul-
la texture da associare al pixel sullo schermo per un determinato valore del coefficien-
40
50. Architettura
te di LOD (Level Of Detail). Il coefficiente di LOD è, tipicamente, calcolato in base al
numero di pixel sulla texture associata al pixel dello schermo.
Realizzazione
Figura 7. Gerarchia di classi delle texture
La classe Texture fornisce i servizi per la gestione delle mappe da applicare du-
rante il processo di texture mapping e per la gestione della memoria della scheda vi-
deo associata alle texture. Come mostrato in figura 6, la classe Texture eredita dalla
classe RefCount i servizi per la gestione dei conteggi di riferimento: è possibile quindi
condividere una texture fra diversi oggetti per risparmiare sia memoria centrale sia
memoria video per il loro immagazzinamento durante l’uso. Una texture è composta
da un numero di immagini che può andare dal minimo di uno al massimo di nove, che
corrispondono ai livelli di dettaglio via via più definititi della mappa usati durante la
tecnica di filtraggio delle texture denominata mipmapping. E’ possibile indicare
l’immagine associata ad ogni livello di dettaglio oppure richiedere il filtraggio auto-
matico a partire dall’immagine che rappresenta il livello massimo. La dimensione
massima della texture è di 1024x1024 pixel, dove la larghezza e l’altezza possono es-
sere diversi (texture rettangolari), ma sempre potenze di due.
Texture
Texture ()
Texture (POM::String const& name)
virtual ~Texture ()
void create (Bitmap& bitmap)
virtual void create (int width, int height)
virtual void destroy ()
virtual void download (bool compress = false)
virtual void unload ()
virtual void reload ()
void use (MATH::Matrix& matrix, int stage = 0, int op = 0)
void use (int stage = 0, int op = 0)
POM::String const& name () const
byte* getBitmap (int level)
41