Dicevamo, la preparazione dei dati. Per poter utilizzare la medesima animazione su scheletri differenti ma derivati dal principale, in primo luogo il grafico ha preparato i lod inferiori skinnandoli solo su alcune bone dello scheletro principale.
Questo significa che ha potuto testare l'animazione direttamente nel software di modellazione, vedendo se la riduzione è stata efficiente.
In
OF:DR si è scelto di usare tre
LOD:
- LOD0: alto dettaglio, con 72 bone
- LOD1: medio dettaglio, comunque con 72 bone
- LOD2: basso dettaglio, con circa 40 bone
Ad esempio: se nel modello ad alto dettaglio tutte le dita sono animate indipendentemente, con falangi, falangine e falangette, nel modello a basso dettaglio tutta la mano sarà animata con solo due o tre bone.
Una volta che il grafico avrà preparato il modello, sarà necessario esportarlo e trattarlo, in modo che l'animazione sia utilizzabile su tutti i LOD, proprio come il grafico la vede in 3dsmax.
|
|
| Character Art by Toby Hynes |
La soluzione è la seguente: prendere le bones dei lod più bassi e metterle in cima alla lista.
In questo modo, quando passeremo al rendering, se vogliamo un’animazione ad alto dettaglio, passeremo il numero di bones completo, altrimenti passeremo un numero inferiore.
In questo modo potremo usare la medesima animazione nonostante sia su scheletri diversi.
Il sistema inoltre si prenderà cura di togliere le bone non pesate.
Quindi:
- stessa animazione
- risparmio di memoria (solo un buffer)
- animazione più leggera automaticamente
Vediamo come funziona il tool:
Supponiamo che queste siano le bones per il LOD2:
E che queste siano le bones per il LOD1:
Il tool rimescolerà le bones in modo che quelle del LOD2 siano in cima all'array:
In questo modo, se vogliamo utilizzare l'animazione in modo che funzioni solo LOD2, basta che prendiamo l'array, passiamo allo shader solo le prime cinque entry e indichiamo che lo scheletro ha cinque bones.
Questo serve anche a dividere in maniera completa la gestione delle animazioni tra gameplay e rendering: il gameplay creerà sempre le animazioni utilizzando il medesimo scheletro, mentre sarà il rendering a utilizzare solo la parte che gli interessa per il rendering, a seconda del LOD scelto per il rendering.
Nel corso dello sviluppo di
OF:DR, abbiamo deciso di utilizzare matrici 4x3 pre-trasposte per contenere le trasformazioni delle bones.
Questa scelta deriva dal fatto che l'utilizzo di matrici 4x4 era uno spreco di 4 float per ogni matrice, così si è deciso di risparmiare memoria. La memoria è preziosa come il platino (o il latinum, se siete fan di Star Trek) sulle console, quindi il suo utilizzo per cose non essenziali è da evitare nel modo più assoluto.
Utilizzare matrici pre-trasposte significa che le matrici vanno, appunto, pre-trasposte dal processore invece che lasciare il lavoro alla GPU.
Questo lavoro può essere compiuto facilmente da noi, utilizzando le istruzioni che lavorano sui vettori che ormai tutti i processori supportano, quindi nessun problema.
Utilizzando queste istruzioni è possibile avere una trasposizione in pochi colpi di clock.
vec4 *src = (vec_float4*)&source4x4Matrix;
vec4 *dest = (vec_float4*)&output4x3Matrix;
vec4 trnsp0, trnsp1, trnsp2, trnsp3;
trnsp0 = vec_mergeh( src[0], src[2] );
trnsp1 = vec_mergeh( src[1], src[3] );
trnsp2 = vec_mergel( src[0], src[2] );
trnsp3 = vec_mergel( src[1], src[3] );
dest[0] = vec_mergeh( trnsp0, trnsp1 );
dest[1] = vec_mergel( trnsp0, trnsp1 );
dest[2] = vec_mergeh( trnsp2, trnsp3 );
Ecco come funziona questo codice:
vec_mergeh:
Il disegno, la cui fonte è http://developer.apple.com/hardwaredrivers/ve/instructions/vec_mergeh.html, mostra come funziona l'istruzione di merge. L'hardware lavora con dei vettori da quattro float, quindi
vec_mergeh lavorerà sui byte più alti, mentre
vec_mergel lavorerà sui bassi.
Il nostro scopo è scambiare le righe con le colonne.
Ecco quello che succede:
Dopo le prime
vec_mergeh e
vec_mergel, le variabili
trnspN conterranno una miscela delle colonne pari e dispari. Nel momento in cui li riprenderemo con le successive merge, prenderemo gli elementi delle colonne e li rimetteremo in fila, ma come righe.
La cosa buona è che tutti questi lavori vengono fatti nei registri e a colpi di 4 per volta, quindi il tutto viene eseguito senza accedere continuamente alla memoria, come sarebbe successo se avessimo copiato gli elementi dei vettori in maniera classica.
Nella prossima puntata vedremo in che modo si sono preprocessati i dati per poter gestire i danni in maniera leggera, senza aggiungere ulteriori draw call al processo di rendering.