Thinking Different





Tutorial 4 - 버퍼, 쉐이더 및 HLSL


원문 : http://www.rastertek.com/dx11s2tut04.html



이 튜토리얼은 DirectX 11에서 버텍스 및 픽셀 쉐이더를 작성하는 방법을 소개합니다. 또한 DirectX 11에서 버텍스 및 인덱스 버퍼를 사용하는 방법에 대해서도 소개합니다. 3D 그래픽 렌더링을 이해하기 위한 가장 기본적인 개념입니다.



버텍스 버퍼


이해해야 할 첫 번째 개념은 정점 버퍼입니다. 이 개념을 설명하기 위해 구의 3D 모델을 예로 들어 보겠습니다. 3D 구형 모델은 실제로 수백 개의 삼각형으로 구성됩니다.



구형 모델의 각 삼각형에는 3 개의 점이 있으며, 각 점을 꼭짓점이라고 부릅니다. 따라서 우리가 구형 모델을 렌더링하려면 구형을 구성하는 모든 정점을 정점 버퍼라고 부르는 특수 데이터 배열에 넣어야합니다. 구형 모델의 모든 점이 버텍스 버퍼에 있으면 우리는 버텍스 버퍼를 GPU에 전송하여 모델을 렌더링 할 수 있습니다.


인덱스 버퍼


인덱스 버퍼는 정점 버퍼와 관련이 있습니다. 그 목적은 정점 버퍼에있는 각 정점의 위치를 ​​기록하는 것입니다. 그런 다음 GPU는 인덱스 버퍼를 사용하여 정점 버퍼의 특정 정점을 빠르게 찾습니다. 색인 버퍼의 개념은 책에서 색인을 사용하는 개념과 유사하며 찾고 있는 주제를 훨씬 더 빠르게 찾을 수 있습니다. DirectX 문서에서는 인덱스 버퍼를 사용하면 비디오 메모리의 더 빠른 위치에 정점 데이터를 캐싱 할 가능성을 높일 수 있다고 합니다. 따라서 성능상의 이유로도 인덱스 버퍼 사용을 권장합니다.



버텍스 쉐이더


정점 셰이더는 주로 정점 버퍼의 정점들을 3D공간으로 변환시켜주는 작은 프로그램입니다. 이 외에도 각 정점의 법선을 계산한다던가 하는 다른 연산도 가능합니다. 정점 셰이더 프로그램은 GPU에서 계산이 필요하다고 판단될 때 호출됩니다. 예를 들어, 5,000개의 폴리곤을 가진 모델을 화면에 표시한다면 단지 저 하나의 모델을 그리기 위해 매 프레임마다 15000번의 정점 셰이더 프로그램이 실행됩니다. 따라서 프로그램이 60fps의 fps를 가진 그래픽 프로그램에서는 단지 5000개의 삼각형을 그리기 위해 매 초마다 900,000번의 정점 셰이더를 호출하게 됩니다. 따라서 효율적인 정점 셰이더를 작성하는 것이 중요합니다.



픽셀 셰이더


픽셀 셰이더는 그리고자 하는 도형에 색상을 입힐 때를 위한 작은 프로그램입니다. 이것은 화면에 보여지는 모든 픽셀들에 대해 GPU에서 연산됩니다. 색상을 입히고(coloring), 텍스쳐를 입히고(texturing), 광원 효과를 주고(lighting), 그 외 다른 많은 도형 채색 효과를 주는 것이 바로 이 픽셀 셰이더 프로그램에서 제어됩니다. 픽셀 셰이더는 GPU에 의해 수없이 호출되기 때문에 반드시 효율적으로 작성되어야 합니다.



HLSL


HLSL은 앞서 설명한 DirectX 11에서 사용하는 작은 정점 및 픽셀 셰이더 프로그램을 작성할 때 사용하는 일종의 언어입니다. 구문은 미리 정의된 타입이 있다는 것을 빼면 C언어와 거의 동일합니다. HLSL 프로그램 파일은 전역 변수, 타입 정의, 정점 셰이더, 픽셀 셰이더, 그리고 기하 셰이더(geometry shader)로 구성되어 있습니다. 이 튜토리얼은 HLSL을 사용하는 첫번째이므로 여기서는 우선 DirectX 11을 이용하여 아주 간단한 HLSL 프로그램을 실행해 볼 것입니다.



