Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

codingfarm

6. Direct3D의 그리기 연산 - 예제 정점 셰이더 본문

computer graphics/DX12 book

6. Direct3D의 그리기 연산 - 예제 정점 셰이더

scarecrow1992 2021. 6. 6. 01:43

HLSL(high level shading language)

  • 정의 : 셰이더를 작성하기 위한 고수준 셰이딩 언어
  • 텍스트 파일로 작성, 확장자 .hlsl로 저장
  • HLSL에서 함수는 항상 인라인화 됨

다음은 간단한 정점 셰이더의 구현이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cbuffer cbPerObject : register(b0) {
    float4x4 gWorldViewProj; 
};
 
void VS(float3 iPosL : POSITION,
    float4 iColor : COLOR,
    out float4 oPosH : SV_POSITION,
    out float4 oColor : COLOR)
{
    // 동차 절단 공간으로 변환
    oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
    
    // 정점 색상을 그대로 픽셀 셰이더에 전달한다.
    oColor = iColor;
}
cs

본질적으로 정점셰이더는 하나의 함수이다.

1~3번째 줄의 코드 구현부는 상수 버퍼에서 배우지만 미리 설명하자면

cbPerObject라는 constant buffer object를 참조하는 constant buffer sturct이다. 이 예에서 constant buffer는 gWorldProj라는 4$\times$4 행렬 하나만 저장한다. 또한 이 constant buffer resource는 constant buffer register slot 0에 binding 된다.

 

정점 셰이더의 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cbuffer cbPerObject : register(b0) {
    float4x4 gWorldViewProj; 
};
 
struct Vertex{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
 
D3D12_INPUT_ELEMENT_DESC vertexDesc[] = {
    {"POSITION"0, DXGI_FORMAT_R32G32B32_FLOAT, 00,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR"0, DXGI_FORMAT_R32G32B32A32_FLOAT, 012,
        D3D12_INPUT_PER_VERTEX_DATA, 0}
};
 
void VS(float3 iPosL : POSITION,
    float4 iColor : COLOR,
    out float4 oPosH : SV_POSITION,
    out float4 oColor : COLOR)
{
    // 동차 절단 공간으로 변환
    oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
    
    // 정점 색상을 그대로 픽셀 셰이더에 전달한다.
    oColor = iColor;
}
cs

ㅇㄹ

D3D12_INPUT_ELEMENT_DESC

  • struct Vertex의 각 성분에 semantic을 부여함
  • 이 semantic을 통해 struct Vertex의 성분들을 vertex shader parameters에 대응시킴

 

입력 매개변수

  • 처음 2개의 매개 변수
  • 정점 셰이더의 입력 서명(input signature)를 형성함
  • 맨 뒤의 의미소(semantic)을 통해 현재의 그리기 작업에 쓰이는 커스텀 정점 구조체의 멤버들에 대응됨

 

출력 매개변수

  • 정점 셰이더의 출력을 pipeline의 다음 단계(기하 셰이더 또는 픽셀 셰이더)의 해당 입력에 대응시킴
  • SV_로 시작하는 semantic은 이것이 system value(시스템 값) semantic임을 의미함
    • 해당 정점 셰이더 출력 성분이 (동차 절단 공간에서의) 정점의 위치를 담고 있음을 나타냄
    • GPU는 절단, 깊이판정, 래스터화 등등 다른 특성들에는 적용하지 않는 특별한 연산들을 위치에 적용하므로, 이처럼 SV_POSITION semantic을 지정해서 GPU에게 이것이 위치를 담은 출력 성분임을 알려주어야 함
  • COLOR semantic은 응용 프로그램이 D3D12_INPUT)ELEMENT_DESC 배열을 통해 지정한 이름임

 

기능

oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);

VS 함수의 첫줄은 정점 위치에 4$\times$4 행렬 gWorldViewProj를 곱해서 정점을 국소 공간에서 동차 절단 공간으로 변환한다. 즉, world 좌표계 기준의 iPosL좌표를 gWorldViewProj기준의 local 좌표계로 바꾸기 위한 변환이다.

gWorldViewProj :  상수 버퍼(contant buffer)라 불리는 버퍼에 들어있는 data

(상수 버퍼에 관해선 추후 논의)

 

oColor = iColor;

마지막줄의 대입 문장을 통해 입력 생상을 그대로 출력 매개변수에 복사한다. 이를 통해 그 색상이 pipeline의 다음 단계에 공급된다.

 

이 정점 셰이더를 아래처럼 구현할 수도 있다. 입, 출력을 위한 매개변수를 별도의 struct로 묶음으로써 정점셰이더의 구현부를 보다 간결하게 표현할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//***************************************************************************************
// color.hlsl by Frank Luna (C) 2015 All Rights Reserved.
//
// Transforms and colors geometry.
//***************************************************************************************
 
cbuffer cbPerObject : register(b0) {
    float4x4 gWorldViewProj; 
};
 
struct VertexIn {
    float3 PosL  : POSITION;
    float4 Color : COLOR;
};
 
struct VertexOut {
    float4 PosH  : SV_POSITION;
    float4 Color : COLOR;
};
 
