2018년 1월 15일 월요일

HLSL Data Types

HLSL Data Types

이 글을 읽기전에 OpenGL GLSL 데이터 타입에 대한 글을 먼저 읽으면 이글을 이해하는데 도움이 될 것이다.



HLSL 은 다양한 고유의 데이터 타입을 지원한다. 이들은 쉐이더 변수로 선언하여 사용할 수 있으며 다음과 같다.

Buffer
Buffer 타입으로 , 한개 이상의 스칼라 값을 갖는다.

Scalar
한개의 스칼라 값

Vector, Matrix
다수의 값을 갖는 벡터 또는 행렬

Sampler, Shader, Texture
Sampler, shader, or texture object

Struct, User Defined
Custom structure or typedef




Buffer Type

다음과 같은 구문을 갖는다.

Buffer<Type> Name;

파라미터들은 다음과 같다.

Buffer  
버퍼 키워드

Type
scalr, vector matrix type중 하나.
matrix type으로 설정할 때는 행렬이 4개의 32비트 수량에 맞기만 하면 버퍼 변수로 선언할 수 있다.  예를들어, Buffer<float2x2> 는 가능하다. 그러나 Buffer<float4x4> 는 불가능하다.

Name
ASCII 문자열로 구성된 중복되지 않는 변수 이름


Buffer type examples

Buffer<float4> g_Buffer;
하나의 입력 인수로 제공되는 Load  내장 함수를 사용하여 버퍼의 데이터를 읽을 수 있다.

float4 bufferData = g_Buffer.Load( 1 );

Buffer는 배열과 같은 형식이다. 그러므로 위의 경우 g_Buffer 가 배열이라 생각하여 그 두번째 값을 가져오는 동작을 한다.



Scalar Types
HLSL 은 다양한 스칼라 데이터 타입을 제공하고 있다.

bool - true or false.

int - 32-bit signed integer.

uint - 32-bit unsigned integer.

dword - 32-bit unsigned integer.

half - 16-bit floating point value.
This data type is provided only for language compatibility.
Direct3D 10 shader targets map all half data types to float data types.
A half data type cannot be used on a uniform global variable (use the /Gec flag if this functionality is desired).

float - 32-bit floating point value.

double - 64-bit floating point value.
You cannot use double precision values as inputs and outputs for a stream.
To pass double precision values between shaders, declare each double as a pair of uint data types. Then, use the asdouble function to pack each double into the pair of uints and the asuint function to unpack the pair of uints back into the double.

Windows 8 HLSL 부터는 다음을 사용할 수 있다.

min16float - minimum 16-bit floating point value.
min10float - minimum 10-bit floating point value.
min16int - minimum 16-bit signed integer.
min12int - minimum 12-bit signed integer.
min16uint - minimum 16-bit unsigned integer.





Scalar Type 의 Literal 리터럴 형식

Floating point numbers

HLSL에서의 floating point 숫자는 다음의 형식으로 작성된다.

형식을 읽는 방법은

digit  는 0 부터 9까지의 숫자중 하나
digit-sequence 는 숫자의 나열 즉 한개 이상의 숫자열이다.
(opt) 는 optional 규칙으로 생략 가능한것.
.  소숫점 문자
sign  + 또는 - 기호



fractional-constant (분수형 상수, 즉 실수)
digit-sequence(opt) . digit-sequence
digit-sequence .

3.141592
.7438
1232.

exponent-part
e sign(opt) digit-sequence
E sign(opt) digit-sequence

sign
기호 + 또는 -

digit-sequence
digit
digit-sequence digit

floating-suffix
one of h H f F l L

“L” 접미사는 64 bit precision floating point literal 이다.
그렇지 않은 것은 32 bit float literal 이 기본이다.

예를 들어 다음과 같은 경우는 32 비트 정밀도를 갖고 그 이외의 비트는 무시된다.
double x = -0.6473313946860445;

다음의 경우 컴파일러는 64 bit precision floating-point로 인식할 것이다.
double x = -0.6473313946860445L;




Integer numbers

integer-constant integer-suffix(opt)

integer-constant
one of #(decimal number)

0# (octal number, 8진수)
0x# (hex number, 16진수)

integer-suffix can be any one of follow
u U l L


Characters

‘c’                                (character)
‘\a’ ‘\b’ ‘\f’ ‘\r’ ‘\t’ ‘\v’     (escapes)
‘\###’                          (octal escape)
‘\x#’                             (hex escape)
‘\c’                               (c is other character, include backslash and quotation marks)


escape 문자는 preprocess 표현식에는 사용할 수 없다.

Strings

“s”   (s is any string with escapes)


Identifiers

[A-Za-z_][A-Za-z0-9_]*

즉 시작은 영어 대소문자 또는 _ 언더바로 시작해야 하고 그 이후는 영어 대소문자 및 _ 언더바 그리고 숫자를 연속적으로 사용할 수 있다는 것이다.
다시 말해 C/C++ 언어에서 일반적인 변수 이름 정하는 규칙과 같다.


Operators