프레임워크 업데이트



이 튜토리얼은 위해 프레임워크가 한층 업데이트되었습니다. GraphicsClass 안에 CameraClass, ModelClass, ColorShaderClass라고 하는 세 클래스를 추가하였습니다. CameraClass는 지난번에 이야기했던 뷰 행렬을 다룰 것입니다. 이것은 현재 월드에서의 카메라의 위치, 보는 방향을 제어하며 이 위치를 필요한 셰이더에 전달합니다. ModelClass는 3D 객체들의 기하학적인 부분을 다룰 것인데, 이 튜토리얼에서는 단순함을 유지하기 위해 3D 객체를 삼각형으로 할 것입니다. 그리고 마지막으로 ColorShaderClass는 직접 작성한 HLSL 셰이더를 호출하여 객체들을 그리는 일을 맡게 될 것입니다.




우선 HLSL 셰이더 프로그램부터 시작하도록 하겠습니다.


이것이 우리의 첫번째 셰이더 프로그램이 될 것입니다. 셰이더는 실제 모델의 렌더링을 수행하는 작은 프로그램입니다. 이 셰이더는 color.vs와 color.ps라는 소스파일에 HLSL로 작성되었습니다. 우선은 이 파일들을 엔진의 cpp파일과 h파일과 같은 위치에 놓았습니다. 이 셰이더의 목적은 우선 첫 HLSL 튜토리얼이기 때문에 가능한 한 단순함을 유지하면서 색상이 있는 삼각형을 그리고자 하는 것입니다. 우선 정점 셰이더부터 보도록 하겠습니다.


Color.vs


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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
////////////////////////////////////////////////////////////////////////////////
// Filename: color.vs
////////////////////////////////////////////////////////////////////////////////
 
 
/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};
 
 
//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float4 color : COLOR;
};
 
struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};
 
 
////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType ColorVertexShader(VertexInputType input)
{
    PixelInputType output;
    
 
    // 적절한 행렬 계산을 위해 위치 벡터를 4 단위로 변경합니다.
    input.position.w = 1.0f;
 
    // 월드, 뷰 및 투영 행렬에 대한 정점의 위치를 ​​계산합니다.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // 픽셀 쉐이더가 사용할 입력 색상을 저장합니다.
    output.color = input.color;
    
    return output;
}
cs



픽셀 셰이더는 화면에 그려지는 도형의 각 픽셀들을 실제로 그립니다. 이 픽셀 셰이더에서는 PixelInputType을 입력으로 사용하고 최종 픽셀의 색상이 저장된 float4 타입을 반환합니다. 여기서 소개하는 픽셀 셰이더는 단지 입력 색상을 바로 출력으로 내보내는 단순한 프로그램입니다. 기억해 둘 것은, 정점 셰이더의 결과물을 픽셀 셰이더에서 사용한다는 점입니다.


Color.ps


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
////////////////////////////////////////////////////////////////////////////////
// Filename: color.ps
////////////////////////////////////////////////////////////////////////////////
 
 
//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};
 
 
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 ColorPixelShader(PixelInputType input) : SV_TARGET
{
    return input.color;
}
cs



다음은 ColorShaderClass에 대해 살펴보겠습니다. ColorShaderClass는 GPU에있는 3D 모델을 그리기 위해 HLSL 쉐이더를 호출하는 데 사용할 것입니다.


Colorshaderclass.h


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
#pragma once
 
class ColorShaderClass : public AlignedAllocationPolicy<16>
{
private:
    struct MatrixBufferType
    {
        XMMATRIX world;
        XMMATRIX view;
        XMMATRIX projection;
    };
 
public:
    ColorShaderClass();
    ColorShaderClass(const ColorShaderClass&);
    ~ColorShaderClass();
 
    bool Initialize(ID3D11Device*, HWND);
    void Shutdown();
    bool Render(ID3D11DeviceContext*int, XMMATRIX, XMMATRIX, XMMATRIX);
 
private:
    bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
    void ShutdownShader();
    void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);
 
    bool SetShaderParameters(ID3D11DeviceContext*, XMMATRIX, XMMATRIX, XMMATRIX);
    void RenderShader(ID3D11DeviceContext*int);
 
