SlideShare ist ein Scribd-Unternehmen logo
1 von 106
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
Ai miei nonni
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
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
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
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.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Architettura




   GR_POINTS                       GR_LINES        GR_TRIANGLES




   GR_LINE_STRIP               GR_TRIANGLE_STRIP    GR_TRIANGLE_FAN



Figura 2. Primitive grafiche




                                       13
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi
Tesi

Weitere ähnliche Inhalte

Ähnlich wie Tesi

Art Everywhere: progetto per workshop Google. Sviluppo di sistemi di pattern ...
Art Everywhere: progetto per workshop Google. Sviluppo di sistemi di pattern ...Art Everywhere: progetto per workshop Google. Sviluppo di sistemi di pattern ...
Art Everywhere: progetto per workshop Google. Sviluppo di sistemi di pattern ...Francesco Cucari
 
Studio di fattibilità e sviluppo di una piattaforma web sul digital manufactu...
Studio di fattibilità e sviluppo di una piattaforma web sul digital manufactu...Studio di fattibilità e sviluppo di una piattaforma web sul digital manufactu...
Studio di fattibilità e sviluppo di una piattaforma web sul digital manufactu...Simone Schifano
 
Intellisystem Technologies - Collezione riviste anno 2003 - Magazine Book Col...
Intellisystem Technologies - Collezione riviste anno 2003 - Magazine Book Col...Intellisystem Technologies - Collezione riviste anno 2003 - Magazine Book Col...
Intellisystem Technologies - Collezione riviste anno 2003 - Magazine Book Col...Cristian Randieri PhD
 
Studio e implementazione di uno strumento di configurazione e visualizzazione...
Studio e implementazione di uno strumento di configurazione e visualizzazione...Studio e implementazione di uno strumento di configurazione e visualizzazione...
Studio e implementazione di uno strumento di configurazione e visualizzazione...Matteo Miotto
 
Programma modellazione 3 d e biografia
Programma modellazione 3 d e biografiaProgramma modellazione 3 d e biografia
Programma modellazione 3 d e biografiaOrnella Pirone
 
Programma modellazione 3 d e biografia
Programma modellazione 3 d e biografiaProgramma modellazione 3 d e biografia
Programma modellazione 3 d e biografiaCollettivo Creativo
 
Collettivo Creativo | Programma modellazione 3D e biografia- WorkshopCreativi...
Collettivo Creativo | Programma modellazione 3D e biografia- WorkshopCreativi...Collettivo Creativo | Programma modellazione 3D e biografia- WorkshopCreativi...
Collettivo Creativo | Programma modellazione 3D e biografia- WorkshopCreativi...Ornella Pirone
 
Digital media technologies - Prof.ssa Albanesi, Prof. Vecchio
Digital media technologies - Prof.ssa Albanesi, Prof. VecchioDigital media technologies - Prof.ssa Albanesi, Prof. Vecchio
Digital media technologies - Prof.ssa Albanesi, Prof. VecchioCulturaInnovazione
 
Collettivo Creativo | Programma Movie Design 3D e biografia - WorkshopCreativ...
Collettivo Creativo | Programma Movie Design 3D e biografia - WorkshopCreativ...Collettivo Creativo | Programma Movie Design 3D e biografia - WorkshopCreativ...
Collettivo Creativo | Programma Movie Design 3D e biografia - WorkshopCreativ...Ornella Pirone
 
Programma movie design 3 d e biografia
Programma movie design 3 d e biografiaProgramma movie design 3 d e biografia
Programma movie design 3 d e biografiaCollettivo Creativo
 
SVILUPPO DI UNA APPLICAZIONE PER L’ACQUISIZIONE DI DATI DA SUPPORTO CARTACEO:...
SVILUPPO DI UNA APPLICAZIONE PER L’ACQUISIZIONE DI DATI DA SUPPORTO CARTACEO:...SVILUPPO DI UNA APPLICAZIONE PER L’ACQUISIZIONE DI DATI DA SUPPORTO CARTACEO:...
SVILUPPO DI UNA APPLICAZIONE PER L’ACQUISIZIONE DI DATI DA SUPPORTO CARTACEO:...guest12aaa586
 
Progetto DrFacto (sintesi)
Progetto DrFacto (sintesi)Progetto DrFacto (sintesi)
Progetto DrFacto (sintesi)Herzum Italia
 
ITALIAN INTERACTION DESIGN DAY Firenze 19.06.2015
ITALIAN INTERACTION DESIGN DAY  Firenze 19.06.2015ITALIAN INTERACTION DESIGN DAY  Firenze 19.06.2015
ITALIAN INTERACTION DESIGN DAY Firenze 19.06.2015Roberto Fazio
 
Analisi e prototipazione di un sistema di streaming per la localizzazione in ...
Analisi e prototipazione di un sistema di streaming per la localizzazione in ...Analisi e prototipazione di un sistema di streaming per la localizzazione in ...
Analisi e prototipazione di un sistema di streaming per la localizzazione in ...TiborRacman
 
Reportage "RAD Studio XE2 World Tour"
Reportage "RAD Studio XE2 World Tour"Reportage "RAD Studio XE2 World Tour"
Reportage "RAD Studio XE2 World Tour"Marco Breveglieri
 

Ähnlich wie Tesi (20)

