DirectX11 Tutorial 34: 빌보드

강좌번역/DirectX 11 2017. 11. 30. 20:28 by 빠재

원문: Tutorial 34: Billboarding

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

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

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

프레임워크

이번 예제의 프레임워크에는 새로운 클래스가 없습니다. 대부분의 빌보드 작업은 GraphicsClass에서 이루어집니다.

Graphicsclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "textureshaderclass.h"


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();

    bool Initialize(int, int, HWND);
    void Shutdown();
    bool Frame(float, float, float);

private:
    bool Render();

private:
    D3DClass* m_D3D;
    CameraClass* m_Camera;
    TextureShaderClass* m_TextureShader;

이 예제에서는 두 개의 모델을 사용합니다. 첫번째는 바닥에 놓은 평평한 격자 모델이고, 유저가 왼쪽이나 오른쪽으로 이동하면서 원근감을 제공할 것입니다. 두번째 모델은 두 개의 삼각형으로 만든 사각형에 텍스쳐를 입힐 것인데, 언제나 유저를 마주보도록 회전하는 빌보드입니다.

    ModelClass *m_FloorModel, *m_BillboardModel;
};

#endif

Graphicsclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
    m_D3D = 0;
    m_Camera = 0;
    m_TextureShader = 0;

생성자에서 두 모델을 null로 초기화합니다.

    m_FloorModel = 0;
    m_BillboardModel = 0;
}


GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
    bool result;


    // Direct3D 객체를 생성합니다.
    m_D3D = new D3DClass;
    if(!m_D3D)
    {
        return false;
    }

    // Direct3D 객체를 초기화합니다.
    result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
        return false;
    }

    // 카메라를 생성합니다.
    m_Camera = new CameraClass;
    if(!m_Camera)
    {
        return false;
    }

    // 카메라의 초기 위치입니다.   
    m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
    
    // 텍스쳐 셰이더 객체를 생성합니다.
    m_TextureShader = new TextureShaderClass;
    if(!m_TextureShader)
    {
        return false;
    }

    // 텍스쳐 셰이더 객체를 초기화합니다.
    result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd);
    if(!result)
    {
        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;
    }

    // 바닥 모델을 초기화합니다.
    result = m_FloorModel->Initialize(m_D3D->GetDevice(), "../Engine/data/floor.txt", L"../Engine/data/grid01.dds");
    if(!result)
    {
        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;
    }

    // 빌보드 모델을 초기화합니다.
    result = m_BillboardModel->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/seafloor.dds");
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the billboard model object.", L"Error", MB_OK);
        return false;
    }

    return true;
}


void GraphicsClass::Shutdown()
{

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;
    }

    // D3D 객체를 해제합니다.
    if(m_D3D)
    {
        m_D3D->Shutdown();
        delete m_D3D;
        m_D3D = 0;
    }

    return;
}


bool GraphicsClass::Frame(float positionX, float positionY, float positionZ)
{
    bool result;

SystemClass의 입력으로 들어온 위치에 기반하여 카메라의 위치를 잡습니다. 유저는 왼쪽과 오른쪽 키를 사용하여 조준할 것이기 때문에 빌보드는 카메라의 갱신된 위치에 따라 회전되어야 합니다.

    // 카메라의 위치를 갱신합니다.
    m_Camera->SetPosition(positionX, positionY, positionZ);

    // 그래픽 씬을 그립니다.
    result = Render();
    if(!result)
    {
        return false;
    }

    return true;
}


bool GraphicsClass::Render()
{
    D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, translateMatrix;
    bool result;
    D3DXVECTOR3 cameraPosition, modelPosition;
    double angle;
    float rotation;


    // 씬 렌더링을 시작하기 위하여 버퍼를 초기화합니다.
    m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

    // 카메라의 위치를 이용하여 뷰 행렬을 생성합니다.
    m_Camera->Render();

    // 카메라 및 d3d 객체로부터 월드, 뷰 그리고 투영 행렬을 구합니다.
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetWorldMatrix(worldMatrix);
    m_D3D->GetProjectionMatrix(projectionMatrix);

우선 바닥 모델을 평소대로 렌더링합니다.

    // 바닥 모델의 정점 및 인덱스 버퍼를 파이프라인에 넣어 그릴 준비를 합니다.
    m_FloorModel->Render(m_D3D->GetDeviceContext());

    // 텍스쳐 셰이더를 이용하여 바닥 모델을 그립니다.
    result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_FloorModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
                     m_FloorModel->GetTexture());
    if(!result)
    {
        return false;
    }

카메라의 위치를 얻어온 뒤 월드에서의 빌보드 모델의 위치를 설정합니다.

    // 카메라의 위치를 가져옵니다.
    cameraPosition = m_Camera->GetPosition();

    // 빌보드 모델의 위치를 설정합니다.
    modelPosition.x = 0.0f;
    modelPosition.y = 1.5f;
    modelPosition.z = 0.0f;

카메라의 위치를 이용하여 빌보드의 회전값을 계산하여 카메라를 마주볼 수 있도록 합니다.

    // atan2 함수를 이용하여 빌보드 모델에 적용될 회전값을 계산합니다. 이렇게 하여 빌보드 모델이 현재 카메라 위치를 바라보게 합니다.
    angle = atan2(modelPosition.x - cameraPosition.x, modelPosition.z - cameraPosition.z) * (180.0 / D3DX_PI);

    // 회전각도를 라디안으로 변환합니다.
    rotation = (float)angle * 0.0174532925f;

rotation값을 사용하여 월드 행렬을 돌리고, 빌보드의 위치로 이동시킵니다.

    // 월드 행렬을 이용하여 원점에서의 빌보드의 회전값을 설정합니다.
    D3DXMatrixRotationY(&worldMatrix, rotation);

    // 빌보드 모델의 이동 행렬을 설정합니다.
    D3DXMatrixTranslation(&translateMatrix, modelPosition.x, modelPosition.y, modelPosition.z);

    // 마지막으로 회전 행렬와 이동 행렬을 조합하여 빌보드 모델의 최종 월드 행렬을 계산합니다.
    D3DXMatrixMultiply(&worldMatrix, &worldMatrix, &translateMatrix); 

빌보드 모델을 그립니다.

    // 모델의 정점 및 인덱스 버퍼를 파이프라인에 넣어 그릴 준비를 합니다.
    m_BillboardModel->Render(m_D3D->GetDeviceContext());

    // 텍스쳐 셰이더를 이용하여 모델을 그립니다.
    result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_BillboardModel->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
                     m_BillboardModel->GetTexture());
    if(!result)
    {
        return false;
    }

    // 렌더링된 씬을 스크린에 노출시킵니다. 
    m_D3D->EndScene();

    return true;
}

마치면서

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

연습 문제

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

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

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

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

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

소스 코드

소스 코드 및 데이터 파일: dx11src34.zip

실행파일만: dx11exe34.zip

'강좌번역 > DirectX 11' 카테고리의 다른 글

DirectX11 Tutorial 36: 블러  (0) 2018.07.03
DirectX11 Tutorial 35: 깊이 버퍼  (0) 2018.05.12
DirectX11 Tutorial 33: 불꽃  (0) 2017.10.25
DirectX11 Tutorial 32: 유리, 얼음  (0) 2017.09.23
DirectX11 Tutorial 31: 3차원 음향  (0) 2017.09.08
Nav