private:
    ID3D11VertexShader* m_vertexShader = nullptr;
    ID3D11PixelShader* m_pixelShader = nullptr;
    ID3D11InputLayout* m_layout = nullptr;
    ID3D11Buffer* m_matrixBuffer = nullptr;
};
cs



Colorshaderclass.cpp

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
#include "stdafx.h"
#include "colorshaderclass.h"
 
 
ColorShaderClass::ColorShaderClass()
{
}
 
 
ColorShaderClass::ColorShaderClass(const ColorShaderClass& other)
{
}
 
 
ColorShaderClass::~ColorShaderClass()
{
}
 
 
bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
    // 정점 및 픽셀 쉐이더를 초기화합니다.
    return InitializeShader(device, hwnd, L"../Dx11Demo_04/color.vs", L"../Dx11Demo_04/color.ps");
}
 
 
void ColorShaderClass::Shutdown()
{
    // 버텍스 및 픽셀 쉐이더와 관련된 객체를 종료합니다.
    ShutdownShader();
}
 
 
bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, 
    XMMATRIX worldMatrix, XMMATRIX viewMatrix, XMMATRIX projectionMatrix)
{
    // 렌더링에 사용할 셰이더 매개 변수를 설정합니다.
    if(!SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix))
    {
        return false;
    }
 
    // 설정된 버퍼를 셰이더로 렌더링한다.
    RenderShader(deviceContext, indexCount);
 
    return true;
}
 
 
bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
    ID3D10Blob* errorMessage = nullptr;
 
    // 버텍스 쉐이더 코드를 컴파일한다.
    ID3D10Blob* vertexShaderBuffer = nullptr;
    if(FAILED(D3DCompileFromFile(vsFilename, NULLNULL"ColorVertexShader""vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS,
 0&vertexShaderBuffer, &errorMessage)))
    {
        // 셰이더 컴파일 실패시 오류메시지를 출력합니다.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
        }
        // 컴파일 오류가 아니라면 셰이더 파일을 찾을 수 없는 경우입니다.
        else
        {
            MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
        }
 
        return false;
    }
 
    // 픽셀 쉐이더 코드를 컴파일한다.
    ID3D10Blob* pixelShaderBuffer = nullptr;
    if(FAILED(D3DCompileFromFile(psFilename, NULLNULL"ColorPixelShader""ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0,
&pixelShaderBuffer, &errorMessage)))
    {
        // 셰이더 컴파일 실패시 오류메시지를 출력합니다.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
        }
        // 컴파일 오류가 아니라면 셰이더 파일을 찾을 수 없는 경우입니다.
        else
        {
            MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
        }
 
        return false;
    }
 
    // 버퍼로부터 정점 셰이더를 생성한다.
    if(FAILED(device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), 
NULL&m_vertexShader)))
    {
        return false;
    }
 
    // 버퍼에서 픽셀 쉐이더를 생성합니다.
    if(FAILED(device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL,
&m_pixelShader)))
    {
        return false;
    }
 
    // 정점 입력 레이아웃 구조체를 설정합니다.
    // 이 설정은 ModelClass와 셰이더의 VertexType 구조와 일치해야합니다.
    D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
    polygonLayout[0].SemanticName = "POSITION";
    polygonLayout[0].SemanticIndex = 0;
    polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    polygonLayout[0].InputSlot = 0;
    polygonLayout[0].AlignedByteOffset = 0;
    polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[0].InstanceDataStepRate = 0;
 
    polygonLayout[1].SemanticName = "COLOR";
    polygonLayout[1].SemanticIndex = 0;
    polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    polygonLayout[1].InputSlot = 0;
    polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
    polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[1].InstanceDataStepRate = 0;
 
    // 레이아웃의 요소 수를 가져옵니다.
    unsigned int numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);
 
    // 정점 입력 레이아웃을 만듭니다.
    if(FAILED(device->CreateInputLayout(polygonLayout, numElements, 
        vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_layout)))
    {
        return false;
    }
 
    // 더 이상 사용되지 않는 정점 셰이더 퍼버와 픽셀 셰이더 버퍼를 해제합니다.
    vertexShaderBuffer->Release();
    vertexShaderBuffer = 0;
 
    pixelShaderBuffer->Release();
    pixelShaderBuffer = 0;
 
    // 정점 셰이더에 있는 행렬 상수 버퍼의 구조체를 작성합니다.
    D3D11_BUFFER_DESC matrixBufferDesc;
    matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
    matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    matrixBufferDesc.MiscFlags = 0;
    matrixBufferDesc.StructureByteStride = 0;
 
    // 상수 버퍼 포인터를 만들어 이 클래스에서 정점 셰이더 상수 버퍼에 접근할 수 있게 합니다.
    if(FAILED(device->CreateBuffer(&matrixBufferDesc, NULL&m_matrixBuffer)))
    {
        return false;
    }
 
    return true;
}
 
 
void ColorShaderClass::ShutdownShader()
{
    // 행렬 상수 버퍼를 해제합니다.
    if(m_matrixBuffer)
    {
        m_matrixBuffer->Release();
        m_matrixBuffer = 0;
    }
 
    // 레이아웃을 해제합니다.
    if(m_layout)
    {
        m_layout->Release();
        m_layout = 0;
    }
 
    // 픽셀 쉐이더를 해제합니다.
    if(m_pixelShader)
    {
        m_pixelShader->Release();
        m_pixelShader = 0;
    }
 
    // 버텍스 쉐이더를 해제합니다.
    if(m_vertexShader)
    {
        m_vertexShader->Release();
        m_vertexShader = 0;
    }
}
 
 
void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
    // 에러 메시지를 출력창에 표시합니다.
    OutputDebugStringA(reinterpret_cast<const char*>(errorMessage->GetBufferPointer()));
 
    // 에러 메세지를 반환합니다.
    errorMessage->Release();
    errorMessage = 0;
 
    // 컴파일 에러가 있음을 팝업 메세지로 알려줍니다.
    MessageBox(hwnd, L"Error compiling shader.", shaderFilename, MB_OK);
}
 
 
bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix,
XMMATRIX viewMatrix, XMMATRIX projectionMatrix)
{
    // 행렬을 transpose하여 셰이더에서 사용할 수 있게 합니다
    worldMatrix = XMMatrixTranspose(worldMatrix);
    viewMatrix = XMMatrixTranspose(viewMatrix);
    projectionMatrix = XMMatrixTranspose(projectionMatrix);
 
    // 상수 버퍼의 내용을 쓸 수 있도록 잠급니다.
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    if(FAILED(deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0&mappedResource)))
    {
        return false;
    }
 
    // 상수 버퍼의 데이터에 대한 포인터를 가져옵니다.
    MatrixBufferType* dataPtr = (MatrixBufferType*)mappedResource.pData;
 
    // 상수 버퍼에 행렬을 복사합니다.
    dataPtr->world = worldMatrix;
    dataPtr->view = viewMatrix;
    dataPtr->projection = projectionMatrix;
 
    // 상수 버퍼의 잠금을 풉니다.
    deviceContext->Unmap(m_matrixBuffer, 0);
 
    // 정점 셰이더에서의 상수 버퍼의 위치를 설정합니다.
    unsigned bufferNumber = 0;
 
    // 마지막으로 정점 셰이더의 상수 버퍼를 바뀐 값으로 바꿉니다.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1&m_matrixBuffer);
 
    return true;
}
 
 
void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
    // 정점 입력 레이아웃을 설정합니다.
    deviceContext->IASetInputLayout(m_layout);
 
    // 삼각형을 그릴 정점 셰이더와 픽셀 셰이더를 설정합니다.
    deviceContext->VSSetShader(m_vertexShader, NULL0);
    deviceContext->PSSetShader(m_pixelShader, NULL0);
 
    // 삼각형을 그립니다.
    deviceContext->DrawIndexed(indexCount, 00);
}
cs



