2.1. Dichiarazione di variabili

5
Il tuo voto: Nessuno Media: 5 (2 voti)

Quello seguente è un esempio di codice contente la dichiarazione di variabili (di differente tipo) che permetteranno l'accesso a risorse definite altrove (ad esempio all'interno del codice C#) e che consentiranno il corretto funzionamento dello shader:

  1 // Matrici di trasformazione in ingresso
  2 uniform float4x4 World;
  3 uniform float4x4 View;
  4 uniform float4x4 ViewProj;
  5 
  6 // Colori ambientale e diffuso del materiale
  7 uniform float4 ambientColor;
  8 uniform float4 diffuseColor;
  9 
 10 // Direzione della sorgente luminosa in world-space
 11 uniform float3 lightVec;
 12 
 13 // Texture in ingresso
 14 texture diffuseTex;
 15 
 16 // Configurazione del sampler da utilizzare nei Pixel Shaders
 17 sampler diffuseSampler : register(s0) = sampler_state
 18 {
 19     Texture = (diffuseTex);
 20     AddressU  = WRAP;
 21     AddressV  = WRAP;
 22     AddressW  = WRAP;
 23     MIPFILTER = LINEAR;
 24     MINFILTER = LINEAR;
 25     MAGFILTER = LINEAR;
 26 };

Come è possibile notare, sono dichiarate le matrici di trasformazione, che proietteranno la geometria in screen-space, tramite il tipo float4x4. Questo tipo rappresenta una matrice di dimensioni 4x4 in row-major order (ciò significa che ognuna delle quattro righe della matrice rappresenta un vettore di quattro elementi; vedremo in seguito come utilizzare questi vettori e quando sono utili).

Successivamente è la volta delle variabili di tipo float4 che possono rappresentare un vettore di quattro elementi (x, y, z, w - corrispondente alla classe Vector4 di XNA) oppure un colore (r, g, b, a) come in questo caso, con la funzione di tonalità ambientale e diffusa di un materiale. È interessante notare che i nomi dei tipi dell'HLSL sono stati progettati in modo da semplificare la loro memorizzazione essendo composti dal nome del tipo di dato (float in questo caso) seguito dal numero di elementi che lo compongono (4 in questo caso). Secondo questa logica è possibile definire un numero in virgola mobile (float) un vettore 2D (float2) un vettore 3D (float3 come nel caso della variabile lightVec) e un vettore 4D (float4). Lo stesso ragionamento è applicabile per ricordare al volo i tipi interi (int, int2, int3, int4), i sampler (sampler1D, sampler2D, sampler3D, samplerCUBE) ecc...

Continuando ad esaminare il codice troveremo una variabile di tipo float3 (corrispondente alla classe Vector3 di XNA) ed una di tipo texture che rappresenta una texture di qualsiasi formato e codifica. Quando inizializzate da codice C# queste due variabili conterrano rispettivamente la direzione da cui proviene la luce e la texture che deve essere applicata alla geometria passata allo shader.

La parola chiave uniform specifica che la variabile seguente conserverà il suo valore tra le diverse esecuzioni del codice dello shader e che non necessiterà quindi di essere reinizializzata da codice C# ad ogni attivazione dello shader.

Infine è definito un sampler (campionatore); insieme alla definizione è possibile notare che per ogni sampler si può specificare il comportamento che avrà nella trasformazione delle coordinate di texture e nel tipo di interpolazione che la texture cui è legato subirà prima di restituire un dato valore all'interno del codice del Pixel Shader. Qui la parola chiave Texture definisce a quale texture collegare il sampler (in questo caso diffuseTex dichiarata poco sopra); le parole chiave AddressU, AddressV, e AddressW si riferiscono al tipo di trasformazione per gli assi x, y e z della texture; le parole chiave MIPFILTER, MINFILTER MAGFILTER definiscono il tipo di interpolazione applicata rispettivamente: tra un mip-map e l'altro della texture; quando i texel della texture hanno una dimensione minore dei pixel dello schermo e quando i texel hanno una dimensione maggiore dei pixel dello schermo. Naturalmente i parametri del sampler non hanno un ordine prestabilito, scriverli nel codice in quel modo è una mia abitudine.

Passiamo ora a definire quali valori possono assumere i parametri Address?: WRAP, MIRROR, CLAMP, BORDER, MIRRORONCE. Il comportamento della GPU e quindi l'effetto ottenuto è rispettivamente questo: ripetizione continua della texture; visualizzazione ripetuta della texture alternativamente in modo normale e a specchio; visualizzazzione della texture per il range di coordinate 0.0 ... 1.0 altrimenti ripetizione del texel del bordo; simile ha CLAMP, ma quando fuori dal range 0.0 ... 1.0 viene visualizzato un colore definito nel RenderState BorderColor; mix del comportamento ottenuto con i valori CLAMP e MIRROR, la texture viene visualizzata una volta diritta poi a specchio, e sucessivamente il comportamento è quello di CLAMP. La presenza dei tre parametri permette di impostare diversi comprtamenti per asse in modo indipendente.

Visualizzazione secondo le differenti impostazioni dei parametri Address?:

 

Analizziamo infine i valori assegnabili ai parametri ???FILTER: NONE (nessuno, valido solo per MIPFILTER), NEAREST (vicino), LINEAR (lineare), ANISOTROPIC (anisotropico). Questi valori specificano quali calcoli deve fare la GPU quando vengono richiesti dei dati da una determinata posizione nella texture; rispettivamente il comportamento è questo: nessun filtro viene utilizzato il mip-map di livello zero; il texel più vicino alla posizione richiesta; interpolazione lineare tra i quattro texel (secondo la loro rispettiva distanza) più vicini alla posizione richiesta; interpolazione anisotropica (quindi non lineare). Quest'ultima viene effettuata per cercare di diminuire al massimo la perdita di dettaglio quando una texture si trova quasi perpendicolare al piano di visualizzazione a costo di una minore rapidità di rendering. Questi valori, quando applicati a MIPFILTER, hanno le stesse caratteristiche ma si riferiscono a due mip-map adiacenti (uno più grande e l'altro più piccolo). Nella grafica sottostante sono stato applicato sempre lo stesso tipo di filtro per tutte e tre i parametri, ma come nel caso del parametro Address? è possibile specificare parametri differenti per ogni situazione secondo le necessità richieste dal design degli shader o dal grafico.

Visualizzazione dei differenti risultati secondo le diverse impostazioni dei parametri ???FILTER:

 

I mip-map di una texture, quando presenti, sono una piramide o catena di immagini della texture stessa, ma di dimensione dimezzata ad ogni livello successivo (queste copie ridotte sono state pensate inizialmente per velocizzare l'interpolazione dei texels, ora i recenti acceleratori grafici sono in grado di generarli in tempo reale se non presenti). Il livello 0 è la texture originale, crescendo di numero la texture si riduce. Il numero di mip-map è ovviamente variabile secondo la dimensione del livello 0 e quello di livello più elevato è rappresentato da un solo texel.

Visualizzazione dei livelli di mip-map di una texture: