DirectX11 Tutorial 22: 텍스쳐에 그리기(RTT)

강좌번역/DirectX 11 2013. 9. 18. 14:10 by 빠재

원문: Tutorial 22: Render to Texture

이 튜토리얼에서는 DirectX 11에서 어떻게 텍스쳐에 렌더링을 할 수 있는지를 다룰 것입니다. 튜토리얼의 코드는 모델 렌더링과 비트맵 렌더링 튜토리얼 코드에 기반합니다. (역자주: 텍스쳐에 렌더링하기는 ‘Render to Texture’을 번역한 것이며, 약어로 RTT라고도 합니다. 아래부터는 Render to Texture가 RTT로 표현됩니다.)

RTT는 백버퍼가 아닌 텍스쳐에도 장면을 그릴 수 있게 하는 방법을 제공합니다. 이 텍스쳐를 활용하면 수많은 경우에 유용하게 쓸 수 있는데, 예를 들어 다른 카메라로 본 장면을 텍스쳐에 그려 거울이나 TV화면의 내용으로 쓸 수 있을 것입니다. 또한 텍스쳐에 특별한 셰이더를 적용하여 더 독특한 효과를 줄 수도 있습니다. 이런 무궁무진한 활용방법은 RTT가 왜 DirectX 11에서 가장 강력한 도구들 중 하나인지 설명해 줍니다.

하지만 RTT를 쓰게 되면 장면을 백버퍼 하나에만 그리는 것이 아니기 때문에 RTT의 비용은 매우 큽니다. 많은 3D 엔진들이 이것 때문에 성능이 저하되지만, 어떻게 사용하느냐에 따라 그 효과는 큰 비용을 그 이상이 될 수 있습니다.

이 튜토리얼에서는 우선 회전하는 3D 육면체 모델을 텍스쳐에 그릴 것입니다. 그리고 나서 렌더링된 텍스쳐를 2D 비트맵으로서 화면의 왼쪽 위에 그릴 것입니다. 그리고 일반 화면에도 같은 육면체 모델을 그리게 할 것입니다. RTT는 파란 배경을 갖게 할 것이므로 화면에 그려진 두 개의 육면체 중에서 어느 것이 RTT로 그린 것인지 알 수 있을 것입니다. 우선 새로 바뀐 프레임워크부터 보겠습니다.


프레임워크

RenderTextureClassDebugWindowClass를 빼고는 프레임워크 대부분이 친숙해 보일 것입니다. RenderTextureClass는 DirectX 11의 RTT기능을 캡슐화한 클래스입니다. DebugWindowClass는 2D 렌더링 튜토리얼의 BitmapClass와 동일하나 자신의 텍스쳐는 가지고 있지 않고 RTT로 그려진 텍스쳐를 사용할 것입니다. 이름을 DebugWindowClass로 한 이유는 필자가 새로운 셰이더나 다중처리된 효과를 디버그할 때 보통 이런 방식으로 몇 개씩 화면에 띄워놓고 작업하는데 실제 구현은 RTT를 사용하기 때문에 그렇게 이름을 지었습니다. 이렇게 하면 각 단계별로 결과물들을 동시의 띄워볼 수 있으므로 어느 단계에서 실수했는지 추측하는 것보다 훨씬 효율적입니다.


Rendertextureclass.h

RenderTextureClass는 백버퍼 대신 텍스쳐를 렌더 타겟으로 설정할 수 있게 해 줍니다. 또한 렌더링된 데이터를 ID3D11ShaderResourceView 형식으로 가져올 수도 있습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: rendertextureclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _RENDERTEXTURECLASS_H_
#define _RENDERTEXTURECLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>


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

    bool Initialize(ID3D11Device*, int, int);
    void Shutdown();

    void SetRenderTarget(ID3D11DeviceContext*, ID3D11DepthStencilView*);
    void ClearRenderTarget(ID3D11DeviceContext*, ID3D11DepthStencilView*, float, float, float, float);
    ID3D11ShaderResourceView* GetShaderResourceView();

private:
    ID3D11Texture2D* m_renderTargetTexture;
    ID3D11RenderTargetView* m_renderTargetView;
    ID3D11ShaderResourceView* m_shaderResourceView;
};

#endif

Rendertextureclass.cpp

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

생성자에서 모든 전용 포인터들을 null로 초기화합니다.

RenderTextureClass::RenderTextureClass()
{
    m_renderTargetTexture = 0;
    m_renderTargetView = 0;
    m_shaderResourceView = 0;
}


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


RenderTextureClass::~RenderTextureClass()
{
}

Initialize함수는 RTT를 할 너비와 높이를 인자로 받습니다. ※중요: 만약 화면의 내용을 텍스쳐에 그린다면 찌그러짐을 방지하기 위해 반드시 RTT의 가로세로 비율을 화면 비율과 같게 해야 합니다.

이 함수는 우선 텍스쳐의 description을 작성하고 텍스쳐를 생성하는 방법으로 타겟이 되는 텍스쳐를 만듭니다. 그리고 이 텍스쳐를 렌더 타겟 뷰로 설정하여 렌더링이 텍스쳐에 일어나도록 합니다. 마지막으로 이 텍스쳐에 대한 ID3D11ShaderResourceView를 만들어 렌더링된 데이터에 접근할 수 있도록 합니다.

bool RenderTextureClass::Initialize(ID3D11Device* device, int textureWidth, int textureHeight)
{
    D3D11_TEXTURE2D_DESC textureDesc;
    HRESULT result;
    D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc;
    D3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;


    // RTT 디스크립션을 초기화합니다.
    ZeroMemory(&textureDesc, sizeof(textureDesc));

    // RTT 디스크립션을 세팅합니다.
    textureDesc.Width = textureWidth;
    textureDesc.Height = textureHeight;
    textureDesc.MipLevels = 1;
    textureDesc.ArraySize = 1;
    textureDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    textureDesc.SampleDesc.Count = 1;
    textureDesc.Usage = D3D11_USAGE_DEFAULT;
    textureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
    textureDesc.CPUAccessFlags = 0;
    textureDesc.MiscFlags = 0;

    // RTT를 생성합니다.
    result = device->CreateTexture2D(&textureDesc, NULL, &m_renderTargetTexture);
    if(FAILED(result))
    {
        return false;
    }

    // 렌더 타겟 뷰에 대한 디스크립션을 설정합니다.
    renderTargetViewDesc.Format = textureDesc.Format;
    renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
    renderTargetViewDesc.Texture2D.MipSlice = 0;

    // 렌더 타겟 뷰를 생성합니다.
    result = device->CreateRenderTargetView(m_renderTargetTexture, &renderTargetViewDesc, &m_renderTargetView);
    if(FAILED(result))
    {
        return false;
    }

    // 셰이더 리소스 뷰에 대한 디스크립션을 설정합니다.
    shaderResourceViewDesc.Format = textureDesc.Format;
    shaderResourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    shaderResourceViewDesc.Texture2D.MostDetailedMip = 0;
    shaderResourceViewDesc.Texture2D.MipLevels = 1;

    // 셰이더 리소스 뷰를 생성합니다.
    result = device->CreateShaderResourceView(m_renderTargetTexture, &shaderResourceViewDesc, &m_shaderResourceView);
    if(FAILED(result))
    {
        return false;
    }

    return true;
}

Shutdown함수는 RenderTextureClass에서 사용된 세 인터페이스들을 해제합니다.

void RenderTextureClass::Shutdown()
{
    if(m_shaderResourceView)
    {
        m_shaderResourceView->Release();
        m_shaderResourceView = 0;
    }

    if(m_renderTargetView)
    {
        m_renderTargetView->Release();
        m_renderTargetView = 0;
    }

    if(m_renderTargetTexture)
    {
        m_renderTargetTexture->Release();
        m_renderTargetTexture = 0;
    }

    return;
}