앞서 언급했듯이, ModelClass는 3D 모델들의 복잡한 모양들을 캡슐화하는 클래스입니다. 이 튜토리얼에서는 단일 녹색 삼각형을 만들기 위한 데이터를 차근차근 만들어 볼 것입니다. 또한 이 삼각형이 화면에 그려지기 위해 필요한 정점 버퍼와 인덱스 버퍼도 역시 만들 것입니다.



Modelclass.h


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
#pragma once
 
class ModelClass : public AlignedAllocationPolicy<16>
{
private:
    struct VertexType
    {
        XMFLOAT3 position;
        XMFLOAT4 color;
    };
 
public:
    ModelClass();
    ModelClass(const ModelClass&);
    ~ModelClass();
 
    bool Initialize(ID3D11Device*);
    void Shutdown();
    void Render(ID3D11DeviceContext*);
 
    int GetIndexCount();
 
private:
    bool InitializeBuffers(ID3D11Device*);
    void ShutdownBuffers();
    void RenderBuffers(ID3D11DeviceContext*);
 
private:
    ID3D11Buffer* m_vertexBuffer = nullptr;
    ID3D11Buffer* m_indexBuffer = nullptr;
    int m_vertexCount = 0;
    int m_indexCount = 0;
};
cs



Modelclass.cpp


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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include "stdafx.h"
#include "modelclass.h"
 
 
ModelClass::ModelClass()
{
}
 
 
ModelClass::ModelClass(const ModelClass& other)
{
}
 
 
ModelClass::~ModelClass()
{
}
 
 
bool ModelClass::Initialize(ID3D11Device* device)
{
    // 정점 및 인덱스 버퍼를 초기화합니다.
    return InitializeBuffers(device);
}
 
 
void ModelClass::Shutdown()
{
    // 버텍스 및 인덱스 버퍼를 종료합니다.
    ShutdownBuffers();
}
 
 
void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
    // 그리기를 준비하기 위해 그래픽 파이프 라인에 꼭지점과 인덱스 버퍼를 놓습니다.
    RenderBuffers(deviceContext);
}
 
 
int ModelClass::GetIndexCount()
{
    return m_indexCount;
}
 
 
bool ModelClass::InitializeBuffers(ID3D11Device* device)
{    
    // 정점 배열의 정점 수를 설정합니다.
    m_vertexCount = 3;
 
    // 인덱스 배열의 인덱스 수를 설정합니다.
    m_indexCount = 3;
 
    // 정점 배열을 만듭니다.
    VertexType* vertices = new VertexType[m_vertexCount];
    if(!vertices)
    {
        return false;
    }
 
    // 인덱스 배열을 만듭니다.
    unsigned long* indices = new unsigned long[m_indexCount];
    if(!indices)
    {
        return false;
    }
 
    // 정점 배열에 데이터를 설정합니다.
    vertices[0].position = XMFLOAT3(-1.0f, -1.0f, 0.0f);  // Bottom left.
    vertices[0].color = XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f);
 
    vertices[1].position = XMFLOAT3(0.0f, 1.0f, 0.0f);  // Top middle.
    vertices[1].color = XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f);
 
    vertices[2].position = XMFLOAT3(1.0f, -1.0f, 0.0f);  // Bottom right.
    vertices[2].color = XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f);
 
    // 인덱스 배열의 값을 설정합니다.
    indices[0= 0;  // Bottom left.
    indices[1= 1;  // Top middle.
    indices[2= 2;  // Bottom right.
 
    // 정적 정점 버퍼의 구조체를 설정합니다.
    D3D11_BUFFER_DESC vertexBufferDesc;
    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;
    vertexBufferDesc.StructureByteStride = 0;
 
    // subresource 구조에 정점 데이터에 대한 포인터를 제공합니다.
    D3D11_SUBRESOURCE_DATA vertexData;
    vertexData.pSysMem = vertices;
    vertexData.SysMemPitch = 0;
    vertexData.SysMemSlicePitch = 0;
 
    // 이제 정점 버퍼를 만듭니다.
    if(FAILED(device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer)))
    {
        return false;
    }
 
    // 정적 인덱스 버퍼의 구조체를 설정합니다.
    D3D11_BUFFER_DESC indexBufferDesc;
    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(unsigned long* m_indexCount;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;
    indexBufferDesc.StructureByteStride = 0;
 
    // 인덱스 데이터를 가리키는 보조 리소스 구조체를 작성합니다.
    D3D11_SUBRESOURCE_DATA indexData;
    indexData.pSysMem = indices;
    indexData.SysMemPitch = 0;
    indexData.SysMemSlicePitch = 0;
 
    // 인덱스 버퍼를 생성합니다.
    if(FAILED(device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer)))
    {
        return false;
    }
 
    // 생성되고 값이 할당된 정점 버퍼와 인덱스 버퍼를 해제합니다.
    delete [] vertices;
    vertices = 0;
 
    delete [] indices;
    indices = 0;
 
    return true;
}
 
 
void ModelClass::ShutdownBuffers()
{
    // 인덱스 버퍼를 해제합니다.
    if(m_indexBuffer)
    {
        m_indexBuffer->Release();
        m_indexBuffer = 0;
    }
 
    // 정점 버퍼를 해제합니다.
    if(m_vertexBuffer)
    {
        m_vertexBuffer->Release();
        m_vertexBuffer = 0;
    }
}
 
 
void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
    // 정점 버퍼의 단위와 오프셋을 설정합니다.
    unsigned int stride = sizeof(VertexType); 
    unsigned int offset = 0;
    
    // 렌더링 할 수 있도록 입력 어셈블러에서 정점 버퍼를 활성으로 설정합니다.
    deviceContext->IASetVertexBuffers(01&m_vertexBuffer, &stride, &offset);
 
    // 렌더링 할 수 있도록 입력 어셈블러에서 인덱스 버퍼를 활성으로 설정합니다.
    deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
 
    // 정점 버퍼로 그릴 기본형을 설정합니다. 여기서는 삼각형으로 설정합니다.
    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}
