operazioni matematiche Per-Component

Con HLSL è possibile programmare shader a livello di algoritmo. Per comprendere il linguaggio, è necessario sapere come dichiarare variabili e funzioni, usare funzioni intrinseche, definire tipi di dati personalizzati e usare la semantica per connettere gli argomenti shader ad altri shader e alla pipeline.

Dopo aver appreso come creare shader in HLSL, è necessario apprendere le chiamate API in modo da poter compilare uno shader per un determinato hardware, inizializzare le costanti shader e inizializzare altri stati della pipeline, se necessario.

Tipo di vettore

Un vettore è una struttura di dati che contiene tra uno e quattro componenti.

bool    bVector;   // scalar containing 1 Boolean
bool1   bVector;   // vector containing 1 Boolean
int1    iVector;   // vector containing 1 int
float3  fVector;   // vector containing 3 floats
double4 dVector;   // vector containing 4 doubles

L'intero immediatamente successivo al tipo di dati è il numero di componenti nel vettore.

Gli inizializzatori possono essere inclusi anche nelle dichiarazioni.

bool    bVector = false;
int1    iVector = 1;
float3  fVector = { 0.2f, 0.3f, 0.4f };
double4 dVector = { 0.2, 0.3, 0.4, 0.5 };

In alternativa, il tipo di vettore può essere usato per effettuare le stesse dichiarazioni:

vector <bool,   1> bVector = false;
vector <int,    1> iVector = 1;
vector <float,  3> fVector = { 0.2f, 0.3f, 0.4f };
vector <double, 4> dVector = { 0.2, 0.3, 0.4, 0.5 };

Il tipo di vettore usa parentesi graffe per specificare il tipo e il numero di componenti.

I vettori contengono fino a quattro componenti, ognuno dei quali può essere accessibile usando uno dei due set di denominazione:

  • Set di posizioni: x,y,z,w
  • Set di colori: r,g,b,a

Queste istruzioni restituiscono entrambi il valore nel terzo componente.

// Given
float4 pos = float4(0,0,2,1);

pos.z    // value is 2
pos.b    // value is 2

I set di denominazione possono usare uno o più componenti, ma non possono essere misti.

// Given
float4 pos = float4(0,0,2,1);
float2 temp;

temp = pos.xy  // valid
temp = pos.rg  // valid

temp = pos.xg  // NOT VALID because the position and color sets were used.

Specificando uno o più componenti vettoriali durante la lettura dei componenti viene chiamato swizzling. Ad esempio:

float4 pos = float4(0,0,2,1);
float2 f_2D;
f_2D = pos.xy;   // read two components 
f_2D = pos.xz;   // read components in any order       
f_2D = pos.zx;

f_2D = pos.xx;   // components can be read more than once
f_2D = pos.yy;

Mascheramento controlla il numero di componenti scritti.

float4 pos = float4(0,0,2,1);
float4 f_4D;
f_4D    = pos;     // write four components          

f_4D.xz = pos.xz;  // write two components        
f_4D.zx = pos.xz;  // change the write order

f_4D.xzyw = pos.w; // write one component to more than one component
f_4D.wzyx = pos;

Le assegnazioni non possono essere scritte nello stesso componente più di una volta. Quindi il lato sinistro di questa istruzione non è valido:

f_4D.xx = pos.xy;   // cannot write to the same destination components 

Inoltre, gli spazi dei nomi del componente non possono essere misti. Si tratta di una scrittura di componente non valida:

f_4D.xg = pos.rgrg;    // invalid write: cannot mix component name spaces 

L'accesso a un vettore come scalare accederà al primo componente del vettore. Le due istruzioni seguenti sono equivalenti.

f_4D.a = pos * 5.0f;
f_4D.a = pos.r * 5.0f;

Tipo matrice

Una matrice è una struttura di dati che contiene righe e colonne di dati. I dati possono essere uno dei tipi di dati scalari, ma ogni elemento di una matrice è lo stesso tipo di dati. Il numero di righe e colonne viene specificato con la stringa di riga per colonna aggiunta al tipo di dati.

int1x1    iMatrix;   // integer matrix with 1 row,  1 column
int2x1    iMatrix;   // integer matrix with 2 rows, 1 column
...
int4x1    iMatrix;   // integer matrix with 4 rows, 1 column
...
int1x4    iMatrix;   // integer matrix with 1 row, 4 columns
double1x1 dMatrix;   // double matrix with 1 row,  1 column
double2x2 dMatrix;   // double matrix with 2 rows, 2 columns
double3x3 dMatrix;   // double matrix with 3 rows, 3 columns
double4x4 dMatrix;   // double matrix with 4 rows, 4 columns

Il numero massimo di righe o colonne è 4; il numero minimo è 1.

Una matrice può essere inizializzata quando viene dichiarata:

float2x2 fMatrix = { 0.0f, 0.1, // row 1
                     2.1f, 2.2f // row 2
                   };   

In alternativa, il tipo di matrice può essere usato per effettuare le stesse dichiarazioni:

matrix <float, 2, 2> fMatrix = { 0.0f, 0.1, // row 1
                                 2.1f, 2.2f // row 2
                               };

Il tipo matrice usa le parentesi graffe per specificare il tipo, il numero di righe e il numero di colonne. In questo esempio viene creata una matrice a virgola mobile, con due righe e due colonne. È possibile usare uno dei tipi di dati scalari.

Questa dichiarazione definisce una matrice di valori float (numeri a virgola mobile a 32 bit) con due righe e tre colonne:

matrix <float, 2, 3> fFloatMatrix;

Una matrice contiene valori organizzati in righe e colonne, a cui è possibile accedere usando l'operatore struttura "." seguito da uno dei due set di denominazione:

  • Posizione della colonna di riga in base zero:
    • _m00, _m01, _m02, _m03
    • _m10, _m11, _m12, _m13
    • _m20, _m21, _m22, _m23
    • _m30, _m31, _m32, _m33
  • Posizione della colonna di riga a una sola base:
    • _11, _12, _13, _14
    • _21, _22, _23, _24
    • _31, _32, _33, _34
    • _41, _42, _43, _44

Ogni set di denominazione inizia con un carattere di sottolineatura seguito dal numero di riga e dal numero di colonna. La convenzione in base zero include anche la lettera "m" prima del numero di riga e colonna. Ecco un esempio che usa i due set di denominazione per accedere a una matrice:

// given
float2x2 fMatrix = { 1.0f, 1.1f, // row 1
                     2.0f, 2.1f  // row 2
                   }; 

float f_1D;
f_1D = matrix._m00; // read the value in row 1, column 1: 1.0
f_1D = matrix._m11; // read the value in row 2, column 2: 2.1

f_1D = matrix._11;  // read the value in row 1, column 1: 1.0
f_1D = matrix._22;  // read the value in row 2, column 2: 2.1

Proprio come i vettori, i set di denominazione possono usare uno o più componenti da entrambi i set di denominazione.

// Given
float2x2 fMatrix = { 1.0f, 1.1f, // row 1
                     2.0f, 2.1f  // row 2
                   };
float2 temp;

temp = fMatrix._m00_m11 // valid
temp = fMatrix._m11_m00 // valid
temp = fMatrix._11_22   // valid
temp = fMatrix._22_11   // valid

È anche possibile accedere a una matrice usando la notazione di accesso alla matrice, ovvero un set di indici in base zero. Ogni indice si trova all'interno di parentesi quadre. Una matrice 4x4 viene accessibile con gli indici seguenti:

  • [0] [0], [0][1], [0][2], [0][3]
  • [1] [0], [1][1], [1][2], [1][3]
  • [2] [0], [2][1], [2][2], [2][3]
  • [3] [0], [3][1], [3][2], [3][3]

Ecco un esempio di accesso a una matrice:

float2x2 fMatrix = { 1.0f, 1.1f, // row 1
                     2.0f, 2.1f  // row 2
                   };
float temp;

temp = fMatrix[0][0] // single component read
temp = fMatrix[0][1] // single component read

Si noti che l'operatore struttura "." non viene usato per accedere a una matrice. La notazione di accesso alla matrice non può usare swizzling per leggere più di un componente.

float2 temp;
temp = fMatrix[0][0]_[0][1] // invalid, cannot read two components

Tuttavia, l'accesso alla matrice può leggere un vettore multi-componente.

float2 temp;
float2x2 fMatrix;
temp = fMatrix[0] // read the first row

Come per i vettori, la lettura di più componenti matrice è chiamata swizzling. È possibile assegnare più di un componente, presupponendo che venga usato solo uno spazio dei nomi. Queste sono tutte le assegnazioni valide:

// Given these variables
float4x4 worldMatrix = float4( {0,0,0,0}, {1,1,1,1}, {2,2,2,2}, {3,3,3,3} );
float4x4 tempMatrix;

tempMatrix._m00_m11 = worldMatrix._m00_m11; // multiple components
tempMatrix._m00_m11 = worldMatrix.m13_m23;

tempMatrix._11_22_33 = worldMatrix._11_22_33; // any order on swizzles
tempMatrix._11_22_33 = worldMatrix._24_23_22;

Mascheramento controlla il numero di componenti scritti.

// Given
float4x4 worldMatrix = float4( {0,0,0,0}, {1,1,1,1}, {2,2,2,2}, {3,3,3,3} );
float4x4 tempMatrix;

tempMatrix._m00_m11 = worldMatrix._m00_m11; // write two components
tempMatrix._m23_m00 = worldMatrix._m00_m11;

Le assegnazioni non possono essere scritte nello stesso componente più di una volta. Quindi il lato sinistro di questa istruzione non è valido:

// cannot write to the same component more than once
tempMatrix._m00_m00 = worldMatrix._m00_m11;

Inoltre, gli spazi dei nomi del componente non possono essere misti. Si tratta di una scrittura di componente non valida:

// Invalid use of same component on left side
tempMatrix._11_m23 = worldMatrix._11_22; 

Ordinamento matrice