SetRenderTarget함수는 이 클래스의 m_renderTargetView를 렌더 타겟으로 설정하여 모든 렌더링이 이 텍스쳐에 적용되게 합니다.

void RenderTextureClass::SetRenderTarget(ID3D11DeviceContext* deviceContext, ID3D11DepthStencilView* depthStencilView)
{
    // 렌더 타겟 뷰와 깊이 스텐실 버퍼를 출력 파이프라인에 바인딩합니다.
    deviceContext->OMSetRenderTargets(1, &m_renderTargetView, depthStencilView);
    
    return;
}

ClearRenderTarget함수는 D3DClass::BeginScene함수와 같으나 백버퍼가 아닌 m_renderTargetView에 클리어가 이루어진다는 점이 다릅니다. 이 함수는 매 프레임 RTT가 일어나기 전에 호출해 주어야 합니다.

void RenderTextureClass::ClearRenderTarget(ID3D11DeviceContext* deviceContext, ID3D11DepthStencilView* depthStencilView, 
                       float red, float green, float blue, float alpha)
{
    float color[4];


    // 버퍼를 초기화할 색상을 지정합니다.
    color[0] = red;
    color[1] = green;
    color[2] = blue;
    color[3] = alpha;

    // 백버퍼를 초기화합니다.
    deviceContext->ClearRenderTargetView(m_renderTargetView, color);
    
    // 깊이 버퍼를 초기화합니다.
    deviceContext->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);

    return;
}

GetShaderResourceView 함수는 텍스쳐 데이터를 셰이더 리소스 뷰로 전달해 줍니다. 이렇게 하여 RTT의 결과물 텍스쳐를 이용하는 서로 다른 셰이더들에서 텍스쳐 데이터에 접근할 수 있게 됩니다. RTT 텍스쳐를 쓰려면 일반적으로 셰이더에 텍스쳐를 직접 전달해 주던 부분에 이 함수를 호출하는 부분을 넣으면 됩니다.

ID3D11ShaderResourceView* RenderTextureClass::GetShaderResourceView()
{
    return m_shaderResourceView;
}

Debugwindowclass.h

DebugWindowClassBitmapClass와 그 내용은 같으며 내부에 TextureClass를 가지지 않습니다. 이 클래스의 목적은 하나의 2D 디버그용 윈도우의 개념으로 일반적인 비트맵 이미지가 아닌 RTT 텍스쳐 그리는 것입니다. 코드 자체는 이전 튜토리얼의 BitmapClass와 완전히 동일하기 때문에 필요하다면 이 클래스를 보기 바랍니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: debugwindowclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _DEBUGWINDOWCLASS_H_
#define _DEBUGWINDOWCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: DebugWindowClass
////////////////////////////////////////////////////////////////////////////////
class DebugWindowClass
{
private:
    struct VertexType
    {
        D3DXVECTOR3 position;
        D3DXVECTOR2 texture;
    };

public:
    DebugWindowClass();
    DebugWindowClass(const DebugWindowClass&);
    ~DebugWindowClass();

    bool Initialize(ID3D11Device*, int, int, int, int);
    void Shutdown();
    bool Render(ID3D11DeviceContext*, int, int);

    int GetIndexCount();

private:
    bool InitializeBuffers(ID3D11Device*);
    void ShutdownBuffers();
    bool UpdateBuffers(ID3D11DeviceContext*, int, int);
    void RenderBuffers(ID3D11DeviceContext*);

private:
    ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
    int m_vertexCount, m_indexCount;
    int m_screenWidth, m_screenHeight;
    int m_bitmapWidth, m_bitmapHeight;
    int m_previousPosX, m_previousPosY;
};

#endif

Debugwindowclass.cpp

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


DebugWindowClass::DebugWindowClass()
{
    m_vertexBuffer = 0;
    m_indexBuffer = 0;
}


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


DebugWindowClass::~DebugWindowClass()
{
}