cs




지금까지 우리는 HLSL의 프로그래밍과, 정점/인덱스 버퍼를 셋업하는 방법 그리고 ColorShaderClass를 사용하여 HLSL 셰이더를 호출하는 방법을 살펴보았습니다. 하지만 우리가 한가지 놓친 것이 있는데, 그것은 바로 월드에서 우리가 보는 시점입니다. 이를 구현하기 위해서는 어떻게 우리가 장면을 보는지에 대한 정보를 DirectX 11에게 전달하는 카메라 클래스가 필요합니다. 카메라 클래스는 카메라의 위치와 현재 회전 상태를 계속 가지고 있어야 합니다. 또한 이 정보를 이용하여 렌더링시에 HLSL 셰이더에서 사용할 뷰 행렬을 생성합니다.



Cameraclass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once
 
class CameraClass : public AlignedAllocationPolicy<16>
{
public:
    CameraClass();
    CameraClass(const CameraClass&);
    ~CameraClass();
 
    void SetPosition(floatfloatfloat);
    void SetRotation(floatfloatfloat);
 
    XMFLOAT3 GetPosition();
    XMFLOAT3 GetRotation();
 
    void Render();
    void GetViewMatrix(XMMATRIX&);
 
private:
    XMFLOAT3 m_position;
    XMFLOAT3 m_rotation;
    XMMATRIX m_viewMatrix;
};
cs


