Disegna un sacco di piastrelle con OpenGL, il modo moderno


35

Sto lavorando a un gioco per PC basato su piccole tessere / sprite con un team di persone e stiamo riscontrando problemi di prestazioni. L'ultima volta che ho usato OpenGL è stato intorno al 2004, quindi mi sono insegnato a usare il profilo principale e mi trovo un po 'confuso.

Devo disegnare sullo schermo di 250-750 tessere 48x48 sullo schermo ogni fotogramma, e forse circa 50 sprite. Le tessere cambiano solo quando viene caricato un nuovo livello e gli sprite cambiano continuamente. Alcune tessere sono composte da quattro pezzi 24x24 e la maggior parte (ma non tutte) degli sprite ha le stesse dimensioni delle tessere. Molte piastrelle e folletti usano la fusione alfa.

In questo momento sto facendo tutto questo in modalità immediata, che so che è una cattiva idea. Tuttavia, quando uno dei membri del nostro team tenta di eseguirlo, ottiene frame rate molto bassi (~ 20-30 fps), ed è molto peggio quando ci sono più tessere, specialmente quando molte di quelle tessere sono del tipo che sono tagliati a pezzi. Tutto ciò mi fa pensare che il problema sia il numero di richiami effettuati.

Ho pensato ad alcune possibili soluzioni a questo, ma volevo gestirle da alcune persone che sanno di cosa stanno parlando, quindi non spreco il mio tempo in qualcosa di stupido:

PIASTRELLE:

  1. Quando viene caricato un livello, disegna tutte le tessere una volta in un frame buffer attaccato a una grande trama di clacson, e disegna un grande rettangolo con quella trama su di essa ogni cornice.
  2. Metti tutte le tessere in un buffer di vertici statico quando il livello è caricato e disegnale in quel modo. Non so se c'è un modo per disegnare oggetti con diverse trame con una sola chiamata a glDrawElements, o se questo è qualcosa che vorrei fare. Forse hai semplicemente messo tutte le tessere in una grande trama gigante e hai usato coordinate di trama divertenti nel VBO?

SPRITES:

  1. Disegna ogni sprite con una chiamata separata a glDrawElements. Questo sembra comportare un sacco di cambio di trama, che mi è stato detto che è male. Gli array di trama sono forse utili qui?
  2. Usa un VBO dinamico in qualche modo. Stessa domanda sulla trama del numero 2 sopra.
  3. Sprite di punti? Questo è probabilmente sciocco.

Qualcuno di queste idee è sensato? C'è una buona implementazione da qualche parte su cui potrei guardare?


Se le tessere non si muovono né cambiano e sembrano allo stesso modo a livello intero, dovresti usare la prima idea - frame buffer. Sarà più efficiente.
Zacharmarz,

Prova a usare un atlante delle trame in modo da non dover cambiare trama, ma mantenendo tutto il resto uguale. Come va il loro framerate?
user253751

Risposte:


25

Il modo più veloce per eseguire il rendering dei riquadri consiste nel raggruppare i dati dei vertici in un VBO statico con indici (come indica glDrawElements). Scriverlo in un'altra immagine non è assolutamente necessario e richiederà solo molta più memoria. La commutazione delle trame è MOLTO costosa, quindi probabilmente vorrai imballare tutte le tessere in un cosiddetto Atlante delle trame e dare ad ogni triangolo nel VBO le giuste coordinate delle trame. Sulla base di questo, non dovrebbe essere un problema renderizzare 1000, anche 100000 tessere, a seconda dell'hardware.

L'unica differenza tra il rendering Tile e il rendering Sprite è probabilmente che gli sprite sono dinamici. Quindi, per le migliori prestazioni, ma facilmente realizzabili, puoi semplicemente mettere le coordinate per i vertici dello sprite in un flusso per disegnare VBO ogni fotogramma e disegnare con glDrawElements. Comprimi anche tutte le trame in un Texture Atlas. Se i tuoi sprite si spostano raramente, potresti anche provare a creare un VBO dinamico e aggiornarlo quando si sposta uno sprite, ma qui è totale overkill, poiché vuoi solo rendere alcuni sprite.