bool DebugWindowClass::Initialize(ID3D11Device* device, int screenWidth, int screenHeight, int bitmapWidth, int bitmapHeight)
{
    bool result;


    // Store the screen size.
    m_screenWidth = screenWidth;
    m_screenHeight = screenHeight;

    // Store the size in pixels that this bitmap should be rendered at.
    m_bitmapWidth = bitmapWidth;
    m_bitmapHeight = bitmapHeight;

    // Initialize the previous rendering position to negative one.
    m_previousPosX = -1;
    m_previousPosY = -1;

    // Initialize the vertex and index buffers.
    result = InitializeBuffers(device);
    if(!result)
    {
        return false;
    }

    return true;
}


void DebugWindowClass::Shutdown()
{
    // Shutdown the vertex and index buffers.
    ShutdownBuffers();

    return;
}


bool DebugWindowClass::Render(ID3D11DeviceContext* deviceContext, int positionX, int positionY)
{
    bool result;


    // Re-build the dynamic vertex buffer for rendering to possibly a different location on the screen.
    result = UpdateBuffers(deviceContext, positionX, positionY);
    if(!result)
    {
        return false;
    }

    // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
    RenderBuffers(deviceContext);

    return true;
}


int DebugWindowClass::GetIndexCount()
{
    return m_indexCount;
}


bool DebugWindowClass::InitializeBuffers(ID3D11Device* device)
{
    VertexType* vertices;
    unsigned long* indices;
    D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
    D3D11_SUBRESOURCE_DATA vertexData, indexData;
    HRESULT result;
    int i;


    // Set the number of vertices in the vertex array.
    m_vertexCount = 6;

    // Set the number of indices in the index array.
    m_indexCount = m_vertexCount;

    // Create the vertex array.
    vertices = new VertexType[m_vertexCount];
    if(!vertices)
    {
        return false;
    }

    // Create the index array.
    indices = new unsigned long[m_indexCount];
    if(!indices)
    {
        return false;
    }

    // Initialize vertex array to zeros at first.
    memset(vertices, 0, (sizeof(VertexType) * m_vertexCount));

    // Load the index array with data.
    for(i=0; i<m_indexCount; i++)
    {
        indices[i] = i;
    }

    // Set up the description of the static vertex buffer.
    vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    vertexBufferDesc.MiscFlags = 0;
    vertexBufferDesc.StructureByteStride = 0;

    // Give the subresource structure a pointer to the vertex data.
    vertexData.pSysMem = vertices;
    vertexData.SysMemPitch = 0;
    vertexData.SysMemSlicePitch = 0;

    // Now create the vertex buffer.
    result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // Set up the description of the static index buffer.
    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;

    // Give the subresource structure a pointer to the index data.
    indexData.pSysMem = indices;
    indexData.SysMemPitch = 0;
    indexData.SysMemSlicePitch = 0;

    // Create the index buffer.
    result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // Release the arrays now that the vertex and index buffers have been created and loaded.
    delete [] vertices;
    vertices = 0;

    delete [] indices;
    indices = 0;

    return true;
}


void DebugWindowClass::ShutdownBuffers()
{
    // Release the index buffer.
    if(m_indexBuffer)
    {
        m_indexBuffer->Release();
        m_indexBuffer = 0;
    }

    // Release the vertex buffer.
    if(m_vertexBuffer)
    {
        m_vertexBuffer->Release();
        m_vertexBuffer = 0;
    }

    return;
}