CameraClass의 헤더는 단지 4개의 함수만을 사용하는 간단한 구조입니다. SetPosition과 SetRotation 함수는 현재 카메라 객체의 위치와 회전 상태를 설정하는 데 사용될 것입니다. Render 함수는 카메라의 위치와 회전 상태에 기반한 뷰 행렬을 생성하는 데 사용됩니다. 그리고 마지막으로 GetViewMatrix 함수는 셰이더에서 렌더링에 사용할 수 있도록 카메라 객체의 뷰 행렬을 받아오는 데 사용합니다.



Cameraclass.cpp


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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include "stdafx.h"
#include "cameraclass.h"
 
 
CameraClass::CameraClass()
{
    m_position = XMFLOAT3(0.0f, 0.0f, 0.0f);;
    m_rotation = XMFLOAT3(0.0f, 0.0f, 0.0f);
}
 
 
CameraClass::CameraClass(const CameraClass& other)
{
}
 
 
CameraClass::~CameraClass()
{
}
 
 
void CameraClass::SetPosition(float x, float y, float z)
{
    m_position.x = x;
    m_position.y = y;
    m_position.z = z;
}
 
 
void CameraClass::SetRotation(float x, float y, float z)
{
    m_rotation.x = x;
    m_rotation.y = y;
    m_rotation.z = z;
}
 
 
XMFLOAT3 CameraClass::GetPosition()
{
    return m_position;
}
 
 
XMFLOAT3 CameraClass::GetRotation()
{
    return m_rotation;
}
 
 
void CameraClass::Render()
{
    XMFLOAT3 up, position, lookAt;
    XMVECTOR upVector, positionVector, lookAtVector;
    float yaw, pitch, roll;
    XMMATRIX rotationMatrix;
 
 
    // 위쪽을 가리키는 벡터를 설정합니다.
    up.x = 0.0f;
    up.y = 1.0f;
    up.z = 0.0f;
 
    // XMVECTOR 구조체에 로드한다.
    upVector = XMLoadFloat3(&up);
 
    // 3D월드에서 카메라의 위치를 ​​설정합니다.
    position = m_position;
 
    // XMVECTOR 구조체에 로드한다.
    positionVector = XMLoadFloat3(&position);
 
    // 기본적으로 카메라가 찾고있는 위치를 설정합니다.
    lookAt.x = 0.0f;
    lookAt.y = 0.0f;
    lookAt.z = 1.0f;
 
    // XMVECTOR 구조체에 로드한다.
    lookAtVector = XMLoadFloat3(&lookAt);
 
    // yaw (Y 축), pitch (X 축) 및 roll (Z 축)의 회전값을 라디안 단위로 설정합니다.
    pitch = m_rotation.x * 0.0174532925f;
    yaw = m_rotation.y * 0.0174532925f;
    roll = m_rotation.z * 0.0174532925f;
 
    //  yaw, pitch, roll 값을 통해 회전 행렬을 만듭니다.
    rotationMatrix = XMMatrixRotationRollPitchYaw(pitch, yaw, roll);
 
    // lookAt 및 up 벡터를 회전 행렬로 변형하여 뷰가 원점에서 올바르게 회전되도록 합니다.
    lookAtVector = XMVector3TransformCoord(lookAtVector, rotationMatrix);
    upVector = XMVector3TransformCoord(upVector, rotationMatrix);
 
    // 회전 된 카메라 위치를 뷰어 위치로 변환합니다.
    lookAtVector = XMVectorAdd(positionVector, lookAtVector);
 
    // 마지막으로 세 개의 업데이트 된 벡터에서 뷰 행렬을 만듭니다.
    m_viewMatrix = XMMatrixLookAtLH(positionVector, lookAtVector, upVector);
}
 
 
void CameraClass::GetViewMatrix(XMMATRIX& viewMatrix)
{
    viewMatrix = m_viewMatrix;
}
cs




