Per-Component Mathematische Vorgänge

Mit HLSL können Sie Shader auf Algorithmusebene programmieren. Um die Sprache zu verstehen, müssen Sie wissen, wie Sie Variablen und Funktionen deklarieren, systeminterne Funktionen verwenden, benutzerdefinierte Datentypen definieren und Semantik verwenden, um Shaderargumente mit anderen Shadern und der Pipeline zu verbinden.

Nachdem Sie gelernt haben, wie Shader in HLSL erstellt werden, müssen Sie sich über API-Aufrufe informieren, damit Sie einen Shader für bestimmte Hardware kompilieren, Shaderkonstanten initialisieren und bei Bedarf andere Pipelinezustände initialisieren können.

Der Vektortyp

Ein Vektor ist eine Datenstruktur, die zwischen einer und vier Komponenten enthält.

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

Die ganze Zahl, die unmittelbar auf den Datentyp folgt, ist die Anzahl der Komponenten auf dem Vektor.

Initialisierer können auch in die Deklarationen einbezogen werden.

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

Alternativ kann der Vektortyp verwendet werden, um dieselben Deklarationen zu machen:

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 };

Der Vektortyp verwendet spitzen Klammern, um den Typ und die Anzahl der Komponenten anzugeben.

Vektoren enthalten bis zu vier Komponenten, auf die jeweils mit einer von zwei Benennungssätzen zugegriffen werden kann:

  • Der Positionssatz: x,y,z,w
  • Der Farbsatz: r,g,b,a

Diese Anweisungen geben beide den Wert in der dritten Komponente zurück.

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

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

Benennungsgruppen können eine oder mehrere Komponenten verwenden, aber sie können nicht gemischt werden.

// 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.

Das Angeben einer oder mehrerer Vektorkomponenten beim Lesen von Komponenten wird als Swizzling bezeichnet. Beispiel:

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;

Maskierung steuert, wie viele Komponenten geschrieben werden.

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;

Zuweisungen können nicht mehrmals in dieselbe Komponente geschrieben werden. Daher ist die linke Seite dieser Anweisung ungültig:

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

Außerdem können die Komponentennamensräume nicht gemischt werden. Dies ist ein ungültiger Komponentenschreibvorgang:

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

Beim Zugriff auf einen Vektor als Skalar wird auf die erste Komponente des Vektors zugegriffen. Die folgenden beiden Anweisungen sind gleichwertig.

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

Matrixtyp

Eine Matrix ist eine Datenstruktur, die Zeilen und Datenspalten enthält. Bei den Daten kann es sich um einen beliebigen skalaren Datentyp handeln, aber jedes Element einer Matrix ist derselbe Datentyp. Die Anzahl der Zeilen und Spalten wird mit der Zeilen-für-Spalten-Zeichenfolge angegeben, die an den Datentyp angefügt wird.

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

Die maximale Anzahl von Zeilen oder Spalten beträgt 4; die Mindestzahl ist 1.

Eine Matrix kann initialisiert werden, wenn sie deklariert wird:

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

Oder der Matrixtyp kann verwendet werden, um dieselben Deklarationen zu machen:

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

Der Matrixtyp verwendet die spitzen Klammern, um den Typ, die Anzahl der Zeilen und die Anzahl der Spalten anzugeben. In diesem Beispiel wird eine Gleitkommamatrix mit zwei Zeilen und zwei Spalten erstellt. Jeder der skalaren Datentypen kann verwendet werden.

Diese Deklaration definiert eine Matrix von Gleitkommawerten (32-Bit-Gleitkommazahlen) mit zwei Zeilen und drei Spalten:

matrix <float, 2, 3> fFloatMatrix;

Eine Matrix enthält Werte, die in Zeilen und Spalten organisiert sind, auf die mithilfe des Strukturoperators "." zugegriffen werden kann, gefolgt von einer von zwei Benennungssätzen:

  • Die nullbasierte Zeilenspaltenposition:
    • _m00, _m01, _m02, _m03
    • _m10, _m11, _m12, _m13
    • _m20, _m21, _m22, _m23
    • _m30, _m31, _m32, _m33
  • Die position einer basierten Zeilenspalte:
    • _11, _12, _13, _14
    • _21, _22, _23, _24
    • _31, _32, _33, _34
    • _41, _42, _43, _44

Jeder Benennungssatz beginnt mit einem Unterstrich gefolgt von der Zeilennummer und der Spaltennummer. Die nullbasierte Konvention enthält auch den Buchstaben "m" vor der Zeilen- und Spaltennummer. Hier sehen Sie ein Beispiel, das die beiden Benennungssätze verwendet, um auf eine Matrix zuzugreifen:

// 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

Genau wie Vektoren können Benennungssätze eine oder mehrere Komponenten aus beiden Benennungssätzen verwenden.

// 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

Auf eine Matrix kann auch mithilfe der Arrayzugriffsnotation zugegriffen werden, bei der es sich um einen nullbasierten Satz von Indizes handelt. Jeder Index befindet sich in eckigen Klammern. Auf eine 4x4-Matrix wird mit den folgenden Indizes zugegriffen:

  • [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]

Hier sehen Sie ein Beispiel für den Zugriff auf eine Matrix:

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

Beachten Sie, dass der Strukturoperator "." nicht für den Zugriff auf ein Array verwendet wird. Die Arrayzugriffsnotation kann nicht das Swizzling verwenden, um mehr als eine Komponente zu lesen.

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

Beim Arrayzugriff kann jedoch ein Vektor mit mehreren Komponenten gelesen werden.

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

Wie bei Vektoren wird das Lesen von mehr als einer Matrixkomponente als Swizzling bezeichnet. Mehrere Komponenten können zugewiesen werden, vorausgesetzt, es wird nur ein Namensraum verwendet. Dies sind alle gültigen Zuweisungen:

// 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;

Maskierung steuert, wie viele Komponenten geschrieben werden.

// 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;

Zuweisungen können nicht mehrmals in dieselbe Komponente geschrieben werden. Daher ist die linke Seite dieser Anweisung ungültig:

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

Außerdem können die Komponentennamensräume nicht gemischt werden. Dies ist ein ungültiger Komponentenschreibvorgang:

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

Matrixreihenfolge

Die Matrixverpackungsreihenfolge für einheitliche Parameter ist standardmäßig auf Column-Major festgelegt. Dies bedeutet, dass jede Spalte der Matrix in einem einzigen Konstantenregister gespeichert wird. Auf der anderen Seite packt eine Zeilen-Hauptmatrix jede Zeile der Matrix in einem einzigen Konstantenregister. Die Matrixverpackung kann mit der #pragmapack_matrix-Anweisung oder mit dem row_major oder dem column_major Schlüsselwort (keyword) geändert werden.

Die Daten in einer Matrix werden in Shaderkonstantenregister geladen, bevor ein Shader ausgeführt wird. Es gibt zwei Möglichkeiten, wie die Matrixdaten gelesen werden: in Zeilen-Hauptreihenfolge oder in Spalten-Hauptreihenfolge. Spaltenhauptreihenfolge bedeutet, dass jede Matrixspalte in einem einzigen Konstantenregister gespeichert wird, und Zeilenhauptreihenfolge bedeutet, dass jede Zeile der Matrix in einem einzigen Konstantenregister gespeichert wird. Dies ist ein wichtiger Aspekt für die Anzahl der Konstantenregister, die für eine Matrix verwendet werden.

Eine Zeilen-Hauptmatrix ist wie folgt angelegt:

11
21
31
41

12
22
32
42

13
23
33
43

14
24
34
44

 

Eine Spaltenhauptmatrix ist wie folgt angeordnet:

11
12
13
14

21
22
23
24

31
32
33
34

41
42
43
44

 

Zeilenhaupt- und Spaltenhauptmatrixreihenfolge bestimmen die Reihenfolge, in der die Matrixkomponenten aus Shadereingaben gelesen werden. Sobald die Daten in konstanten Registern geschrieben wurden, hat die Matrixreihenfolge keine Auswirkung darauf, wie die Daten aus dem Shadercode verwendet oder darauf zugegriffen wird. Außerdem werden matrizen, die in einem Shadertext deklariert sind, nicht in konstanten Registern gepackt. Zeilen-Haupt- und Spalten-Hauptverpackungsauftrag hat keinen Einfluss auf die Verpackungsreihenfolge der Konstruktoren (die immer der Zeilenmajorreihenfolge folgt).

Die Reihenfolge der Daten in einer Matrix kann zur Kompilierzeit deklariert werden, oder der Compiler sortiert die Daten zur Laufzeit für die effizienteste Verwendung.

Beispiele

HLSL verwendet zwei spezielle Typen, einen Vektortyp und einen Matrixtyp, um die Programmierung von 2D- und 3D-Grafiken zu vereinfachen. Jeder dieser Typen enthält mehr als eine Komponente; ein Vektor enthält bis zu vier Komponenten, und eine Matrix enthält bis zu 16 Komponenten. Wenn Vektoren und Matrizen in HLSL-Standardgleichungen verwendet werden, ist die durchgeführte Mathematik so konzipiert, dass sie pro Komponente funktioniert. Für instance implementiert HLSL diese Multiplikation:

float4 v = a*b;

als Vier-Komponenten-Multiplikation. Das Ergebnis sind vier Skalare:

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;

Dies sind vier Multiplikationen, bei denen jedes Ergebnis in einer separaten Komponente von v gespeichert wird. Dies wird als Multiplikation mit vier Komponenten bezeichnet. HLSL verwendet Komponenten mathematisch, wodurch das Schreiben von Shadern sehr effizient ist.

Dies unterscheidet sich stark von einer Multiplikation, die in der Regel als Punktprodukt implementiert wird, das einen einzelnen Skalar erzeugt:

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

Eine Matrix verwendet auch Komponentenvorgänge in HLSL:

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

Das Ergebnis ist eine Komponenten-Multiplikation der beiden Matrizen (im Gegensatz zu einer standardmäßigen 3x3-Matrix multiplizieren). Eine Multiplikation pro Komponentenmatrix ergibt diesen ersten Begriff:

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

Dies unterscheidet sich von einer 3x3-Matrix multiplizieren, die diesen ersten Begriff ergeben würde:

// 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;

Überladene Versionen der multiplizierten Funktion behandeln Fälle, in denen ein Operand ein Vektor und der andere Operand eine Matrix ist. Beispiel: Vektor * Vektor, Vektor * Matrix, Matrix * Vektor und Matrix * Matrix. Beispiel:

float4x3 World;

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

    return val;
}   

erzeugt das gleiche Ergebnis wie:

float4x3 World;

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

    return val;
}   

In diesem Beispiel wird der pos-Vektor mithilfe der Umwandlung (float1x4) in einen Spaltenvektor umgewandelt. Das Ändern eines Vektors durch Umwandlung oder Austauschen der Reihenfolge der bereitgestellten Argumente zum Multiplizieren entspricht dem Transponieren der Matrix.

Die automatische Umwandlungskonvertierung bewirkt, dass die intrinsischen Funktionen multiplizieren und Punkt die gleichen Ergebnisse zurückgeben, die hier verwendet werden:

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

Dieses Ergebnis der Multiplikation ist ein 1x4 * 4x1 = 1x1-Vektor. Dies entspricht einem Punktprodukt:

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

gibt einen einzelnen Skalarwert zurück.

Datentypen (DirectX HLSL)