C'è un modo per usare un numero arbitrario di luci in uno shader di frammenti?


19

Esiste un modo per passare un numero arbitrario di posizioni (e colori) di luce per lo shader di frammento e passarci sopra nello shader?

In caso contrario, come dovrebbero essere simulate più luci? Ad esempio per quanto riguarda l'illuminazione direzionale diffusa, non puoi semplicemente passare una somma dei pesi di luce per lo shader.


Non ho lavorato con WebGL, ma in OpenGL hai al massimo 8 sorgenti luminose. Secondo me, se vuoi passare più di questo, devi usare ad esempio variabili uniformi.
Zacharmarz,

Il vecchio metodo era di passare sempre in tutte le luci, le luci inutilizzate erano impostate su 0 luminanza e quindi non avrebbero influenzato la scena. Probabilmente non più usato molto ;-)
Patrick Hughes

7
Quando fai cose di Google come queste, non usare il termine "WebGL": la tecnologia è troppo giovane perché le persone possano pensare di affrontare questi problemi. Prendi questa ricerca, ad esempio, "Mi sento fortunato" avrebbe funzionato. Ricorda che un problema WebGL dovrebbe tradursi perfettamente nello stesso identico problema OpenGL.
Jonathan Dickinson,

Per più di 8 luci nel rendering in avanti generalmente utilizzo uno shader multi-passaggio e do ad ogni passaggio un gruppo diverso di 8 luci da elaborare, usando la fusione additiva.
ChrisC,

Risposte:


29

Esistono generalmente due metodi per gestirlo. Oggi vengono chiamati rendering in avanti e rendering differito. C'è una variazione su questi due che discuterò di seguito.

Rendering in avanti

Rendi ogni oggetto una volta per ogni luce che lo colpisce. Questo include la luce ambientale. Si utilizza una modalità di fusione additiva ( glBlendFunc(GL_ONE, GL_ONE)), quindi i contributi di ciascuna luce si sommano. Poiché il contributo di luci diverse è additivo, il framebuffer alla fine ottiene il valore

È possibile ottenere HDR eseguendo il rendering in un framebuffer a virgola mobile. Quindi fai un passaggio finale sulla scena per ridimensionare i valori di illuminazione dell'HDR su un intervallo visibile; questo sarebbe anche il luogo in cui implementate la fioritura e altri post-effetti.

Un miglioramento delle prestazioni comune per questa tecnica (se la scena ha molti oggetti) è l'uso di un "pre-passaggio", in cui si esegue il rendering di tutti gli oggetti senza disegnare nulla sul framebuffer dei colori (utilizzare glColorMaskper disattivare le scritture dei colori). Questo riempie solo il buffer di profondità. In questo modo, se si esegue il rendering di un oggetto dietro un altro, la GPU può saltare rapidamente quei frammenti. Deve ancora eseguire lo shader di vertice, ma può saltare i calcoli dello shader di frammenti in genere più costosi.

Questo è più semplice da codificare e più facile da visualizzare. E su alcuni hardware (principalmente GPU mobili e integrate), può essere più efficiente dell'alternativa. Ma su hardware di fascia alta, l'alternativa generalmente vince per le scene con molte luci.

Rendering differito

Il rendering differito è un po 'più complicato.

L'equazione di illuminazione utilizzata per calcolare la luce per un punto su una superficie utilizza i seguenti parametri di superficie:

  • Posizione di superficie
  • Normali di superficie
  • Colore diffuso in superficie
  • Colore speculare superficiale
  • Lucentezza speculare superficiale
  • Forse altri parametri di superficie (a seconda della complessità dell'equazione dell'illuminazione)

Nel rendering in avanti, questi parametri raggiungono la funzione di illuminazione dello shader di frammento sia passando direttamente dallo shader di vertice, sia estraendo da trame (di solito attraverso coordinate di trama passate dallo shader di vertice), o generati da tutto il tessuto nello shader di frammento basato su altri parametri. Il colore diffuso può essere calcolato combinando un colore per vertice con una trama, combinando più trame, qualunque cosa.

Nel rendering differito, rendiamo tutto questo esplicito. Nel primo passaggio, eseguiamo il rendering di tutti gli oggetti. Ma non rendiamo i colori . Al contrario, eseguiamo il rendering dei parametri di superficie . Quindi ogni pixel sullo schermo ha una serie di parametri di superficie. Questo viene fatto tramite rendering su trame fuori schermo. Una trama memorizzerebbe il colore diffuso come RGB, e possibilmente la lucentezza speculare come l'alfa. Un'altra trama memorizzerebbe il colore speculare. Un terzo memorizzerebbe il normale. E così via.

La posizione di solito non è memorizzata. Viene invece ricostituito nel secondo passaggio dalla matematica che è troppo complesso per entrare qui. Basti dire che usiamo il buffer di profondità e la posizione del frammento dello spazio dello schermo come input per capire la posizione dello spazio della telecamera del punto su una superficie.

