다음을 통해 공유


Per-Component 수학 연산

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;

행렬 형식

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

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;

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

  • 0부터 시작하는 행 열 위치:
    • _m00, _m01, _m02, _m03
    • _m10, _m11, _m12, _m13
    • _m20, _m21, _m22, _m23
    • _m30, _m31, _m32, _m33
  • 1부터 시작하는 행 열 위치:
    • _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)