Freigeben über


Per-Component mathematische Vorgänge

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

Sobald Sie erfahren, wie Sie Shader in HLSL erstellen, müssen Sie mehr über API-Aufrufe erfahren, sodass Sie einen Shader für bestimmte Hardware kompilieren, Shaderkonstanten initialisieren und ggf. anderen Pipelinestatus 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 im 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 erstellen:

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 winkelige Klammern, um den Typ und die Anzahl der Komponenten anzugeben.

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

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

Diese Anweisungen geben 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

Benennungssätze 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.

Angeben einer oder mehrerer Vektorkomponenten beim Lesen von Komponenten wird als Schwarm bezeichnet. Zum 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;

Durch maskieren wird gesteuert, 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;

Zuordnungen 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 Komponentennamenszeichen nicht gemischt werden. Dies ist ein ungültiger Komponentenschreibvorgang:

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

Der Zugriff auf einen Vektor als Skalar greift auf die erste Komponente des Vektors zu. Die folgenden beiden Anweisungen sind gleichwertig.

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

Der Matrixtyp

Eine Matrix ist eine Datenstruktur, die Zeilen und Spalten von Daten enthält. Die Daten können beliebige skalare Datentypen sein, jedoch ist jedes Element einer Matrix derselbe Datentyp. Die Anzahl der Zeilen und Spalten wird mit der Zeilen-nach-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 erstellen:

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

Der Matrixtyp verwendet die winkeligen 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 Float-Werten (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 einem von zwei Benennungssätzen:

  • Die Null-basierte Zeilenspaltenposition:
    • _m00, _m01, _m02, _m03
    • _m10, _m11, _m12, _m13
    • _m20, _m21, _m22, _m23
    • _m30, _m31, _m32, _m33
  • Die Position einer 1-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. Nachfolgend finden Sie ein Beispiel, in dem die beiden Benennungssätze für den Zugriff auf eine Matrix verwendet werden:

// 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 einem Benennungssatz 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 ist 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 zum Lesen von mehr als einer Komponente verwendet werden.

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

Arrayzugriff kann jedoch einen mehrteiligen Vektor lesen.

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

Wie bei Vektoren wird das Lesen mehrerer Matrixkomponenten als Schwarm bezeichnet. Mehrere Komponenten können zugewiesen werden, vorausgesetzt, es wird nur ein Namensraum verwendet. Dies sind alle gültigen Zuordnungen:

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

Durch maskieren wird gesteuert, 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;

Zuordnungen 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 Komponentennamenszeichen nicht gemischt werden. Dies ist ein ungültiger Komponentenschreibvorgang:

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

Matrix sortierung

Die Matrixverpackungsreihenfolge für einheitliche Parameter ist standardmäßig auf Spaltenmatrizen festgelegt. Dies bedeutet, dass jede Spalte der Matrix in einem einzigen Konstantenregister gespeichert wird. Andererseits enthält eine Matrix mit Zeilenmatrizen jede Zeile der Matrix in einem einzigen Konstantenregister. Matrixverpackungen können mit der Direktive #pragmapack_matrix oder mit dem row_major oder dem schlüsselwort column_major geändert werden.

Die Daten in einer Matrix werden in Shaderkonstantenregister geladen, bevor ein Shader ausgeführt wird. Es gibt zwei Auswahlmöglichkeiten für das Lesen der Matrixdaten: in der Zeilen-Hauptreihenfolge oder in Spalten-Hauptreihenfolge.There are two choices for how the matrix data is read: in row-major order or in column-major order. Spaltenmastreihenfolge bedeutet, dass jede Matrixspalte in einem einzigen Konstantenregister gespeichert wird, und die Reihenfolge der Zeilenmatrizen bedeutet, dass jede Zeile der Matrix in einem einzigen Konstantenregister gespeichert wird. Dies ist eine wichtige Überlegung, wie viele Konstantenregister für eine Matrix verwendet werden.

Eine Zeilenmatrizenmatrix ist wie folgt angeordnet:

11
21
31
41

12
22
32
42

13
23
33
43

14
24
34
44

 

Eine Spaltenmatrizenmatrix ist wie folgt angeordnet:

11
12
13
14

21
22
23
24

31
32
33
34

41
42
43
44

 

Zeilenmatrizen- und Spaltenmatrizenreihenfolge bestimmen die Reihenfolge, in der die Matrixkomponenten aus Shadereingaben gelesen werden. Nachdem die Daten in Konstantenregister geschrieben wurden, hat die Matrixreihenfolge keine Auswirkungen auf die Verwendung oder den Zugriff auf die Daten aus dem Shadercode. Außerdem werden matrizen, die in einem Shadertext deklariert sind, nicht in Konstantenregister gepackt. Der Zeilen-Haupt- und Spalten-Hauptverpackungsauftrag hat keinen Einfluss auf den Verpackungsauftrag von Konstruktoren (die immer auf die Zeilenhauptreihenfolge folgt).

Die Reihenfolge der Daten in einer Matrix kann zur Kompilierungszeit 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 standard-HLSL-Formeln verwendet werden, wird die berechnete Mathematik für die Arbeit pro Komponente entwickelt. Beispielsweise implementiert HLSL diese Multiplikation:

float4 v = a*b;

als Vierkomponenten multiplizieren. Das Ergebnis ist 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 multiplizierte vier Komponenten bezeichnet. HLSL verwendet Komponentenmathematik, was das Schreiben von Shadern sehr effizient macht.

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

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

Eine Matrix verwendet auch Vorgänge pro Komponente in HLSL:

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

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

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 systeminternen Funktionsbehandlungsfälle, bei denen ein Operand ein Vektor ist und der andere Operand eine Matrix ist. Beispiel: Vektor * Vektor, Vektor * Matrix, Matrix * Vektor und Matrix * Matrix * Matrix. Zum Beispiel:

float4x3 World;

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

    return val;
}   

erzeugt dasselbe 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 umgestellt. Das Ändern eines Vektors durch Umwandlung oder Das Austauschen der Reihenfolge der zu multiplizierenden Argumente entspricht der Transponierung der Matrix.

Die automatische Umwandlung bewirkt, dass die systeminternen Multiplikations- und Punktfunktionen dieselben Ergebnisse zurückgeben wie hier:

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

der einen einzelnen skalaren Wert zurückgibt.

Datentypen (DirectX HLSL)