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.