※ 아래의 내용들은 DirectX 11을 이용한 3D 게임 프로그래밍 입문 책의 내용을 바탕으로 작성된 것입니다.
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
|
//***************************************************************************************
// HillsDemo.cpp by Frank Luna (C) 2011 All Rights Reserved.
//
// Demonstrates drawing hills using a grid and 2D function to set the height of each vertex.
//
// Controls:
// Hold the left mouse button down and move the mouse to rotate.
// Hold the right mouse button down to zoom in and out.
//
//***************************************************************************************
#include "d3dApp.h"
#include "d3dx11Effect.h"
#include "GeometryGenerator.h"
#include "MathHelper.h"
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
class HillsApp : public D3DApp
{
public:
HillsApp(HINSTANCE hInstance);
~HillsApp();
bool Init();
void OnResize();
void UpdateScene(float dt);
void DrawScene();
void OnMouseDown(WPARAM btnState, int x, int y);
void OnMouseUp(WPARAM btnState, int x, int y);
void OnMouseMove(WPARAM btnState, int x, int y);
private:
float GetHeight(float x, float z)const;
void BuildGeometryBuffers();
void BuildFX();
void BuildVertexLayout();
private:
ID3D11Buffer* mVB;
ID3D11Buffer* mIB;
ID3DX11Effect* mFX;
ID3DX11EffectTechnique* mTech;
ID3DX11EffectMatrixVariable* mfxWorldViewProj;
ID3D11InputLayout* mInputLayout;
// Define transformations from local spaces to world space.
XMFLOAT4X4 mGridWorld;
UINT mGridIndexCount;
XMFLOAT4X4 mView;
XMFLOAT4X4 mProj;
float mTheta;
float mPhi;
float mRadius;
POINT mLastMousePos;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
HillsApp theApp(hInstance);
if (!theApp.Init())
return 0;
return theApp.Run();
}
HillsApp::HillsApp(HINSTANCE hInstance)
: D3DApp(hInstance), mVB(0), mIB(0), mFX(0), mTech(0),
mfxWorldViewProj(0), mInputLayout(0), mGridIndexCount(0),
mTheta(1.5f * MathHelper::Pi), mPhi(0.1f * MathHelper::Pi), mRadius(200.0f)
{
mMainWndCaption = L"Hills Demo";
mLastMousePos.x = 0;
mLastMousePos.y = 0;
XMMATRIX I = XMMatrixIdentity();
XMStoreFloat4x4(&mGridWorld, I);
XMStoreFloat4x4(&mView, I);
XMStoreFloat4x4(&mProj, I);
}
HillsApp::~HillsApp()
{
ReleaseCOM(mVB);
ReleaseCOM(mIB);
ReleaseCOM(mFX);
ReleaseCOM(mInputLayout);
}
bool HillsApp::Init()
{
if (!D3DApp::Init())
return false;
BuildGeometryBuffers();
BuildFX();
BuildVertexLayout();
return true;
}
// 투영 행렬 다시 계산
void HillsApp::OnResize()
{
D3DApp::OnResize();
XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f * MathHelper::Pi, AspectRatio(), 1.0f, 1000.0f);
XMStoreFloat4x4(&mProj, P);
}
// 카메라가 바라보는 방향 설정(시야 행렬 구축)
void HillsApp::UpdateScene(float dt)
{
// Convert Spherical to Cartesian coordinates.
float x = mRadius * sinf(mPhi) * cosf(mTheta);
float z = mRadius * sinf(mPhi) * sinf(mTheta);
float y = mRadius * cosf(mPhi);
// Build the view matrix.
XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
XMMATRIX V = XMMatrixLookAtLH(pos, target, up);
XMStoreFloat4x4(&mView, V);
}
// 화면에 그림 그리기
void HillsApp::DrawScene()
{
md3dImmediateContext->ClearRenderTargetView(mRenderTargetView, reinterpret_cast<const float*>(&Colors::LightSteelBlue));
md3dImmediateContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
md3dImmediateContext->IASetInputLayout(mInputLayout);
md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
UINT stride = sizeof(Vertex);
UINT offset = 0;
md3dImmediateContext->IASetVertexBuffers(0, 1, &mVB, &stride, &offset);
md3dImmediateContext->IASetIndexBuffer(mIB, DXGI_FORMAT_R32_UINT, 0);
// Set constants
XMMATRIX view = XMLoadFloat4x4(&mView);
XMMATRIX proj = XMLoadFloat4x4(&mProj);
XMMATRIX world = XMLoadFloat4x4(&mGridWorld);
XMMATRIX worldViewProj = world * view * proj;
D3DX11_TECHNIQUE_DESC techDesc;
mTech->GetDesc(&techDesc);
for (UINT p = 0; p < techDesc.Passes; ++p)
{
// Draw the grid.
// mfxWorldViewProj에 효과 파일의 상수 버퍼 변수 포인터를 대입해뒀으므로
// 아래 코드는, 효과 파일 상수 버퍼 변수를 worldViewProj 값으로 설정해준 것과 같다.
// 이 코드가 반복문 안에 있으면 각 기법안의 패스 마다 세계ㆍ뷰ㆍ투영 결합 행렬을 설정해주는 것이다(?)
// 굳이 이 코드는 반복문 안에 없어도 정상적으로 실행도 됨. 왜 반복문 안에 있는지 모르겠음.
mfxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
mTech->GetPassByIndex(p)->Apply(0, md3dImmediateContext);
md3dImmediateContext->DrawIndexed(mGridIndexCount, 0, 0);
}
HR(mSwapChain->Present(0, 0));
}
// 마우스 클릭 좌표 지정 및 SetCapture 호출
void HillsApp::OnMouseDown(WPARAM btnState, int x, int y)
{
mLastMousePos.x = x;
mLastMousePos.y = y;
SetCapture(mhMainWnd);
}
// ReleaseCapture 호출
void HillsApp::OnMouseUp(WPARAM btnState, int x, int y)
{
ReleaseCapture();
}
// 마우스 버튼 클릭 상태와 이동에 따라 카메라 이동
void HillsApp::OnMouseMove(WPARAM btnState, int x, int y)
{
if ((btnState & MK_LBUTTON) != 0)
{
// Make each pixel correspond to a quarter of a degree.
float dx = XMConvertToRadians(0.25f * static_cast<float>(x - mLastMousePos.x));
float dy = XMConvertToRadians(0.25f * static_cast<float>(y - mLastMousePos.y));
// Update angles based on input to orbit camera around box.
mTheta += dx;
mPhi += dy;
// Restrict the angle mPhi.
mPhi = MathHelper::Clamp(mPhi, 0.1f, MathHelper::Pi - 0.1f);
}
else if ((btnState & MK_RBUTTON) != 0)
{
// Make each pixel correspond to 0.2 unit in the scene.
float dx = 0.2f * static_cast<float>(x - mLastMousePos.x);
float dy = 0.2f * static_cast<float>(y - mLastMousePos.y);
// Update the camera radius based on input.
mRadius += dx - dy;
// Restrict the radius.
mRadius = MathHelper::Clamp(mRadius, 50.0f, 500.0f);
}
mLastMousePos.x = x;
mLastMousePos.y = y;
}
// 각 정점에 적용할 높이 함수
float HillsApp::GetHeight(float x, float z)const
{
return 0.3f * (z * sinf(0.1f * x) + x * cosf(0.1f * z));
}
// 정점버퍼, 색인버퍼 생성
void HillsApp::BuildGeometryBuffers()
{
GeometryGenerator::MeshData grid;
GeometryGenerator geoGen;
geoGen.CreateGrid(160.0f, 160.0f, 50, 50, grid);
mGridIndexCount = grid.Indices.size();
//
// Extract the vertex elements we are interested and apply the height function to
// each vertex. In addition, color the vertices based on their height so we have
// sandy looking beaches, grassy low hills, and snow mountain peaks.
//
// 필요한 정점 특성들을 추출하고, 각 정점에 높이 함수를 적용한다. 또한
// 그 높이에 기초해서 정점의 색상도 적절히 설정한다. 이를 통해서
// 모래 색의 해변과 녹색의 언덕, 그리고 흰 눈 덮인 봉우리 같은 모습이
// 만들어진다.
//
std::vector<Vertex> vertices(grid.Vertices.size());
for (size_t i = 0; i < grid.Vertices.size(); ++i)
{
XMFLOAT3 p = grid.Vertices[i].Position;
p.y = GetHeight(p.x, p.z);
vertices[i].Pos = p;
// Color the vertex based on its height.
// 높이에 기초해서 정점의 색상들을 설정한다.
if (p.y < -10.0f)
{
// Sandy beach color.
// 해변의 모래 색
vertices[i].Color = XMFLOAT4(1.0f, 0.96f, 0.62f, 1.0f);
}
else if (p.y < 5.0f)
{
// Light yellow-green.
// 밝은 녹황색
vertices[i].Color = XMFLOAT4(0.48f, 0.77f, 0.46f, 1.0f);
}
else if (p.y < 12.0f)
{
// Dark yellow-green.
// 짙은 녹황색
vertices[i].Color = XMFLOAT4(0.1f, 0.48f, 0.19f, 1.0f);
}
else if (p.y < 20.0f)
{
// Dark brown.
// 짙은 갈색.
vertices[i].Color = XMFLOAT4(0.45f, 0.39f, 0.34f, 1.0f);
}
else
{
// White snow.
// 흰 색(눈)
vertices[i].Color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
}
}
// 정점 버퍼를 서술하는 구조체를 채운다.
D3D11_BUFFER_DESC vbd;
vbd.Usage = D3D11_USAGE_IMMUTABLE; // 버퍼가 쓰이는 방식
vbd.ByteWidth = sizeof(Vertex) * grid.Vertices.size(); // 생성할 정점 버퍼의 크기
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 정점 버퍼의 경우 해당 플래그 사용하면 됨.
vbd.CPUAccessFlags = 0; // CPU가 버퍼에 접근하는 방식을 결정하는 플래그들 지정(버퍼 생성 이후 CPU가 버퍼 읽기, 쓰기를 하지 않는다면 0을 지정)
vbd.MiscFlags = 0; // 정점 버퍼에 대해서는 그냥 0 지정
// 정점 버퍼를 초기화할 자료를 지정한다.
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = &vertices[0]; // 정점 버퍼를 초기화할 자료를 담은 시스템 메모리 배열을 가리키는 포인터
// vbd : 생성할 버퍼를 서술하는 구조체
// vinitData : 버퍼를 초기화하는 데 사용할 자료
// mBoxVB : 생성된 버퍼가 여기에 설정된다.
HR(md3dDevice->CreateBuffer(&vbd, &vinitData, &mVB));
//
// Pack the indices of all the meshes into one index buffer.
// 모든 메시의 색인들을 하나의 색인 버퍼에 합쳐 넣는다.
//
// 색인 버퍼를 서술하는 구조체를 채운다.
// 정점 버퍼를 서술하는 구조체와 거의 유사하다.
D3D11_BUFFER_DESC ibd;
ibd.Usage = D3D11_USAGE_IMMUTABLE;
ibd.ByteWidth = sizeof(UINT) * mGridIndexCount;
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.CPUAccessFlags = 0;
ibd.MiscFlags = 0;
// 색인 버퍼를 초기화할 자료를 지정한다.
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = &grid.Indices[0];
// 색인 버퍼를 생성한다.
HR(md3dDevice->CreateBuffer(&ibd, &iinitData, &mIB));
}
// 효과 파일 생성(셰이더 코드 컴파일)
void HillsApp::BuildFX()
{
std::ifstream fin("fx/color.fxo", std::ios::binary);
fin.seekg(0, std::ios_base::end);
int size = (int)fin.tellg();
fin.seekg(0, std::ios_base::beg);
std::vector<char> compiledShader(size);
fin.read(&compiledShader[0], size);
fin.close();
// compiledShader : 컴파일된 효과 자료를 가리키는 포인터
// size : 컴파일된 효과 자료의 바이트 단위 크기
// 0 : 효과 플래그(D3DX11CompileFromFile 함수의 Flags2에 지정한 것과 일치해야 함)
// md3dDevice : Direct3D 11 장치를 가리키는 포인터
// mFX : 생성된 효과 파일을 가리키는 포인터
HR(D3DX11CreateEffectFromMemory(&compiledShader[0], size,
0, md3dDevice, &mFX));
// 효과 객체에 있는 기법 객체를 가리키는 포인터 얻어오기
mTech = mFX->GetTechniqueByName("ColorTech");
// 효과 객체를 통해서 상수 버퍼 변수에 대한 포인터 얻기
mfxWorldViewProj = mFX->GetVariableByName("gWorldViewProj")->AsMatrix();
}
// 입력 배치 생성
void HillsApp::BuildVertexLayout()
{
// Create the vertex input layout.
// 입력 배치 서술 배열 생성
D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
// Create the input layout
// 입력 배치 생성(자세한 설명은 BoxDemo.cpp 참고)
D3DX11_PASS_DESC passDesc;
mTech->GetPassByIndex(0)->GetDesc(&passDesc);
HR(md3dDevice->CreateInputLayout(vertexDesc, 2, passDesc.pIAInputSignature,
passDesc.IAInputSignatureSize, &mInputLayout));
}
|
cs |
예제 코드의 구체적인 동작 과정은 Box 예제와 거의 일치합니다.
차이점은 그리는 기하구조의 차이이므로 해당 부분에 중점을 두고 코드를 분석해보겠습니다.
- Box 예제 분석 링크 : [DirectX11] Box 예제 (tistory.com)
분석에 앞서 Hills 예제는 삼각형 격자 메시를 절차적으로 구축하는 방법을 보여주는 것입니다. 이러한 절차적 기하구조 생성은 특히 지형과 물 렌더링에 유용합니다.
즉, 이 예제의 주된 과제 중 하나는 xz 평면에 격자를 만드는 것입니다. 그 후, 만들어진 격자 정점에 높이 함수를 적용해서 각 정점마다 다른 높이를 주게 되면 나머지 진행 과정은 Box 예제와 동일하므로 쉽게 이해하실 수 있을 것입니다.
이번 예제에서는 격자의 생성을 위해 GeometryGenerator 클래스가 등장합니다. 해당 클래스는 예제 프레임워크의 일부로 격자나 구, 원기둥, 상자 등 책의 여러 예제들에 쓰이는 간단한 기하도형을 생성하기 위한 편의용 클래스입니다.
해당 클래스는 자료를 그냥 시스템 메모리에 생성하므로, 해당 자료를 실제로 사용하려면 개별적인 과정을 통해서 정점 버퍼와 색인 버퍼에 복사해주어야 합니다.
GeometryGenerator 클래스는 정점 목록과 색인 목록을 담는 MeshData라는 간단한 구조체도 내포하고 있습니다.
먼저, GeometryGenerator 클래스를 이용해 xz 평면에 격자를 생성하기 위해 정점 및 색인을 생성하는 것을 확인한 후 해당 내용을 D3D 정점 및 색인 버퍼에 저장하는 부분을 분석해보겠습니다.
xz 평면에 격자 생성
- 생성할 격자
- 해당 사진을 보고 아래 코드를 천천히 읽어보면 어렵지 않게 이해할 수 있습니다.
- 격자의 중앙이 (0, 0) 이라는 것과 x, z의 + 방향을 유의해주세요.
- 정점 생성
- 매개변수 width, depth는 격자의 가로, 세로의 길이라고 생각하면 됩니다.
- 매개변수 m, n은 격자의 가로, 세로 정점의 개수입니다.
- 마지막 매개변수는 생성한 정점 및 색인들을 Hills 예제 main 코드에 다시 넘겨주기 위한 구조체입니다.
- 색인 생성
- 삼각형 정점 감기 순서를 유의해서 색인을 생성합니다.
- 격자의 사각형 한 칸을 만들기 위해선 삼각형 2개가 필요합니다.
정점 버퍼 및 색인 버퍼
- 정점 버퍼
- GeometryGenerator 클래스에서 생성한 격자의 정점 및 색인을 grid 구조체를 이용해 반환받습니다.
- 만들어진 xz 평면 격자의 정점들의 y값에 높이 함수를 적용하여 각 정점마다 높이값을 다르게 합니다.
- 색인 버퍼
- 색인의 경우 크게 변경해줄 사항이 없으므로 gird 구조체의 색인 값들을 그대로 이용해서 색인 버퍼에 저장합니다.
이외 과정은 Box 예제와 거의 일치하므로 각자 분석해보시길 바랍니다.
개인적으로 이해한 내용을 바탕으로 작성하였기 때문에 틀린 내용이 있을 수 있습니다. 참고하실 때 주의 해주세요.
틀린 부분이나 이상한 부분이 있으면 댓글로 편하게 지적해주세요.
감사합니다!
'DirectX11 > 코드정리' 카테고리의 다른 글
[Directx11] 6장 연습문제(2) (0) | 2022.11.22 |
---|---|
[Directx11] 6장 연습문제(1) (0) | 2022.11.17 |
[DirectX11] Skull, Waves 예제 (0) | 2022.11.17 |
[DirectX11] Shapes 예제 (0) | 2022.11.17 |
[DirectX11] Box 예제 (0) | 2022.11.17 |
댓글