Se sei uno sviluppatore di giochi 3D, probabilmente hai già trovato i termini rendering avanzato e rendering differito nella tua ricerca di motori grafici moderni. E, spesso, dovrai sceglierne uno da usare nel tuo gioco. Ma cosa sono, come si differenziano e quale scegliere?
Rendering differito per molte luci (Immagine per gentile concessione di Hannes Nevalainen)Per iniziare, dobbiamo capire un po 'di pipeline grafiche moderne o programmabili.
Nel corso della giornata, eravamo limitati a ciò che la pipeline grafica della scheda video aveva. Non è stato possibile modificare il modo in cui ha disegnato ciascun pixel, a parte l'invio di una trama diversa e non siamo riusciti a deformare i vertici una volta che erano sulla carta. Ma i tempi sono cambiati, e ora abbiamo pipeline grafiche programmabili. Ora possiamo inviare il codice alla scheda video per cambiare il modo in cui i pixel appaiono, dando loro un aspetto irregolare con mappe normali e aggiungendo riflessione (e una grande dose di realismo).
Questo codice è sotto forma di geometria, vertice, e frammenti di shader, e in sostanza cambiano il modo in cui la scheda video rende i tuoi oggetti.
Il rendering avanzato è la tecnica di rendering standard, pronta all'uso, utilizzata dalla maggior parte dei motori. Fornisci alla scheda grafica la geometria, la proietta e la suddivide in vertici, quindi quelli vengono trasformati e suddivisi in frammenti, o pixel, che ottengono il trattamento di rendering finale prima che vengano passati sullo schermo.
È piuttosto lineare e ogni geometria viene passata lungo il tubo uno alla volta per produrre l'immagine finale.
Nel rendering posticipato, come suggerisce il nome, il rendering viene differito un po 'fino a quando tutte le geometrie non hanno passato la pipa; l'immagine finale viene quindi prodotta applicando l'ombreggiatura alla fine.
Ora, perché dovremmo farlo?
L'illuminazione è la ragione principale per percorrere una rotta rispetto all'altra. In una pipeline standard di rendering in avanti, i calcoli di illuminazione devono essere eseguiti su ogni vertice e su ogni frammento nella scena visibile, per ogni luce nella scena.
Se hai una scena con 100 geometrie e ogni geometria ha 1.000 vertici, potresti avere circa 100.000 poligoni (una stima molto approssimativa). Le schede video possono gestirlo abbastanza facilmente. Ma quando quei poligoni vengono inviati allo shader di frammenti, è qui che avvengono i costosi calcoli di illuminazione e può verificarsi il vero rallentamento.
Gli sviluppatori cercano di spingere quanti più calcoli di illuminazione nel vertex shader possibile per ridurre la quantità di lavoro che il framment shader deve fare.I costosi calcoli di illuminazione devono essere eseguiti per ogni frammento visibile di ogni poligono sullo schermo, indipendentemente se si sovrappone o è nascosto da altri frammenti di un poligono. Se il tuo schermo ha una risoluzione di 1024x768 (che è, con ogni mezzo, non ad altissima risoluzione) hai circa 800.000 pixel che devono essere renderizzati. Potresti facilmente raggiungere un milione di operazioni sui frammenti ogni fotogramma. Inoltre, molti dei frammenti non arriveranno mai sullo schermo perché sono stati rimossi con test di profondità, e quindi il calcolo dell'illuminazione è stato sprecato su di essi.
Se hai un milione di quei frammenti e all'improvviso devi rendere nuovamente quella scena per ogni luce, sei saltato su [num lights] x 1.000.000
operazioni di frammentazione per fotogramma! Immagina se tu avessi una città piena di lampioni dove ognuno è una fonte di luce puntiforme ...
La formula per stimare questa complessità di rendering in avanti può essere scritta, in notazione O grande, come O (num_geometry_fragments * num_lights)
. Qui puoi vedere che la complessità è direttamente correlata al numero di geometrie e al numero di luci.
Ora, alcuni motori ottimizzano questo, tagliando le luci che sono lontane, combinando luci o usando mappe luminose (molto popolari, ma statiche). Ma se vuoi luci dinamiche e molte di esse, abbiamo bisogno di una soluzione migliore.
Il Rendering differito è un approccio molto interessante che riduce il conteggio degli oggetti, e in particolare il conteggio dei frammenti totali, ed esegue i calcoli di illuminazione sui pixel sullo schermo, usando quindi la dimensione della risoluzione invece del conteggio totale dei frammenti.
La complessità del rendering differito, nella notazione O grande, è: O (screen_resolution * num_lights)
.
Puoi vedere che ora non importa quanti oggetti hai sullo schermo che determina il numero di luci che usi, così puoi aumentare felicemente il numero di luci. (Questo non significa che puoi avere oggetti illimitati, devono ancora essere disegnati nei buffer per produrre il risultato finale del rendering.)
Vediamo come funziona.
Ogni geometria viene renderizzata, ma senza ombreggiatura, a diversi buffer di spazio dello schermo che utilizzano più obiettivi di rendering. In particolare, la profondità, le normali e il colore sono tutti scritti per separare i buffer (immagini). Questi buffer vengono quindi combinati per fornire informazioni sufficienti per ciascuna luce per illuminare i pixel.
Sapendo quanto è distante un pixel e il suo vettore normale, possiamo combinare il colore di quel pixel con la luce per produrre il nostro rendering finale.
La risposta breve è, se stai usando molte luci dinamiche, allora dovresti usare il rendering differito. Tuttavia, ci sono alcuni svantaggi significativi:
Se non hai molte luci o vuoi essere in grado di girare su hardware vecchio, allora dovresti continuare con il rendering in avanti e sostituire le tue numerose luci con mappe di luce statiche. I risultati possono ancora sembrare sorprendenti.
Spero che abbia fatto luce sull'argomento. Le tue opzioni sono lì per risolvere i tuoi problemi di rendering, ma è molto importante scegliere quello giusto all'inizio dello sviluppo del gioco per evitare modifiche difficili in seguito.