Puoi guardare un piccolo prototipo che ho realizzato in C ++ con OpenGL: Particulate

Rendo circa 10000 sprite di punti immagino, con un fps medio di 400 su una normale macchina (Quad Core a 2,66 GHz). È limitato alla CPU, il che significa che la scheda grafica potrebbe renderizzare ancora di più. Nota che non uso gli atlanti delle trame qui, poiché ho solo una trama per le particelle. Le particelle vengono renderizzate con GL_POINTS e gli shader calcolano le dimensioni effettive del quad allora, ma penso che ci sia anche un Quad Renderer.

Oh, e sì, a meno che tu non abbia un quadrato e usi gli shader per la mappatura delle trame, GL_POINTS è abbastanza sciocco. ;)


Gli sprite cambiano posizione e quale trama stanno usando, e la maggior parte di loro lo fa ogni fotogramma. Inoltre, sprite ed essere creati e distrutti molto spesso. Sono queste le cose che può gestire un VBO di stream stream?
Nic

2
Lo stream draw in pratica significa: "Invia questi dati alla scheda grafica e scartali dopo il disegno". Quindi devi inviare di nuovo i dati per ogni frame e ciò significa che non importa quanti sprite rendi, quale posizione hanno, quali coordinate di trama o quale colore. Ma inviare tutti i dati contemporaneamente e lasciare che la GPU elabori è MOLTO più veloce della modalità immediata, ovviamente.
Marco,

Tutto questo ha senso. Ne vale la pena usare un buffer indice per questo? Gli unici vertici che verranno ripetuti sono due angoli di ogni rettangolo, giusto? (La mia comprensione è che gli indici sono la differenza tra glDrawElements e glDrawArrays. Esatto?)
Nic

1
Senza indici non è possibile utilizzare GL_TRIANGLES, che di solito è negativo, poiché questo metodo di disegno è quello con le migliori prestazioni garantite. Inoltre, l'implementazione di GL_QUADS è obsoleta in OpenGL 3.0 (fonte: stackoverflow.com/questions/6644099/… ). I triangoli sono la mesh nativa di qualsiasi scheda grafica. Quindi, "usi" 2 * 6 byte in più per salvare 2 esecuzioni shader di vertici e vertex_size * 2 byte. Quindi, puoi generalmente dire che è SEMPRE migliore.
Marco,

2
Il link a Particulate è morto ... Potresti fornirne uno nuovo, per favore?
SWdV,

4

Anche con questo numero di chiamate di disegno non dovresti vedere quel tipo di calo delle prestazioni - la modalità immediata può essere lenta ma non è così lenta (per riferimento, anche il caro Quake può gestire diverse migliaia di chiamate in modalità immediata per frame senza cadere giù così male).

Sospetto che qui stia succedendo qualcosa di più interessante. La prima cosa che devi fare è investire un po 'di tempo nella profilazione del tuo programma, altrimenti corri un rischio enorme di rearchitecting sulla base di un presupposto che potrebbe comportare un aumento delle prestazioni pari a zero. Quindi eseguilo anche attraverso qualcosa di semplice come GLIntercept e vedi dove sta andando il tuo tempo. Sulla base dei risultati, sarai in grado di affrontare il problema con alcune informazioni reali su quali sono / sono i tuoi principali colli di bottiglia.


Ho fatto un po 'di profilazione, anche se è imbarazzante perché i problemi di prestazioni non si verificano sulla stessa macchina dello sviluppo. Sono un po 'scettico sul fatto che il problema sia altro perché i problemi aumentano sicuramente con il numero di tessere e le tessere non fanno letteralmente altro che essere disegnate.
Nic,

Che ne dici di cambiare stato allora? Stai raggruppando le tue piastrelle opache per stato?
Maximus Minimus,