bool DebugWindowClass::UpdateBuffers(ID3D11DeviceContext* deviceContext, int positionX, int positionY)
{
    float left, right, top, bottom;
    VertexType* vertices;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    VertexType* verticesPtr;
    HRESULT result;


    // If the position we are rendering this bitmap to has not changed then don't update the vertex buffer since it
    // currently has the correct parameters.
    if((positionX == m_previousPosX) && (positionY == m_previousPosY))
    {
        return true;
    }
    
    // If it has changed then update the position it is being rendered to.
    m_previousPosX = positionX;
    m_previousPosY = positionY;

    // Calculate the screen coordinates of the left side of the bitmap.
    left = (float)((m_screenWidth / 2) * -1) + (float)positionX;

    // Calculate the screen coordinates of the right side of the bitmap.
    right = left + (float)m_bitmapWidth;

    // Calculate the screen coordinates of the top of the bitmap.
    top = (float)(m_screenHeight / 2) - (float)positionY;

    // Calculate the screen coordinates of the bottom of the bitmap.
    bottom = top - (float)m_bitmapHeight;

    // Create the vertex array.
    vertices = new VertexType[m_vertexCount];
    if(!vertices)
    {
        return false;
    }

    // Load the vertex array with data.
    // First triangle.
    vertices[0].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    vertices[0].texture = D3DXVECTOR2(0.0f, 0.0f);

    vertices[1].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    vertices[1].texture = D3DXVECTOR2(1.0f, 1.0f);

    vertices[2].position = D3DXVECTOR3(left, bottom, 0.0f);  // Bottom left.
    vertices[2].texture = D3DXVECTOR2(0.0f, 1.0f);

    // Second triangle.
    vertices[3].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    vertices[3].texture = D3DXVECTOR2(0.0f, 0.0f);

    vertices[4].position = D3DXVECTOR3(right, top, 0.0f);  // Top right.
    vertices[4].texture = D3DXVECTOR2(1.0f, 0.0f);

    vertices[5].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    vertices[5].texture = D3DXVECTOR2(1.0f, 1.0f);

    // Lock the vertex buffer so it can be written to.
    result = deviceContext->Map(m_vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // Get a pointer to the data in the vertex buffer.
    verticesPtr = (VertexType*)mappedResource.pData;

    // Copy the data into the vertex buffer.
    memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * m_vertexCount));

    // Unlock the vertex buffer.
    deviceContext->Unmap(m_vertexBuffer, 0);

    // Release the vertex array as it is no longer needed.
    delete [] vertices;
    vertices = 0;

    return true;
}


void DebugWindowClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
    unsigned int stride;
    unsigned int offset;


    // Set vertex buffer stride and offset.
    stride = sizeof(VertexType); 
    offset = 0;
    
    // Set the vertex buffer to active in the input assembler so it can be rendered.
    deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

    // Set the index buffer to active in the input assembler so it can be rendered.
    deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

    // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    return;
}

D3dclass.h

D3DClass는 조금 수정되었습니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_


/////////////
// LINKING //
/////////////
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")


//////////////
// INCLUDES //
//////////////
#include <dxgi.h>
#include <d3dcommon.h>
#include <d3d11.h>
#include <d3dx10math.h>


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

    bool Initialize(int, int, bool, HWND, bool, float, float);
    void Shutdown();
    
    void BeginScene(float, float, float, float);
    void EndScene();

    ID3D11Device* GetDevice();
    ID3D11DeviceContext* GetDeviceContext();

    void GetProjectionMatrix(D3DXMATRIX&);
    void GetWorldMatrix(D3DXMATRIX&);
    void GetOrthoMatrix(D3DXMATRIX&);

    void TurnZBufferOn();
    void TurnZBufferOff();

    void TurnOnAlphaBlending();
    void TurnOffAlphaBlending();

GetDepthStencilView라는 새로운 함수를 만들어 깊이 스텐실 뷰를 가져올 수 있게 하고 SetBackBufferRenderTarget이라는 함수도 만들어 백버퍼를 현재 렌더 타겟으로 설정할 수 있게 합니다.

    ID3D11DepthStencilView* GetDepthStencilView();
    void SetBackBufferRenderTarget();