GraphicsClass는 이제 세 개의 새로운 클래스가 추가되었습니다. 따라서 CameraClass, ModelClass, ColorShader 클래스의 헤더뿐만 아니라 멤버 변수가 추가됩니다. GraphicsClass가 이 프로젝트에서 사용되는 모든 그래픽 객체에 대한 호출을 담당한다는 점을 기억하시기 바랍니다.


Graphicsclass.h


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
36
#pragma once
 
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
 
 
class D3DClass;
class CameraClass;
class ModelClass;
class ColorShaderClass;
 
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();
 
    bool Initialize(intint, HWND);
    void Shutdown();
    bool Frame();
 
private:
    bool Render();
 
private:
    D3DClass* m_Direct3D = nullptr;
    CameraClass* m_Camera = nullptr;
    ModelClass* m_Model = nullptr;
    ColorShaderClass* m_ColorShader = nullptr;
};
cs




Graphicsclass.cpp


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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include "stdafx.h"
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "colorshaderclass.h"
#include "graphicsclass.h"
 
 
GraphicsClass::GraphicsClass()
{
}
 
 
GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}
 
 
GraphicsClass::~GraphicsClass()
{
}
 
 
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
    // Direct3D 객체 생성
    m_Direct3D = new D3DClass;
    if(!m_Direct3D)
    {
        return false;
    }
 
    // Direct3D 객체 초기화
    if(!m_Direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, 
        hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR))
    {
        MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
        return false;
    }
 
    // m_Camera 객체 생성
    m_Camera = new CameraClass;
    if (!m_Camera)
    {
        return false;
    }
 
    // 카메라 포지션 설정
    m_Camera->SetPosition(0.0f, 0.0f, -5.0f);
 
    // m_Model 객체 생성
    m_Model = new ModelClass;
    if (!m_Model)
    {
        return false;
    }
 
    // m_Model 객체 초기화
    if (!m_Model->Initialize(m_Direct3D->GetDevice()))
    {
        MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
        return false;
    }
 
    // m_ColorShader 객체 생성
    m_ColorShader = new ColorShaderClass;
    if (!m_ColorShader)
    {
        return false;
    }
 
    // m_ColorShader 객체 초기화
    if (!m_ColorShader->Initialize(m_Direct3D->GetDevice(), hwnd))
    {
        MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK);
        return false;
    }
 
    return true;
}
 
 
void GraphicsClass::Shutdown()
{
    // m_ColorShader 객체 반환
    if (m_ColorShader)
    {
        m_ColorShader->Shutdown();
        delete m_ColorShader;
        m_ColorShader = 0;
    }
 
    // m_Model 객체 반환
    if (m_Model)
    {
        m_Model->Shutdown();
        delete m_Model;
        m_Model = 0;
    }
 
    // m_Camera 객체 반환
    if (m_Camera)
    {
        delete m_Camera;
        m_Camera = 0;
    }
 
    // Direct3D 객체 반환
    if(m_Direct3D)
    {
        m_Direct3D->Shutdown();
        delete m_Direct3D;
        m_Direct3D = 0;
    }
}
 
 
bool GraphicsClass::Frame()
{
    // 그래픽 랜더링 처리
    return Render();
}
 
 
bool GraphicsClass::Render()
{
    // 씬을 그리기 위해 버퍼를 지웁니다
    m_Direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
 
    // 카메라의 위치에 따라 뷰 행렬을 생성합니다
    m_Camera->Render();
 
    // 카메라 및 d3d 객체에서 월드, 뷰 및 투영 행렬을 가져옵니다
    XMMATRIX worldMatrix, viewMatrix, projectionMatrix;
    m_Direct3D->GetWorldMatrix(worldMatrix);
    m_Camera->GetViewMatrix(viewMatrix);
    m_Direct3D->GetProjectionMatrix(projectionMatrix);
 
    // 모델 버텍스와 인덱스 버퍼를 그래픽 파이프 라인에 배치하여 드로잉을 준비합니다.
    m_Model->Render(m_Direct3D->GetDeviceContext());
 
    // 색상 쉐이더를 사용하여 모델을 렌더링합니다.
    if (!m_ColorShader->Render(m_Direct3D->GetDeviceContext(), 
        m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix))
    {
        return false;
    }
 
    // 버퍼의 내용을 화면에 출력합니다
    m_Direct3D->EndScene();
 
    return true;
}
cs




