Let's Program Doom di 3DSage, ma in LibGDX: Parte 1

Introduzione

Ci sono quei video che a volte danno il "la" a qualcosa nel tuo cervello. Qualcosa scatta e dici "devo provarci anche io", specie se quel video è un tutorial su qualcosa di inusuale, ad esempio come creare candele di soia o, nel mio caso,  come programmare un renderer simile a quello di Doom



Include foto di un bel gattone

Il video è fatto da 3DSage , un esperto di 3D e programmazione su piattaforme particolari, ma soprattutto ha un bellissimo micione che fa comparsate nei video. Ha inoltre tanta roba interessante sul suo canale, compresa una serie di video per creare un renderer di tipo raycaster (parte 1, 2 e 3). 

Il video sul renderer stile Doom aveva titillato quella parte di me che vuole far tutto per conto suo, sbattendo la testa e sporcandosi le mani. E mentre fino a poco tempo fa mi ero ripromesso di imparare a a lavorare con GZDoom, mi ha fatto venir voglia complicarmi la vita.

Si, ma in pratica?

Per farla molto breve, il vecchio Doom divideva le mappe in "settori". Ogni settore era fondamentalmente un poligono di n lati (praticamente il soffitto o il pavimento). Dai lati venivano estrusi dei rettangoli sempre perpendicolari al settore che rappresentavano i muri. Tanti settori consecutivi costruivano una o più "stanze" nel quale il personaggio si muove. Questa spiegazione è estremamente semplicistica, per questo consiglio, se siete davvero interessati, di approfondire in uno degli innumerevoli siti o video che ne spiegano il funzionamento.

Java vs C

Visto che la situazione di partenza non era abbastanza complicata, ho deciso di riscrivere tutto in Java, usando LibGDX che conoscevo bene. Che come idea fa già ridere: nel video il progetto è sviluppato in C e Opengl. Per chi sa di cosa sto parlando, basti sapere che LibGDX utilizza come backend una versione Java di Opengl, mascherando molta della logica di "basso livello". Per chi non sa di cosa sto parlando è invece inutile che glielo spieghi.



Uguale uguale...

il buon 3DSage ha anche quello che io reputo un brutto vizio: usare nomi delle variabili molto corti. Sarò prolisso, ma quando scrivo codice ho bisogno che tutto mi sia immediatamente chiaro. Inoltre, mi sono preso la libertà di rendere il codice più vicino alle normali convenzioni di Java e di programmazione ad oggetti, non facendo così una semplice traduzione 1:1 del codice. 

Le coordinate float, mannaggia a loro

Una delle cose delle quali non avevo immediatamente tenuto conto è il fatto che nel video lui usi quasi sempre variabili int per le coordinate del rendering. LibGDX invece utilizza variabili float. Questo, unito al fatto che le formule con sin e cos danno spesso risultati non interi, causavano problemi grafici piuttosto evidenti.


    Quelle cose proprio belle da vedere.

Castare i float ad int non portava a risultati soddisfacenti, perché causava altri tipi di artefatti o errori. Dopo ore a capire come poter sistemare il problema, ho dovuto utilizzare il metodo più ignorante, che però si è rivelato vincente: arrotondare tutti i risultati dei calcoli e delle coordinate. 


   Un pochino meglio.

Dal 2.5D al 3D

Finisco finalmente il progetto per come è indicato nel video. Vuoi aspettare la seconda parte prima di continuare, in modo da poter fare altro? Ovvio che no.

Ora, è noto a tutti che la Doom Engine non è letteralmente in 3D. Ci sono decine di discussioni portate avanti da centinaia di utenti su decine di forum e siti. Non voglio addentrarmi nel ginepraio, quindi mi limito a dire che il renderer che stiamo sviluppando è, di fatto, un 2.5D (giusto per confondere ancora di più le idee). 

Ho quindi deciso di sviluppare parallelamente un vero renderer 3D: lo scopo è ottenere una classe Java che, predendo gli stessi dati della mappa da renderizzare di quello preso a modello dal video (chiamiamolo DoomLikeRenderer) , ottenga un vero modello 3D e lo renderizzi a dovere (chiamiamolo True3dRenderer). LibGDX mette a disposizione degli strumenti per costruire e renderizzare modelli 3D, quindi parto parzialmente avvantaggiato. O forse no, visto che è la prima volta che lavoro in ambiente 3D senza l'ausilio di engine come Unity o Godot.    



Da questo l'idea era ottenere...


    ... questo. L'illuminazione e la risoluzione non sono l'unica cosa diversa.

In realtà è stato meno difficile del previsto. Il True3dRenderer si prende tutti i dati in pancia e genera un modello parte per parte (tramite MeshPartBuilder). Le superfici sono generate raccogliendo tutti i vertici del poligono, e poi triangolate tramite triangolazione earclipping. Per farla breve: son riuscito ad implementare lo stesso identico funzionamento del DoomLikeRender anche con un vero rendering 3D.

Eh (looking) Up & Down

Non tutto però è stato facile. Uno dei problemi nati dalla mia ignoranza e incompetenza matematica (avevo la media del 6 scarso alle superiori) è stato quello su come implementare la possibilità di "guardare verso l'alto o il basso". Inizialmente pensavo di ruotare lungo l'asse X, ma ovviamente non era applicabile se la camera aveva già cambiato angolazione. La soluzione, come al solito, richiedeva di ragionarci partendo dalle cose più semplici, ovvero la regola della mano destra
Mezzi potentissimi.

In modo totalmente empirico e deduttivo, andando a tentoni, sono riuscito ad ottenere un corretto rendering e movimento di camera ruotando sull'asse X di 90 gradi  e ruotando lungo il vettore perpendicolare (con z = 0) alla normale della direzione della telecamera. Ovviamente ho ancora dei dubbi sul perché non abbia funzionato senza questi accorgimenti, e troverò delle risposte solo sbattendoci la testa nelle prossime volte. Per ora mi godo il mio risultato.

Per chi volesse darsi una lettura all'attuale codice, trova il repo qua. 

EDIT 26/08/22: Ecco il video del renderer in funzione 


 

Commenti

Post più popolari