Le basi del 3D (Ver 1.1)
Premetto che il seguente documento non è rigoroso né pienamente
esauriente riguardo ai meccanismi dei veri motori 3D: è solo una spiegazione
di come un appassionato videogiocatore come me, giocando e rigiocando a Descent,
un giorno si mise in testa di costruire un motore 3D con il Turbo Pascal (privo
di texture) senza avere letto nulla in proposito e basandosi solo sulle proprie
conoscenze di matematica e osservando la realtà. Ciò, quindi, può suscitare
interessante a chi è profano del 3D (così come lo ero io prima di iniziare a
lavorarci!)
Tutti corpi che esistono appaiono ai nostri occhi secondo
regole geometriche ben precise.
Per rendersene conto basta osservare che un oggetto più
si trova distante dall’osservatore e più appare rimpicciolito. Oppure, chi ha
qualche conoscenza del disegno in prospettiva, sa che se ci troviamo in una
stanza con gli spigoli paralleli i prolungamenti immaginari di questi ultimi si
incontrano in un unico punto, detto punto di fuga.
Tutto ciò è conseguenza di alcune regole fondamentali
che sono alla base dei motori 3D.
Qualcuno si potrebbe chiedere: “Quali regole
fondamentali? Non basta forse sfruttare le regole della prospettiva?”, la
risposta è che la prospettiva è utile per i disegnatori, ma per quanto
riguarda un motore 3D le conoscenze che necessitano sono di tipo diverso. Per
rappresentare qualsiasi poliedro e, ancora più generalmente, qualsiasi punto
nello spazio, occorre fare un ragionamento più a “basso livello” e scoprire
la causa più elementare da cui derivano anche le regole della prospettiva.
Cominciamo con limmaginare losservatore e i punti in uno spazio cartesiano (disegnati in assonometria):

Ogni punto è definito da tre coordinate (x, y, z).
Nel nostro esempio O è losservatore e P il punto che egli osserva, che può essere parte di un poliedro qualsiasi.
Con dx, dy e dz sono indicate le distanze dellosservatore dal punto in termini di coordinate, quindi:
dx = Px Ox; dy = Py Oy; dz = Pz Oz
La distanza reale dellosservatore O dal punto P è data dalla radice quadrata della somma dei quadrati delle tre distanze, poiché essa non è che la diagonale del parallelepipedo avente per dimensioni dx, dy e dz.
Nel nostro esempio dx, dy e dz sono tutte positive perché la coordinata x di P è maggiore di quella di O e così per la coordinata y e z. Poiché, come è già stato detto:
dx = Px Ox; dy = Py Oy; dz = Pz Oz
le tre distanze risultano di conseguenza valori positivi. In particolare, volendo fare riferimento al nostro esempio:
dx = 13 10 = +3; dy = 7 3 = +4; dz = 15 5 = +10;
Ciò significa che il punto P si trova davanti, in alto e a destra rispetto allosservatore. Attenzione ora, immaginiamoci dove andrebbe rappresentato in un piano 2D (lo schermo) tale punto dovendo produrre un effetto 3D: sicuramente in alto a destra rispetto al centro del piano. Pressappoco così:

E ovvio che il centro dello schermo coincide con il punto di fuga, poiché il disegno deve rappresentare una visione in prima persona dellambiente da parte dellosservatore.
E se dx fosse stato negativo? La posizione del punto sullo schermo sarebbe stata sempre in alto, ma a sinistra (per seguire meglio non perdere docchio lo spazio cartesiano precedente).
Quindi:
Se dx > 0 e dy > 0: in alto a destra.
Se dx < 0 e dy > 0: in alto a sinistra
Se dx > 0 e dy < 0: in basso a destra
Se dx < 0 e dy < 0: in basso a sinistra
Detto ciò ci si accorge che il centro dello schermo, in pratica, non è che lorigine del piano cartesiano in cui è rappresentato il punto generico P(dx, dy).
Nelle osservazioni precedenti non abbiamo considerato dz. E scontato che se tale valore è positivo il punto P si trova davanti allosservatore e quindi va rappresentato, altrimenti, trovandosi dietro di lui, non ci si pone neppure il problema di rappresentarlo.
Il nostro problema, invece, è stabilire come influisce il valore di dz nella rappresentazione del punto P.
Se dz è molto grande significa che il punto è molto lontano dallosservatore. Cosa ne consegue dal punto di vista visivo? Qualsiasi oggetto più è lontano e più ci appare piccolo, ma non solo: ci appare anche più vicino al punto centrale del nostro campo visivo, o, in altre parole, converge sempre più verso il punto di fuga.
E proprio questa particolarità che dà luogo alle regole della prospettiva: ora analizziamola dal punto di vista matematico.
Cosa significa avvicinare un punto allorigine? In termini di coordinate di un punto significa diminuire questultime mantenendole sempre in proporzione fra di loro, ovvero dividerle per un certo valore. Siccome più dz è grande e più il punto dovrà essere vicino allorigine, le sue coordinate andranno divise per un valore direttamente proporzionale a dz, ovvero:
k · dz
dove k è una costante il cui effetto dipende dalle unità di misura scelte per lo schermo e per la rappresentazione dei punti nello spazio cartesiano (ovvero le loro coordinate in assonometria). Se k è troppo elevato si avrà un campo visivo esageratemente vasto, al contrario tutti gli oggetti sembreranno più vicino e il campo visivo sarà troppo limitato: la scelta di tale valore dipende quindi dal buon senso del programmatore.
Quindi, i primi passi fondamentali per rappresentare un ambiente in 3D senza movimento sono:
1. Stabilire le coordinate dei vari punti tramite uno spazio cartesiano.
2. Calcolare le distanze dellosservatore dai punti (dx, dy, dz).
3. Calcolare le coordinate dei punti nel monitor in base alle tre distanze.
In particolare, se chiamiamo c il coefficiente pari a k·dz e il punto centrale del monitor ha coordinate (320, 240), risulterà:
Px = 320 + dx · c; Py = 240 + dy · c;
Ora facciamo un passo un po più avanti: vogliamo che il nostro osservatore possa muoversi o, per essere più precisi, possa spostarsi lungo i tre assi. Spostarsi lungo lasse z significa andare avanti o indietro, lungo lasse x significa spostarsi a destra o a sinistra e lungo lasse y significa alzarsi o abbassarsi.
Il semplice spostamento lungo un asse, quindi, si traduce con un incremento o un decremento delle coordinate dellosservatore. Ecco le direzioni e i conseguenti cambiamenti (che oltretutto sono molto intuitivi):
Avanti: incremento Oz; Indietro: decremento Oz;
Destra: incremento Ox; Sinistra: decremento Ox;
Su: incremento Oy; Giù: decremento Oy;
La Rotazione
Ecco la parte più interessante del nostro studio, ovvero il problema della rotazione: come permettere allosservatore di ruotare attorno a se stesso su un certo asse? Consideriamo prima il problema su di un piano in 2D con un punto P generico:

