In quanto utente nuovo inizio con una breve introduzione: sono uno studente di informatica estremamente appassionato di vari aspetti della programmazione, ma soprattutto di computer grafica. Non ho alcuna esperienza professionale nel campo, ma da alcuni anni ormai passo una buona porzione del mio tempo libero a scrivere bozze di engine, in particolare della parte riguardante il rendering. Prima che tutti smettano di leggere faccio seguire qualche screenshot per dare un’onesta possibilità al mio engine di far scaturire interesse.
Sorgenti
https://gitlab.com/pac85/GameKernel/
Screenshot e feature
In quesi screenshot si notano:
- screen space reflections
- rendering PBR (si tratta di cook torrance)
- HDR rendering con tone mapping
- shadow mapping
- cascaded shadow mapping
- penombra di dimensione variabile
Gli artefatti visibili negli screen all’esterno sono dovuti all’assenza del cielo (che si riflette letteralmente sulle varie superfici).
Dettagli tecnici
L’engine usa Vulkan, contiene un ECS ed è in grado di caricare scene da un formato custom (attualmente rappresentato in JSON) per il quale ho realizzato un crudo exporter per Blender. Supporta caricamento di modelli da gltf ed obj. Utilizza ktx per tutte le textures.
Il renderer è di tipo “deferred lighting”, di seguito mostro alcuni dei passaggi intermedi.
Primo pass
Viene renderizzata la scena e vengono scritti in un buffer depth, normali e motion vector (che ometto).
Si nota che le normal maps sono state applicate.
SSR
Viene fatto il dispatch di un compute shader che utilizza il frame precedente riproiettato con i motion vector ed il depth buffer per approssimare i riflessi. Tutto ciò avviene a metà della risoluzione del frame buffer finale.
Shadow pass
In questo pass la scena viene renderizzata dai punti di vista delle luci in un target 8k x 8k. Viene settato un viewport appropriato per ogni luce.
(non si tratta dell’intero buffer, in quanto questo è quasi vuoto nell’esempio)
Lighting
Un compute shader utilizza i risultati del primo pass per calcolare le componenti diffuse e speculari dell’irradianza.
(i dettagli che si notano sono dovuti alle normal maps)
Terzo pass
In questo pass viene nuovamente renderizzata la scena leggendo le proprità dei materiali per calcolare la radianza. Per ragioni di performance si utilizza lo stesso depth buffer e si setta il depth test ad EQUAL in modo da non avere overdraw. Vengono dati in output due buffer:
- uno in cui non viene tenuto conto del contributo di SSR:
- uno che invece contiene anche il contributo di SSR:
Il primo target verrà utilizzato nel prossimo frame per calcolare SSR.
Pass finale
A questo punto viene applicato il tone mapping, uso la classica funzione nata per Uncharted 2. Lo scopo di questo pass è simulare la risposta non lineare che avrebbe una pellicola. Viene anche applicata gamma correction.
A seguire avvengono operazioni meno interessanti come il rendering della UI.
Per concludere vorrei raccontare brevemente alcuni dei miei tentativi precedenti di scrivere un engine.
I primi tentativi
Dopo essere stato deluso dalla “magia” che avveniva usando engine come Unreal o Unity decisi di inizare a studiare OpenGL. Dopo aver letto vari tutorial online ed aver appreso le basi del rendering (tra cui il defunto http://www.arcsynthesis.org/gltut/) decisi di partire da un esempio che renderizzava un triangolo e di estenderlo per caricare scene da file obj. Decisi poi di implementare deferred shading e dopo ancora shadow mapping e dopo diverse bestemmie e schermi neri ottenni il seguente risultato:
Feci un tentativo di portare questo progetto a Vulkan, ma dopo aver capito che l’architettura (che abusava dell’ereditarietà) era incasinata ed inappropriata, ho deciso di ripartire da zero con Rust. Svariate bestemme, schermi neri e GPU crashate dopo, il risultato è quello descritto sopra. In futuro ho intenzione di implementare svariate feature.