L'ordine di compressione matrice per i parametri uniformi è impostato su column-major per impostazione predefinita. Ciò significa che ogni colonna della matrice viene archiviata in un singolo registro costante. D'altra parte, una matrice principale di riga contiene ogni riga della matrice in un singolo registro costante. La compressione della matrice può essere modificata con la direttiva #pragmapack_matrix o con la parola chiaverow_major o column_major.

I dati in una matrice vengono caricati nei registri costanti shader prima dell'esecuzione di uno shader. Sono disponibili due opzioni per la lettura dei dati matrice: nell'ordine principale della riga o nell'ordine principale della colonna. L'ordine principale della colonna indica che ogni colonna matrice verrà archiviata in un singolo registro costante e l'ordine principale della riga significa che ogni riga della matrice verrà archiviata in un singolo registro costante. Si tratta di una considerazione importante per il numero di registri costanti usati per una matrice.

Una matrice principale di riga è disposta come segue:

11
21
31
41

12
22
32
42

13
23
33
43

14
24
34
44

 

Una matrice colonna-principale è disposta come segue:

11
12
13
14

21
22
23
24

31
32
33
34

41
42
43
44

 

L'ordinamento della matrice principale e della colonna determina l'ordine in cui i componenti della matrice vengono letti dagli input dello shader. Dopo che i dati vengono scritti in registri costanti, l'ordine della matrice non ha alcun effetto sul modo in cui i dati vengono usati o accessibili dall'interno del codice shader. Inoltre, le matrici dichiarate in un corpo dello shader non vengono compresse in registri costanti. L'ordine di compressione principale e principale della riga non influisce sull'ordine di compressione dei costruttori (che segue sempre l'ordinamento principale delle righe).

L'ordine dei dati in una matrice può essere dichiarato in fase di compilazione oppure il compilatore ordina i dati in fase di esecuzione per l'uso più efficiente.

Esempio

HLSL usa due tipi speciali, un tipo vettore e un tipo matrice per semplificare la programmazione della grafica 2D e 3D. Ognuno di questi tipi contiene più componenti; un vettore contiene fino a quattro componenti e una matrice contiene fino a 16 componenti. Quando i vettori e le matrici vengono usati nelle equazioni HLSL standard, la matematica eseguita è progettata per funzionare per ogni componente. Ad esempio, HLSL implementa questa moltiplicazione:

float4 v = a*b;

come moltiplicazione a quattro componenti. Il risultato è costituito da quattro scalari:

float4 v = a*b;

v.x = a.x*b.x;
v.y = a.y*b.y;
v.z = a.z*b.z;
v.w = a.w*b.w;

Si tratta di quattro moltiplicazioni in cui ogni risultato viene archiviato in un componente separato di v. Si tratta di una moltiplicazione a quattro componenti. HLSL usa la matematica dei componenti che rende molto efficiente la scrittura di shader.

Ciò è molto diverso da una moltiplicazione che viene in genere implementata come prodotto punto che genera un singolo scalare:

v = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;

Una matrice usa anche operazioni per componente in HLSL:

float3x3 mat1,mat2;
...
float3x3 mat3 = mat1*mat2;

Il risultato è una moltiplicazione per componente delle due matrici (anziché una moltiplicazione di matrice 3x3 standard). Una matrice per componente moltiplica questo primo termine:

mat3.m00 = mat1.m00 * mat2._m00;

Questo valore è diverso da una moltiplicazione di 3x3 che produrrebbe questo primo termine:

// First component of a four-component matrix multiply
mat.m00 = mat1._m00 * mat2._m00 + 
          mat1._m01 * mat2._m10 + 
          mat1._m02 * mat2._m20 + 
          mat1._m03 * mat2._m30;

Versioni di overload dei casi di handle di funzione intrinseca moltiplicati in cui un operando è un vettore e l'altro operando è una matrice. Ad esempio: vector * vector, vector * matrix, matrix * vector e matrix * matrix. Ad esempio:

float4x3 World;

float4 main(float4 pos : SV_POSITION) : SV_POSITION
{
    float4 val;
    val.xyz = mul(pos,World);
    val.w = 0;

    return val;
}   

produce lo stesso risultato di:

float4x3 World;

float4 main(float4 pos : SV_POSITION) : SV_POSITION
{
    float4 val;
    val.xyz = (float3) mul((float1x4)pos,World);
    val.w = 0;

    return val;
}   

Questo esempio esegue il cast del vettore pos a un vettore di colonna usando il cast (float1x4). La modifica di un vettore tramite il cast o lo scambio dell'ordine degli argomenti forniti da moltiplicare equivale alla trasposizione della matrice.

La conversione automatica del cast fa sì che le funzioni intrinseche di moltiplicazione e punto restituiscano gli stessi risultati usati qui:

{
  float4 val;
  return mul(val,val);
}

Questo risultato della moltiplicazione è un vettore 1x4 * 4x1 = 1x1. Equivale a un prodotto punto:

{
  float4 val;
  return dot(val,val);
}

che restituisce un singolo valore scalare.

Tipi di dati (DirectX HLSL)