private:
    bool m_vsync_enabled;
    
    IDXGISwapChain* m_swapChain;
    ID3D11Device* m_device;
    ID3D11DeviceContext* m_deviceContext;
    ID3D11RenderTargetView* m_renderTargetView;
    ID3D11Texture2D* m_depthStencilBuffer;
    ID3D11DepthStencilState* m_depthStencilState;
    ID3D11DepthStencilView* m_depthStencilView;
    ID3D11RasterizerState* m_rasterState;

    D3DXMATRIX m_projectionMatrix;
    D3DXMATRIX m_worldMatrix;
    D3DXMATRIX m_orthoMatrix;

    ID3D11DepthStencilState* m_depthDisabledStencilState;
    ID3D11BlendState* m_alphaEnableBlendingState;
    ID3D11BlendState* m_alphaDisableBlendingState;
};

#endif

D3dclass.cpp

간단히 바뀐 함수들만 설명하겠습니다.

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

GetDepthStencilView 함수는 깊이 스텐실 뷰에 대한 접근을 제공합니다.

ID3D11DepthStencilView* D3DClass::GetDepthStencilView()
{
    return m_depthStencilView;
}

SetBackBufferRenderTarget함수는 이 클래스의 백버퍼를 현재의 렌더 타겟으로 설정합니다. 대개 RTT가 끝나고 렌더 타겟을 백버퍼로 돌려야 할 때 호출됩니다.

void D3DClass::SetBackBufferRenderTarget()
{
    // 출력 렌더링 파이프라인에 렌더 타겟 뷰와 깊이 스텐실 버퍼를 바인딩합니다.
    m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);

    return;
}

Graphicsclass.h

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


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

3D 모델 튜토리얼에서 include문을 가져왔고 이에 더해 디버그 윈도우와 RTT 클래스의 헤더도 포함시킵니다.

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "lightshaderclass.h"
#include "lightclass.h"
#include "rendertextureclass.h"
#include "debugwindowclass.h"
#include "textureshaderclass.h"


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

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

두 종류의 렌더링 함수를 만들었습니다. 앞서 설명했듯이 렌더링이 두 번 일어나도록 분리했기 때문에(첫번째는 텍스쳐에 그리고 그 다음으로 일반 백버퍼에 그리는 것) 두 종류의 함수가 필요합니다.

private:
    bool RenderToTexture();
    bool RenderScene();

private:
    D3DClass* m_D3D;
    CameraClass* m_Camera;
    ModelClass* m_Model;
    LightShaderClass* m_LightShader;
    LightClass* m_Light;
    RenderTextureClass* m_RenderTexture;
    DebugWindowClass* m_DebugWindow;
    TextureShaderClass* m_TextureShader;
};

#endif

Graphicsclass.cpp

달라진 함수들만 설명하겠습니다.

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

생성자에서 모든 전용 변수들을 null로 초기화합니다.

GraphicsClass::GraphicsClass()
{
    m_D3D = 0;
    m_Camera = 0;
    m_Model = 0;
    m_LightShader = 0;
    m_Light = 0;
    m_RenderTexture = 0;
    m_DebugWindow = 0;
    m_TextureShader = 0;
}


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

        
    // Create the Direct3D object.
    m_D3D = new D3DClass;
    if(!m_D3D)
    {
        return false;
    }

    // Initialize the Direct3D object.
    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;
    }

    // Create the camera object.
    m_Camera = new CameraClass;
    if(!m_Camera)
    {
        return false;
    }

    // Create the model object.
    m_Model = new ModelClass;
    if(!m_Model)
    {
        return false;
    }

    // Initialize the model object.
    result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds", "../Engine/data/cube.txt");
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
        return false;
    }

    // Create the light shader object.
    m_LightShader = new LightShaderClass;
    if(!m_LightShader)
    {
        return false;
    }

    // Initialize the light shader object.
    result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
        return false;
    }

    // Create the light object.
    m_Light = new LightClass;
    if(!m_Light)
    {
        return false;
    }

    // Initialize the light object.
    m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
    m_Light->SetDirection(0.0f, 0.0f, 1.0f);

