구성 요소별 수학 연산

HLSL을 사용하면 알고리즘 수준에서 셰이더를 프로그래밍할 수 있습니다. 언어를 이해하려면 변수 및 함수를 선언하고, 내장 함수를 사용하고, 사용자 지정 데이터 형식을 정의하고, 시맨틱을 사용하여 셰이더 인수를 다른 셰이더 및 파이프라인에 연결하는 방법을 알아야 합니다.

HLSL에서 셰이더를 작성하는 방법을 배우면 API 호출에 대해 알아보아야 합니다. 그러면 특정 하드웨어에 대한 셰이더를 컴파일하고, 셰이더 상수를 초기화하고, 필요한 경우 다른 파이프라인 상태를 초기화할 수 있습니다.

벡터 형식

벡터는 1~4개의 구성 요소를 포함하는 데이터 구조입니다.

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

벡터 형식은 꺾쇠 괄호를 사용하여 구성 요소의 형식과 개수를 지정합니다.

벡터에는 각각 두 개의 명명 집합 중 하나를 사용하여 액세스할 수 있는 구성 요소가 최대 4개 포함되어 있습니다.

  • 위치 집합: 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;

행렬 형식

행렬은 데이터의 행과 열을 포함하는 데이터 구조입니다. 데이터는 스칼라 데이터 형식일 수 있지만 행렬의 모든 요소는 데이터 형식이 동일합니다. 행 및 열의 수는 데이터 형식에 추가되는 row-by-column 문자열로 지정됩니다.

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

행렬 형식은 꺾쇠 괄호를 사용하여 형식, 행 수 및 열 수를 지정합니다. 다음 예제에서는 두 개의 행과 두 개의 열이 있는 부동 소수점 행렬을 만듭니다. 스칼라 데이터 형식 중 하나를 사용할 수 있습니다.

이 선언은 두 개의 행과 세 개의 열이 있는 float 값(32비트 부동 소수점 숫자)의 행렬을 정의합니다.

matrix <float, 2, 3> fFloatMatrix;

행렬에는 구조 연산자 “.”와 그 뒤에 오는 두 개의 명명 집합 중 하나를 사용하여 액세스할 수 있는 행과 열로 구성된 값이 포함됩니다.

  • 0 기준 행-열 위치:
    • _m00, _m01, _m02, _m03
    • _m10, _m11, _m12, _m13
    • _m20, _m21, _m22, _m23
    • _m30, _m31, _m32, _m33
  • 0 기준 행-열 위치:
    • _11, _12, _13, _14
    • _21, _22, _23, _24
    • _31, _32, _33, _34
    • _41, _42, _43, _44

각 명명 집합은 밑줄과 그 뒤에 오는 행 번호 및 열 번호로 시작합니다. 0 기준 규칙에는 행 및 열 번호 앞에 문자 “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

0 기준 인덱스 집합인 배열 액세스 표기법을 사용하여 행렬에 액세스할 수도 있습니다. 각 인덱스는 대괄호로 둘러싸여 있습니다. 다음 인덱스를 사용하여 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; 

행렬 순서 지정

균일 매개 변수에 대한 행렬 패킹 순서는 기본적으로 열 우선으로 설정됩니다. 즉, 행렬의 각 열이 단일 상수 레지스터에 저장됩니다. 반면에 행 우선 행렬은 행렬의 각 행을 단일 상수 레지스터로 압축합니다. 행렬 패킹은 #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은 프로그래밍 2D 및 3D 그래픽을 더 쉽게 만들기 위해 벡터 형식과 행렬 형식이라는 두 가지 특수 형식을 사용합니다. 이러한 각 형식에는 둘 이상의 구성 요소가 포함되며, 벡터에는 최대 4개의 구성 요소가, 행렬에는 최대 16개의 구성 요소가 포함됩니다. 표준 HLSL 수식에서 벡터와 행렬을 사용하는 경우 수행되는 수학은 구성 요소별로 작동하도록 설계되었습니다. 예를 들어 HLSL은 다음 곱하기를

float4 v = a*b;

4구성 요소 곱하기로 구현합니다. 그 결과는 다음과 같은 4개의 스칼라입니다.

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의 별도 구성 요소에 저장되는 네 가지 곱셈입니다. 이를 4-구성 요소 곱하기라고 합니다. 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;
}   

다음은 (float1x4) 캐스트를 사용하여 pos 벡터를 열 벡터로 캐스팅하는 예제입니다. 캐스팅을 통해 벡터를 변경하거나 곱하기 위해 제공된 인수의 순서를 교환하는 것은 행렬 전치와 같습니다.

자동 캐스트 변환을 사용하면 곱하기 및 점 내장 함수가 여기서 사용된 것과 동일한 결과를 반환합니다.

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

이 곱하기의 결과는 1x4 * 4x1 = 1x1 벡터입니다. 이는 내적과 동일합니다.

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

위 예제는 단일 스칼라 값을 반환합니다.

데이터 형식(DirectX HLSL)