Thinking Different




Tutorial 34 - 빌보드



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



 DirectX 11에서 빌보드는 먼 거리에 있는 3D 지형을 표시하기 위하여 텍스쳐를 입힌 사각형을 사용하는 과정입니다. 그다지 중요하지는 않지만 많은 폴리곤이 필요하기 때문에 그리지 않는 복잡한 씬도 이 방법을 사용하여 성능의 향상을 꾀할 수 있습니다.


빌보드가 어떻게 사용되는지에 상상해 보기 위하여 수천개의 나무들이 있는 숲 씬이 있다고 생각해 보겠습니다. 폴리곤의 개수가 많은 나무들로 구성된 복잡한 숲을 실시간 렌더링으로 그려낸다는 것은 현존하는 대부분의 그래픽 카드의 가용성을 넘어서는 일입니다. 따라서 50개 정도의 가장 가까운 나무들만 제대로 그리고 나머지 나무들은 빌보드로 그리게 하는 방법을 쓰게 됩니다. 이렇게 함으로 전체 숲을 실시간으로 렌더링하면서 전체 폴리곤 개수는 낮게 유지할 수 있습니다. 유저가 가까운 50개의 나무를 지나치면 그 다음 50개의 나무가 점차적으로 빌보드에서 풀 폴리곤 모델로 바뀌게 될 것입니다. 이런 식으로 가장 가까운 나무들은 항상 디테일한 폴리곤으로 보여주고 멀리 있는 나무들은 항상 적은 폴리곤을 사용하는 빌보드가 되게 할 수 있습니다. 이 방법은 건물이나 구름, 산, 구조물, 식물, 파티클 등등 다른 많은 물체에도 사용할 수 있습니다.


빌보드를 구현하는 방법간의 차이점이 있다면 사각형이 어떻게 회전하는지가 다릅니다. 어떤 경우 사각형이 유저가 보는 방향에 따라 돌아가는데, 대부분의 파티클 시스템이 이렇게 동작합니다. 다른 경우 3D 텍스트처럼 2D같이 항상 스크린을 바라보게끔 하는 경우도 있습니다. 하지만 이번 예제에서는 유저의 위치에 따라 유저를 항상 마주보게 하는 세번째 방법을 다룰 것입니다. 방금 나무 예제에서 여러분이라면 이 방법을 사용할 것입니다.


프레임워크






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
#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 TextureShaderClass;
 
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();
 
    bool Initialize(intint, HWND);
    void Shutdown();
    bool Frame(XMFLOAT3&);
 
private:
    bool Render();
 
private:
    D3DClass* m_Direct3D = nullptr;
    CameraClass* m_Camera = nullptr;
    ModelClass* m_FloorModel = nullptr;
    ModelClass* m_BillboardModel = nullptr;
    TextureShaderClass* m_TextureShader = 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