RTT 객체를 생성하고 초기화합니다. 모든 화면을 텍스쳐에 그리고 싶기 때문에 화면의 너비와 높이를 텍스쳐의 크기로 지정했다는 걸 기억하기 바랍니다.

    // RTT 객체를 생성합니다.
    m_RenderTexture = new RenderTextureClass;
    if(!m_RenderTexture)
    {
        return false;
    }

    // RTT 객체를 초기화합니다.
    // Initialize the render to texture object.
    result = m_RenderTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight);
    if(!result)
    {
        return false;
    }

디버그 윈도우 객체를 생성하고 초기화합니다. 참고로 윈도우의 크기는 100x100으로 잡았습니다. 결국 전체 화면이 100x100 텍스쳐로 보이게 될 것이므로 명확히 찌그러짐이 있을 것입니다. 찌그러지지 않는 것이 중요하다면 이 비율이 일치하도록 윈도우 크기를 더 줄일 수 있습니다.

    // 디버그 윈도우를 생성합니다.
    m_DebugWindow = new DebugWindowClass;
    if(!m_DebugWindow)
    {
        return false;
    }

    // 디버그 윈도우를 초기화합니다.
    result = m_DebugWindow->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, 100, 100);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the debug window object.", L"Error", MB_OK);
        return false;
    }

    // 텍스쳐 셰이더를 생성합니다.
    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;
    }

    return true;
}


void GraphicsClass::Shutdown()
{
    // Release the texture shader object.
    if(m_TextureShader)
    {
        m_TextureShader->Shutdown();
        delete m_TextureShader;
        m_TextureShader = 0;
    }

DebugWindowClass객체와 RenderTextureClass객체를 해제합니다.

    // 디버그 윈도우를 해제합니다.
    if(m_DebugWindow)
    {
        m_DebugWindow->Shutdown();
        delete m_DebugWindow;
        m_DebugWindow = 0;
    }

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

    // Release the light object.
    if(m_Light)
    {
        delete m_Light;
        m_Light = 0;
    }

    // Release the light shader object.
    if(m_LightShader)
    {
        m_LightShader->Shutdown();
        delete m_LightShader;
        m_LightShader = 0;
    }

    // Release the model object.
    if(m_Model)
    {
        m_Model->Shutdown();
        delete m_Model;
        m_Model = 0;
    }

    // Release the camera object.
    if(m_Camera)
    {
        delete m_Camera;
        m_Camera = 0;
    }

    // Release the Direct3D object.
    if(m_D3D)
    {
        m_D3D->Shutdown();
        delete m_D3D;
        m_D3D = 0;
    }

    return;
}


bool GraphicsClass::Render()
{
    D3DXMATRIX worldMatrix, viewMatrix, orthoMatrix;
    bool result;

첫 단계는 텍스쳐에 렌더링하는 것입니다.

    // 전체 씬을 텍스쳐에 그립니다.
    result = RenderToTexture();
    if(!result)
    {
        return false;
    }

렌더링의 다음 단계는 평소처럼 백버퍼에 그리는 것입니다.

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

    // 백버퍼에 평소처럼 전체 씬을 그립니다.
    result = RenderScene();
    if(!result)
    {
        return false;
    }

렌더링이 완료되면 디버그 윈도우를 2D 이미지로써 50x50 위치에 그립니다.

    // 2D 렌더링을 하기 위해 Z버퍼를 끕니다.
    m_D3D->TurnZBufferOff();

    // 카메라와 d3d 객체로부터 월드, 뷰, 직교 행렬을 얻어옵니다.
    m_D3D->GetWorldMatrix(worldMatrix);
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetOrthoMatrix(orthoMatrix);

    // 디버그 윈도우의 정점과 인덱스 버퍼를 그래픽 파이프라인에 넣어 렌더링할 준비를 합니다.
    result = m_DebugWindow->Render(m_D3D-GetDeviceContext(), 50, 50);
    if(!result)
    {
        return false;
    }

    // 텍스쳐 셰이더를 이용하여 디버그 윈도우를 그립니다.
    result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_DebugWindow->GetIndexCount(), worldMatrix, viewMatrix,
                     orthoMatrix, m_RenderTexture->GetShaderResourceView());
    if(!result)
    {
        return false;
    }


    // 2D렌더링이 끝났으므로 다시 Z버퍼를 킵니다.
    m_D3D->TurnZBufferOn();

    // 화면에 렌더링된 씬을 표시합니다.
    m_D3D->EndScene();

    return true;
}

