\\ Home : Articoli : Stampa
Introduzione ad HLSL
Di RobyDx (del 27/03/2007 @ 10:34:09, in Direct3D10, linkato 3241 volte)
HLSL
 
Gli shader come mostrato finora sono scritti in HLSL, un linguaggio simil C molto semplice che permette di poter gestire con estrema libertà i nostri effetti. Far pratica con questo linguaggio è la cosa più importante in DirectX10 visto che ciò che vedremo dipenderà per la maggior parte dalla nostra abilità nello sfruttarlo. In effetti DirectX10 non fa altro che creare e passare risorse per il codice HLSL. Il compilatore come abbiamo visto è incluso nella classe ID3D10Effect ma potete trovare un tool nella SDK chiamato FXC per la compilazione offline. Se inoltre salvate su file l’output di fxc potrete conservare il codice shader in binario rendendo il vostro codice impossibile da leggere.Il suo caricamento sarà identico a quello per un file ASCI.
Nei precedenti tutorial abbiamo visto la struttura di un file fx, le variabili e le strutture. Passiamo ora a vedere in dettaglio HLSL.
La versione di HLSL utilizzata in DirectX10 è la 4.0. Potrete usare anche versioni precedenti ma ve lo sconsiglio visto che tutte le schede DirectX10 supportano tale standard, a meno che non creare effetti compatibili anche con DirectX9.
 
Funzioni
 
Come in C in HLSL è possibile dichiarare funzioni che possono essere richiamate da altre funzioni e così via.
Ecco un esempio
 
Float4 Sum(float4 a, float4 b)
{
return a + b;
}
 
Come in C è possibile aggiungere la parola chiave inline per obbligare il compilatore ad evitare chiamate a funzioni. I parametri invece possono avere una parola chiave per indicarne la direzione.
Tramite le parole chiave in, out o inout è possibile specificare se la variabile sarà in ingresso, in uscita o in entrambe le direzioni.
 
Void Sum(float4 a, float4 b,out float4 c)
{
C=a+b;
}
 
Notare che questo vale anche per variabili con semantica. Potrete quindi passare valori dal vertex al pixel shader senza dover creare strutture. La parola chiave uniform davanti al parametro indica invece che il valore sarà una costante assegnata in compilazione (ad esempio per creare funzioni con parametri fissi da passare dalla definizione del pass).
Ultima cosa da sapere è il fatto che la ricorsione non è consentita.
 
Flow Controll
 
DirectX10 permette una gestione reale dei blocchi di codice. In DirectX9 nelle versioni shader 2.0 cicli for o if erano convertiti in codice lineare. Un if diventava il calcolo sia del blocco if che di quello else per poi fare una interpolazione tra i due risultati. I cicli for addirittura erano fissi e convertiti nella ripetizione di codice. Con gli shader 3.0 la situazione era stata risolta ed ora con DirectX10 e gli shader 4.0 la situazione è definitivamente risolta. I blocchi if, while o for si usano esattamente come in C ma ci sono alcune cose da sapere
 
Blocco if
 
Il blocco if viene convertito, a seconda delle situazioni, in un blocco if tradizionale oppure nel confronto tra i 2 valori generati dal blocco if ed else. A volte infatti è più conveniente calcolare i 2 valori piuttosto che gestire il blocco if. Questa scelta è lasciata al compilatore ma è possibile forzare la cosa
 
[branch] if //si comporta in maniera tradizione eseguendo solo uno dei 2 blocchi
[flatten] if // vengono elaborati entrambi i blocchi e fatto un confronto tra i 2 risultati
 
Blocco For e While
 
Anche il blocco for ha 2 attributi. Il primo è unroll (x)
 
Unroll(3) for(int i=0;i<4;i++)
{
}
 
In questo modo il compilatore trasforma il for in codice lineare. Il valore passato all’unroll si può usare per definire il massimo numero di cicli che esegue (e quindi ottimizzare il tempo di compilazione)
 
Loop invece equivale al canonico blocco for.
Loop ) for(int i=0;i<4;i++)
{
}
 
Gli attributi del blocco while sono gli stessi del for.
 
Blocco Switch
 
Il blocco switch ha molti più attributi
 
Flatten : trasforma il blocco in una serie di if a cascata dichiarati con attributo flatten
Branch : trasforma il blocco in una serie di if a cascata dichiarati con attributo branch
Forcecase : eseguito nel modo consueto
Call : trasforma ogni blocco case in una funzione
 
 
Esistono infine Do, Stop, Continue e Break che non hanno attributi e funzionano come gli equivalenti in C.
 
Funzioni
 
HLSL possiede già una ricca serie di funzioni per le operazioni più importanti. Ogni istruzione supporta in genere primitive multiple (ad esempio min che restituisce il minimo tra 2 valori si può applicare sia a float che a float2, float3 o float4). In genere in caso di primitive formate da più componenti la funzione viene applicata per ogni componente separatamente. Riporterò qui sotto l’elenco delle funzioni compatibili con tutti i tipi di shader (Vertex, Pixel e Geometry), escludendo le funzioni relative alle texture che saranno oggetto dei prossimi tutorial.
 