Questa è una possibilità. Questo merita sicuramente più attenzione da parte mia.
Nic,

2

Ok, da quando la mia ultima risposta mi è sfuggita di mano ecco una nuova che forse è più utile.


Informazioni sulle prestazioni 2D

Prima di tutto un consiglio generale: il 2D non è impegnativo per l'hardware attuale, anche il codice in gran parte non ottimizzato funzionerà. Ciò non significa che dovresti comunque utilizzare la Modalità Intermedia, almeno assicurati di non cambiare stato quando non necessario (ad esempio non associare una nuova trama con glBindTexture quando la stessa trama è già associata, un controllo if sulla CPU è di tonnellate più veloce di una chiamata glBindTexture) e non usare qualcosa di così totalmente sbagliato e stupido come glVertex (anche glDrawArrays sarà molto più veloce e non è più difficile da usare, non è però molto "moderno"). Con queste due regole molto semplici il tempo di frame dovrebbe essere almeno fino a 10ms (100 fps). Ora, per ottenere ancora più velocità, il prossimo passo logico è il raggruppamento, ad esempio raggruppando quante più chiamate di disegno in una, per questo dovresti prendere in considerazione l'implementazione di atlanti texture, in questo modo è possibile ridurre al minimo la quantità di rilegature di trama e quindi aumentare la quantità di rettangoli che è possibile disegnare con una chiamata su una grande quantità. Se ora non scendi a circa 2ms (500fps) stai facendo qualcosa di sbagliato :)


Mappe di tessere

L'implementazione del codice di disegno per le mappe di riquadri sta trovando l'equilibrio tra flessibilità e velocità. Puoi usare i VBO statici ma non funzionerà con i riquadri animati oppure puoi semplicemente generare i dati dei vertici in ogni frame e applicare le regole che ho spiegato sopra, è molto flessibile ma di gran lunga non così veloce.

Nella mia precedente risposta avevo introdotto un modello diverso in cui lo shader di frammenti si occupa dell'intera trama, ma è stato sottolineato che richiede una ricerca di trama dipendente e quindi potrebbe non essere veloce come gli altri metodi. (L'idea è fondamentalmente di caricare solo le indicazioni delle tessere e nello shader di frammenti di calcolare le coordinate della trama, il che significa che puoi disegnare l'intera mappa con un solo rettangolo)


sprites

Gli sprite richiedono molta flessibilità, il che rende molto difficile l'ottimizzazione, a parte quelli discussi nella sezione "Informazioni sulle prestazioni 2D". E a meno che tu non voglia diecimila sprite sullo schermo allo stesso tempo, probabilmente non ne vale la pena.


1
E anche se hai diecimila sprite, l'hardware moderno dovrebbe funzionare a una velocità decente :)
Marco,

@ API-Beast aspetta cosa? come si calcolano i Texture UV nello shader di frammenti? Non dovresti inviare i raggi UV allo shader di frammenti?
HgMerk,

0

Se tutti gli altri falliscono...

Imposta un metodo di disegno flip-flop. Aggiorna solo ogni altro sprite alla volta. Tuttavia, anche con VisualBasic6 e semplici metodi bit-blit, puoi disegnare attivamente migliaia di sprite per frame. Forse dovresti esaminare questi metodi, poiché il tuo metodo diretto di disegnare solo sprite sembra fallire. (Sembra più che tu stia usando un "metodo di rendering", ma provando a usarlo come un "metodo di gioco". Il rendering è sulla chiarezza, non sulla velocità.)

È probabile che ridisegni costantemente tutto lo schermo, ancora e ancora. Invece di ridisegnare solo le aree modificate. Questo è MOLTO sovraccarico. Il concetto è semplice, ma non facile da capire.

