\\ Home : Articoli : Stampa
Vertex Shader Assembler 2.0
Di RobyDx (del 13/02/2007 @ 09:22:45, in DirectX9, linkato 1712 volte)

La grande novità di DirectX9 è il perfezionarsi della tecnologia degli shader che diventano sempre più importanti per il rendering di effetti nuovi ed innovativi. I vertex shader arrivano fino alla versione 3.0. In questo tutorial introdurrò il funzionamento della versione 2.0 dei vertex shader che portano un gran numero di funzionalità. Oltre al maggior numero di istruzioni la nuova versione include nuovi registri, l'introduzioni di cicli di controllo e soprattutto la possibilità di creare subroutine all'interno del codice shader. Ora i vertex shader possono realizzare davvero l'impossibile permettendo ad esempio di gestire con un solo shader più effetti tramite l'utilizzo di istruzioni if then else o di ripetere una stessa funzione un numero qualsiasi di volte tramite il loop. L'esempio riportato in fondo al tutorial mostra come sia possibile tramite vertex shader utilizzare un numero qualsiasi di luci puntiformi in una scena, ben oltre le otto ottenibili con le schede più recenti. Il tutorial richiede la conoscenza minima della versione 1.0 e 1.1 dei vertex shader.

Registri

I registri sono le variabili usate dal processore grafico per i propri calcoli. Possono essere in lettura, scrittura o sia in entrambi. Ognuno è composto da 4 componenti: XYZW indicabili anche come RGBA. Potete usare anche una sola parte del registro es c0.w, r0.xy etc. Alcune volte sarà obbligatorio.

Input register

Sono i registri che si usano nel codice per leggere i dati dal codice, dai modelli o utilizzabili come variabili.

Simbolo

Nome

Numero

Read/Write

Descrizione

v#

input

16

R

Prendono le informazioni direttamente dal modello (posizioni, normali, coordinate texture etc). Vanno dichiarate

r#

temporary

12

R/W

Usate come variabili per scrivere e leggere valori in single

c#

costant

variabile

R

Vengono passate da codice (VB,C etc) o definite nel codice shader. Sono in single. Si passano tramite istruzione setVertexShaderConstant passandogli array di single, vector4 o matrici

a0

address

1

R/W

Può essere usato per trattare come un array le costanti. Vedi sezione apposita. Viene usato solo un componente

b#

boolean

16

R

Vengono passate da codice (VB,C etc) o definite nel codice shader. Sono in boolean. Si passano tramite istruzione setVertexShaderConstant passandogli array di booleani

i#

integer

16

R

Vengono passate da codice (VB,C etc) o definite nel codice shader. Sono in interi.  Si passano tramite istruzione setVertexShaderConstant passandogli array di interi.

aL

loop counter

1

R

Viene utilizzato in sola lettura per i cicli loop. Viene usato solo un componente

Output register

I registri di output vengono riempiti per assegnare i valori finale dei vertici da renderizzare. Il registro Position è obbligatorio (la posizione del vertice).

Simbolo

Nome

Numero

Read/Write

Descrizione

oPos

Position

1

W

Posizione finale del vertice sullo schermo

oFog

Fog

1

W

Il suo valore di nebbia. Viene usato solo un componente

oPts

Point size

1

W

Dimensione del vertice per i point sprite. Viene usato solo un componente

oD#

Color

2

W

Colore del vertice, oD0 è il diffuse, oD1 lo specular

oT#

Texture coordinate

8

W

Coordinate texture del vertice.

Questa tabella mostra come creare il vertexDeclaration

 

Registro a0

Il registro a0 è usato come indirizzo. Ad esempio usare c[a0.x] equivale a c10 se a0.x=10. Risulta quindi utile per scorrere i registri come array. Anche il registro aL può essere usato nello stesso modo ma solo all'interno di un ciclo.

Istruzioni standard