##, #@, ++, --, &, &, &, ||, ==, ::, <<, <<=, >>, >>=, ...,
<=, >=, !=, *=, /=, +=, -=, %=, &=, |=, ^=, ->



Vector Type
Syntax
TypeNumber Name

TypeComponents Name

TypeComponents 의 구성형식은 다음과 같다.

Type 부분
sclar type중 하나를 사용한다.

Components 부분
요소 숫자를 나타내는 부분으로 1 부터 4까지 숫자중 하나이어야 한다.

Name
ASCII 문자열로 구성된다. 변수 이름 규칙과 같다.

예를 들어 보면

bool    bVector;   // scalar containing 1 Boolean
int1    iVector = 1;
float3  fVector = { 0.2f, 0.3f, 0.4f };


Vector는 다음과 같이 선언할 수도 있다.

vector <Type, Number> VariableName


vector <int,    1> iVector = 1;
vector <double, 4> dVector = { 0.2, 0.3, 0.4, 0.5 };



Matrix Type

matrix 는 특수한 데이터 타입이다. matrix 는 1개에서 16개 까지의 구성 요소를 갖을수 있다.

다음의 형식으로 선언할 수 있다.

TypeComponents Name

TypeComponents 는 두 부분으로 나뉜다.

Type 부분은 scalar type중 하나를 사용한다.

Components 부분은 nxm 의 구조를 갖는다. n 과 m 은 각각 1에서 부터 4까지의 값을 가질수 있다. 즉 최대 4x4 , 4행 4열이 최대이다.

n 은 row 열, m 은 column 행 이 된다.
이는 GLSL 과 다르다. GLSL 은 앞의 숫자가 행이고 뒤의 숫자가 열이다.

Name
일반적인 변수 이름 지정 방식의 이름이다.

int1x1    iMatrix;   // integer matrix with 1 row,  1 column
int4x1    iMatrix;   // integer matrix with 4 rows, 1 column
int1x4    iMatrix;   // integer matrix with 1 row, 4 columns
double3x3 dMatrix;   // double matrix with 3 rows, 3 columns

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


matrix 는 다음과 같은 형식으로도 선언될 수 있다.

matrix <Type, Number> VariableName

여기서 Number 는 n,m 의 형식으로 열과 행을 의미하는 두개의 숫자를 나열한다.

이러한 방식의 예를 보여주면 다음과 같다.
matrix <float, 2, 2> fMatrix = { 0.0f, 0.1, // row 1
                                2.1f, 2.2f // row 2
                              };




Pre-Component Math Operations


HLSL 을 사용하면 알고리즘 수준의 쉐이더 프로그래밍을 할수 있다. 어떤 언어를 이해하려면 가장 먼저 변수와 함수를 선언하고 내장 함수를 사용하며, 사용자 정의 데이터 형식을 정의 할 줄 알고 그 의미를 알아야 한다.
또한 이러한 데이터들을 쉐이더 인수로 사용하며 쉐이어와 파이프라인에 연결하는 방법을 알아야 한다.

또한 HLSL 에서 쉐이더 코드 작성법을 배우기 위해서는 쉐이더 상수를 초기화 사고 필요한 경우 이들을 이용하여 다른 파이프 라인의 상태를 초기화 하는 방법을 알아야 한다.

여기서는 HLSL 에서 변수를 선언하고 그 값을 초기화 하는 다양한 방법에 대하여 설명하도록 할 것이다.

The Vector Type
벡터는 하나 또는 최대 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개의 component 구성요소를 갖게 된다.

위치에 사용할 수 있도록 x,y,z,w 셋
색상에 사용할 수 있도록 r,g,b,a 셋

이들은 이름으로 구분되지만 내부적으로는 같은내용이 된다.

다음의 예에서 이를 확인할 수 있다. z 와 b 는 서로 다른 셋 이지만 결국 같은 값을 의미하게 된다.

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

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

각 요소의 이름지정은 한개 이상을 사용할 수 있으나 혼용하지는 못한다.

즉 pos.xyz 이 가능하지만 pos.xyb 라고는 못쓴다.

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


Swizzling 이 가능하다. Swizzling 에 관해서는 OpenGL 의 GLSL 문서를 잠고하기 바란다.

다음은 swizzling 의 몇가지 예를 보여준다.

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;





또한 다음과 같이 이러한 swizzling 은 수식의 L-Value 즉 좌측값으로 사용할 수 있다.
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



또한 L-Value 에서도 컴포넌트 믹싱은 불가능하다. x 와 g 는 서로 다른 셋이기 때문에
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;

즉 위의 두 라인은 같은 결과가 된다.



The Matrix Type

행렬은 행과 열의 데이터로 구성된다. 내장되는 각각의 데이터들은 scalar 데이터 타입을 사용하게 된다.
또한 하나의 행렬에 있는 모든 데이터들은 같은 데이터 타입을 사용하게 된다.
열과 행의 구성 규칙은 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


row 와 column 갯수의 최대 값은 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
                              };

