Thinking Different




Tutorial 16 - 프러스텀 컬링



원문 : http://rastertek.com/dx11tut16.html



 화면에 그려지는 3차원 영역의 보이는 부분을 시야 프러스텀(viewing frustum, 시야 절두체, 역자주: 절두체라는 표현도 많이 쓰기는 하지만 이 글에서는 영어 발음대로 프러스텀이라는 용어를 쓰겠습니다)이라고 합니다. 프러스텀 안에 있는 모든 것은 비디오카드에 그려집니다. 반대로 프러스텀 바깥에 있는 것은 렌더링 과정에서 판단하고 그리지 않습니다.


 하지만 큰 화면을 가진 경우 비디오 카드에 의존한 채 컬링을 하는 것은 많은 연산을 요구할 수 있습니다. 예를 들어 각각 5000개의 삼각형으로 이루어진 2000개 이상의 모델이 있는 장면에서 단지 10~20개의 모델만 시야에 있다고 해 봅시다. 비디오 카드는 시야에 있는 10개를 제외한 1990개의 모델들을 그리지 않기 위해서 2000개의 모델의 모든 삼각형을 시험해야 합니다. 충분히 상상할 수 있듯이 이것은 매우 비효율적입니다.


 프러스텀 컬링은 렌더링 직전에 모델들이 프러스텀 내부에 있는지 아닌지 확인하는 일을 해 줍니다. 이를 통해 모든 삼각형들을 비디오 카드로 보내는 것이 아니라 정확히 그려야 할 것만 카드로 보낼 수 있게 됩니다. 이것이 처리되는 방식은 각각의 모델들을 정육면체나 그냥 육면체 또는 구체로 감싸고 이 물체가 보이는지는 정육면체나 상자, 구체가 보이는지의 여부로 판단하는 것입니다. 이를 위해 필요한 수학은 단지 몇줄뿐이지만 수천개의 삼각형들의 테스트 연산들을 절약해 줍니다.


 이것이 어떻게 동작하는지를 보이기 위해서 우선 랜덤으로 생성된 25개의 구체들이 있는 장면을 생성할 것입니다. 그리고 나서 카메라를 왼쪽/오른쪽 버튼으로 돌리면서 시야 바깥에 있는 구체들이 컬링되는지 테스트할 것입니다. 또한 더 확실히 하기 위해서 현재 보이고 보이지 않는 구체들의 개수를 화면에 표시하도록 할 것입니다. 코드는 역시 이전 튜토리얼에서 이어집니다.




 프레임워크는 주로 이전 튜토리얼들의 클래스로 구성되어 있습니다. 새로운 클래스는 FrustumClass, PositionClass, ModelListClass입니다. FrustumClass는 이 튜토리얼에서 다룰 프러스텀 컬링 기능을 캡슐화하고 있습니다. ModelListClass는 매 실행마다 임의로 생성된 25개의 구체들의 위치와 색상 정보를 포함합니다. PositionClass는 유저가 왼쪽/오른쪽 버튼을 누를때 시야 방향을 조절할 수 있도록 하는 기능을 담당할 것입니다.


프레임워크




FrustumClass의 헤더 파일은 단순합니다. 이 클래스에서는 아무런 초기화나 마무리 함수를 요구하지 않습니다. 카메라가 한번 그리고 난 뒤로 각 프레임마다 ConstructFrustum 함수가 호출됩니다. ConstructFrustum 함수는 갱신된 카메라 위치를 기초로 m_planes 변수를 이용하여 시야 프러스텀의 6개 평면을 계산하고 저장합니다. 여기서 점이나 정육면체, 육면체, 구체 각각 시야 프러스텀 안에 있는지 그렇지 않은지 체크하는 4개의 함수를 만듭니다.


FrustumClass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
 
class FrustumClass : public AlignedAllocationPolicy<16>
{
public:
    FrustumClass();
    FrustumClass(const FrustumClass&);
    ~FrustumClass();
 
    void ConstructFrustum(float, XMMATRIX, XMMATRIX);
 
    bool CheckPoint(floatfloatfloat);
    bool CheckCube(floatfloatfloatfloat);
    bool CheckSphere(floatfloatfloatfloat);
    bool CheckRectangle(floatfloatfloatfloatfloatfloat);
 
private:
    XMVECTOR m_planes[6];
};
cs



