Monday, January 21, 2008

Buffer non troppo profondo


Appena finito di litigare con il coprocessore matematico si inizia subito col depth buffer.
Ovvero quello strumento della scheda video che permette alla medesima di decidere quali sono gli oggetti che stanno davanti, quelli che stanno dietro, e quindi quelli che vanno disegnati e quelli invece che non serve rappresentare visto che son comunque nascosti da qualcosa di più vicino.
Nell'ottica infatti di rendere le schede più veloci, si è deciso che è inutile disegnare un oggetto che poi verrà comunque nascosto da un altro oggetto più vicino.
Insomma se mi trovo dentro una stanza senza finestre, e fuori c'è un albero, non ha senso disegnare l'albero che tanto poi verrànascosto dalle mura della stanza.
Il problema è che il depth-buffer non è preciso al 100%, anzi, più gli oggetti sono lontani e più diventa impreciso. Figuriamoci con pianeti e stelle che son lontanissime.. ed infatti ecco rappresentato (in figura) cosa succede.
La domanda è: come risolvo?
L'idea che per prima mi viene in mente è quella di ridurre distanze e dimensioni. In fondo ho già una scala in double che mi rappresenza con precisione "al metro" posizioni e distanze, potrei provare a fare un cambio di coordinate in modo che i pianeti ad esempio che visualizzo siano 1000 volte piu' piccoli e mille volte piu' vicini, l'effetto sarebbe identico.
Quel che pero' mi spaventa è che se riduco le dimensioni, lo devo far per tutto, anche per gli oggetti piccoli, e non vorrei che la cosa finisse per crearmi dei problemi quando ho a che fare con piccole astronavi che volano vicino, che al momento del rendering magari appaiano sovrapposte o con altri problemi.
Altrimenti potrei provare a disegnare gli oggetti in ordine di posizione: prima i più lontani e poi quelli più vicini, perdendo in ottimizzazione, visto che di fatto non userei piu' il depth buffer, ma almeno risolvendo il problema.
Sennò... altre idee?

AGGIORNAMENTO 17.00 : ho probabilmente trovato l'escamotage per risolvere il problema una volta per tutte. Ma ora è tardi, ne parlo domani.

6 comments:

Anonymous said...

Primo, si disegna SEMPRE in ordine di posizione. Ok, c'e' il depth buffer, ma meno lo costringi a scriversi e meglio e'. L'algoritmo del pittore implementato su CPU e' quanto di piu' gratuito ci sia, sfruttalo :-D

Secondo, per la questione della scala...hai provato l'uso di una scala non lineare? (tanto chi vuoi che se ne accorga, a miliardi di chilometri di distanza, se c'e' un errore di un milione di km in piu' o in meno??)

PdG said...

Primo (chiedo): cosa intendi per gratuito? Comunque immagino di dover comunque eseguire un sort degli oggetti in base alla distanza, a meno che non mi sfugga qualcosa. Dici di non usare lo Z Buffer, beh, per disegnar delle mesh e' indispensabile, altrimenti dovrei sortare ogni singolo triangolo e la faccenda immagino diventi complessa. Tra l'altro e' possibile semplicemente disattivare lo z-buffer? Fin'ora ho visto che la scheda video si arrangia da sola, immagino lo debba esplicitamente escludere

Secondo: per la scala non lineare, ci avevo pensato, il problema sono comumque le distanze in parallasse e la mia pignoleria :), e comunque credo di aver risolto impostando di volta in volta i piani far e near di clipping in modo che avvolgano gli oggetti lontani, cosi' il depth buffer funziona all'interno delle mesh, evitando di farmi vedere porcherie negli oggetti concavi, e sarei cosi' costretto ad eseguire l'ordinamento solo per i macro oggetti e non per ogni singolo triangolo.

Anonymous said...

Non dico di non usare lo ZBuffer, assolutamente.
Dico solo che e' buona norma sfruttare tutto quello che hai, e la CPU e' li' che aspetta di essere sfruttata.

Ti faccio un esempio semplice: immagina che la tua scena sia composta da 1000 punti e che in un determinato momento appaiano lungo l'asse Z uno dietro l'altro. In pratica vedi solo il primo, quello piu' vicino, gli altri sono coperti.

