Le basi del 3D (Ver 1.1)

di Marco "Ludwig" Malentacchi

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 l’immaginare l’osservatore e i punti in uno spazio cartesiano (disegnati in assonometria):

Ogni punto è definito da tre coordinate (x, y, z).

Nel nostro esempio O è l’osservatore e P il punto che egli osserva, che può essere parte di un poliedro qualsiasi.

Con dx, dy e dz sono indicate le distanze dell’osservatore dal punto in termini di coordinate, quindi:

dx = Px – Ox;                      dy = Py – Oy;                           dz = Pz – Oz

La distanza reale dell’osservatore 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 all’osservatore. 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 dell’ambiente da parte dell’osservatore.

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 d’occhio 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 l’origine 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 all’osservatore 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 dall’osservatore. 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 all’origine?” In termini di coordinate di un punto significa diminuire quest’ultime mantenendole sempre in proporzione fra di loro, ovvero dividerle per un certo valore. Siccome più dz è grande e più il punto dovrà essere vicino all’origine, 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 dell’osservatore 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 l’asse z significa andare avanti o indietro, lungo l’asse x significa spostarsi a destra o a sinistra e lungo l’asse y significa alzarsi o abbassarsi.

Il semplice spostamento lungo un asse, quindi, si traduce con un incremento o un decremento delle coordinate dell’osservatore. 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 all’osservatore 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 l’osservatore (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 dell’osservatore, che in effetti si può ricondurre ad uno spostamento circolare del punto.  Come è immediato intuire, le distanze dell’osservatore dal punto P sull’asse x e y prima della rotazione sono indicate con dx e dy, dopo la rotazione con dx’ e dy’.

La distanza complessiva dell’osservatore dal punto è indicata con d, che chiaramente non varia dopo la rotazione.

Quest’ultima è misurata in gradi, in particolare l’angolo generico b è l’angolo di rotazione, esprime cioè quanto l’osservatore abbia girato su se stesso. L’angolo a è l’angolo di origine, cioè quello formato dall’osservatore con il punto prima della rotazione. E’ spontaneo osservare che il nuovo angolo generato dall’osservatore e il punto dopo la rotazione sarà pari alla somma dell’angolo 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:

  a = arctan(dy / dx)

      Se il punto P è dietro all’osservatore, però, a sarà maggiore dell’angolo piatto perché il punto si trova nel terzo o nel quarto quadrante, quindi (siccome la funzione arcotangente restituisce l’angolo che si trova nei primi due quadranti), occorre aggiungere quest’ultimo controllo che provvede eventualmente a sommare 180° all’angolo a.

  

      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:

  a’ = a + b

      4.  A questo punto, ottenuto il valore del nuovo angolo, possiamo calcolare le incognite dx’ e dy’. Poiché, come è stato già spiegato, il grafico non è che una circonferenza geometrica, dy’ e dx’ sono il seno e il coseno dell’angolo a’. Quindi sarà semplice calcolarli tramite la funzione seno e coseno. Entrambi i valori, però, vanno moltiplicati per la distanza d calcolata in precedenza: siccome essa è il raggio della circonferenza e quindi il seno di a’ è uguale a dy’ / d, se vogliamo ottenere dy’ dal seno dovremo applicare la formula inversa:

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).