GameKernel, il mio engine scritto in Rust

0

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.

C’e’ sempre web archive.

Bel lavoro! Ora vogliamo vedere San Miguel 😄

Ben fatto. Sarebbe interessante se illustrassi le motivazioni delle scelte su quali tecniche usare, e le fonti dalle quali hai preso la conoscenza.

theGiallo
Per le risorse:

SSR

Tiled rendering

PBR

Ringrazio Corralx per avermene mandate una buona parte.

Fahien

Oppure qui https://paroj.github.io/gltut/!

Figo!

Come mai sei andato su Rust, contro il tradizionalissimo C++?

Gounemond Il vecchio engine era in C++, Rust aveva attratto la mia attenzione poco prima che decidessi di riscrivere tutto. Ho trovato rust un linguaggio molto più pulito e che presenta features che C++ ad oggi non ha (ad esempio il pattern matching).
Delle features funzionali di rust alcune sono presenti in C++ ma sono molto più complesse o incomplete (per esempio i concepts rispetto ai trait di rust) e finiscono per non essere usate molto spesso. Rust incoraggia molto di più codice vicino alla programmazione funzionale rendendo naturali molte operazioni. In più rust da le sue garanzie di safety che risparmiano mal di testa provocati da vari SIGSEGV ed UB (cosa che ovviamente usando vulkan comunque non manca).

Animazioni e modelli skeletal

Dopo un po più di tempo del dovuto (per via del poco tempo a disposizione) ho finalmente fatto funzionare mesh skeletal ed animazioni.

Video dimostrativo

Il seguente video mostra il risultato ottenuto, tenendo a mente che il modello e l’animazione sono stati fatti da me con blender e che la mia arte è peggio della “programmers art” media.

[embed-video id=”1492″ url=”https://cdn.discordapp.com/attachments/177443107355230209/805953232022142986/4_5818944344001874257.mp4″ type=”normal” live=”true”]

Dettagli tecnici

Modelli, scheletri ed animazioni sono caricati da un file glb. Le animazioni in gltf sono rappresentate da una serie di canali e samplers, un singolo keyframe può essere formato da più samplers ( nel caso in cui questo modificasse più attributi ) che avranno lo stesso tempo. Utilizzo una mappa ordinata (BTreeMap in rust ) per unire sampler dello stesso keyframe ed ordinare per tempo.
Il codice che interpola i keyframe è particolarmente compatto per cui ne condivido il link.
Lo skinning avviene in uno shader (link) . La rappresentazione di un Bone sulla gpu non usa una matrice ma scala, traslazione e rotazione (quaternione) separati.

Risorse

Comments are closed.