Tesi

2,129 views

Published on

Tesi Politecnico di Torino. Italian.

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
2,129
On SlideShare
0
From Embeds
0
Number of Embeds
95
Actions
Shares
0
Downloads
0
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Tesi

  1. 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
  2. 2. Ai miei nonni
  3. 3. 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
  4. 4. 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
  5. 5. 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
  6. 6. 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.
  7. 7. 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
  8. 8. Indice 3. ESEMPI D’USO ..................................................................................70 Classroom ..................................................................................................70 SFZ .............................................................................................................75 4. CONCLUSIONI E POSSIBILI SVILUPPI ...........................................92 Conclusioni ................................................................................................92 Possibili Sviluppi .......................................................................................93 5. BIBLIOGRAFIA ..................................................................................95 II
  9. 9. 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
  10. 10. 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
  11. 11. 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
  12. 12. 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
  13. 13. 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
  14. 14. 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
  15. 15. 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
  16. 16. 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
  17. 17. 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
  18. 18. 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
  19. 19. 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
  20. 20. 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
  21. 21. Architettura GR_POINTS GR_LINES GR_TRIANGLES GR_LINE_STRIP GR_TRIANGLE_STRIP GR_TRIANGLE_FAN Figura 2. Primitive grafiche 13
  22. 22. 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
  23. 23. 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
  24. 24. 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
  25. 25. 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
  26. 26. 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
  27. 27. 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
  28. 28. 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
  29. 29. 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
  30. 30. 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
  31. 31. 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
  32. 32. 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
  33. 33. 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
  34. 34. 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
  35. 35. 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
  36. 36. 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
  37. 37. 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
  38. 38. 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
  39. 39. 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
  40. 40. 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
  41. 41. 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
  42. 42. 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
  43. 43. 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
  44. 44. 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
  45. 45. 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
  46. 46. 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
  47. 47. 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
  48. 48. 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
  49. 49. 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

×