Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Com o HLSL, você pode programar sombreadores em um nível de algoritmo. Para entender a linguagem, você precisará saber como declarar variáveis e funções, usar funções intrínsecas, definir tipos de dados personalizados e usar semântica para conectar argumentos de sombreador a outros sombreadores e ao pipeline.
Depois de aprender a criar sombreadores em HLSL, você precisará aprender sobre chamadas de API para que possa: compilar um sombreador para hardware específico, inicializar constantes de sombreador e inicializar outro estado de pipeline, se necessário.
O tipo de vetor
Um vetor é uma estrutura de dados que contém entre um e quatro componentes.
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
O inteiro imediatamente após o tipo de dados é o número de componentes no vetor.
Os inicializadores também podem ser incluídos nas declarações.
bool bVector = false;
int1 iVector = 1;
float3 fVector = { 0.2f, 0.3f, 0.4f };
double4 dVector = { 0.2, 0.3, 0.4, 0.5 };
Como alternativa, o tipo de vetor pode ser usado para fazer as mesmas declarações:
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 };
O tipo de vetor usa colchetes angulares para especificar o tipo e o número de componentes.
Os vetores contêm até quatro componentes, cada um dos quais pode ser acessado usando um dos dois conjuntos de nomenclatura:
- O conjunto de posições: x,y,z,w
- O conjunto de cores: r,g,b,a
Essas instruções retornam o valor no terceiro componente.
// Given
float4 pos = float4(0,0,2,1);
pos.z // value is 2
pos.b // value is 2
Os conjuntos de nomenclatura podem usar um ou mais componentes, mas não podem ser misturados.
// 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.
A especificação de um ou mais componentes vetoriais ao ler componentes é chamada de swizzling. Por exemplo:
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;
O mascaramento controla quantos componentes são gravados.
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;
As atribuições não podem ser gravadas no mesmo componente mais de uma vez. Portanto, o lado esquerdo desta afirmação é inválido:
f_4D.xx = pos.xy; // cannot write to the same destination components
Além disso, os espaços de nome do componente não podem ser misturados. Esta é uma gravação de componente inválida:
f_4D.xg = pos.rgrg; // invalid write: cannot mix component name spaces
Acessar um vetor como escalar acessará o primeiro componente do vetor. As duas afirmações a seguir são equivalentes.
f_4D.a = pos * 5.0f;
f_4D.a = pos.r * 5.0f;
O tipo de matriz
Uma matriz é uma estrutura de dados que contém linhas e colunas de dados. Os dados podem ser qualquer um dos tipos de dados escalares, no entanto, cada elemento de uma matriz é o mesmo tipo de dados. O número de linhas e colunas é especificado com a cadeia de caracteres linha por coluna que é acrescentada ao tipo de dados.
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
O número máximo de linhas ou colunas é 4; o número mínimo é 1.
Uma matriz pode ser inicializada quando é declarada:
float2x2 fMatrix = { 0.0f, 0.1, // row 1
2.1f, 2.2f // row 2
};
Ou, o tipo de matriz pode ser usado para fazer as mesmas declarações:
matrix <float, 2, 2> fMatrix = { 0.0f, 0.1, // row 1
2.1f, 2.2f // row 2
};
O tipo de matriz usa os colchetes angulares para especificar o tipo, o número de linhas e o número de colunas. Este exemplo cria uma matriz de ponto flutuante, com duas linhas e duas colunas. Qualquer um dos tipos de dados escalares pode ser usado.
Esta declaração define uma matriz de valores flutuantes (números de vírgula flutuante de 32 bits) com duas linhas e três colunas:
matrix <float, 2, 3> fFloatMatrix;
Uma matriz contém valores organizados em linhas e colunas, que podem ser acessados usando o operador de estrutura "." seguido por um dos dois conjuntos de nomenclatura:
- A posição da coluna de linha baseada em zero:
- _m00, _m01, _m02 _m03
- _m10, _m11, _m12 _m13
- _m20, _m21, _m22 _m23
- _m30, _m31, _m32 _m33
- A posição de coluna de linha com base em um:
- _11, _12, _13, _14
- _21, _22, _23, _24
- _31, _32, _33, _34
- _41, _42, _43, _44
Cada conjunto de nomenclatura começa com um sublinhado seguido do número da linha e do número da coluna. A convenção baseada em zero também inclui a letra "m" antes do número da linha e da coluna. Aqui está um exemplo que usa os dois conjuntos de nomenclatura para acessar uma matriz:
// 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
Assim como os vetores, os conjuntos de nomenclatura podem usar um ou mais componentes de qualquer conjunto de nomes.
// 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
Uma matriz também pode ser acessada usando a notação de acesso à matriz, que é um conjunto de índices baseado em zero. Cada índice está entre parênteses retos. Uma matriz 4x4 é acessada com os seguintes índices:
- [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]
Aqui está um exemplo de acesso a uma matriz:
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
Observe que o operador de estrutura "." não é usado para acessar uma matriz. A notação de acesso à matriz não pode usar swizzling para ler mais de um componente.
float2 temp;
temp = fMatrix[0][0]_[0][1] // invalid, cannot read two components
No entanto, o acesso à matriz pode ler um vetor de vários componentes.
float2 temp;
float2x2 fMatrix;
temp = fMatrix[0] // read the first row
Tal como acontece com os vetores, a leitura de mais de um componente da matriz é chamada de swizzling. Mais de um componente pode ser atribuído, supondo que apenas um espaço de nome seja usado. Estas são todas as atribuições válidas:
// 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;
O mascaramento controla quantos componentes são gravados.
// 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;
As atribuições não podem ser gravadas no mesmo componente mais de uma vez. Portanto, o lado esquerdo desta afirmação é inválido:
// cannot write to the same component more than once
tempMatrix._m00_m00 = worldMatrix._m00_m11;
Além disso, os espaços de nome do componente não podem ser misturados. Esta é uma gravação de componente inválida:
// Invalid use of same component on left side
tempMatrix._11_m23 = worldMatrix._11_22;
Ordenação matricial
A ordem de empacotamento da matriz para parâmetros uniformes é definida como coluna-maior por padrão. Isto significa que cada coluna da matriz é armazenada num único registo constante. Por outro lado, uma matriz de linha maior agrupa cada linha da matriz em um único registro constante. A embalagem matricial pode ser alterada com a diretiva #pragmapack_matrix ou com a palavra-chave row_major ou column_major.
Os dados em uma matriz são carregados em registros constantes de sombreador antes de um sombreador ser executado. Há duas opções para como os dados da matriz são lidos: em ordem de linha maior ou em ordem de coluna maior. Ordem de coluna maior significa que cada coluna da matriz será armazenada em um único registro constante, e ordem de linha maior significa que cada linha da matriz será armazenada em um único registro constante. Esta é uma consideração importante para quantos registros constantes são usados para uma matriz.
Uma matriz de linha maior é apresentada da seguinte forma:
11
21
31
41
12
22
32
42
13
23
33
43
14
24
34
44
Uma matriz de coluna maior é apresentada da seguinte forma:
11
12
13
14
21
22
23
24
31
32
33
34
41
42
43
44
A ordenação da matriz de linha maior e de coluna maior determina a ordem em que os componentes da matriz são lidos a partir das entradas de sombreador. Uma vez que os dados são gravados em registros constantes, a ordem da matriz não tem efeito sobre como os dados são usados ou acessados a partir do código de sombreador. Além disso, as matrizes declaradas em um corpo de sombreador não são agrupadas em registros constantes. A ordem de empacotamento de linha maior e coluna principal não tem influência sobre a ordem de empacotamento dos construtores (que sempre segue a ordem de linha principal).
A ordem dos dados em uma matriz pode ser declarada em tempo de compilação ou o compilador ordenará os dados em tempo de execução para o uso mais eficiente.
Exemplos
O HLSL usa dois tipos especiais, um tipo vetorial e um tipo de matriz para facilitar a programação de gráficos 2D e 3D. Cada um desses tipos contém mais de um componente; Um vetor contém até quatro componentes e uma matriz contém até 16 componentes. Quando vetores e matrizes são usados em equações HLSL padrão, a matemática realizada é projetada para trabalhar por componente. Por exemplo, o HLSL implementa esta multiplicação:
float4 v = a*b;
como uma multiplicação de quatro componentes. O resultado são quatro escalares:
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;
São quatro multiplicações em que cada resultado é armazenado em um componente separado de v. Isso é chamado de multiplicação de quatro componentes. O HLSL usa matemática de componentes, o que torna os sombreadores de escrita muito eficientes.
Isso é muito diferente de uma multiplicação que normalmente é implementada como um produto ponto que gera um único escalar:
v = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
Uma matriz também usa operações por componente em HLSL:
float3x3 mat1,mat2;
...
float3x3 mat3 = mat1*mat2;
O resultado é uma multiplicação por componente das duas matrizes (em oposição a uma multiplicação de matriz padrão 3x3). Uma multiplicação por matriz por componente produz este primeiro termo:
mat3.m00 = mat1.m00 * mat2._m00;
Isto é diferente de uma multiplicação matricial 3x3 que produziria este primeiro termo:
// 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;
Versões sobrecarregadas da função intrínseca multiplicar manipulam casos em que um operando é um vetor e o outro operando é uma matriz. Tais como: vetor * vetor, vetor * matriz, matriz * vetor e matriz * matriz. Por exemplo:
float4x3 World;
float4 main(float4 pos : SV_POSITION) : SV_POSITION
{
float4 val;
val.xyz = mul(pos,World);
val.w = 0;
return val;
}
produz o mesmo resultado que:
float4x3 World;
float4 main(float4 pos : SV_POSITION) : SV_POSITION
{
float4 val;
val.xyz = (float3) mul((float1x4)pos,World);
val.w = 0;
return val;
}
Este exemplo converte o vetor pos em um vetor coluna usando o cast (float1x4). Alterar um vetor por casting ou trocar a ordem dos argumentos fornecidos para multiplicar é equivalente a transpor a matriz.
A conversão automática de transmissão faz com que as funções intrínsecas de multiplicação e ponto retornem os mesmos resultados usados aqui:
{
float4 val;
return mul(val,val);
}
Este resultado da multiplicação é um vetor 1x4 * 4x1 = 1x1. Isto é equivalente a um produto ponto:
{
float4 val;
return dot(val,val);
}
que retorna um único valor escalar.
Tópicos relacionados