Nome
Sintassi
Descrizione
abs
value abs(value a)
Restituisce il valore assoluto
acos
acos(x)
Restituisce l’arcocoseno di x
all
all(x)
Se tutti i componenti di x sono diversi da zero restituisce True, altrimenti False
any
any(x)
Se almeno un componente di x è diverso da zero restituisce True, altrimenti False
asfloat
asfloat(x)
Converte il valore in float (ad esempio passando int2 restituirà float2)
asin
asin(x)
Restituisce l’arcoseno di x
asint
asint(x)
Converte il valore in intero
asuint
asuint(x)
Converte il valore in intero senza segno ( uint)
atan
atan(x)
Restituisce l’arcotangente di x
atan2
atan2(y, x)
Restituisce l’arcotangente di y/x
ceil
ceil(x)
Restituisce l’intero più piccolo maggiore di x
clamp
clamp(x, min, max)
Se x è minore di min allore pone x = min, se x è maggiore di max allora x = max
cos
cos(x)
Restituisce il coseno di x
cosh
cosh(x)
Restituisce il coseno iperbolico di x
cross
cross(a, b)
Restituisce il cross product tra a e b
degrees
degrees(x)
Converte da radianti a gradi
determinant
determinant(m)
Restituisce il determinante di una matrice
distance
distance(a, b)
Restituisce la distanza tra due punti
dot
dot(a, b)
Effettua il prodotto fattoriale
exp
exp(x)
Restituisce e^x
exp2
value exp2(value a)
Restituisce 2^X
faceforward
faceforward(n, i, ng)
Restituisce -n * sign(•(i, ng)).
floor
floor(x)
Restituisce il più grande intero minore o uguale ad x
fmod
fmod(a, b)
Returns the floating point remainder f of a / b such that a = i * b + f, where i is an integer, f has the same sign as x, and the absolute value of f is less than the absolute value of b.
frac
frac(x)
Restituisce la parte frazionaria di x
frexp
frexp(x, out exp)
Restituisce la mantissa di x ed il suo esponente nella variabile exp
isfinite
isfinite(x)
Restituisce true se x è finito
isinf
isinf(x)
Restituisce true se x è infinito
isnan
isnan(x)
Restituisce true se x è NAN o QNAN
ldexp
ldexp(x, exp)
Restituisce x * 2exp
length
length(v)
Restituisce la lunghezza di v
lerp
lerp(a, b, s)
Restituisce a + s(b - a). Questa è l’interpolazione lineare, variando da s da 0 ad 1 il valore varia linearmente da “a” a “b”
lit
lit(n • l, n • h, m)
Effettua un calcolo generico di illuminazione. Prende come parametri i prodotti fattoriali tra i vettori n (normale), l (direzione della luce), h (vettore half-angle) e l’esponziale m.
log
log(x)
Restituisce l’algoritmo in base “e” di x
log10
log10(x)
Restituisce l’algoritmo in base 10 di x
log2
log2(x)
Restituisce l’algoritmo in base 2 di x
max
max(a, b)
Restituisce il massimo tra a e b
min
min(a, b)
Restituisce il minimo tra a e b
modf
modf(x, out ip)
Restituisce la parte frazionaria di x e mette in ip la parte intera
mul
mul(a, b)
Effettua un prodotto riga per colonna tra vettori
normalize
normalize(v)
Effettua la normalizzazione (vettore / lunghezza del vettore)
pow
pow(x, y)
Restituisce x^y
radians
radians(x)
Converte da gradi in radianti
reflect
reflect(i, n)
Restituisce il vettore riflessione tra la normale n ed il raggio incidente i ( v = i - 2n * (i•n))
refract
refract(i, n, R)
Restituisce il vettore rifrazione tra la normale, il raggio incidente e l’indice di rifrazione
round
round(x)
Arrotonda x
rsqrt
rsqrt(x)
Restituisce 1 / sqrt(x)
saturate
saturate(x)
Limita il valore tra 0 ed 1
sign
sign(x)
Restituisce 1 se x > 0, -1 se x<0 e 0 se x = 0
sin
sin(x)
Restituisce il seno di x
sincos
sincos(x, out s, out c)
Calcola contemporaneamente seno e coseno di x mettendoli in s e c
sinh
sinh(x)
Restituisce il seno iperbolico
smoothstep
smoothstep(min, max, x)
Restituisce 0 if x < min. Restituisce 1 if x > max. Altrimenti effettua una interpolazione
sqrt
value sqrt(value a)
Restituisce la radice quadrata
step
step(a, x)
Se x è >= a allora restituisce 1, zero altrimenti
tan
tan(x)
Restituisce la tangente di x
tanh
tanh(x)
Restituisce la tangente iperbolica di x
transpose
transpose(m)
Restituisce la trasposta della matrice m
 
 
Preprocessore
 
Come in C è possibile definire direttive per il preprocessore. La più utile è senza dubbio la classica
#include
Ha la stessa funzione del C, permettere di importare funzioni e variabili da altri file. Il link sarà gestito automaticamente dal compilatore.
Eccone altre
 
#define , #elif, #else, #endif, #error , #if, #ifdef, #ifndef, #include ,#line , #undef
 
Fate riferimento al C o al C++ per il loro uso.
 
E’ anche possibile utilizzare il typedef per definire dei propri tipi.
 
Vi consiglio quando programmate codice HLSL di utilizzare sia la stringa di ritorno per debuggare velocemente gli errori, che l’istruzione disassemble che restituisce il codice assembler del vostro shader. Il codice assembler generato è molto semplice e potrete vedere quanto avete ottimizzato il vostro codice (per esempio potrete verificare il numero di istruzioni utilizzate dandovi una stima del numero di cicli che la GPU dovrà effettuare).
Ad ogni modo il compilatore HLSL di DirectX è molto potente, impiega un istante per compilarlo ed ottimizza in maniera incredibile il codice. Fate più prove possibili. DirectX10 è per quasi esclusivamente programmazione shader e con buona probabilità una volta che avrete creato un vostro motore grafico utilizzerete soprattutto questo linguaggio.