Utilizzare un buffer per lo sfondo statico vergine. Questo non viene mai reso se stesso, a meno che non ci siano sprite sullo schermo. Questo viene costantemente utilizzato per "ripristinare" dove è stato disegnato uno sprite, per annullare lo sprite nella chiamata successiva. È inoltre necessario un buffer per "disegnare", che non è lo schermo. Disegna lì, quindi, una volta tutto disegnato, lo capovolgi sullo schermo, una volta. Dovrebbe essere una videochiamata per tutti i tuoi sprite. (Al contrario di disegnare ogni sprite sullo schermo, uno alla volta, o tentare di farlo tutto in una volta, ciò farà fallire la tua fusione alfa.) Scrivere in memoria è veloce e non richiede tempo sullo schermo per "disegnare ". Ogni richiamo attende un segnale di ritorno prima di tentare nuovamente di disegnare. (Non una v-sync, un vero tick hardware, che è molto più lento del tempo di attesa che ha la RAM.)

Immagino che sia parte del motivo per cui vedi questo problema solo su un computer. Oppure, sta tornando al rendering software di ALPHA-BLEND, che tutte le carte non supportano. Verifichi se tale funzionalità è supportata dall'hardware prima di tentare di utilizzarla? Hai un fallback (modalità non-alpha-blend), se non ce l'hanno? Ovviamente, non hai un codice che limiti (numero di elementi miscelati), poiché presumo che degraderebbe il contenuto del tuo gioco. (A differenza del caso in cui si trattasse solo di effetti particellari, che sono tutti miscelati alfa, e quindi perché i programmatori li limitano, poiché sono altamente tassativi sulla maggior parte dei sistemi, anche con il supporto hardware.)

Infine, suggerirei di limitare ciò che stai mescolando alfa, solo alle cose che ne hanno bisogno. Se tutto è necessario ... Non hai altra scelta che richiedere ai tuoi utenti requisiti hardware migliori o degradare il gioco per le prestazioni desiderate.


-1

Crea un foglio sprite per gli oggetti e un set di tessere per il terreno come faresti in un altro gioco 2D, non è necessario cambiare trama.

Il rendering delle tessere può essere una seccatura perché ogni coppia di triangoli ha bisogno delle proprie coordinate di trama. C'è una soluzione a questo problema, tuttavia, si chiama rendering con istanza .

Finché puoi ordinare i tuoi dati in modo tale che, ad esempio, puoi avere un elenco di tessere erba e le loro posizioni, puoi eseguire il rendering di ogni tessera erba con una singola chiamata di disegno, tutto ciò che devi fare è fornire un array da modello a matrici mondiali per ogni piastrella. L'ordinamento dei dati in questo modo non dovrebbe essere un problema anche con il grafico della scena più semplice.


-1: Instancing è un'idea peggiore della soluzione di puro shader di Mr. Beast. L'istanza funziona meglio per le prestazioni durante il rendering di oggetti di moderata complessità (~ 100 triangoli o giù di lì). Ogni tessera triangolo che necessita di coordinate di trama non è un problema. Devi solo creare una mesh con un mucchio di quadranti sciolti che capita di formare una piastrella.
Nicol Bolas,

1
@NicolBolas va bene, lascerò la risposta per motivi di apprendimento
dreta,

1
Per chiarezza, Nicol Bolas, qual è il tuo suggerimento su come affrontare tutto questo? Il flusso di Marco disegna qualcosa? C'è un posto in cui posso vedere un'implementazione di questo?
Nic,

@Nic: lo streaming su oggetti buffer non è un codice particolarmente complesso. Ma davvero, se stai parlando solo di 50 dispetti, non è niente . Le probabilità sono buone che sia il tuo disegno del terreno a causare il problema delle prestazioni, quindi passare a buffer statici per quello sarebbe probabilmente abbastanza buono.
Nicol Bolas,

In realtà, se l'istanziamento funzionasse come potremmo pensare, sarebbe la soluzione migliore, ma dal momento che non lo fa, cuocere tutte le istanze in un singolo vbo statico è la strada da percorrere.
Jari Komppa,
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.