Поделиться через


Математические операции Per-Component

С помощью HLSL можно программирует шейдеры на уровне алгоритма. Чтобы понять язык, необходимо знать, как объявлять переменные и функции, использовать встроенные функции, определять пользовательские типы данных и использовать семантику для подключения аргументов шейдера к другим шейдерам и конвейеру.

После того как вы узнаете, как создавать шейдеры в HLSL, вам потребуется ознакомиться с вызовами API, чтобы можно было: скомпилировать шейдер для определенного оборудования, инициализировать константы шейдера и при необходимости инициализировать другое состояние конвейера.

Тип вектора

Вектор — это структура данных, содержащая от одного до четырех компонентов.

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

Целое число сразу после типа данных — это количество компонентов в векторе.

Инициализаторы также могут быть включены в объявления.

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

Кроме того, тип вектора можно использовать для создания одинаковых объявлений:

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

Тип вектора использует угловые скобки для указания типа и количества компонентов.

Векторы содержат до четырех компонентов, доступ к каждому из которых можно получить с помощью одного из двух наборов именования:

  • Набор позиций: x, y, z, w
  • Набор цветов: r, g, b, a

Эти операторы возвращают значение в третьем компоненте.

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

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

Наборы имен могут использовать один или несколько компонентов, но их нельзя смешивать.

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

Указание одного или нескольких компонентов вектора при чтении компонентов называется поворотным. Пример:

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;

Маскирование определяет количество записанных компонентов.

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;

Назначения не могут быть записаны в один и тот же компонент более одного раза. Таким образом, левая часть этой инструкции недопустима:

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

Кроме того, пространства имен компонентов нельзя смешивать. Это недопустимая запись компонента:

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

Доступ к вектору как скалярный будет обращаться к первому компоненту вектора. Следующие два оператора эквивалентны.

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

Тип матрицы

Матрица — это структура данных, содержащая строки и столбцы данных. Данные могут иметь любой из скалярных типов данных, однако каждый элемент матрицы имеет один и тот же тип данных. Количество строк и столбцов указывается с помощью строки построчно, которая добавляется к типу данных.

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

Максимальное количество строк или столбцов — 4; минимальное число — 1.

Матрицу можно инициализировать, когда она объявлена:

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

Или тип матрицы можно использовать для создания одинаковых объявлений:

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

Тип матрицы использует угловые скобки для указания типа, количества строк и количества столбцов. В этом примере создается матрица с плавающей запятой с двумя строками и двумя столбцами. Можно использовать любой из скалярных типов данных.

Это объявление определяет матрицу значений с плавающей запятой (32-разрядные числа с плавающей запятой) с двумя строками и тремя столбцами:

matrix <float, 2, 3> fFloatMatrix;

Матрица содержит значения, упорядоченные по строкам и столбцам, доступ к которым можно получить с помощью оператора структуры "." и одного из двух наборов именования:

  • Отсчитываемая от нуля позиция столбца строки:
    • _m00, _m01, _m02, _m03
    • _m10, _m11, _m12, _m13
    • _m20, _m21, _m22, _m23
    • _m30, _m31, _m32, _m33
  • Позиция столбца с одной строкой:
    • _11, _12, _13, _14
    • _21, _22, _23, _24
    • _31, _32, _33, _34
    • _41, _42, _43, _44

Каждый набор именования начинается с символа подчеркивания, за которым следует номер строки и номер столбца. Соглашение от нуля также включает букву "m" перед номером строки и столбца. Ниже приведен пример, в котором используются два набора именования для доступа к матрице:

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

Как и векторы, наборы именования могут использовать один или несколько компонентов из любого набора именования.

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

Доступ к матрице также можно получить с помощью нотации доступа к массиву, которая представляет собой отсчитываемый от нуля набор индексов. Каждый индекс находится внутри квадратных скобок. Доступ к матрице 4x4 осуществляется со следующими индексами:

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

Ниже приведен пример доступа к матрице:

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

Обратите внимание, что оператор структуры "." не используется для доступа к массиву. Нотация доступа к массиву не может использовать свертки для чтения нескольких компонентов.

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

Однако доступ к массиву может считывать многокомпонентный вектор.

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