#include "stdafx.h"
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "textureshaderclass.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(XMFLOAT3(0.0f, 0.0f, -10.0f));
 
    // 텍스처 쉐이더 객체를 생성한다.
    m_TextureShader = new TextureShaderClass;
    if(!m_TextureShader)
    {
        return false;
    }
 
    // 텍스처 쉐이더 객체를 초기화한다.
    if(!m_TextureShader->Initialize(m_Direct3D->GetDevice(), hwnd))
    {
        MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK);
        return false;
    }
 
    // 바닥 모델 객체를 만듭니다.
    m_FloorModel = new ModelClass;
    if(!m_FloorModel)
    {
        return false;
    }
 
    // 바닥 모델 객체를 초기화합니다.
    if(!m_FloorModel->Initialize(m_Direct3D->GetDevice(), "../Dx11Demo_34/data/floor.txt", L"../Dx11Demo_34/data/grid01.dds"))
    {
        MessageBox(hwnd, L"Could not initialize the floor model object.", L"Error", MB_OK);
        return false;
    }
 
    // 빌보드 모델 객체를 만듭니다.
    m_BillboardModel = new ModelClass;
    if(!m_BillboardModel)
    {
        return false;
    }
 
    // 빌보드 모델 객체를 초기화합니다.
    if(!m_BillboardModel->Initialize(m_Direct3D->GetDevice(), "../Dx11Demo_34/data/square.txt", L"../Dx11Demo_34/data/seafloor.dds"))
    {
        MessageBox(hwnd, L"Could not initialize the billboard model object.", L"Error", MB_OK);
        return false;
    }
 
    return true;
}
 
 
void GraphicsClass::Shutdown()
{
    // 빌보드 모델 객체를 해제합니다.
    if(m_BillboardModel)
    {
        m_BillboardModel->Shutdown();
        delete m_BillboardModel;
        m_BillboardModel = 0;
    }
 
    // 바닥 모델 객체를 해제합니다.
    if(m_FloorModel)
    {
        m_FloorModel->Shutdown();
        delete m_FloorModel;
        m_FloorModel = 0;
    }
 
    // 텍스처 쉐이더 객체를 해제합니다.
    if(m_TextureShader)
    {
        m_TextureShader->Shutdown();
        delete m_TextureShader;
        m_TextureShader = 0;
    }
 
    // 카메라 객체를 해제합니다.
    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(XMFLOAT3& position)
{
    // 카메라 위치를 업데이트합니다.
    m_Camera->SetPosition(position);
 
    // 그래픽 장면을 렌더링합니다.
    return Render();
}
 
 
bool GraphicsClass::Render()
{
    XMMATRIX worldMatrix, viewMatrix, projectionMatrix, translateMatrix;
    XMFLOAT3 cameraPosition, modelPosition;
 
    // 장면을 시작할 버퍼를 지운다.
    m_Direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
 
    // 카메라의 위치에 따라 뷰 행렬을 생성합니다.
    m_Camera->Render();
 
    // 카메라 및 d3d 객체에서 월드, 뷰 및 오쏘 (ortho) 행렬을 가져옵니다.
    m_Direct3D->GetWorldMatrix(worldMatrix);
    m_Camera->GetViewMatrix(viewMatrix);
    m_Direct3D->GetProjectionMatrix(projectionMatrix);
 
    // 드로잉을 준비하기 위해 바닥 파이프 모델 버텍스와 인덱스 버퍼를 그래픽 파이프 라인에 배치합니다.
    m_FloorModel->Render(m_Direct3D->GetDeviceContext());
 
    // 텍스처 쉐이더를 사용하여 바닥 모델을 렌더링합니다.
    if(!m_TextureShader->Render(m_Direct3D->GetDeviceContext(), m_FloorModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
        m_FloorModel->GetTexture()))
    {
        return false;
    }
 
    // 카메라 위치를 얻는다.
    cameraPosition = m_Camera->GetPosition();
 
    // 빌보드 모델의 위치를 ​​설정합니다.
    modelPosition.x = 0.0f;
    modelPosition.y = 1.5f;
    modelPosition.z = 0.0f;
 
    // 아크 탄젠트 함수를 사용하여 현재 카메라 위치를 향하도록 빌보드 모델에 적용해야하는 회전을 계산합니다.
    double angle = atan2(modelPosition.x - cameraPosition.x, modelPosition.z - cameraPosition.z) * (180.0 / XM_PI);
 
    // 회전을 라디안으로 변환합니다.
    float rotation = (float)angle * 0.0174532925f;
 
    // 세계 행렬을 사용하여 원점에서 빌보드 회전을 설정합니다.
    worldMatrix = XMMatrixRotationY(rotation);
 
    // 빌보드 모델에서 번역 행렬을 설정합니다.
    translateMatrix = XMMatrixTranslation(modelPosition.x, modelPosition.y, modelPosition.z);
 
    // 마지막으로 회전 및 변환 행렬을 결합하여 빌보드 모델의 최종 행렬을 만듭니다.
    worldMatrix = XMMatrixMultiply(worldMatrix, translateMatrix);
 
    // 모델 버텍스와 인덱스 버퍼를 그래픽 파이프 라인에 배치하여 드로잉을 준비합니다.
    m_BillboardModel->Render(m_Direct3D->GetDeviceContext());
 
    // 텍스처 셰이더를 사용하여 모델을 렌더링합니다.
    if(!m_TextureShader->Render(m_Direct3D->GetDeviceContext(), m_BillboardModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
        m_BillboardModel->GetTexture()))
    {
        return false;
    }
 
    // 렌더링 된 장면을 화면에 표시합니다.
    m_Direct3D->EndScene();
 
    return true;
}
cs



출력 화면




마치면서


여러분이 빌보드 모델 왼쪽 오른쪽으로 옴직여도 모델이 항상 여러분의 위치를 바라볼 것입니다. 이 방법 외에도 다른 빌보딩 기법을 사용하여 또다른 효과를 줄 수 있다는 점을 알아 두시기 바랍니다.



연습문제


1. 프로그램을 다시 컴파일하여 실행해 보십시오. 왼쪽과 오른쪽 화살표 키를 사용하여 양쪽으로 움직여 보고 빌보드가 알맞게 회전하는지 확인해 보십시오.


2. 빌보드의 텍스쳐를 알파블렌딩을 사용한 나무 그림으로 바꾸어 보십시오.


3. 평평한 지형에 빌보드를 이용하여 작은 숲을 만들어 보십시오.


4. 각각의 나무와의 거리를 이용하여 적당한 지점에서 빌보드와 실제 나무 모델이 서로 변환되게끔 블렌드 효과를 만들어 보십시오.


5. 뷰 기반의 빌보드를 공부해 보십시오.



소스코드


소스코드 : Dx11Demo_34.zip