Usare i vertex shader non è facile ma ancor meno lo è scrivere dei codici assembler. La cosa è senza dubbio interessante per la possibilità di creare effetti come ad esempio il Cell shading. A parte la spettacolarità degli effetti i vertex shader permettono di anche di generare dei codici utili per ogni occasione. Potete infatti inserire il codice per il tweening (una tecnica per l’animazione dei poligoni) all’interno del codice assembler con un guadagno enorme sulla velocità. Tornando al disegno pubblicato nel precedente tutorial osserviamo più attentamente la struttura ed i registri.
Ognuna delle zone di memoria viene chiamata registro ed è composto da 4 valori di tipo single. Per questo è necessario passare un array di 4 single o un vector4. Nel precedente tutorial abbiamo anche visto che si può passare una matrice in modo che riempia 4 di queste zone. Ora dobbiamo solo capire come scrivere i codici da inserire nei file di testo.
CODICE
Basi
Ogni file deve iniziare con la dichiarazione della versione in quanto ne esistono di diverse (una versione più avanzata contiene magari più registri ma è meno compatibile con le vecchie schede)
vs.1.1; la più recente
vs.1.0; più vecchia ma più compatibile
Nella dichiarazione della versione ho già usato la struttura per i commenti che è il ";" (punto e virgola) tutto ciò che si trova dopo il ; fino alla fine della riga è commento.
Con l’uscita di DirectX9 esistono tuttavia nuovi formati come il 2.0, 3.0 che permettono addirittura cicli. Tuttavia nessuna scheda realizzata prima del 2003 è compatibile con questo formato.
Registri
I registri sono usati per i calcoli matematici e ne esistono di 4 tipi (ne esiste un quinto ma andiamo con ordine). I 4 tipi di registri sono:
V :i registri che contengono i dati dei vertici in base alla dichiarazione fatta nel codice VB (ad esempio V0 contiene la posizione dei vertici mentre V3 le normali). Vanno da V0 a V16 e non ci si può scrivere sopra
R :i registri temporali che possono essere usati per contenere i dati sia in lettura che scrittura (i più comuni) e vanno da 0 a 15.
C :sono i registri che si passano da visual basic, non ci si può scrivere sopra e vanno da 0 a 96
O: sono i registri che contengono i dati che finiranno nella scheda e saranno usati per rappresentare il vertice. Ce ne sono di diversi tipi e sono solo in scrittura:
oD0 ed oD1 contengono il colore del vertice(diffuse e specular)
oPos contiene la posizione del vertice
oPts contiene la grandezza del punto (per i sistemi a particelle)
oT0 a oT8 contengono i valori per le texture
oFog contiene il valore della nebbia
I registri hanno 4 componenti identificati con le lettere x,y,z,w. Ad esempio C1.x indica la componente x del registro C1 ma si può anche usare più componenti contemporaneamente come ad esempio C1.xy. Per indicare però ad esempio C1.xyzw si può scrivere semplicemente C1. Si può infine porre avanti al registro un meno (-) per decidere di usare l'opposto del registro (-V1)
C Register
I registri C contengono i valori che vengono passati da Visual Basic allo shader. Questi rappresentano l'unico modo di interagire con il codice. Matrici e colori che cambiano continuamente vanno ad esempio passate tramite i registri c.
I registri si possono scrivere in 2 modi:
c1,c2.....c96
oppure
c[1], c[2],...c[96]
INPUT REGISTER
I vettori V non sono fissi ma dipendono dalla dichiarazione. Questa è l’unica differenza tra DirectX8 e 9. Infatti qui siete costretti a dichiarare i vertici tramite l’istruzione
dcl_Nome della caratteristica Vn
Ad esempio qui dichiarerò che la posizione andrà in V0
dcl_position v0
Vi mostro la tabella delle dichiarazioni. Il numero del registro V indica una convenzione usata da DirectX per i valori (che non dovete obbligatoriamente rispettare anzi non lo fa quasi nessuno!!!).
dcl_position v0 |
Posizione del vertice così come è memorizzato nella mesh o nell’array di vertici |
dcl_blendweight v1 |
Pesi per il blending, usati per il geometry blending |
dcl_blendindices v2 |
Indici di blending usati per lo skinning |
dcl_normal v3 |
Normali |
dcl_psize v4 |
Dimensioni dei punti, usati per i sistemi a particelle |
dcl_texcoordn v5 |
Coordinate texture, la n va sostituita con il numero della texture (da 0 a 7) |
dcl_tangent v6 |
Vettore tangente del vertice |
dcl_binormal v7 |
Binormale al vertice |
dcl_tessfactor v8 |
Usato per la tesselazione |
dcl_positiont v9 |
Posizione trasformata (per I formati con texture trasformata) |
dcl_color v10 |
Colore del vertice |
dcl_fog v11 |
Nebbia |
dcl_depth v12 |
Profondità |
dcl_sample v13 |
Sample data |
Questa tabella mostra il tipo di dato da usare in base al formato del vertice (per la dichiarazione del vertexDeclaration).
Ad esempio per la posizione viene indicato il float3 e cioè
New VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0)
Spero sia chiaro.
OUTPUT REGISTER
Posizioni, texture e colore sono le più semplici da usare e per quello è sufficiente passare i valori ai relativi registri. Se volete usare però il fog (la nebbia per i profani) e il oPts (la grandezza dei punti) dovrete attivare tramite device la nebbia e il pointsprite (per i sistemi a particelle). Ma i calcoli per i vari valori dipendono da voi.
Istruzioni
Dimenticativi quelle belle istruzioni del tipo A=B+C. Se conoscete l'assembler capirete che non è così. Il codice che si deve sviluppare deve avere questa struttura
vs.1.0 ; versione
dcl_position v0
dcl_normal v3
dcl_texcoord0 v7
dp4 r0.x, v0, c0; istruzioni che usando i registri V e C(solo lettura) e R (scrittura e lettura)
dp4 r0.y, v0, c1; modificano i valori V dei vertici in base ai valori di C
dp4 r0.z, v0, c2
dp4 r0.w, v0, c3
dp3 r1.x, v3, c20
max r1, r1.x, c4.x
mul r0, r1.x, c21
add r0, r0, c22
min oD0, r0, c5.x; e le passano ai registri di output (oD0 e oPos in questo caso)
mov oPos, r0
Non ci sono ne numeri ne operatori. Se volete usare dei valori numerici (potreste volere moltiplicare per 3 un valore ad esempio) dovete passarli tramite i registri C (se leggete gli shader fatti da terzi si trovano nei commenti cosa devono contenere tali registri). Ora passiamo alle istruzioni:
MOV
Copia il secondo registro nel primo
(esempio Mov oPos, r0 copia r0 in oPos)
ADD , MUL, SUB
somma, moltiplica, sottrae due valori trasferendo il risultato nel registro in prima posizione
(esempio Add r0, r1 ,v0 somma tutto il vettore r1 e v0 trasferendo tutto in r0
(esempio 2 Mul r0.x, r1.x, v0.x la moltiplicazione viene eseguita solo sulla X)
EXPP, LOGP
esponenzione in base 2, logaritmo in base 2
(esempio Expp r0, v1 trasferisce in r0 il risultato di 2 elevato a V1)
MAD
trasferisce nel primo registro il prodotto di del secondo e terzo registro e ci somma il quarto
(esempio Mad r0, r1, r2, r3 equivale a r0=r1 x r2 + r3)
MAX, MIN
trasferisce nel primo registro il massimo (o il minimo) tra gli altri registri
(esempio Max r0, v1, v2)
DP4
Usato per il prodotto vettore x matrice
dp4 r0.x, v0, c0
dp4 r0.y, v0, c1
dp4 r0.z, v0, c2
dp4 r0.w, v0, c3
in questo esempio il registro V0 che contiene la posizione viene moltiplicato in Dot product per i registri da C0 a C3. Se inserite una matrice in C0 e se questa contiene la trasposta delle matrici world x view x proj allora eseguirà lo spostamento.
DP3
Simile a sopra ma solo su una matrice da 3
SGE,SLT
Se il secondo registro è maggiore o uguale al terzo trasferisce nel primo il valore 1, 0 altrimenti. Per Slt vale il minore-uguale.
Sge r0, r1, r2
MACRO
Le macro sono istruzioni che ne contengono in realtà più di una.
M4x4, M4x3, M3x3, M3x4, M3x2
eseguono i Dot product in maniera compatta
(esempio m4x4 oPos , v0 , c0 equivale all'esempio del DP4, in questo caso vengono letti anche i registri C1, C2, C3)
EXP, LOG
Uguali a EXPP e LOGP ma con maggiore precisione
FRC
trasferisce nel primo la parte frazionaria del secondo
(Esempio Frc r0, v1)
Queste sono le istruzioni da utilizzare per scrivere i codici shader. Ognuna di queste costa un ciclo al processore tranne le macro che necessitano di più cicli. L'unica cosa da dover capire è il dot product (come la M4x4) che se fatto su normali e posizioni possono essere trasformate con la matrice trasposta del prodotto di world x view x proj. In questo modo eseguirete il movimento corretto dell'oggetto. Ecco un esempio di codice
vs.1.1
dcl_position v0
dcl_normal v3
dcl_texcoord0 v7
dp4 r0.x, v0, c0
dp4 r0.y, v0, c1
dp4 r0.z, v0, c2
dp4 r0.w, v0, c3
mov oD0, c4 ; colore
mov oPos, r0 ; posizione
Si passa la matrice a C0, C1, C2, C3 e il colore (tramite array di single del tipo rosso, verde, blu, alfa). In questo esempio gli oggetti vengono rappresentati con luce costante.
A register
Questo registro funziona solo per la versione 1.1. Il suo unico scopo è quello di servire da indice per il registro C (come fosse matrice).
c[a.x] (potete usare solo la X del registro a).
In questo modo potete utilizzare un sistema di indirizzamento.
Il registro a si può adoperare come destinazione solo con l'istruzione mov
mov a,r0
per trasferire il valore di r0.x in a.x.
Potete usare anche un numero intero
c[a.x + 3]
In questo modo potete scegliere il registro successivo a quello specificato da a (ovviamente regolatevi con il fatto che i registri c sono solo 96.
Consigli
La cosa che più comunemente si adopera in uno shader è la trasformazioni dei vertici in base alle matrici. Passate tramite registro la matrice trasposta della matrice prodotto di world x view x projection (ad esempio in c0..c3).
dp4 r0.x, v0, c0
dp4 r0.y, v0, c1
dp4 r0.z, v0, c2
dp4 r0.w, v0, c3
Ora r0 contiene la posizione finale del vertice da muovere i oPos
Potete farlo in modo più compatto
m4x4 oPos , v0 , c0
Le texture in genere vengono rimandate ai registri
mov oT0,v7
Per luci dovete elaborare con calcoli quello che volete in modo da generare il colore da passare all'oD0.
Conclusione
Ho cercato di riassumere in questa pagina la maggior parte della guida in linea della microsoft sull'assembler dei vertex shader. Per approfondimenti o vi scaricate la SDK dalla microsoft o vi cercate tutorial dell'argomento sulla rete (alla data odierna ne ho trovati solo sui siti di C++ ma tanto il linguaggio è comune tra i due). Ricordate infine che il numero massimo di istruzioni sono 128 e quindi dovrete rientrare in questo limite (non credo che ne esistano di così grandi). Vi rimando ai prossimi tutorial su effetti di vertex shader.