Quindi, ora che queste trame contengono essenzialmente tutte le informazioni sulla superficie per ogni pixel visibile nella scena, iniziamo a renderizzare quad a schermo intero. Ogni luce ottiene un rendering quad a schermo intero. Campioniamo dalle trame dei parametri di superficie (e ricostituiamo la posizione), quindi le usiamo solo per calcolare il contributo di quella luce. Questo viene aggiunto (di nuovo glBlendFunc(GL_ONE, GL_ONE)) all'immagine. Continuiamo a farlo finché non finiamo le luci.

L'HDR di nuovo è un passaggio post-process.

Il principale svantaggio del rendering differito è l'antialiasing. Richiede un po 'più di lavoro per antialias correttamente.

Il vantaggio più grande, se la tua GPU ha molta larghezza di banda di memoria, sono le prestazioni. Eseguiamo il rendering della geometria effettiva una sola volta (o 1 + 1 per luce con ombre, se stiamo eseguendo la mappatura delle ombre). Non passiamo mai del tempo su pixel o geometria nascosti che non sono più visibili dopo questo. Tutto il tempo di passaggio dell'illuminazione viene speso per cose che sono effettivamente visibili.

Se la tua GPU non ha molta larghezza di banda di memoria, il passaggio della luce può davvero farti male. Trarre da 3-5 trame per pixel dello schermo non è divertente.

Pre-Pass leggero

Questa è una sorta di variazione sul rendering differito che presenta interessanti compromessi.

Proprio come nel rendering differito, i parametri della superficie vengono sottoposti a rendering in un set di buffer. Tuttavia, hai abbreviato i dati di superficie; gli unici dati di superficie che ti interessano in questo momento sono il valore del buffer di profondità (per ricostruire la posizione), la lucentezza normale e speculare.

Quindi per ogni luce, calcoli solo i risultati dell'illuminazione. Nessuna moltiplicazione con i colori delle superfici, niente. Solo il punto (N, L) e il termine speculare, completamente senza i colori della superficie. I termini speculari e diffusi dovrebbero essere conservati in buffer separati. I termini speculari e diffusi per ciascuna luce sono riassunti nei due buffer.

Quindi, si esegue nuovamente il rendering della geometria, utilizzando i calcoli di illuminazione speculare e diffusa totali per eseguire la combinazione finale con il colore della superficie, producendo così la riflessione complessiva.

I lati positivi qui sono che si ottiene indietro il multicampionamento (almeno, più facile che con differito). Si esegue meno rendering per oggetto rispetto al rendering in avanti. Ma la cosa principale oltre che differita che questo fornisce è un momento più facile per avere diverse equazioni di illuminazione per diverse superfici.

Con il rendering differito, in genere si disegna l'intera scena con lo stesso shader per luce. Quindi ogni oggetto deve usare gli stessi parametri del materiale. Con il pre-passaggio della luce, puoi dare a ogni oggetto uno shader diverso, in modo che possa eseguire da solo il passaggio finale di illuminazione.

Ciò non offre la stessa libertà del caso di rendering in avanti. Ma è ancora più veloce se hai la larghezza di banda delle trame da risparmiare.


-1: mancata menzione di LPP / PPL. -1 differito: il rendering è una vittoria istantanea su qualsiasi hardware DX9.0 (sì, anche sul mio laptop 'business') - che è requisiti di base intorno al 2009. A meno che non si stia prendendo di mira DX8.0 (che non può fare Deferred / LPP) Differito / LPP è predefinito . Infine, "molta larghezza di banda della memoria" è folle: generalmente non stiamo nemmeno saturando PCI-X x4, ma LPP riduce sostanzialmente la larghezza di banda della memoria. Infine, -1 per il tuo commento; loop come questo OK? Sai che questi loop stanno accadendo 2073600 volte per frame, giusto? Anche con il parrelismo della scheda grafica, è male.
Jonathan Dickinson,

1
@JonathanDickinson Penso che il suo punto fosse che la larghezza di banda di memoria per il pre-pass posticipato / leggero è in genere più volte maggiore rispetto al rendering in avanti. Ciò non invalida l'approccio differito; è solo qualcosa da considerare quando lo si sceglie. A proposito: i tuoi buffer differiti dovrebbero essere nella memoria video, quindi la larghezza di banda PCI-X è irrilevante; è la larghezza di banda interna della GPU che conta. I pixel shader lunghi, ad esempio con un loop non srotolato, non sono nulla di cui preoccuparsi se stanno facendo un lavoro utile. E non c'è niente di sbagliato nel trucco di prepass z-buffer; funziona benissimo.
Nathan Reed,

