Documentazione

Progetto di Computer Graphics a.a 2023/2024

Source code on Github
Go to project

Introduzione

Il nome del progetto è where-i-wrote-this ed è una rappresentazione 3D della stanza dove è stato scritto questo codice. La riproduzione dei dettagli e dei materiali non è del tutto fedele alla realtà ma è stata realizzata con l'intento di essere il più possibile simile alla stanza reale.

Il progetto è stato realizzato per il corso di Computer Graphics nell'a.a 2023/2024 . Viene utilizzata come base una stanza isometrica aperta e al suo interno vengono resi in proiezione prospettica vari oggetti. Tutti gli oggetti della scena sono stati modellati utilizzando Blender (v4.1.1), esportati in formato .obj e caricati sulla scena utilizzando le librerie fornite dal docente.

Per la realizzazione è stato utilizzata la libreria grafica WebGL (che utilizza l'elemento Canvas di HTML5) e codice JavaScript.

main
Scena principale

Struttura del progetto

Il progetto è strutturato come segue:

Viene consegnato come file .zip strutturato come segue:

Librerie utilizzate

Sono state utilizzate diverse librerie per facilitare lo sviluppo e diminuire la quantità di codice.

Inizialmente si è cercato di sviluppare da zero un parser per i file .obj fallendo nel tentativo. In secondo luogo si è deciso di utilizzare le librerie fornite a lezione ma infine si è deciso di utilizzare webgl-utils.js in aggiunta a qualche riga di codice JavaScript, ritenendola la scelta più conveniente e semplice.

Comandi

Interfaccia grafica 2D

Per muoversi all'interno della scena è possibile utilizzare il cursore, il mouse e la tastiera. Inoltre grazie alla libreria dat.gui.js viene fornita un'interfaccia grafica 2D per modificare alcuni parametri della scena. Per mostrare o nascondere l'interfaccia è possibile cliccare il tasto H sulla tastiera.

gui
Interfaccia grafica 2D (dat.gui.js)

Movimento della camera

Utilizzando il cursore è possibile cliccare e trascinare sullo schermo per ruotare la camera. L'icona del cursore cambia stato da grab a grabbing per mostrare all'utente che il movimento della camera è attivo. La gestione del movimento della camera è implementata nel file utils/keys.js. Viene catturato l'evento opportuno e vengono aggiornati i parametri che defnisciono la posizione della camera nella scena.

move-with-cursor
Esempio di movimento della camera con il cursore

Per avvicinare o allontare la camera è possibile utilizzare la rotellina del mouse oppure, in alternativa, è possibile utilizzare due dita e scorrere verso l'alto o verso il basso sul trackpad. Anche questa funzione è gestita da utils/keys.js.

move-with-wheel
Esempio di movimento della camera con la rotellina del mouse o con il trackpad

È possibile inoltre modificare la posizione della camera utilizzando le frecce della tastiera o i tasti WASD. Sono stati previsti 8 keybindings, i quali incrementano o decrementano i parametri che definiscono la posizione della camera di un certo delta angolare dr = (5.0 * Math.PI) / 180.0 oppure modificando la distanza della camera dal target.

I comandi disponibili sono:

Modelli 3D

La maggior parte dei modelli 3D sono stati ottenuti da Sketchfab. Sono stati importati su Blender utilizzando il plugin di Sketchfab per Blender e sono stati modificati per adattarli alla scena.

model
Esempio di modellazione 3D su Blender

Caricamento dei modelli

I modelli vengono caricati utilizzando le funzionalità presenti in utils/loader.js. Viene sfruttata la guida offerta da WebGLFundamentals e la libreria webgl-utils.js per caricare i file .obj e i relativi materiali (file .mtl).

L'utilizzo della libreria permette di risparmiare molte righe di codice e di caricare in maniera semplice e veloce i file .obj comprensivi di materiali, texture e normali. Viene infatti fornito un parser che gestisce tutti i dati e li converte in un formato utilizzabile dalla libreria. Oltre a questo vengono fornite funzionalità utili per l'impostazione dei campi uniform degli shader.

Resa della scena

Viene definita una lista di oggetti 3D che devono essere resi in geometria 3D prospettica. Ogni oggetto 3D può specificare quale vertex shader e quale fragemnt shader utilizzare definendo quindi un programma (combinazione dei 2 shader). Così facendo ogni oggetto 3D può essere renderizzato con un programma diverso garantendo flessibilità.

Gli oggetti dispongono anche del campo modelMatrix, il quale definisce una matrice 4x4 che rappresenta la matrice specifica per il modello. Questa matrice è cruciale nel rendering, poiché trasforma l'oggetto dallo spazio locale al mondo prima di applicare ulteriori trasformazioni (vista, proiezione).

Una volta definiti gli oggetti da renderizzare, viene iterata la lista degli oggetti e per ciascuno di essi viene calcolata la matrice modelViewMatrix e la matrice modelViewTranspose. La matrice modelViewTranspose è necessaria per trasformare le normali quando varia la posizione della camera. Se i punti vengono trasformati con una matrice M, le normali devono essere trasformate con la matrice inversa trasposta (M^-1)^T. Il calcolo di una nuova normale sarà dato da n' = (M^-1)^T * n dove n indica una normale e M indica la matrice modelViewMatrix. il motivo per il quale è necessario l'utilizzo della matrice inversa trasposta per trasformare le normali è spiegato qui.

La libreria webgl-utils.js fornisce dei metodi molto utili per definire il setup dei programmi shader. I metodi più utili sono sicuramente setBuffersAndAttributes e setUniforms i quali impostano i buffer, gli attributi e i campi uniform dei programmi shader.

Shaders

Ogni modello 3D specifica quale vertex shader e quale fragment shader utilizzare. Gli shader sono stati implementati come file .glsl nella directory shaders/ e vengono caricati utilizzando la funzione loadTextResource presente nel codice custom utils/glUtils.js. I fragment shaders utilizzano un modello di illuminazione che si ispira al Phong Shading presente a questo link.

In aggiunta all'interno della scena è presente un oggetto 3D (il logo di WebGL) che simula una luce al neon. Per ottenere questo effetto sono stati previsti due appositi fragment shaders applicati alla parete della stanza e all'oggetto stesso (fragment-room.glsl e fragment-neon.glsl).

neon
Esempio di illuminazione al neon sulla parete e sul logo di WebGL

Rendering avanzato

Bump mapping

Quando gli oggetti utilizzano una texture è possibile applicare anche un effetto di resa avanzata (bump mapping) abilitandolo dai controlli. L'effetto è ottenuto facendo riferimento a questa guida e agli shaders che utilizza.

bump-mapping
Esempio di bump mapping

Shadow mapping

Per ottenere l'effetto di shadow mapping è necessario abilitare l'opzione dal pannello delle impostazioni. Viene renderizzata la scena dalla prospettiva della luce e il risultato viene utilizzato per scrivere su un buffer che rappresenta la profondità della scena. Successivamente, durante il rendering della scena dal punto di vista della camera, viene utilizzato il buffer creato per ottenere l'effetto di shadow mapping proiettandolo sotto forma di texture. Per l'implementazione è stata seguita questa guida riadattandola opportunamente.

shadow
Esempio di shadow mapping
shadows-up
Esempio di shadow mapping

Supporto per dispositivi mobili

Il progetto è stato sviluppato garantendo il suo funzionamento anche su dispositivi mobili. Per muoversi all'interno della scena è possibile utilizzare il touch-screen oppure i controlli disponibili grazie all'interfaccia grafica 2D. Gli eventi generati dal touch-screen sono catturati e gestiti dal file utils/keys.js.

Per muovere la camera è possibile scorrere il dito verso l'alto o verso il basso mentre per zoomare all'interno della stanza serve utilizzare due dita e svolgere il cosiddetto pinching, ovvero avvicinare o allontanare le dita appogiate sullo schermo.

Limitazioni su iOS

I moderni dispositivi mobili iOS presentano limitazioni nei confronti della quantità di memoria utilizzabile dai contesti WebGL. Questo potrebbe risultare in un crash del browser se la scena è troppo complessa. Questo problema è stato riscontrato da vari utenti iOS come riportato anche sul forum ufficiale di Apple.

mobile
Screenshot su mobile

Riferimenti

L'implementazione del progetto tiene conto degli anti-pattern WebGL che sono stati evitati con attenzione. Nonostante ciò la paura di non rispettare i pattern è così tanta che alle volte porta ad inventare dei nuovi anti-pattern. Probabilmente anche per questo progetto è stato così.