row 와 column 값을 참조하기 위해서는 참조 연선자 . 을 사용하면 된다.
행렬 변수이름 뒤에 . 을 붙이고 각각 행렬 값에 대한 인식자를 쓰면 된다.

다음은 행렬 요소들의 이름 셋이다.
0 기준에서의 이름셋
The zero-based row-column position:
_m00, _m01, _m02, _m03
_m10, _m11, _m12, _m13
_m20, _m21, _m22, _m23
_m30, _m31, _m32, _m33

1 기준으로의 이름셋
The one-based row-column position:
_11, _12, _13, _14
_21, _22, _23, _24
_31, _32, _33, _34
_41, _42, _43, _44


다음 예는 행렬의 각 인자들을 참조하는 방법을 보여주고 있다.
// 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

또한 다음과 같이 2차 배열 접근 방식으로도 참조할 수 있으며 이경우 0 을 시작으로 한다.

[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

배열 형식으로의 접근은 . 연산자를 사용하지 않는다. 배열 형식에 있어서는 swizzling 방법을 사용할 수 없다. 그러므로 다음과 같은 방법은 오류 이다.

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



다음과 같이 행렬의 한 열을 접근할 수도 있다.
float2 temp;
float2x2 fMatrix;
temp = fMatrix[0] // read the first row



다음과 같이 swizzling 에 의한 접근도 가능하다.

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





왼쪽 값으로 사용할 경우 같은 요소를 중복하여서는 swizzling 사용할 수 없다.
// cannot write to the same component more than once
tempMatrix._m00_m00 = worldMatrix.m00_m11;


왼쪽 값으로 사용할 경우 1베이스와 0베이스의 혼용도 가능하다.

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





Matrix Ordering
행렬 packing 순서는, uniform 파라미터들에 대한 행렬 값 설정은 column-major 이 기본값이다. 즉 행 우선을 사용한다.

이 말은 행렬의 각 행들이 단일 상수 레지스터에 저장된다는 것이다.
이에 반해, row-major 일 경우 각 row 가 단일 상수 레지스터에 저장된다.

이러한 것을 matrix packing 이라 하는디 이는 #pragmapack_matrix 전처리기로 있으며 , row_major 또는 column_major 키워드로도 설정할 수 있다.

row-major matrix 는 다음과 같은 모습이 된다.



column-major matrix일 경우는 다음과 같다.

Row-major 또는 Column-Major 행렬 패킹 방법은 shader로의 입력에 대해 그 값을 읽어 들이는데 영향을 준다.

데이터가 상수 레지스터에 쓰여지면 행렬 순서는 셰이더 코드 내에서 데이터가 사용되거나 액세스되는 방식에 영향을 미치지 않는다.

또한 쉐이더 본문에서 선언된 행렬은 상수 레지스터에 선언될 수 없다.

Row-major 또는 Column-major 방식은 constructor의 패킹 순서에 영항을 주지 않는다. constructor에서는 항상 row-major ordering을 따른다.

행렬에 대한 이러한 데이터 순서는 컴파일시에 선언할 수 있거나 또는 컴파일러가 가장 효율적인 사용을 위하여 런타임시에 정렬한다.






Examples

HLSL uses two special types, a vector type and a matrix type to make programming 2D and 3D graphics easier. Each of these types contain more than one component; a vector contains up to four components, and a matrix contains up to 16 components. When vectors and matrices are used in standard HLSL equations, the math performed is designed to work per-component. For instance, HLSL implements this multiply:



float4 v = a*b;


as a four-component multiply. The result is four scalars:



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*a.w;


This is four multiplications where each result is stored in a separate component of v. This is called a four-component multiply. HLSL uses component math which makes writing shaders very efficient.

This is very different from a multiply which is typically implemented as a dot product which generates a single scalar:



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


A matrix also uses per-component operations in HLSL:



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


The result is a per-component multiply of the two matrices (as opposed to a standard 3x3 matrix multiply). A per component matrix multiply yields this first term:



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


This is different from a 3x3 matrix multiply which would yield this first term:



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


Overloaded versions of the multiply intrinsic function handle cases where one operand is a vector and the other operand is a matrix. Such as: vector * vector, vector * matrix, matrix * vector, and matrix * matrix. For instance:



float4x3 World;

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

   return val;
}


produces the same result as:



float4x3 World;

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

   return val;
}


This example casts the pos vector to a column vector using the (float1x4) cast. Changing a vector by casting, or swapping the order of the arguments supplied to multiply is equivalent to transposing the matrix.

Automatic cast conversion causes the multiply and dot intrinsic functions to return the same results as used here:



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


This result of the multiply is a 1x4 * 4x1 = 1x1 vector. This is equivalent to a dot product:



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


which returns a single scalar value.

댓글 없음:

댓글 쓰기

Vertex , 정점 정보의 확장

Vertex , 정점 정보의 확장 그동안의 설명에 있어서 우리는 VERTEX 정보에 대하여 3 차원의 위치 정보만 사용하였다. struct VERTEX {float x,y,z;}; 이제 이를 확장하여 색상을...