FrustumClass.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
#include "stdafx.h"
#include "FrustumClass.h"
 
 
FrustumClass::FrustumClass()
{
}
 
 
FrustumClass::FrustumClass(const FrustumClass& other)
{
}
 
 
FrustumClass::~FrustumClass()
{
}
 
 
 
 
void FrustumClass::ConstructFrustum(float screenDepth, XMMATRIX projectionMatrix, XMMATRIX viewMatrix)
{
    // 투영 행렬을 XMFLOAT4X4 유형으로 변환합니다.
    XMFLOAT4X4 pMatrix;
    XMStoreFloat4x4(&pMatrix, projectionMatrix);
 
    // 절두체에서 최소 Z 거리를 계산합니다.
    float zMinimum = -pMatrix._43 / pMatrix._33;
    float r = screenDepth / (screenDepth - zMinimum);
 
    // 업데이트 된 값을 다시 투영 행렬에 설정합니다.
    pMatrix._33 = r;
    pMatrix._43 = -* zMinimum;
    projectionMatrix = XMLoadFloat4x4(&pMatrix);
 
    // 뷰 매트릭스와 업데이트 된 프로젝션 매트릭스에서 절두체 매트릭스를 만듭니다.
    XMMATRIX finalMatrix = XMMatrixMultiply(viewMatrix, projectionMatrix);
 
    // 최종 행렬을 XMFLOAT4X4 유형으로 변환합니다.
    XMFLOAT4X4 matrix;
    XMStoreFloat4x4(&matrix, finalMatrix);
 
    // 절두체의 가까운 평면을 계산합니다.
    float x = (float)(matrix._14 + matrix._13);
    float y = (float)(matrix._24 + matrix._23);
    float z = (float)(matrix._34 + matrix._33);
    float w = (float)(matrix._44 + matrix._43);
    m_planes[0= XMVectorSet(x, y, z, w);
    m_planes[0= XMPlaneNormalize(m_planes[0]);
 
    // 절두체의 먼 평면을 계산합니다.
    x = (float)(matrix._14 - matrix._13);
    y = (float)(matrix._24 - matrix._23);
    z = (float)(matrix._34 - matrix._33);
    w = (float)(matrix._44 - matrix._43);
    m_planes[1= XMVectorSet(x, y, z, w);
    m_planes[1= XMPlaneNormalize(m_planes[1]);
 
    // 절두체의 왼쪽 평면을 계산합니다.
    x = (float)(matrix._14 + matrix._11);
    y = (float)(matrix._24 + matrix._21);
    z = (float)(matrix._34 + matrix._31);
    w = (float)(matrix._44 + matrix._41);
    m_planes[2= XMVectorSet(x, y, z, w);
    m_planes[2= XMPlaneNormalize(m_planes[2]);
 
    // 절두체의 오른쪽 평면을 계산합니다.
    x = (float)(matrix._14 - matrix._11);
    y = (float)(matrix._24 - matrix._21);
    z = (float)(matrix._34 - matrix._31);
    w = (float)(matrix._44 - matrix._41);
    m_planes[3= XMVectorSet(x, y, z, w);
    m_planes[3= XMPlaneNormalize(m_planes[3]);
 
    // 절두체의 윗 평면을 계산합니다.
    x = (float)(matrix._14 - matrix._12);
    y = (float)(matrix._24 - matrix._22);
    z = (float)(matrix._34 - matrix._32);
    w = (float)(matrix._44 - matrix._42);
    m_planes[4= XMVectorSet(x, y, z, w);
    m_planes[4= XMPlaneNormalize(m_planes[4]);
 
    // 절두체의 아래 평면을 계산합니다.
    x = (float)(matrix._14 + matrix._12);
    y = (float)(matrix._24 + matrix._22);
    z = (float)(matrix._34 + matrix._32);
    w = (float)(matrix._44 + matrix._42);
    m_planes[5= XMVectorSet(x, y, z, w);
    m_planes[5= XMPlaneNormalize(m_planes[5]);
}
 
 
bool FrustumClass::CheckPoint(float x, float y, float z)
{
    for (int i = 0; i<6; i++)
    {
        // 포인트가 뷰 frustum의 6 개 평면 모두 안에 있는지 확인합니다.
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet(x, y, z, 1.0f))) < 0.0f)
            return false;
    }
 
    return true;
}
 
bool FrustumClass::CheckCube(float xCenter, float yCenter, float zCenter, float radius)
{
    // 뷰 프러스 텀에 큐브의 한 점이 있는지 확인합니다.
    for (int i = 0; i<6; i++)
    {
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter - radius), (yCenter - radius), 
(zCenter - radius), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter + radius), (yCenter - radius), 
(zCenter - radius), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter - radius), (yCenter + radius),
 (zCenter - radius), 1.0f))) >= 0.0f)
            continue;

        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter + radius), (yCenter + radius),
  (zCenter - radius), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter - radius), (yCenter - radius),
  (zCenter + radius), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter + radius), (yCenter - radius),
  (zCenter + radius), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter - radius), (yCenter + radius),
 (zCenter + radius), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter + radius), (yCenter + radius),
 (zCenter + radius), 1.0f))) >= 0.0f)
            continue;
 
        return false;
    }
 
    return true;
}
 
 
bool FrustumClass::CheckSphere(float xCenter, float yCenter, float zCenter, float radius)
{
    for (int i = 0; i<6; i++)
    {
        // 구의 반경이 뷰 frustum 안에 있는지 확인합니다.
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet(xCenter, yCenter, zCenter, 1.0f))) < -radius)
            return false;
    }
 
    return true;
}
 
 
bool FrustumClass::CheckRectangle(float xCenter, float yCenter, float zCenter, float xSize, float ySize, float zSize)
{
    // 사각형의 6 개의 평면 중 하나가 뷰 frustum 안에 있는지 확인합니다.
    for (int i = 0; i<6; i++)
    {
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter - xSize), (yCenter - ySize), (zCenter - zSize), 1.0f))) >= 0.0f)
            continue;
        
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter + xSize), (yCenter - ySize), (zCenter - zSize), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter - xSize), (yCenter + ySize), (zCenter - zSize), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter - xSize), (yCenter - ySize), (zCenter + zSize), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter + xSize), (yCenter + ySize), (zCenter - zSize), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter + xSize), (yCenter - ySize), (zCenter + zSize), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter - xSize), (yCenter + ySize), (zCenter + zSize), 1.0f))) >= 0.0f)
            continue;
 
        if (XMVectorGetX(XMPlaneDotCoord(m_planes[i], XMVectorSet((xCenter + xSize), (yCenter + ySize), (zCenter + zSize), 1.0f))) >= 0.0f)
            continue;
 
        return false;
    }
 
    return true;
}
cs