3
@JonathanDickinson: si tratta di WebGL, quindi qualsiasi discussione sui "modelli shader" è irrilevante. E che tipo di rendering usare non è un "argomento religioso": è semplicemente una questione di hardware su cui stai eseguendo. Una GPU integrata, in cui la "memoria video" è solo una normale RAM della CPU, funzionerà molto male con il rendering differito. Su un renderer mobile basato su tile, è anche peggio . Il rendering differito non è una "vincita istantanea" indipendentemente dall'hardware; ha i suoi compromessi, proprio come qualsiasi hardware.
Nicol Bolas,

2
@JonathanDickinson: "Inoltre, con il trucco pre-pass del buffer z, dovrai lottare per eliminare il combattimento z con gli oggetti che devono essere disegnati." Questa è una totale assurdità. Stai eseguendo il rendering degli stessi oggetti con le stesse matrici di trasformazione e lo stesso shader di vertici. Il rendering multipass è stato eseguito in Voodoo 1 giorni; questo è un problema risolto . Accumulare l'illuminazione non fa nulla per cambiarlo.
Nicol Bolas,

8
@JonathanDickinson: Ma non stiamo parlando del rendering di un wireframe, vero? Stiamo parlando del rendering degli stessi triangoli di prima. OpenGL garantisce l' invarianza per lo stesso oggetto reso (purché tu stia usando lo stesso vertex shader, ovviamente, e anche allora, c'è la invariantparola chiave per garantirlo per altri casi).
Nicol Bolas,

4

È necessario utilizzare il rendering differito o l'illuminazione pre-pass . Alcune delle vecchie pipeline a funzione fissa (leggi: no shader) supportavano fino a 16 o 24 luci - ma questo è tutto . Il rendering differito elimina il limite di luce; ma a costo di un sistema di rendering molto più complicato.

Apparentemente WebGL supporta MRT che è assolutamente necessario per qualsiasi forma di rendering differito, quindi potrebbe essere fattibile; Non sono sicuro di quanto sia plausibile.

In alternativa, puoi esaminare Unity 5 , che ha differito il rendering immediatamente.

Un altro modo semplice per affrontare questo è semplicemente dare la priorità alle luci (forse, in base alla distanza dal giocatore e se sono nel frustum della fotocamera) e abilitare solo le prime 8. Molti titoli AAA sono riusciti a farlo senza molto impatto sulla qualità dell'output (ad esempio Far Cry 1).

Puoi anche esaminare le mappe luminose pre-calcolate . Giochi come Quake 1 hanno ottenuto un sacco di chilometraggio da questi - e possono essere piuttosto piccoli (il filtro bilineare ammorbidisce abbastanza bene le mappe di luce allungate). Purtroppo pre-calcolato esclude la nozione di luci dinamiche al 100%, ma sembra davvero eccezionale . Potresti combinarlo con il tuo limite di 8 luci, quindi ad esempio solo i razzi o simili avrebbero una vera luce - ma le luci sul muro o simili sarebbero mappe di luce.

Nota a margine : non vuoi avvolgerli in uno shader? Dì addio alla tua esibizione. Una GPU non è una CPU e non è progettata per funzionare allo stesso modo di, ad esempio, JavaScript. Ricorda che ogni pixel che visualizzi (anche se viene sovrascritto) deve eseguire il loop - quindi se esegui l'esecuzione a 1920x1080 e un semplice loop che esegue 16 volte, esegui effettivamente tutto all'interno di quel loop 33177600 volte. La tua scheda grafica eseguirà molti di quei frammenti in parallelo, ma quei loop mangeranno comunque hardware più vecchio.


-1: "È necessario utilizzare il rendering posticipato" Questo non è affatto vero. Il rendering differito è certamente un modo per farlo, ma non è l' unico modo. Inoltre i loop non sono così male in termini di prestazioni, specialmente se si basano su valori uniformi (cioè: ogni frammento non ha una lunghezza del loop diversa).
Nicol Bolas,

1
Si prega di leggere il 4 ° paragrafo.
Jonathan Dickinson,

2

Puoi usare un pixel shader che supporta n luci (dove n è un numero piccolo come 4 o 8) e ridisegnare la scena più volte, passando ogni volta un nuovo gruppo di luci e usando la fusione additiva per combinarli tutti insieme.

Questa è l'idea di base. Naturalmente ci sono molte ottimizzazioni necessarie per renderlo abbastanza veloce per una scena di dimensioni ragionevoli. Non disegnare tutte le luci, solo quelle visibili (abbattimento di frustum e occlusione); in realtà non ridisegnare l' intera scena ad ogni passaggio, ma solo gli oggetti nel raggio d'azione delle luci in quel passaggio; hanno più versioni dello shader che supportano diversi numeri di luci (1, 2, 3, ...) in modo da non perdere tempo a valutare più luci del necessario.

Il rendering differito, come indicato nell'altra risposta, è una buona scelta quando hai molte piccole luci, ma non è l'unico modo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.