Il problema sta nell'uso della memoria: ogni volta che fai il test sul depth buffer ne devi leggere un valore accedendo alla ram della scheda; se poi il test ti dice che il nuovo pixel e' "buono", accedi alla ram della scheda per scrivere la nuova Depth e il nuovo pixel.
Prova adesso ad immaginare il caso peggiore della situazione di sopra: per puro caso stai disegnando a partire dall'oggetto piu' lontano ed andando in ordine.
Il primo oggetto che consideri e' visibile, ovviamente, per cui effettui una scrittura sul depth buffer per aggiornarlo con la Z del nuovo pixel, ed una scrittura sul frame buffer per il pixel vero e proprio. Il secondo oggetto e' davanti al primo, per cui hai di nuovo da scrivere il valore Z del pixel nel depth buffer ed il pixel nel frame buffer. Il terzo pixel vince sul secondo, per cui altre due scritture. E cosi' via, con il risultato che per visualizzare 1 pixel hai effettuato 2000 scritture.

Ovviamente nel caso normale disegni in ordine casuale, per cui il problema e' meno evidenziato, ma se pensi a due pianeti in cui uno dei due e' completamente coperto dall'altro...se disegni prima quello dietro, hai effettuato migliaia di scritture in memoria per nulla.


Se invece disegni a partire dagli oggetti piu' vicini, continui si a fare i test sul depth, ma per la maggior parte si risolvera' in un nulla di fatto che non richiedera' scritture sul buffer.


Pensa al Sole che copre completamente la Luna; se la Luna occupasse 40.000 pixel (nemmeno troppo, se fai il conto) e disegnassi prima quella...avresti disegnato 40.000 pixel (magari applicandoci sopra anche un buon numero di istruzioni di shader) ed aggiornato il depth buffer 40.000 volte. Poi disegni il sole, questo copre tutto, ed ecco che le 80.000 scritture effettuate prima...non sono servite a nulla.


Chiaro, adesso?

PdG said...

Si tutto chiaro.
Meno chiaro come implementare la cosa via cpu.
Ovvero, se non uso il depth (come sto facendo per i pianeti), chiaramente tutto e' delegato alla cpu.
Ora, se devo implementare io la scrittura mi vien difficile pensare di disegnare diversamente che da lontano a vicino. E questo ovviamente e' poco ottimizzato quando vi son oggetti sovrapposti, disegno quello piu' lontano e poi lo cancello con quello piu' vicino.
Anche se va detto che comunque nello spazio e' poco probabile l'eclissi.

Anonymous said...

Non riesco a spiegarmi, in effetti non e' argomento di due righe.

Cmq, i calcoli sulla CPU si cerca di farne il meno possibile, perlomeno quando la GPU puo' farli al posto tuo.


Non devi trasformare gli oggetti sulla CPU: semplicemente prendi il bounding-box di ogni oggetto, lo trasformi per tutte le matrici che ti interessano (tipo, il pianeta si e' mosso? matrice di traslazione + world matrix), poi fai un sort in ordine dal piu' vicino al piu' lontano, poi fai le normali chiamate di rendering.

O lo fai semplicemente sul punto mediano di ogni oggetto.

O sulla bounding sphere, che e' generalmente piu' precisa.


In ogni caso sono pochi nanosecondi di calcolo sulla CPU che ti portano enormi vantaggi sulla GPU.

PdG said...

Abbi pazienza, sono agli inizi e alcune cose mi sono poco chiare.

Attualmente faccio come dici tu, ovvero prendo i pianeti, ne valuto le distanze da camera al centro, ordino in base alla distanza e infine renderizzo.
La differenza e' che io eseguo il rendering in ordine inverso dal tuo, ovvero prima il piu' lontano, poi il piu' vicino. Diversamente dovrei usare lo z-buffer, ma in tal caso il sort sarebbe inutile e comunque avrei i problemi di cui ho discusso in precedenza.
Niente z-buffer, tempo risparmiato, ma ovviamente la gpu si ritrova a disegnare (a volte) pezzi che poi lei stessa cancella con oggetti piu' vicini. Se partissi dal piu' vicino, come dici tu, la cpu si troverebbe costretta a valutare per ogni mesh quale parte e' visibile, e quale no, cosa impensabile, credo che il tempo che sprecherebbe sarebbe persino maggiore del tempo che (raramente) perde la gpu nel dover disegnare cose inutilmente.

Ovviamente dico "credo" perche', insisto, sono un novellino.