L'elaborato fa un uso estremo del texture mapping per dare un aspetto più realistico ai vari elementi della scena. Quasi tutti gli oggetti renderizzati a schermo (inclusa l'interfaccia utente, con poche eccezioni) prevedono l'utilizzo di texture colore.
Gli elementi della scena che fanno uso di texture derivanti da dei
materiali applicati sono l'elicottero, le mongolfiere, il tabellone e
le casse. La gestione delle librerie .mtl
rende infatti estremamente semplice applicare il texture mapping a
dei modelli preparati con tool di modellazione e opportunamente
adattati. In questi casi il programmatore non deve nemmeno curarsi di
fornire le coordinate texture al sottosistema grafico visto che lo
sforzo è preso in carico dal formato .obj e dalla funzione
renderModel() di model.c.
In molti altri casi
(specialmente nella gestione dell'interfaccia utente) le texture
vengono caricate esplicitamente e le coordinate vengono fornite
direttamente dal codice.
Nella realizzazione dell'enviromental
mapping e nella gestione della minimappa, le texture vengono invece
generate manualmente, piuttosto che caricate dal disco fisso.
È chiaro che con un utilizzo così frequente del texturing è
utile avvalersi di un componente che si occupi di fornire un supporto
flessibile al caricamento del file di immagine, al binding in OpenGL
e al filtering secondo i parametri che più si adattano al contesto
di utilizzo.
Queste funzionalità vengono da due importantissime
procedure definite in texture.h.
La prima delle due, loadTextureFromTGA(), è una funzione di basso livello che carica dal filesystem un'immagine nel formato .tga (viene eventualmente supportata la compressione RLE) e la riversa in un'area di memoria, pronta per essere utilizzata.
La procedura bindTexture() – utilizzata
praticamente ovunque dagli altri componenti – si prende l'incarico
di caricare un'immagine specificata in input (richiamando
loadTextureFromTGA()) e di realizzarne il binding in OpenGL,
preoccupandosi di richiedere un nome per la texture (un intero senza
segno che identifica univocamente la texture nel contesto OpenGL
attivo) e svolgendo altri compiti utilissimi quali la generazione
automatica dei diversi livelli di mipmap e il setting delle funzioni
di filtering.
Per fare in modo che la funzione possa adattarsi
alle diverse esigenze, viene presa in input una semplice struttura
(TexParam) che specifica i diversi parametri da utilizzare per
gestire la texture richiesta: un flag per richiedere la generazione
automatica dei livelli di mipmap, i filtri di minification e
magnification, il formato della texture (utile per poter utilizzare
sia immagini GL_RGB che GL_RGBA) e il tipo di wrap da impiegare
(GL_REPEAT oppure GL_CLAMP). In alternativa è possibile passare un
valore nullo e la funzione proseguirà utilizzando dei parametri di
default.
Ovviamente queste procedure non alterano lo stato OpenGL nel
momento in cui vengono richiamate. Nei punti del codice dove è
necessario fare utilizzo di texture mapping ci si preoccupa di
abilitare il flag GL_TEXTURE_2D. Si è deciso, per esigenza di
uniformità, di adottare la convenzione per cui – durante la fase
di rendering – soltanto il flag GL_DEPTH_TEST e GL_CULL_FACE sono
abilitati. Qualora una funzione di rendering abbia bisogno di
funzionalità più avanzate (texturing, shading, blending,
stenciling, …) deve preoccuparsi non solo di abilitare o
disabilitare i flag a seconda dell'effetto che vuole ottenere, ma
anche ripristinare lo stato precedente alla sua chiamata per non
lasciare le procedure successive in uno stato intermedio non
prevedibile. Una funzionalità estremamente utile offerta da OpenGL a
tale scopo è data dalle istruzioni glPushAttrib() e
glPopAttrib() che consentono per mezzo di opportuni flag di
salvare selettivamente lo stato OpenGL e ripristinarlo
successivamente in maniera del tutto analoga a quanto avviene con le
matrici di trasformazione.
È importante sottolineare che durante
la fase di disegno dell'interfaccia grafica, viene utilizzata una
convenzione diversa: il flag GL_DEPTH_TEST viene disabilitato per
ovvi motivi, GL_TEXTURE_2D e GL_BLENDING vengono invece tenuti attivi
visto che quasi tutti i componenti dell'interfaccia utilizzano queste
funzionalità. Anche in questo caso, qualora una funzione intenda
alterare lo stato deve avere cura di riportarlo nelle condizioni in
cui lo ha trovato.
L'elaborato supporta soltanto immagini .tga, ma è comunque facile estenderne il supporto ad altri formati in quanto è sufficiente implementare una procedura adeguata per il parsing dei formati che si desidera utilizzare e richiamare da bindTexture() la funzione di caricamento adeguata, magari distinguendo in base all'estensione del nome del file passato in input.
NB: anche se le specifiche originali di OpenGL imponevano l'utilizzo di texture dalle dimensioni corrispondenti a potenze di due, questo requisito è in realtà stato rilassato da numerose versioni a questa parte, visto che le moderne architetture hardware riescono tranquillamente a supportare immagini di ogni dimensione. Laddove possibile si è comunque cercato di adeguarsi a questa direttiva, ma non tutte le texture utilizzate dal progetto seguono strettamente questa imposizione.