출력 화면





마치면서


여기까지 잘 따라오셨다면 정점 버퍼와 인덱스 버퍼가 어떻게 동작하는지에 대한 기초를 잘 이해할 수 있을 겁니다. 또한 정점/픽셀 셰이더의 기초와 HLSL을 이용하여 이것을 프로그래밍하는 법도 아시게 되었을 겁니다. 마지막으로 화면에 녹색 삼각형 하나를 그리기 위해 위 개념들을 어떻게 활용하는지에 대해서도 배웠습니다. 


물론 삼각형 하나를 그리는 코드는 단순히 모든 기능을 main() 함수 하나에 때려박아 간단하게 만들 수도 있습니다. 하지만 이 프레임워크를 사용함으로 앞으로의 튜토리얼들에서 기존 코드에 조금의 수정만으로도 더 복잡한 그래픽 작업을 할 수 있도록 하였습니다.



연습문제


1. 튜토리얼을 컴파일하고 실행해 보십시오. 화면에 초록색 삼각형이 그려지는 것을 확인하십시오. Esc키를 눌러 종료하십시오.


2. 삼각형의 색깔을 빨간색으로 바꿔 보십시오.


3. 삼각형 대신 사각형이 그려지도록 해 보십시오.


4. 카메라를 10단위만큼 뒤로 물러나게 해 보십시오.


5, 픽셀 셰이더를 수정하여 출력되는 밝기가 절반이 되도록 해 보십시오. (힌트: ColorPixelShader의 무언가에 0.5f를 곱해 보세요)



소스코드


소스코드 : Dx11Demo_04.zip