원문: 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로 그린 것인지 알 수 있을 것입니다. 우선 새로 바뀐 프레임워크부터 보겠습니다.
프레임워크
RenderTextureClass
와 DebugWindowClass
를 빼고는 프레임워크 대부분이 친숙해 보일 것입니다. 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
DebugWindowClass
는 BitmapClass
와 그 내용은 같으며 내부에 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를 사용하는 기초적인 내용이 이해가 되실 겁니다. 또한 이 아이디어를 여러분의 프로젝트에 어떤 부분에 적용할 수 있는지에 대한 영감도 떠올랐으리라 생각됩니다.
연습 문제
- 프로젝트를 다시 컴파일하고 실행해 보십시오. 회전하는 육면체가 있고 RTT의 효과로 푸른 배경의 텍스쳐 안에 회전하는 육면체도 보이는지 확인해 보십시오.
- 디버그 윈도우를 모니터 해상도와 화면 비율이 맞도록 고쳐 보십시오.
- 3D 장면을 바꾸어서 그 내용이 RTT 객체에도 잘 적용되는지 확인해 보십시오.
- 3D 장면을 보는 카메라 각도를 바꾸어 보십시오.
- RTT 텍스쳐를 여러분의 셰이더의 입력으로 넣어 결과를 바꾸어 보십시오(노이즈를 추가하거나, 스캔 라인 또는 비슷한 효과 등등).
소스 코드
Visual Studio 2008 프로젝트: dx11tut22.zip
소스 코드: dx11src22.zip
실행 파일: dx11exe22.zip
'강좌번역 > DirectX 11' 카테고리의 다른 글
DirectX11 Tutorial 24: 클리핑 평면 (2) | 2015.03.21 |
---|---|
DirectX11 Tutorial 23: 안개 (6) | 2013.09.20 |
DirectX11 Tutorial 21: 반사 매핑 (1) | 2013.09.15 |
DirectX11 Tutorial 20: 범프 매핑 (3) | 2013.09.08 |
DirectX11 Tutorial 19: 알파 매핑 (0) | 2013.06.10 |