matematické operace Per-Component
Pomocí HLSL můžete programovat shadery na úrovni algoritmu. Abyste porozuměli jazyku, budete muset vědět, jak deklarovat proměnné a funkce, používat vnitřní funkce, definovat vlastní datové typy a používat sémantiku k propojení argumentů shaderu s jinými shadery a kanálu.
Jakmile se naučíte vytvářet shadery v HLSL, budete se muset seznámit s voláními rozhraní API, abyste mohli zkompilovat shader pro konkrétní hardware, inicializovat konstanty shaderu a v případě potřeby inicializovat další stav kanálu.
Typ vektoru
Vektor je datová struktura, která obsahuje jednu a čtyři komponenty.
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
Celé číslo bezprostředně za datovým typem je počet součástí vektoru.
Inicializátory lze také zahrnout do deklarací.
bool bVector = false;
int1 iVector = 1;
float3 fVector = { 0.2f, 0.3f, 0.4f };
double4 dVector = { 0.2, 0.3, 0.4, 0.5 };
Alternativně lze k vytvoření stejných deklarací použít typ vektoru:
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 };
Typ vektoru používá k určení typu a počtu součástí úhlové závorky.
Vektory obsahují až čtyři komponenty, z nichž každý je přístupný pomocí jedné ze dvou sad pojmenování:
- Sada pozic: x,y,z,w
- Sada barev: r,g,b,a
Oba tyto příkazy vrátí hodnotu ve třetí komponentě.
// Given
float4 pos = float4(0,0,2,1);
pos.z // value is 2
pos.b // value is 2
Sady názvů můžou používat jednu nebo více součástí, ale nedají se kombinovat.
// 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.
Určení jedné nebo více vektorových komponent při čtení komponent se nazývá swizzling. Například:
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;
Maskování určuje, kolik součástí se zapisuje.
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;
Přiřazení nelze zapsat do stejné komponenty více než jednou. Levá strana tohoto příkazu je proto neplatná:
f_4D.xx = pos.xy; // cannot write to the same destination components
Názvové prostory komponent se také nedají kombinovat. Toto je neplatný zápis komponenty:
f_4D.xg = pos.rgrg; // invalid write: cannot mix component name spaces
Přístup k vektoru jako skaláru bude přistupovat k první komponentě vektoru. Následující dva příkazy jsou ekvivalentní.
f_4D.a = pos * 5.0f;
f_4D.a = pos.r * 5.0f;
Typ matice
Matice je datová struktura, která obsahuje řádky a sloupce dat. Data mohou být libovolným skalárním datovým typem, ale každý prvek matice je stejný datový typ. Počet řádků a sloupců se určuje pomocí řetězce řádku po sloupci, který je připojený k datovému typu.
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
Maximální počet řádků nebo sloupců je 4; minimální číslo je 1.
Matici lze inicializovat, když je deklarována:
float2x2 fMatrix = { 0.0f, 0.1, // row 1
2.1f, 2.2f // row 2
};
Nebo lze maticový typ použít k vytvoření stejných deklarací:
matrix <float, 2, 2> fMatrix = { 0.0f, 0.1, // row 1
2.1f, 2.2f // row 2
};
Typ matice používá úhlové závorky k určení typu, počtu řádků a počtu sloupců. Tento příklad vytvoří matici s plovoucí desetinou čárkou se dvěma řádky a dvěma sloupci. Můžete použít libovolný skalární datový typ.
Tato deklarace definuje matici plovoucích hodnot (32bitová čísla s plovoucí desetinou čárkou) se dvěma řádky a třemi sloupci:
matrix <float, 2, 3> fFloatMatrix;
Matice obsahuje hodnoty uspořádané v řádcích a sloupcích, ke kterým lze přistupovat pomocí operátoru struktury ". následované jednou ze dvou sad pojmenování:
- Pozice sloupce řádku založeného na nule:
- _m00, _m01, _m02, _m03
- _m10, _m11, _m12, _m13
- _m20, _m21, _m22, _m23
- _m30, _m31, _m32, _m33
- Pozice sloupce s jedním řádkem:
- _11, _12, _13, _14
- _21, _22, _23, _24
- _31, _32, _33, _34
- _41, _42, _43, _44
Každá sada názvů začíná podtržítkem následovaným číslem řádku a číslem sloupce. Konvence založená na nule obsahuje také písmeno "m" před číslem řádku a sloupce. Tady je příklad, který pro přístup k matici používá dvě sady názvů:
// 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
Stejně jako vektory můžou sady pojmenování používat jednu nebo více součástí z obou sad pojmenování.
// 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
K matici lze přistupovat také pomocí zápisu přístupu k poli, což je nula-based sada indexů. Každý index je uvnitř hranatých závorek. K matici 4x4 se přistupuje s následujícími indexy:
- [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]
Tady je příklad přístupu k matici:
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
Všimněte si, že operátor struktury "" se nepoužívá pro přístup k poli. Zápis přístupu k poli nemůže použít pro čtení více než jedné komponenty.
float2 temp;
temp = fMatrix[0][0]_[0][1] // invalid, cannot read two components
Přístup k poli ale může číst vektor více komponent.
float2 temp;
float2x2 fMatrix;
temp = fMatrix[0] // read the first row
Stejně jako u vektorů se čtení více než jedné maticové komponenty nazývá potačení. Lze přiřadit více než jednu komponentu za předpokladu, že se použije pouze jeden prostor názvů. Toto jsou všechna platná přiřazení:
// 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;
Maskování určuje, kolik součástí se zapisuje.
// 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;
Přiřazení nelze zapsat do stejné komponenty více než jednou. Levá strana tohoto příkazu je proto neplatná:
// cannot write to the same component more than once
tempMatrix._m00_m00 = worldMatrix._m00_m11;
Názvové prostory komponent se také nedají kombinovat. Toto je neplatný zápis komponenty:
// Invalid use of same component on left side
tempMatrix._11_m23 = worldMatrix._11_22;
Řazení matic
Pořadí balení matice pro jednotné parametry je standardně nastaveno na hlavní sloupec. To znamená, že každý sloupec matice je uložený v jednom konstantním registru. Na druhou stranu matice hlavního řádku zabalí každý řádek matice v jednom konstantním registru. Balení matice lze změnit pomocí direktivy #pragmapack_matrix nebo pomocí row_major nebo klíčového slova column_major.
Data v matici se načtou do konstantních registrů shaderu před spuštěním shaderu. Existují dvě možnosti čtení maticových dat: v hlavním pořadí řádků nebo v pořadí hlavního sloupce. Hlavní pořadí sloupců znamená, že každý sloupec matice bude uložen v jednom konstantním registru a pořadí hlavního řádku znamená, že každý řádek matice bude uložen v jednom konstantním registru. To je důležité pro to, kolik konstantních registrů se používá pro matici.
Matice hlavního řádku je rozložená takto:
11
21
31
41
12
22
32
42
13
23
33
43
14
24
34
44
Matice hlavního sloupce je rozložená takto:
11
12
13
14
21
22
23
24
31
32
33
34
41
42
43
44
Pořadí matice hlavního řádku a hlavního sloupce určuje pořadí, ve které jsou součásti matice načteny ze vstupů shaderu. Jakmile se data zapíšou do konstantních registrů, pořadí matic nemá žádný vliv na způsob použití dat nebo přístupu k datům z kódu shaderu. Matice deklarované v těle shaderu se také nezabalí do konstantních registrů. Pořadí balení hlavního řádku a hlavního sloupce nemá žádný vliv na pořadí balení konstruktorů (které se vždy řídí řazením hlavního řádku).
Pořadí dat v matici lze deklarovat v době kompilace nebo kompilátor bude data za běhu uspořádat pro nejúčinnější použití.
Příklady
HLSL používá dva speciální typy, vektorový typ a maticový typ k usnadnění programování 2D a 3D grafiky. Každý z těchto typů obsahuje více než jednu komponentu; vektor obsahuje až čtyři komponenty a matice obsahuje až 16 složek. Při použití vektorů a matic ve standardních rovnicích HLSL je matematika navržena tak, aby fungovala pro jednotlivé součásti. HLSL například implementuje toto násobení:
float4 v = a*b;
jako čtyřsložkové násobení. Výsledkem jsou čtyři skaláry:
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;
Jedná se o čtyři násobení, kdy je každý výsledek uložen v samostatné komponentě v. Tomu se říká čtyřsložkové násobení. HLSL používá matematiku komponent, díky čemuž je psaní shaderů velmi efektivní.
To se velmi liší od násobení, které se obvykle implementuje jako tečkovaný produkt, který generuje jeden skalární:
v = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
Matice také používá operace jednotlivých komponent v HLSL:
float3x3 mat1,mat2;
...
float3x3 mat3 = mat1*mat2;
Výsledkem je násobení dvou matic podle jednotlivých součástí (na rozdíl od standardního 3x3 maticového násobení). Matice jednotlivých součástí vynásobí tento první termín:
mat3.m00 = mat1.m00 * mat2._m00;
Toto se liší od násobení matice 3x3, která by přinesla tento první termín:
// 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;
Přetížené verze násobení vnitřní funkce zpracovávají případy, kdy jeden operand je vektor a druhý operand je matice. Například: vektor * vektor, vektor * matice, matice * vektor a matice * matice . Například:
float4x3 World;
float4 main(float4 pos : SV_POSITION) : SV_POSITION
{
float4 val;
val.xyz = mul(pos,World);
val.w = 0;
return val;
}
vytvoří stejný výsledek jako:
float4x3 World;
float4 main(float4 pos : SV_POSITION) : SV_POSITION
{
float4 val;
val.xyz = (float3) mul((float1x4)pos,World);
val.w = 0;
return val;
}
Tento příklad přetypuje vektor pos na vektor sloupce pomocí přetypování (float1x4). Změna vektoru přetypováním nebo prohozením pořadí argumentů zadaných k násobení je ekvivalentní transponování matice.
Automatický převod přetypování způsobí, že funkce násobení a tečky vrátí stejné výsledky jako zde:
{
float4 val;
return mul(val,val);
}
Výsledkem násobení je vektor 1x4 × 4x1 = 1x1. Toto je ekvivalentem tečkového produktu:
{
float4 val;
return dot(val,val);
}
vrátí jednu skalární hodnotu.
Související témata
-
datových typů (DirectX HLSL)