Lavorare con molti cubi. Migliorare le prestazioni?


12

Modifica: per riassumere la domanda, ho un mondo basato su voxel (stile Minecraft (Grazie all'anatra comunista)) che soffre di scarse prestazioni. Non sono positivo sulla fonte, ma vorrei eventuali consigli su come sbarazzarsene.

Sto lavorando a un progetto in cui un mondo è costituito da una grande quantità di cubi (ti darei un numero, ma sono mondi definiti dall'utente). Il mio test è di circa (48 x 32 x 48) blocchi.

Fondamentalmente questi blocchi non fanno nulla da soli. Si siedono lì.

Iniziano ad essere utilizzati quando si tratta di interazione con il giocatore.

Devo controllare con quali cubi interagisce il mouse dell'utente (passaggio del mouse, clic, ecc.) E per rilevare la collisione mentre il giocatore si muove.

Ora ho avuto un enorme ritardo all'inizio, ripetendo ciclicamente ogni blocco.

Sono riuscito a ridurre quel ritardo, eseguendo il ciclo attraverso tutti i blocchi e trovando quali blocchi si trovano all'interno di un determinato intervallo del personaggio, quindi eseguendo il ciclo solo attraverso quei blocchi per il rilevamento delle collisioni, ecc.

Tuttavia, sto ancora andando a 2fps deprimenti.

Qualcuno ha altre idee su come posso ridurre questo ritardo?

A proposito, sto usando XNA (C #) e sì, è 3d.


Intendi come Minecraft? Cioè, voxel?
L'anatra comunista

4
Hai guardato in ocre? en.wikipedia.org/wiki/Octree
bummzack

1
Hai provato a profilare il tuo gioco? Potrebbe mostrare alcune aree chiave in cui si trascorre la maggior parte del tempo. Potrebbe non essere quello che pensi che sia.
deceleratedcaviar

2
Invece di disegnare tutte e 6 le facce di ciascun cubo, puoi solo disegnare le facce che non sono in contatto con nulla
David Ashmore,

1
@David: Sì, o potrebbe smettere di fare prima una singola chiamata di estrazione per cubo e poi preoccuparsi dei singoli poligoni in seguito.
Olhovsky,

Risposte:


20

Sembra che tu stia cercando di conoscere gli alberi!

E sto seriamente, se al momento stai eseguendo il loop su una matrice di tutti i tuoi cubi, allora dovresti davvero esaminare varie strutture di dati spaziali. In questo caso, il modo migliore per reinventare il tuo mondo cubo è come un albero.

Prima di addentrarci nei motivi del perché, pensiamo al nostro problema. Stiamo cercando una soluzione in cui, per il minor costo possibile, possiamo recuperare un elenco di cubi vicini con cui il giocatore potrebbe scontrarsi. Questo elenco dovrebbe essere il più piccolo, ma preciso possibile.

Ora per determinare questa zona, dobbiamo mappare lo spazio delle coordinate del nostro giocatore allo spazio delle coordinate della mappa del cubo; vale a dire, dobbiamo mappare la posizione in virgola mobile del lettore su un indice discreto della matrice multidimensionale di cubi (potrebbe essere la notazione di esempio world[31][31][31], ovvero il centro esatto per una matrice multidimensionale 64 * 64 * 64).

Potremmo semplicemente calcolare i blocchi circostanti utilizzando questa stessa indicizzazione discreta, magari campionando solo i cubi vicini, ma ciò richiede comunque un ricalcolo costante e non consente alcun oggetto che non sia discreto nel posizionamento (cioè potrebbe non essere mappato al cubo carta geografica).

La situazione ideale è un insieme di secchi che contengono i nostri gruppi di cubi per sezioni particolari della nostra mappa dei cubi, divisi equamente in modo che, invece di ricalcolare l'area circostante, ci muoviamo semplicemente dentro e fuori da queste zone . Per qualsiasi calcolo non banale, conservare i nostri dati in questo modo potrebbe eliminare l'iterazione di tutti i cubi e solo questi singoli set che si trovano nelle vicinanze.

La domanda è: come lo implementiamo?

Per il mondo 64 * 64 * 64, immagina che sia suddiviso in 8 * 8 * 8 zone . Ciò significa che nel tuo mondo avrai 8 zone per asse (X, Y, Z). Ognuna di queste zone conterrà 8 cubi, facilmente recuperabili da questo nuovo indice semplificato.

Se avessi bisogno di eseguire un'operazione su una serie di cubi vicini, invece di iterare ogni cubo nel tuo mondo, potresti semplicemente iterare su queste zone , abbattendo il numero massimo di iterazioni dall'originale 64 * 64 * 64 (262144) a solo 520 (8 * 8 * 8 + 8).

Ora diminuire da questo mondo di zone, e posizionare le zone in grandi super-zone ; in cui ogni superzona contiene 2 * 2 * 2 zone regolari . Mentre il vostro mondo attualmente contiene 512 (8 * 8 * 8) zone , siamo in grado di rompere i * 8 * 8 8 zone in 64 (4 * 4 * 4) super-zone dividendo 8 zone da 2 zone per super-zona . Applicando la stessa logica dall'alto, ciò interromperebbe le iterazioni massime da 512 a 8 per trovare la super-zona ; e quindi un massimo di 64 per trovare la zona di avanzamento(totale massimo 72)! Puoi vedere come questo ti sta già salvando molte iterazioni (262144: 72).

Sono sicuro che ora puoi vedere quanto sono utili gli alberi. Ogni zona è un ramo sull'albero, con ogni superzona come ramo precedente. Stai semplicemente attraversando l'albero per trovare ciò di cui hai bisogno; utilizzando set di dati più piccoli per ridurre al minimo i costi complessivi.

Lo schema seguente dovrebbe aiutarti a visualizzare il concetto. (immagine tratta da Wikipedia: Octrees ): octree

Disclaimer:

In una configurazione ideale come sopra, in cui il tuo mondo voxel è già strutturato in un array multidimensionale di dimensioni fisse, potresti semplicemente interrogare la posizione del giocatore, quindi indicizzare i blocchi circostanti con un costo O (1)! (Vedi la spiegazione di Olhovskys) Ma questo diventa più difficile quando inizi a considerare che il tuo mondo ha raramente dimensioni fisse in un gioco voxel; e potrebbe essere necessario che la struttura dei dati sia in grado di caricare intere superzone dall'HDD alla memoria. A differenza di un array multidimensionale a dimensione fissa, gli alberi lo consentono prontamente senza troppo tempo impiegato in algoritmi combinatori.


Direi di averlo capito, ma sfortunatamente no. Le scatole di collisione dei blocchi non si muovono, solo la scatola di collisione del giocatore. E ora ho un metodo che (senza scorrere tutti i blocchi) restituisce tutti i blocchi che si trovano entro un raggio di 5 blocchi dal giocatore. Ci scusiamo per la seccatura, ma qualche chiarimento? A proposito, per renderlo più semplice potresti supporre che il mondo sia 64 x 64 x 64?
Gioele,

E sto ancora aggirando i 5fps :(
Joel

Ho riformulato la mia risposta, fammi sapere se mi ha aiutato.
deceleratedcaviar

Quindi restringo i blocchi in cui il giocatore potrebbe essere, usando questa tecnica di ottetto. Sono abbastanza sicuro di averlo capito. Ma stai suggerendo di usare il mio rilevamento delle collisioni dopo averlo ristretto a una piccola selezione di blocchi o hai qualche altro metodo?
Gioele,

2
A meno che il giocatore non sia molto grande rispetto alla dimensione dei cubi, probabilmente controllare la collisione attorno al giocatore è il modo più veloce. Se il giocatore non occupa più spazio di un cubo per esempio, allora deve controllare al massimo 27 cubi circostanti, per trovare una collisione. Ciò non richiede un albero in quanto è possibile indicizzare direttamente in quelle posizioni dei cubi, supponendo che memorizzi i cubi in un array in cui può indicizzare, con uno slot allocato per ogni possibile posizione del cubo.
Olhovsky,

13

Sono d'accordo con la risposta di Daniels, in quanto l'iterazione di grandi quantità di riquadri è la causa più probabile e l'utilizzo del partizionamento spaziale potrebbe velocizzare molto il gioco, ma il problema potrebbe anche essere altrove e si potrebbe perdere tempo .

Per aumentare significativamente la velocità del tuo gioco devi profilare il tuo codice. Identifica dove si trova il collo di bottiglia, questo ti consentirà di apportare i maggiori miglioramenti.

Esistono molti modi per profilare il codice, è possibile eseguire il rollup della propria classe di analisi delle prestazioni (che potrebbe utilizzare la classe Stopwatch (MSDN) ) o utilizzare PIX per avere un'idea generale di quanto sia impegnata la CPU / GPU .

È inoltre possibile inserire marcatori di eventi PIX nel codice, che verranno visualizzati come aree colorate nelle letture di PIX. Non esiste un'interfaccia C # ufficiale per queste funzioni, ma questa discussione mostra come puoi creare un'interfaccia C # da solo.


2
+1, sono totalmente d'accordo. Non dovresti cercare algoritmi, dovresti cercare di profilare il tuo codice. La mia ipotesi è che non ha nulla a che fare con il fatto che stai iterando attraverso i blocchi (non sono così tanti), è quello che stai facendo con ogni blocco. Alla fine sì, devi avere un metodo migliore della forza bruta, ma se non riesci a gestire una semplice iterazione su cubi 48x32x48, devi ripensare a ciò che stai facendo con ciascun cubo, non al modo in cui esegui il loop.
Tim Holt,

4
@Tim: A meno che il suo giocatore non sia abbastanza grande da occupare uno spazio di 48x32x48, allora non dovrebbe scorrere da nessuna parte vicino a tanti cubi. Se sta ripetendo 73000 cubi per fotogramma, allora posso dirti senza fare alcun profiling, che vale la pena per lui sistemarlo, se non altro per imparare a evitare di fare decine di migliaia di volte più iterazioni di sono necessari. Questo non è ciò che definirei ottimizzazione micro o prematura.
Olhovsky,

Il mio giocatore è inferiore alla dimensione di 1 cubo, anche se potrebbe essere più grande in qualche momento (ma non molto)
Gioele

Randomman159: Quindi devi solo provare contro i suoi 27 cubi circostanti per trovare la collisione. Vedi la mia risposta
Olhovsky,

6

Se il tuo giocatore è grande rispetto alla dimensione dei cubi, probabilmente vorrai un octree o un'altra struttura di partizione spaziale, come altri hanno suggerito.

Tuttavia, se il tuo giocatore è piccolo rispetto alla dimensione dei cubi, probabilmente il modo più veloce per rilevare la collisione con i cubi è fare una semplice ricerca lineare dell'area intorno al giocatore.

Poiché il tuo giocatore è più piccolo di 1 cubo, devi solo testare la collisione contro i 27 cubi vicini, al massimo.

Ciò presuppone che i cubi vengano archiviati in un array in cui è possibile indicizzare, con uno slot nell'array per ogni cubo.

Come altri hanno sottolineato, devi profilare il tuo codice per vedere cosa ti sta effettivamente rallentando.

Se dovessi indovinare, direi che probabilmente stai facendo un richiamo per ogni cubo, che sarebbe di gran lunga il tuo collo di bottiglia. Per risolvere ciò, dovresti esaminare l'istanziazione della geometria.


Oppure potresti avere un 'riquadro di delimitazione' che circonda il giocatore, e poi controlli semplicemente con quali oggetti si scontrano per determinare con quali oggetti il ​​giocatore dovrebbe scontrarsi. Un buon motore fisico sarà in grado di fare tutte le ottimizzazioni per te; consentirà inoltre che più di semplici "blocchi" si scontrino.
deceleratedcaviar

Personalmente, non vorrei fare affidamento sulla fase larga di un motore fisico per testare contro 73000 cubi per me, quando potrei semplicemente scrivere due dozzine di righe di codice per testare efficacemente la collisione per me. Inoltre, probabilmente non ha un motore fisico da attingere in questo momento.
Olhovsky,

1

Un altro suggerimento per accelerare le cose: i tuoi blocchi sono approssimativamente fissi - ciò significa che non c'è modo in cui un giocatore possa scontrarsi con la maggior parte di essi. Aggiungi un booleano ai blocchi che indicano se sono esposti o meno. (Questo può essere ricalcolato guardando i loro vicini.) Un blocco che non è esposto non deve essere verificato per le collisioni.

È ovvio che Minecraft fa qualcosa di simile a questo - ho colpito un pezzo non caricato una volta che mi ha dato una visione del mondo - Ho potuto vedere attraverso il terreno solido, tutto ciò che è apparso erano gli spazi aperti (il lato più lontano di erano una superficie esposta e quindi resa.)


-1

Ho avuto quel problema con il mio motore voxel.

Soluzione: (molto più semplice delle ocre) Invece di passare in rassegna tutti i blocchi, basta usare un'equazione per determinare la posizione del blocco nella matrice dei blocchi.

BlockIndex = (x * WorldWidth * WorldHeight) + (z * WorldHeight) + y;

Quindi se vuoi vedere se esiste un blocco:

Blocks[BlockIndex].Type > -1;

O comunque si determina se il blocco esiste.


Il problema qui è più complicato, perché hai solo 2 coordinate per il tuo mouse, da testare con il mondo 3D. Se la vista era dall'alto verso il basso, si potevano trovare 2 su 3 indici a destra per una posizione del mouse, quindi scorrere per l'altezza, iniziando dall'alto - più vicino alla telecamera e trovare la prima occorrenza di un blocco.
Markus von Broady,
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.