ModelListClass 클래스는 장면의 모든 모델들에 대한 정보를 유지/관리하는 클래스입니다. 이 튜토리얼에서는 단지 한 가지 모델만 있으므로 지금은 구체 모델의 크기와 색상만을 유지합니다. 이 클래스는 한 장면 내의 다른 타입들의 모델들을 모두 관리할 수 있게 확장할 수 있으나 일단 지금은 단순하게 만들겠습니다.


ModelListClass.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
#pragma once
 
class ModelListClass
{
private:
    struct ModelInfoType
    {
        XMFLOAT4 color;
        float positionX, positionY, positionZ;
    };
 
public:
    ModelListClass();
    ModelListClass(const ModelListClass&);
    ~ModelListClass();
 
    bool Initialize(int);
    void Shutdown();
 
    int GetModelCount();
    void GetData(intfloat&float&float&, XMFLOAT4&);
 
private:
    int m_modelCount = 0;
    ModelInfoType* m_ModelInfoList = nullptr;
};
cs



ModelListClass.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
#include "stdafx.h"
#include <time.h>
#include "ModelListClass.h"
 
 
ModelListClass::ModelListClass()
{
}
 
 
ModelListClass::ModelListClass(const ModelListClass& other)
{
}
 
 
ModelListClass::~ModelListClass()
{
}
 
 
bool ModelListClass::Initialize(int numModels)
{
    float red = 0.0f;
    float green = 0.0f;
    float blue = 0.0f;
 
    // 모델 수를 저장합니다.
    m_modelCount = numModels;
 
    // 모델 정보의 리스트 배열을 만든다.
    m_ModelInfoList = new ModelInfoType[m_modelCount];
    if (!m_ModelInfoList)
    {
        return false;
    }
 
    // 랜덤 제네레이터에 현재 시간을 시드합니다.
    srand((unsigned int)time(NULL));
 
    // 모든 모델을 거쳐 무작위로 모델 색상과 위치를 생성합니다.
    for (int i = 0; i<m_modelCount; i++)
    {
        // 모델에 임의의 색상을 생성합니다.
        red = (float)rand() / RAND_MAX;
        green = (float)rand() / RAND_MAX;
        blue = (float)rand() / RAND_MAX;
 
        m_ModelInfoList[i].color = XMFLOAT4(red, green, blue, 1.0f);
 
        // 모드에 대한 뷰어 앞에 임의의 위치를 ​​생성합니다.
        m_ModelInfoList[i].positionX = (((float)rand() - (float)rand()) / RAND_MAX) * 10.0f;
        m_ModelInfoList[i].positionY = (((float)rand() - (float)rand()) / RAND_MAX) * 10.0f;
        m_ModelInfoList[i].positionZ = ((((float)rand() - (float)rand()) / RAND_MAX) * 10.0f) + 5.0f;
    }
 
    return true;
}
 
 
void ModelListClass::Shutdown()
{
    // 모델 정보 목록을 해제합니다.
    if (m_ModelInfoList)
    {
        delete[] m_ModelInfoList;
        m_ModelInfoList = 0;
    }
}
 
 
int ModelListClass::GetModelCount()
{
    return m_modelCount;
}
 
 
void ModelListClass::GetData(int index, float& positionX, float& positionY, float& positionZ, XMFLOAT4& color)
{
    positionX = m_ModelInfoList[index].positionX;
    positionY = m_ModelInfoList[index].positionY;
    positionZ = m_ModelInfoList[index].positionZ;
 
    color = m_ModelInfoList[index].color;
}
cs