Le istruzioni sono notevolmente aumentate, quindi le illustrerò in breve istruzione esempio Descrizione abs abs r0,r1 Restituisce il valore assoluto add add r0,r1,r2 somma 2 registri crs crs r0,r1,r2 esegue un crossproduct dcl_usage dcl_position v0 dichiara un input register def def c0,1,2,3,0.5 dichiara un registro costante di tipo single nel codice shaker defb defb b0,true dichiara un registro costante di tipo boolean nel codice shaker defi defi i0,1,2,3,0.5 dichiara un registro costante di tipo integer nel codice shaker dp3 dp3 r0,r1,r2 esegue un dotProduct3 dp4 dp4 r0,r1,r2 esegue un dotProduct4 dst dst r0,r1,r2 il primo registro deve essere (ignored, d*d, d*d, ignored) il secondo(ignored, 1/d, ignored, 1/d). ed il risultato sarà vector (1, d, d*d, 1/d). exp exp r0,r1.x esegue 2 elevato a r1 con piena precisione expp expp r0,r1.x esegue 2 elevato a r1 con precisione parziale frc frc r0,r1 Restituisce la parte fratta lit lit r0,r1 effettua un parziale calcolo della potenza della luce src.x = N*L src.y = N*H src.z = ignored src.w = exponent ; tra -128.0 e 128.0 log log r0,r1 Logaritmo in base 2 di r1 logp logp r0,r1 Logaritmo in base 2 di r1 con precisione parziale lrp lrp r0,r1,r2,r3 fa un interpolazione tra r1 e r2 usando r3 m3x2 m3x2 r0,r1,c0 Moltiplica il src per la matrice che inizia da c0 m3x3 m3x3 r0,r1,c0 Moltiplica il src per la matrice che inizia da c0 m3x4 m3x4 r0,r1,c0 Moltiplica il src per la matrice che inizia da c0 m4x3 m4x3 r0,r1,c0 Moltiplica il src per la matrice che inizia da c0 m4x4 m4x4 r0,r1,c0 Moltiplica il src per la matrice che inizia da c0 mad mad r0,r1,r2,r3 esegue r1 x r2 + r3 max max r0,r1,r2 per ogni componente restituisce il maggiore tra r1 e r2 min min r0,r1, r2 per ogni componente restituisce il minimo tra r1 e r2 mov mov r0,r1 copia un vettore in un altro mova mova a0,r0.x copia un valore in un registro address mul mul r0,r1,r2 esegue un prodotto fra vettori nop nop non esegue nulla nrm nrm r0,r1 normalizza un vettore pow pow r0,r1,r2 Restituisce r1 elevato a r2 rcp rcp r0,r1.x Restituisce il reciproco rsq rsq r0,r1.x Restituisce la radice del reciproco sge sge r0,r1,r2 se r1>r2 restituisce 1 altrimenti 0 sgn sgn r0,r1,r2,r3 se il primo registro è positivo restituisce 1 altrimenti -1. I rimanenti registri sono usati per i conti intermedi sincos sin r0,r1.x,c0,c1 calcola sin e cos di r1. I registri costanti devono essere riempiti per i calcoli c0 -1.55, -2.170, 0.0026, 0.00026 c1 -0.020833334f, -0.12500000f, 1.0f, 0.50000000f la destinazione conterrà il sin in X e cos in Y slt slt r0,r1,r2 se r1Cicli loop

I cicli loop sono dei cicli identici ai cicli For che permetteno di eseguire più volte blocchi di istruzioni. loop aL,i0 istruzioni endloop il registro costante intero deve contenere in X il numero di cicli, in Y il valore iniziale, in Z lo step e W=0. Il registro aL è utilizzabile come address register.

Cicli rep

rep i0 istruzioni endrep Simile al loop ma più semplice. Solo la X del registro i# viene usata come contatore

If else endif

if b0 istruzioni else istruzioni endif usa il registro b0 per decidere quale blocco eseguire. Nota bene, nella versione 2.0 i booleani possono essere solo passati come costanti e quindi passate da codice. Per poter generare booleani è necessario usare la versione vertex shader 2_x o 3 che hanno istruzioni per gestire i booleani.

Subroutine

Per chi conosce il normale assembler non è un problema capire il funzionamento delle subroutine. Ecco un codice con subroutine vs.2.0 dichiarazioni istruzioni call l0 ret label l0 istruzioni ret il primo blocco è il main del codice mentre il secondo è la subroutine. La label l0 è qualcosa di simile al processo GoTo. L'istruzione call l0 fa spostare l'esecuzione del codice alla label omonima e da lì continua ad eseguire il codice. Quando si incontra l'istruzione return si ritorna nel punto dove ci si trovava al momento della chiamata. Se usate subroutine queste devono essere scritte sotto la parte principale. In questo caso per delimitare la zona principale è necessario che la main termini con ret. le istruzioni da usare sono call l# ;chiama una subroutine l# callnz l#,b# ; chiama la subroutine se b è true ret ; chiude la subroutine potete usare più routine come volete. I registri sono globali e visibili in tutto il codice shader.

Consigli finali

La novità principale di questa versione è la possibilità di controllare il flusso delle istruzioni ed è questa la cosa da sfruttare maggiormente. Potete quindi prevedere un unico shader diversi trattamenti dei vertici; ad esempio con il bool potete alternare luci puntiformi a direzionali, ognuna scritta in una subroutine oppure prevedere degli effetti differenti a secondo se l'oggetto si trovi ad esempio in acqua o fuori. La possibilità dei loop (quella che personalmente ho apprezzato di più) permette di eseguire più volte blocchi di istruzioni decidendo quindi il numero di iterazioni che un ciclo deve fare. In questo semplice esempio vi mostro come sia possibile utilizzare un numero elevato di luci puntiformi (fino a 30) calcolate tramite shader anzichè usare quelle di directX che al massimo possono essere 8. Per sfruttare al massimo i vertex shader 2.0 consiglio un uso congiunto con i pixel shader (meglio se 2.0), un pò di matematica vettoriale, molta fantasia e tanta, tanta pratica.

Multilight Shader

Sull schermo ci sono 28 luci puntiformi. Tramite Vertex Shader 2.0 possiamo inserire un numero pressochè illimitato di luci dinamiche superando il limite di 8 delle normali luci directX.

Esempio VB.Net