Как и векторы, чтение нескольких матричных компонентов называется свертыванием. Можно назначить несколько компонентов, при условии, что используется только одно пространство имен. Это все допустимые назначения:

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

Маскирование определяет количество записанных компонентов.

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

Назначения не могут быть записаны в один и тот же компонент более одного раза. Таким образом, левая часть этой инструкции недопустима:

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

Кроме того, пространства имен компонентов нельзя смешивать. Это недопустимая запись компонента:

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

Упорядочение матриц

Порядок упаковки матрицы для универсальных параметров по умолчанию имеет значение column-major. Это означает, что каждый столбец матрицы хранится в одном регистре констант. С другой стороны, матрица с основной строкой упаковывает каждую строку матрицы в один постоянный регистр. Упаковку матрицы можно изменить с помощью директивы #pragmapack_matrix, row_major или ключевое слово column_major.

Данные в матрице загружаются в регистры констант шейдера перед запуском шейдера. Существует два варианта считывания матричных данных: в основном по строкам или по столбцам. Основной порядок столбцов означает, что каждый столбец матрицы будет храниться в одном регистре констант, а порядок основных строк означает, что каждая строка матрицы будет храниться в одном регистре констант. Это важный фактор, определяющий, сколько регистров констант используется для матрицы.

Матрица основных строк выложена следующим образом:

11
21
31
41

12
22
32
42

13
23
33
43

14
24
34
44

 

Матрица основного столбца выглядит следующим образом:

11
12
13
14

21
22
23
24

31
32
33
34

41
42
43
44

 

Упорядочение матриц по основным строкам и столбцам определяет порядок считывания компонентов матрицы из входных данных шейдера. После записи данных в постоянные регистры порядок матрицы не влияет на то, как данные используются или обращаются из кода шейдера. Кроме того, матрицы, объявленные в теле шейдера, не упаковываются в постоянные регистры. Порядок упаковки основных строк и столбцов не влияет на порядок упаковки конструкторов (который всегда соответствует упорядочению по основным строкам).

Порядок данных в матрице можно объявить во время компиляции или компилятор упорядочит данные во время выполнения для наиболее эффективного использования.

Примеры

HLSL использует два специальных типа: векторный и матричный, чтобы упростить программирование двухмерной и трехмерной графики. Каждый из этих типов содержит несколько компонентов; вектор содержит до четырех компонентов, а матрица содержит до 16 компонентов. Если векторы и матрицы используются в стандартных уравнениях HLSL, выполняемая математика предназначена для каждого компонента. Например, HLSL реализует это умножение:

float4 v = a*b;

в виде четырехкомпонентного умножения. Результатом будет четыре скалярных значения:

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;

Это четыре умножения, где каждый результат хранится в отдельном компоненте v. Это называется умножением из четырех компонентов. HLSL использует математику компонентов, что делает написание шейдеров очень эффективным.

Это очень отличается от умножения, которое обычно реализуется как точечное произведение, которое создает один скаляр:

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

Матрица также использует операции с компонентами в HLSL:

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

Результатом является умножение на каждый компонент двух матриц (в отличие от стандартного матричного умножения 3x3). Умножение матрицы компонентов дает первый термин:

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

Это отличается от матричного умножения 3x3, которое даст этот первый термин:

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

Перегруженные версии встроенной функции умножения обрабатывают случаи, когда один операнд является вектором, а другой — матрицей. Например: вектор * вектор, вектор * матрица, матрица * вектор и матрица * матрица. Например:

float4x3 World;

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

    return val;
}   

дает тот же результат, что и:

float4x3 World;

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

    return val;
}   

В этом примере вектор pos приводится к вектору столбца с помощью приведения (float1x4). Изменение вектора путем приведения или переключения порядка аргументов, предоставленных для умножения, эквивалентно транспонированием матрицы.

Автоматическое преобразование приведения приводит к тому, что встроенные функции умножения и точки возвращают те же результаты, что и здесь:

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

Результатом умножения является вектор 1x4 * 4x1 = 1x1. Это эквивалентно точечному продукту:

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

возвращает одно скалярное значение.

Типы данных (DirectX HLSL)