이번 튜토리얼의 GraphicsClass 는 이전 튜토리얼의 많은 클래스들을 포함합니다. 또한 새로 frustumclass.h와 modellistclass.h 헤더 파일도 포함시킵니다.


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
37
38
39
40
41
42
43
#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 TextClass;
class ModelClass;
class LightShaderClass;
class LightClass;
class ModelListClass;
class FrustumClass;
 
 
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();
 
    bool Initialize(intint, HWND);
    void Shutdown();
    bool Frame(float);
    bool Render();
 
private:
    D3DClass* m_Direct3D = nullptr;
    CameraClass* m_Camera = nullptr;
    TextClass* m_Text = nullptr;
    ModelClass* m_Model = nullptr;
    LightShaderClass* m_LightShader = nullptr;
    LightClass* m_Light = nullptr;
    ModelListClass* m_ModelList = nullptr;
    FrustumClass* m_Frustum = 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
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
#include "stdafx.h"
#include "d3dclass.h"
#include "cameraclass.h"
#include "textclass.h"
#include "modelclass.h"
#include "lightshaderclass.h"
#include "lightclass.h"
#include "modellistclass.h"
#include "frustumclass.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;
    }
 
    // 카메라 포지션 설정
    XMMATRIX baseViewMatrix;
    m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
    m_Camera->Render();
    m_Camera->GetViewMatrix(baseViewMatrix);
 
    // m_Text 객체 생성
    m_Text = new TextClass;
    if(!m_Text)
    {
        return false;
    }
 
    // m_Text 객체 초기화
    if (!m_Text->Initialize(m_Direct3D->GetDevice(), m_Direct3D->GetDeviceContext(), hwnd, screenWidth, screenHeight,
 baseViewMatrix))
    {
        MessageBox(hwnd, L"Could not initialize the text object.", L"Error", MB_OK);
        return false;
    }
 
    // 모델 객체 생성
    m_Model = new ModelClass;
    if(!m_Model)
    {
        return false;
    }
 
    // 모델 객체 초기화
    if(!m_Model->Initialize(m_Direct3D->GetDevice(), L"../Dx11Demo_16/data/seafloor.dds""../Dx11Demo_16/data/sphere.txt"))
    {
        MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
        return false;
    }
 
    // 조명 쉐이더 객체 생성
    m_LightShader = new LightShaderClass;
    if(!m_LightShader)
    {
        return false;
    }
 
    // 조명 쉐이더 객체 초기화
    if(!m_LightShader->Initialize(m_Direct3D->GetDevice(), hwnd))
    {
        MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
        return false;
    }
 
    // 조명 객체 생성
    m_Light = new LightClass;
    if(!m_Light)
    {
        return false;
    }
 
    // 조명 객체 초기화
    m_Light->SetDirection(0.0f, 0.0f, 1.0f);
 
    // 모델 목록 객체 생성
    m_ModelList = new ModelListClass;
    if(!m_ModelList)
    {
        return false;
    }
 
    // 모델 목록 객체 초기화
    if(!m_ModelList->Initialize(25))
    {
        MessageBox(hwnd, L"Could not initialize the model list object.", L"Error", MB_OK);
        return false;
    }
 
    // 프러스텀 객체 생성
    m_Frustum = new FrustumClass;
    if(!m_Frustum)
    {
        return false;
    }
 
    return true;
}
 
 
void GraphicsClass::Shutdown()
{
    // 프러스텀 객체 반환
    if(m_Frustum)
    {
        delete m_Frustum;
        m_Frustum = 0;
    }
 
    // 모델 목록 객체 반환
    if(m_ModelList)
    {
        m_ModelList->Shutdown();
        delete m_ModelList;
        m_ModelList = 0;
    }
 
    // 조명 객체 반환
    if(m_Light)
    {
        delete m_Light;
        m_Light = 0;
    }
 
    // 조명 쉐이더 객체 반환
    if(m_LightShader)
    {
        m_LightShader->Shutdown();
        delete m_LightShader;
        m_LightShader = 0;
    }
 
    // 모델 객체 반환
    if(m_Model)
    {
        m_Model->Shutdown();
        delete m_Model;
        m_Model = 0;
    }
    
    // m_Text 객체 반환
    if (m_Text)
    {
        m_Text->Shutdown();
        delete m_Text;
        m_Text = 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(float rotationY)
{
    // 카메라 위치 설정
    m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
 
    // 카메라의 회전을 설정합니다
    m_Camera->SetRotation(0.0f, rotationY, 0.0f);
 
    return true;
}
 
 
bool GraphicsClass::Render()
{
    float positionX = 0;
    float positionY = 0;
    float positionZ = 0;
    float radius = 1.0f; // 구의 반지름을 1.0f로 설정
    XMFLOAT4 color;
 
    // 씬을 그리기 위해 버퍼를 지웁니다
    m_Direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
 
    // 카메라의 위치에 따라 뷰 행렬을 생성합니다
    m_Camera->Render();
 
    // 카메라 및 d3d 객체에서 월드, 뷰 및 투영 행렬을 가져옵니다
    XMMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;
    m_Camera->GetViewMatrix(viewMatrix);
    m_Direct3D->GetWorldMatrix(worldMatrix);
    m_Direct3D->GetProjectionMatrix(projectionMatrix);
    m_Direct3D->GetOrthoMatrix(orthoMatrix);
 
    // 절두체를 만듭니다.
    m_Frustum->ConstructFrustum(SCREEN_DEPTH, projectionMatrix, viewMatrix);
 
    // 렌더링 될 모델의 수를 얻는다.
    int modelCount = m_ModelList->GetModelCount();
 
    //  렌더링 된 모델의 개수를 초기화합니다.
    int renderCount = 0;
 
    // 모든 모델을 살펴보고 카메라 뷰에서 볼 수 있는 경우에만 렌더링합니다.
    for(int index=0; index<modelCount; index++)
    {
        //이 인덱스에서 구형 모델의 위치와 색상을 가져옵니다.
        m_ModelList->GetData(index, positionX, positionY, positionZ, color);
 
        // 구형 모델이 뷰 frustum에 있는지 확인합니다.
        if(m_Frustum->CheckSphere(positionX, positionY, positionZ, radius))
        {
            // 모델을 렌더링 할 위치로 이동합니다.
            worldMatrix = XMMatrixTranslation(positionX, positionY, positionZ);
 
            // 모델 버텍스와 인덱스 버퍼를 그래픽 파이프 라인에 배치하여 드로잉을 준비합니다.
            m_Model->Render(m_Direct3D->GetDeviceContext());
 
            // 라이트 쉐이더를 사용하여 모델을 렌더링합니다.
            m_LightShader->Render(m_Direct3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix,
  projectionMatrix, m_Model->GetTexture(), m_Light->GetDirection(), color);
 
            // 원래의 월드 매트릭스로 리셋.
            m_Direct3D->GetWorldMatrix(worldMatrix);
 
            //이 모델이 렌더링 되었기 때문에이 프레임의 수를 늘립니다.
            renderCount++;
        }
    }
 
    //이 프레임에서 실제로 렌더링 된 모델의 수를 설정합니다.
    if(!m_Text->SetRenderCount(renderCount, m_Direct3D->GetDeviceContext()))
    {
        return false;
    }
 
    // 모든 2D 렌더링을 시작하려면 Z 버퍼를 끕니다.
    m_Direct3D->TurnZBufferOff();
 
    // 텍스트를 렌더링하기 전에 알파 블렌딩을 켭니다
    m_Direct3D->TurnOnAlphaBlending();
 
    // 텍스트 문자열을 렌더링 합니다
    if(!m_Text->Render(m_Direct3D->GetDeviceContext(), worldMatrix, orthoMatrix))
    {
        return false;
    }
 
    // 텍스트를 렌더링 한 후 알파 블렌딩을 해제합니다
    m_Direct3D->TurnOffAlphaBlending();
 
    // 모든 2D 렌더링이 완료되었으므로 Z 버퍼를 다시 켜십시오.
    m_Direct3D->TurnZBufferOn();
 
    // 버퍼의 내용을 화면에 출력합니다
    m_Direct3D->EndScene();
 
    return true;
}
cs




이 튜토리얼에서 카메라를 왼쪽/오른쪽 화살표 키로 조작하기 위해 카메라의 위치를 계산하고 유지하는 일을 하는 클래스를 만듭니다. 지금 당장 이 클래스는 좌우로 회전하는 것밖에 할 수 없지만 나중에 다른 움직임을 넣도록 확장할 수도 있습니다. 구현하는 움직임 중에는 부드러운 카메라 이동 효과를 위한 가속이나 감속도 포함됩니다.


PositionClass.h


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma once
 
class PositionClass
{
public:
    PositionClass();
    PositionClass(const PositionClass&);
    ~PositionClass();
 
    void SetFrameTime(float);
    void GetRotation(float&);
 
    void TurnLeft(bool);
    void TurnRight(bool);
 
private:
    float m_frameTime = 0.0f;;
    float m_rotationY = 0.0f;;
    float m_leftTurnSpeed = 0.0f; 
    float m_rightTurnSpeed = 0.0f;;
};
cs



PositionClass.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
#include "stdafx.h"
#include "PositionClass.h"
 
 
PositionClass::PositionClass()
{
}
 
 
PositionClass::PositionClass(const PositionClass& other)
{
}
 
 
PositionClass::~PositionClass()
{
}
 
 
void PositionClass::SetFrameTime(float time)
{
    m_frameTime = time;
}
 
 
void PositionClass::GetRotation(float& y)
{
    y = m_rotationY;
}
 
 
void PositionClass::TurnLeft(bool keydown)
{
    // 키를 누르면 카메라가 왼쪽으로 돌아는 속도가 증가합니다. 회전 속도를 늦추지 않으면.
    if (keydown)
    {
        m_leftTurnSpeed += m_frameTime * 0.01f;
 
        if (m_leftTurnSpeed > (m_frameTime * 0.15f))
        {
            m_leftTurnSpeed = m_frameTime * 0.15f;
        }
    }
    else
    {
        m_leftTurnSpeed -= m_frameTime* 0.005f;
 
        if (m_leftTurnSpeed < 0.0f)
        {
            m_leftTurnSpeed = 0.0f;
        }
    }
 
    // 회전 속도를 사용하여 회전을 업데이트합니다.
    m_rotationY -= m_leftTurnSpeed;
    if (m_rotationY < 0.0f)
    {
        m_rotationY += 360.0f;
    }
}
 
 
void PositionClass::TurnRight(bool keydown)
{
    // 키를 누르면 카메라가 오른쪽으로 회전하는 속도가 증가합니다. 회전 속도를 늦추지 않으면.
    if (keydown)
    {
        m_rightTurnSpeed += m_frameTime * 0.01f;
 
        if (m_rightTurnSpeed > (m_frameTime * 0.15f))
        {
            m_rightTurnSpeed = m_frameTime * 0.15f;
        }
    }
    else
    {
        m_rightTurnSpeed -= m_frameTime* 0.005f;
 
        if (m_rightTurnSpeed < 0.0f)
        {
            m_rightTurnSpeed = 0.0f;
        }
    }
 
    // 회전 속도를 사용하여 회전을 업데이트합니다.
    m_rotationY += m_rightTurnSpeed;
    if (m_rotationY > 360.0f)
    {
        m_rotationY -= 360.0f;
    }
}
cs




PositionClass를 사용하기 위해 SystemClass 클래스를 수정합니다.


SystemClass.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
37
38
#pragma once
 
class InputClass;
class GraphicsClass;
class TimerClass;
class PositionClass;
 
class SystemClass
{
public:
    SystemClass();
    SystemClass(const SystemClass&);
    ~SystemClass();
 
    bool Initialize();
    void Shutdown();
    void Run();
 
    LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);
 
private:
    bool Frame();
    void InitializeWindows(int&int&);
    void ShutdownWindows();
 
private:
    LPCWSTR m_applicationName;
    HINSTANCE m_hinstance;
    HWND m_hwnd;
 
    InputClass* m_Input = nullptr;
    GraphicsClass* m_Graphics = nullptr;
    TimerClass* m_Timer = nullptr;
    PositionClass* m_Position = nullptr;
};
 
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static SystemClass* ApplicationHandle = 0;
cs



SystemClass.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
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
#include "stdafx.h"
#include "inputclass.h"
#include "graphicsclass.h"
#include "TimerClass.h"
#include "PositionClass.h"
#include "systemclass.h"
 
 
SystemClass::SystemClass()
{
}
 
 
SystemClass::SystemClass(const SystemClass& other)
{
}
 
 
SystemClass::~SystemClass()
{
}
 
 
bool SystemClass::Initialize()
{
    // 윈도우 창 가로, 세로 넓이 변수 초기화
    int screenWidth = 0;
    int screenHeight = 0;
 
    // 윈도우 생성 초기화
    InitializeWindows(screenWidth, screenHeight);
 
    // m_Input 객체 생성. 이 클래스는 추후 사용자의 키보드 입력 처리에 사용됩니다.
    m_Input = new InputClass;
    if (!m_Input)
    {
        return false;
    }
 
    // m_Input 객체 초기화
    if(!m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight))
    {
        MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
        return false;
    }
 
    // m_Graphics 객체 생성.  그래픽 랜더링을 처리하기 위한 객체입니다.
    m_Graphics = new GraphicsClass;
    if (!m_Graphics)
    {
        return false;
    }
 
    // m_Graphics 객체 초기화.
    if(!m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd))
    {
        return false;
    }
    
    // 타이머 객체를 만듭니다.
    m_Timer = new TimerClass;
    if(!m_Timer)
    {
        return false;
    }
 
    // 타이머 객체를 초기화합니다.
    if(!m_Timer->Initialize())
    {
        MessageBox(m_hwnd, L"Could not initialize the Timer object.", L"Error", MB_OK);
        return false;
    }
 
    // 위치 개체를 생성합니다.
    m_Position = new PositionClass;
    if(!m_Position)
    {
        return false;
    }
 
    return true;
}
 
 
void SystemClass::Shutdown()
{
    // 위치 객체 반환
    if(m_Position)
    {
        delete m_Position;
        m_Position = 0;
    }
 
    // 타이머 객체를 해제합니다.
    if(m_Timer)
    {
        delete m_Timer;
        m_Timer = 0;
    }
 
    // m_Graphics 객체 반환
    if (m_Graphics)
    {
        m_Graphics->Shutdown();
        delete m_Graphics;
        m_Graphics = 0;
    }
 
    // m_Input 객체 반환
    if (m_Input)
    {
        m_Input->Shutdown();
        delete m_Input;
        m_Input = 0;
    }
 
    // Window 종료 처리
    ShutdownWindows();
}
 
 
void SystemClass::Run()
{
    // 메시지 구조체 생성 및 초기화
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));
 
    // 사용자로부터 종료 메시지를 받을때까지 메시지루프를 돕니다
    while (true)
    {
        // 윈도우 메시지를 처리합니다
        if (PeekMessage(&msg, NULL00, PM_REMOVE))
        {
            // 종료 메시지를 받을 경우 메시지 루프를 탈출합니다
            if (msg.message == WM_QUIT)
                break;
 
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            // 그 외에는 Frame 함수를 처리합니다.
            if (!Frame())
            {
                MessageBox(m_hwnd, L"Frame Processing Failed", L"Error", MB_OK);
                break;
            }
        }
        
        // 사용자가 ESC키를 눌렀는지 확인 후 종료 처리함
        if(m_Input->IsEscapePressed() == true)
        {
            break;
        }
    }
}
 
 
bool SystemClass::Frame()
{
    // 시스템 통계를 업데이트 합니다
    m_Timer->Frame();
 
    // 입력 프레임 처리를 수행합니다
    if(!m_Input->Frame())
    {
        return false;
    }
 
    // 업데이트 된 위치를 계산하기 위한 프레임 시간을 설정합니다.
    m_Position->SetFrameTime(m_Timer->GetTime());
 
    // 왼쪽 또는 오른쪽 화살표 키를 눌렀는지 확인하십시오. 그렇지 않으면 카메라를 적절히 회전하십시오.
    bool keyDown = m_Input->IsLeftArrowPressed();
    m_Position->TurnLeft(keyDown);
 
    keyDown = m_Input->IsRightArrowPressed();
    m_Position->TurnRight(keyDown);
 
    // 현재 뷰 포인트 회전을 가져옵니다.
    float rotationY = 0.0f;
    m_Position->GetRotation(rotationY);
 
    // 그래픽 객체에 대한 프레임 처리를 수행합니다.
    if(!m_Graphics->Frame(rotationY))
    {
        return false;
    }
 
    return m_Graphics->Render();
}
 
 
LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
    return DefWindowProc(hwnd, umsg, wparam, lparam);
}
 
 
void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight)
{
    // 외부 포인터를 이 객체로 지정합니다
    ApplicationHandle = this;
 
    // 이 프로그램의 인스턴스를 가져옵니다
    m_hinstance = GetModuleHandle(NULL);
 
    // 프로그램 이름을 지정합니다
    m_applicationName = L"Dx11Demo_16";
 
    // windows 클래스를 아래와 같이 설정합니다.
    WNDCLASSEX wc;
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = m_hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
    wc.hIconSm = wc.hIcon;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = m_applicationName;
    wc.cbSize = sizeof(WNDCLASSEX);
 
    // windows class를 등록합니다
    RegisterClassEx(&wc);
 
    // 모니터 화면의 해상도를 읽어옵니다
    screenWidth = GetSystemMetrics(SM_CXSCREEN);
    screenHeight = GetSystemMetrics(SM_CYSCREEN);
 
    int posX = 0;
    int posY = 0;
 
    // FULL_SCREEN 변수 값에 따라 화면을 설정합니다.
    if (FULL_SCREEN)
    {
        // 풀스크린 모드로 지정했다면 모니터 화면 해상도를 데스크톱 해상도로 지정하고 색상을 32bit로 지정합니다.
        DEVMODE dmScreenSettings;
        memset(&dmScreenSettings, 0sizeof(dmScreenSettings));
        dmScreenSettings.dmSize = sizeof(dmScreenSettings);
        dmScreenSettings.dmPelsWidth = (unsigned long)screenWidth;
        dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight;
        dmScreenSettings.dmBitsPerPel = 32;
        dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
 
        // 풀스크린으로 디스플레이 설정을 변경합니다.
        ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
    }
    else
    {
        // 윈도우 모드의 경우 800 * 600 크기를 지정합니다.
        screenWidth = 800;
        screenHeight = 600;
 
        // 윈도우 창을 가로, 세로의 정 가운데 오도록 합니다.
        posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth) / 2;
        posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2;
    }
 
    // 윈도우를 생성하고 핸들을 구합니다.
    m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName,
        WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP,
        posX, posY, screenWidth, screenHeight, NULLNULL, m_hinstance, NULL);
 
    // 윈도우를 화면에 표시하고 포커스를 지정합니다
    ShowWindow(m_hwnd, SW_SHOW);
    SetForegroundWindow(m_hwnd);
    SetFocus(m_hwnd);
}
 
 
void SystemClass::ShutdownWindows()
{
    // 풀스크린 모드였다면 디스플레이 설정을 초기화합니다.
    if (FULL_SCREEN)
    {
        ChangeDisplaySettings(NULL0);
    }
 
    // 창을 제거합니다
    DestroyWindow(m_hwnd);
    m_hwnd = NULL;
 
    // 프로그램 인스턴스를 제거합니다
    UnregisterClass(m_applicationName, m_hinstance);
    m_hinstance = NULL;
 
    // 외부포인터 참조를 초기화합니다
    ApplicationHandle = NULL;
}
 
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
    switch (umessage)
    {
        // 윈도우 종료를 확인합니다
    case WM_DESTROY:
    {
        PostQuitMessage(0);
        return 0;
    }
 
    // 윈도우가 닫히는지 확인합니다
    case WM_CLOSE:
    {
        PostQuitMessage(0);
        return 0;
    }
 
    // 그 외의 모든 메시지들은 시스템 클래스의 메시지 처리로 넘깁니다.
    default:
    {
        return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam);
    }
    }
}
cs




출력 화면




마치면서


이제 오브젝트를 어떻게 컬링되는지 보았습니다. 서로 다른 모양의 오브젝트들을 빠르게 컬링하는 한 가지 트릭은 물체를 육면체나 구로 감싸거나 점 자체를 효율적으로 사용하는 것입니다.



연습문제


1. 프로그램을 다시 컴파일하고 실행해 보십시오. 왼쪽/오른쪽 화살표 키를 이용하여 카메라를 움직이고 왼쪽 위 화면에 구체 수가 나오는지 확인하십시오.


2. CheckCube 함수를 확인하기 위해 정육면체 모델을 로드하도록 해 보십시오.


3. 몇 가지 다른 모델들을 만들고 어느 모양을 이용한 컬링 방식이 가장 좋은지 찾아 보십시오.



소스코드


소스코드 : 

Dx11Demo_16.zip