Art Everywhere: progetto per workshop Google. Sviluppo di sistemi di pattern ...
Art Everywhere: progetto per workshop Google. Sviluppo di sistemi di pattern ...Art Everywhere: progetto per workshop Google. Sviluppo di sistemi di pattern ...
Art Everywhere: progetto per workshop Google. Sviluppo di sistemi di pattern ...
 
Studio di fattibilità e sviluppo di una piattaforma web sul digital manufactu...
Studio di fattibilità e sviluppo di una piattaforma web sul digital manufactu...Studio di fattibilità e sviluppo di una piattaforma web sul digital manufactu...
Studio di fattibilità e sviluppo di una piattaforma web sul digital manufactu...
 
Progetto Euridice
Progetto EuridiceProgetto Euridice
Progetto Euridice
 
Intellisystem Technologies - Collezione riviste anno 2003 - Magazine Book Col...
Intellisystem Technologies - Collezione riviste anno 2003 - Magazine Book Col...Intellisystem Technologies - Collezione riviste anno 2003 - Magazine Book Col...
Intellisystem Technologies - Collezione riviste anno 2003 - Magazine Book Col...
 
Studio e implementazione di uno strumento di configurazione e visualizzazione...
Studio e implementazione di uno strumento di configurazione e visualizzazione...Studio e implementazione di uno strumento di configurazione e visualizzazione...
Studio e implementazione di uno strumento di configurazione e visualizzazione...
 
Tesi Todone
Tesi TodoneTesi Todone
Tesi Todone
 
Tesi Tamiazzo09
Tesi Tamiazzo09Tesi Tamiazzo09
Tesi Tamiazzo09
 
Programma modellazione 3 d e biografia
Programma modellazione 3 d e biografiaProgramma modellazione 3 d e biografia
Programma modellazione 3 d e biografia
 
Programma modellazione 3 d e biografia
Programma modellazione 3 d e biografiaProgramma modellazione 3 d e biografia
Programma modellazione 3 d e biografia
 
Collettivo Creativo | Programma modellazione 3D e biografia- WorkshopCreativi...
Collettivo Creativo | Programma modellazione 3D e biografia- WorkshopCreativi...Collettivo Creativo | Programma modellazione 3D e biografia- WorkshopCreativi...
Collettivo Creativo | Programma modellazione 3D e biografia- WorkshopCreativi...
 
Digital media technologies - Prof.ssa Albanesi, Prof. Vecchio
Digital media technologies - Prof.ssa Albanesi, Prof. VecchioDigital media technologies - Prof.ssa Albanesi, Prof. Vecchio
Digital media technologies - Prof.ssa Albanesi, Prof. Vecchio
 
Tesi_Adamou
Tesi_AdamouTesi_Adamou
Tesi_Adamou
 
Tesi_Adamou
Tesi_AdamouTesi_Adamou
Tesi_Adamou
 
Collettivo Creativo | Programma Movie Design 3D e biografia - WorkshopCreativ...
Collettivo Creativo | Programma Movie Design 3D e biografia - WorkshopCreativ...Collettivo Creativo | Programma Movie Design 3D e biografia - WorkshopCreativ...
Collettivo Creativo | Programma Movie Design 3D e biografia - WorkshopCreativ...
 
Programma movie design 3 d e biografia
Programma movie design 3 d e biografiaProgramma movie design 3 d e biografia
Programma movie design 3 d e biografia
 
SVILUPPO DI UNA APPLICAZIONE PER L’ACQUISIZIONE DI DATI DA SUPPORTO CARTACEO:...
SVILUPPO DI UNA APPLICAZIONE PER L’ACQUISIZIONE DI DATI DA SUPPORTO CARTACEO:...SVILUPPO DI UNA APPLICAZIONE PER L’ACQUISIZIONE DI DATI DA SUPPORTO CARTACEO:...
SVILUPPO DI UNA APPLICAZIONE PER L’ACQUISIZIONE DI DATI DA SUPPORTO CARTACEO:...
 
Progetto DrFacto (sintesi)
Progetto DrFacto (sintesi)Progetto DrFacto (sintesi)
Progetto DrFacto (sintesi)
 
ITALIAN INTERACTION DESIGN DAY Firenze 19.06.2015
ITALIAN INTERACTION DESIGN DAY  Firenze 19.06.2015ITALIAN INTERACTION DESIGN DAY  Firenze 19.06.2015
ITALIAN INTERACTION DESIGN DAY Firenze 19.06.2015
 
Analisi e prototipazione di un sistema di streaming per la localizzazione in ...
Analisi e prototipazione di un sistema di streaming per la localizzazione in ...Analisi e prototipazione di un sistema di streaming per la localizzazione in ...
Analisi e prototipazione di un sistema di streaming per la localizzazione in ...
 
Reportage "RAD Studio XE2 World Tour"
Reportage "RAD Studio XE2 World Tour"Reportage "RAD Studio XE2 World Tour"
Reportage "RAD Studio XE2 World Tour"
 

Tesi

  • 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.
  • 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
  • 9. 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
  • 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
  • 22. Architettura GR_POINTS GR_LINES GR_TRIANGLES GR_LINE_STRIP GR_TRIANGLE_STRIP GR_TRIANGLE_FAN Figura 2. Primitive grafiche 13
  • 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