VertexOut VS(VertexIn vin) {
    VertexOut vout;
    
    // Transform to homogeneous clip space.
    vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
    
    // Just pass vertex color into the pixel shader.
    vout.Color = vin.Color;
    
    return vout;
}
cs

 

 

기하 셰이더(12장)을 사용하지 않으면, 정점 셰이더의 출력은 반드시 의미소가 SV_POSITION인, 동차 절단 공간에서의 정점위치이어야 한다. 기하 셰이더가 없을 때 HW는 정점 셰이더를 떠난 정점들이 동차 절단공간에 있다고 가정하기 때문이다. 기하 셰이더를 사용하는 경우에는 동차 절단 공간 위치의 출력을 기하 셰이더에 미룰 수 있다.
정점 셰이더(또는 기하 셰이더)가 원근 나누기까지 수행하지는 말아야 한다. 투영 행렬을 곱하는 부분만 책임지면 된다. 원근 나누기는 나중에 HW가 수행한다.

 

 

1. 입력 배치(input layout) 서술과 입력 서명(input signature) 연결

입력 배치 서술(input layout description) : 앞서 말했듯이 pipeline에 공급되는 vertex들의 특성들과 vertex shader의 argument들 사이에는 연관 관계를 정의하는것

pipeline에 공급된 vertex들이 vertex shader가 기대하는 모든 입력을 제공하지 못하면 오류가 발생한다.

가령 아래의 vertex shader input signature와 vertex data는 호환되지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct Vertex{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
 
D3D12_INPUT_ELEMENT_DESC vertexDesc[] = {
    {"POSITION"0, DXGI_FORMAT_R32G32B32_FLOAT, 00,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR"0, DXGI_FORMAT_R32G32B32A32_FLOAT, 012,
        D3D12_INPUT_PER_VERTEX_DATA, 0}
};
 
struct VertexIn {
    float3 PosL  : POSITION;
    float4 Color : COLOR;
    float3 Normal : NORMAL;
};
 
struct VertexOut {
    float4 PosH  : SV_POSITION;
    float4 Color : COLOR;
};
 
VertexOut VS(VertexIn vin) {
    VertexOut vout;
    
    // Transform to homogeneous clip space.
    vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
    
    // Just pass vertex color into the pixel shader.
    vout.Color = vin.Color;
    
    return vout;
}
 
cs

위 코드는 Pipeline에 공급된 정점들(Input layout description으로 확인 가능)이 VertexShader가 기대하는 모든 입력을 제공하지 못하여 에러가 발생한다.

 

하지만, 정점 자료와 input signature가 정확히 일치할 필요는 없다. 가령 아래 정점 자료와 정점 셰이더는 호환된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct Vertex{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
    XMFLOAT3 Normal;
};
 
D3D12_INPUT_ELEMENT_DESC vertexDesc[] = {
    {"POSITION"0, DXGI_FORMAT_R32G32B32_FLOAT, 00,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR"0, DXGI_FORMAT_R32G32B32A32_FLOAT, 012,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"NORMAL"0, DXGI_FORMAT_R32G32B32_FLOAT, 028,
        D3D12_INPUT_PER_VERTEX_DATA, 0}
};
 
struct VertexIn {
    float3 PosL  : POSITION;
    float4 Color : COLOR;
};
 
struct VertexOut {
    float4 PosH  : SV_POSITION;
    float4 Color : COLOR;
};
 
VertexOut VS(VertexIn vin) { ... }
 
cs

 

정점 자료는 정점 셰이더가 기대하는 ($\approx$VS의 입력 매개변수로 주어지는) 모든 정보를 (struct Vertex와 input layout description을 통해) 제공해야한다.

 

그렇다면 아래 예 처럼 struct Vertex와 input signature의 정점 특성들이 부합하긴 하지만 구체적인 형식이 다를경우는 어떨까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Vertex{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
 
D3D12_INPUT_ELEMENT_DESC vertexDesc[] = {
    {"POSITION"0, DXGI_FORMAT_R32G32B32_FLOAT, 00,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR"0, DXGI_FORMAT_R32G32B32A32_FLOAT, 012,
        D3D12_INPUT_PER_VERTEX_DATA, 0},
};
 
struct VertexIn {
    float3 PosL  : POSITION;
    int4 Color : COLOR;
};
 
struct VertexOut {
    float4 PosH  : SV_POSITION;
    float4 Color : COLOR;
};
 
VertexOut VS(VertexIn vin) { ... }
cs

에러도 안나오고 컴파일도 제대로 된다. Direct3D는 입력 레지스터 비트들의 재해석(reinterpret)를 허용하기 때문이다. 다만 warning 메시지는 나올 수 있다.

 

6.9에서 보겠지만, ID3D12PipelineState 객체를 생성할 때 반드시 입력 배치 서술과 정점 셰이더를 함께 지정해야 한다. 그러면 Direct3D는 주어진 input layout description과 vertex shader가 호환되는지 점검한다.

 

사실 너무 기능 위주로만 설명해서 대체 저 기능들이 프로그램의 어디에 적혀야 하는지, 그래서 앞서 배웠던 vertex buffer와 어떤식으로 함께 쓰이는지 등에 대한 정보가 전무하다. 이에 대한 정보는 이 포스팅에 나온다.

Comments