RenderToTexture함수는 새로운 private 렌더링 함수입니다. 이 함수에서 렌더 타겟을 텍스쳐로 설정합니다. 화면이 렌더링된 후에는 렌더 타겟을 D3DClass를 이용하여 다시 백버퍼로 돌립니다.

bool GraphicsClass::RenderToTexture()
{
    bool result;


    // RTT가 렌더링 타겟이 되도록 합니다.
    m_RenderTexture->SetRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView());

일반 화면과 구분하기 위해 배경 색을 파란색으로 합니다.

    // RTT를 초기화합니다.
    m_RenderTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), m_D3D->GetDepthStencilView(), 0.0f, 0.0f, 1.0f, 1.0f);

    // 여기서 씬을 그리면 백버퍼 대신 RTT에 렌더링됩니다.
    result = RenderScene();
    if(!result)
    {
        return false;
    }

    // 렌더링 타겟을 RTT에서 다시 백버퍼로 돌립니다.
    m_D3D->SetBackBufferRenderTarget();

    return;
}

RenderScene함수는 또다른 private 렌더링 함수입니다. 전체 화면을 그리는 기능을 이 함수 하나에 담았기 때문에 미리 텍스쳐든 백버퍼든 렌더 타겟만 설정하고 이 함수를 호출하면 됩니다. 이 튜토리얼에서는 RenderToTexture함수에서 호출하여 텍스쳐에 그리고, Render함수에서도 호출하여 평소처럼 백버퍼에 그리도록 하였습니다.

bool GraphicsClass::RenderScene()
{
    D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
    bool result;
    static float rotation = 0.0f;


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

    // 카메라와 d3d객체에서 월드, 뷰, 투영 행렬을 얻어옵니다.
    m_D3D->GetWorldMatrix(worldMatrix);
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetProjectionMatrix(projectionMatrix);

    // 매 프레임마다 회전값을 갱신합니다.
    rotation += (float)D3DX_PI * 0.005f;
    if(rotation > 360.0f)
    {
        rotation -= 360.0f;
    }

    D3DXMatrixRotationY(&worldMatrix, rotation);

    // 모델의 정점과 인덱스 버퍼를 그래픽스 파이프라인에 넣어 렌더링할 준비를 합니다.
    m_Model->Render(m_D3D-GetDeviceContext());

    // 라이트 셰이더를 이용하여 모델을 그립니다.
    result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
                       m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor());
    if(!result)
    {
        return false;
    }

    return;
}

마치면서

이제 RTT를 사용하는 기초적인 내용이 이해가 되실 겁니다. 또한 이 아이디어를 여러분의 프로젝트에 어떤 부분에 적용할 수 있는지에 대한 영감도 떠올랐으리라 생각됩니다.

연습 문제

  1. 프로젝트를 다시 컴파일하고 실행해 보십시오. 회전하는 육면체가 있고 RTT의 효과로 푸른 배경의 텍스쳐 안에 회전하는 육면체도 보이는지 확인해 보십시오.
  2. 디버그 윈도우를 모니터 해상도와 화면 비율이 맞도록 고쳐 보십시오.
  3. 3D 장면을 바꾸어서 그 내용이 RTT 객체에도 잘 적용되는지 확인해 보십시오.
  4. 3D 장면을 보는 카메라 각도를 바꾸어 보십시오.
  5. RTT 텍스쳐를 여러분의 셰이더의 입력으로 넣어 결과를 바꾸어 보십시오(노이즈를 추가하거나, 스캔 라인 또는 비슷한 효과 등등).

소스 코드

Visual Studio 2008 프로젝트: dx11tut22.zip

소스 코드: dx11src22.zip

실행 파일: dx11exe22.zip

Nav