Il grafico indica losservatore (O), il punto prima della rotazione (P) e dopo (P). La prima impressione è che sia il punto P a ruotare poiché è esso a cambiare posizione sulla circonferenza, ma attenzione: ciò che a noi interessa è il risultato ottenuto dopo la rotazione su se stesso dellosservatore, che in effetti si può ricondurre ad uno spostamento circolare del punto. Come è immediato intuire, le distanze dellosservatore dal punto P sullasse x e y prima della rotazione sono indicate con dx e dy, dopo la rotazione con dx e dy.
La distanza complessiva dellosservatore dal punto è indicata con d, che chiaramente non varia dopo la rotazione.
Questultima è misurata in gradi, in particolare langolo generico b è langolo di rotazione, esprime cioè quanto losservatore abbia girato su se stesso. Langolo a è langolo di origine, cioè quello formato dallosservatore con il punto prima della rotazione. E spontaneo osservare che il nuovo angolo generato dallosservatore e il punto dopo la rotazione sarà pari alla somma dellangolo originale con quello di rotazione (nella figura: a = a + b).
A questo punto dovrebbe essere intuito il motivo per cui la rotazione è stata rappresentata in questo modo: il suo grafico non è che una circonferenza goniometrica che, grazie alle sue particolarità, renderà semplicissimo il problema della rotazione. Basta organizzarsi e stabilire i passi necessari per ottenere le incognite (cioè a, dx e dy), sapendo che i valori a noi noti sono solamente dx e dy.
1.
Innanzitutto dobbiamo conoscere l’angolo originale a. Tutti i linguaggi di
programmazione hanno una funzione trigonometrica inversa: l’arcotangente.
Questa, ottenuto in ingresso il valore della tangente di un certo angolo,
restituisce l’angolo. La tangente di a è pari al rapporto:
dy
/ dx
poiché dy e dx sono il seno e il coseno dell’angolo a. Quindi la chiamata
alla funzione sarà la seguente:
2. Un’altro dato che ci è indispensabile è la distanza d: essa è
l’ipotenusa del triangolo rettangolo avente per cateti dx e dy, quindi:
d
= ![]()
3. Ora possiamo calcolare a’ sommando all’angolo di origine
a
l’angolo di rotazione b:
dy’
= sen(a’) · d
e, analogamente per dx’:
dx’
= cos(a’) · d
Ed ecco che abbiamo
ottenuto tutti i dati necessari per la rappresentazione del punto dopo una
rotazione. Il problema è stato analizzato su di un piano: l’esempio
precedente simula una rotazione solo sull’asse z, in quanto variano le
distanze sull’asse x e y (dx e dy), come accade ad un aereo quando compie un
rollio.
Se vogliamo implementare una rotazione sull’asse y o x è sufficiente cambiare
i dati del problema.
Come avrete notato,
grazie a poche regole matematiche siamo riusciti a realizzare un motore 3D con
piena rotazione a 360°! Il segreto sta nel trovare un modello geometrico per i
vari problemi e ricordarsi che le idee più semplici sono sempre le migliori
(basta pensare al grande Archimede che, grazie ad un’idea semplicissima, riuscì
a calcolare il primo valore approssimato di p!)
“Come mai che la matematica,
essendo fondamentalmente un prodotto del pensiero umano indipendente dell'esperienza,
spiega in modo così ammirevole le cose reali ?” (Albert Einstein).