Render Target(이하 렌더타겟)은 렌더링 디바이스(IDirect3DDevice9)가 렌더링을 수행할 Surface를 의미한다. 일반적으로는 클라이언트 전체에 대한 Surface가 Target이 되지만, Target으로 다른 Surface가 지정될 수도 있다.
만일 게임 내에 거울과 같은, 현재의 카메라 시점과는 조금 다른 카메라 시점을 가진 기능을 구현하고자 한다면, 클라이언트에 직접 그려주는 것이 아니라 렌더타겟에 렌더링 한 후에 그 결과를 거울의 해당 영역에 텍스쳐로 사용하는 방법도 사용될 수 있다.
이것뿐만 아니라, 게임의 User Interface(이하 UI)와 같은 객체들에도 렌더타겟을 속도를 높이는 수단으로 활용할 수 있다. UI는 여러 가지 구성 요소들이 복합적으로 결합되는 경우가 많고, 당연히 각각의 요소들을 모두 렌더링 해줘야 한다. 특히 폰트 렌더링에 소모되는 비용은 상당히 비싼 편이다. 거기에 외곽선이나 그림자를 추가하는 경우라면 조금 더 많은 비용이 들어간다. 속도를 높일 수 있다는 것은 UI 객체들은 화면에 정적으로 배치되는 경우가 많기 때문이다. UI객체들의 외형적인 변화의 횟수가 빈번하지 않다는 가정을 하면, 처음에 한 번 렌더타겟에 렌더링을 해주고, 외형적 변화가 발생하는 경우에만 다시 Target을 업데이트 해주는 방법으로 복합적으로 구성된 UI 객체를 단일 텍스쳐로 대체할 수 있기 때문이다.
아래와 같은 방법으로 렌더타겟으로 활용하기 위한 텍스쳐를 생성한다.
D3DXCreateTexture(
pDevice,
uWidth, // width
uHeight, // height
1, // Mipmap 레벨은 1
D3DUSAGE_RENDERTARGET,
eSurfaceFormat, // surface format
D3DPOOL_DEFAULT,
&pTexture)
위의 함수가 성공적으로 수행되었다면 렌더타겟으로 사용될 새로운 텍스쳐가 마련된 것이다. 이 텍스쳐를 렌더타겟으로 활용하는 순서는 다음과 같다.
IDirect3DSurface9* pRenderTarget_New;
IDirect3DSurface9* pRenderTarget_Old;
HRESULT hr;
hr = pTexture->GetSurfaceLevel(0, &pRenderTarget_New);
if(FAILED(hr) || pRenderTarget_New == 0) {-- error --}
hr = pDevice->GetRenderTarget(0, &pRenderTarget_Old);
if(FAILED(hr) || m_ pRenderTarget _New == 0) {-- error --}
hr = pDevice->SetRenderTarget(0, pRenderTarget_New);
if(FAILED(hr)) {-- error --}
우선 텍스쳐로부터 GetSurfaceLevel()함수를 통하여 새로운 렌더타겟 Surface를 얻어온다. 이것이 성공했다면 IDirect3DDevice9::SetRenderTarget()을 통하여, 새로운 렌더타겟으로 설정할 수 있겠지만, 렌더타겟의 사용이 끝난 후 이전 렌더타겟으로 복구시키기 위하여 이전의 렌더타겟 Surface를 GetRenderTarget()을 통하여 저장해 놓는 것이다. 이전 렌더타겟의 복구는 다음과 같이 수행한다.
pDevice->SetRenderTarget(0, pRenderTarget);
구현으로 들어가면 일단 mResTextureRenderTarget이라고 이름을 짓고 mResTexture로부터 상속을 받을 것이다. 당연하지만 렌더타겟 역시 일반적인 텍스쳐와 동일하게 활용할 생각이기 때문이다.
class mResTextureRenderTarget : public mResTexture
{
protected:
// render target properties
UINT m_uWidth;
UINT m_uHeight;
D3DFORMAT m_eSurfaceFormat;
// configurations
bool m_bRenderTargetClear;
D3DCOLOR m_dwRenderTargetClearColor;
// is target empty?
bool m_bIsTargetEmpty;
// restore target
DWORD m_dwUseZ_Old;
IDirect3DSurface9* m_pRenderTarget_New;
IDirect3DSurface9* m_pRenderTarget_Old;
protected:
// create render target texture
virtual bool createTargetTexture();
public:
// 생성자/소멸자
mResTextureRenderTarget();
virtual ~mResTextureRenderTarget();
// render target properties
bool setRenderTargetParam(UIN, UIN, D3DFORMAT);
void getRenderTargetParam(UINT&,UINT&,D3DFORMAT&);
// configurations
void setRenderTargetClear(bool bFlag);
bool getRenderTargetClear();
void setRenderTargetClearColor(D3DCOLOR dwColor);
D3DCOLOR getRenderTargetClearColor();
// is target empty?
bool getIsTargetEmpty();
void setIsTargetEmpty(bool bFlag = true);
// using render target
virtual bool setupTarget();
virtual bool restoreTarget();
// create/reset/lost/destroy
virtual bool onCreateDevice(IDirect3DDevice9* pDevice);
virtual bool onResetDevice(IDirect3DDevice9* pDevice);
virtual void onLostDevice();
virtual void onDestroyDevice();
// Serialize
virtual bool save(mStreamBase& rStream);
virtual bool load(mStreamBase& rStream);
}; // class mResTextureRenderTarget
우선 렌더타겟의 속성은 폭(m_uWidth), 너비(m_uHeight), Surface포맷(m_eSurfaceFormat)이다. 일반적인 이미지와 마찬가지로 크기와 색상정보를 가지는 것이다. setRenderTargetParam() 함수는 이 속성들을 저장하는 동시에 onCreateDevice()를 호출한다.
bool mResTextureRenderTarget::setRenderTargetParam
(UINT uWidth, UINT uHeight, D3DFORMAT eFmt)
{
mASSERT(uWidth > 0);
mASSERT(uHeight > 0);
if((uWidth <= 0)||(uHeight <= 0)) return false;
if((m_pTexture)&&
(uWidth == m_uWidth)&&
(uHeight == m_uHeight)&&
(eFmt == m_eSurfaceFormat)) return true;
m_uWidth = uWidth;
m_uHeight = uHeight;
m_eSurfaceFormat = eFmt;
if(m_pDevice) return onCreateDevice(g_museKernel.getDevice());
return true;
}
파라메터의 유효성을 검사하고, 이미 존재하는 렌더타겟과 동일한지 비교한 후에 저장하고 onCreateDevice()를 호출한다.
onCreateDevice()는 저장되었던 렌더타겟 속성을 가지고 처음에 등장했던 D3DXCreateTexture()를 수행하는 createTargetTexture ()를 호출한다.
bool mResTextureRenderTarget::onCreateDevice(IDirect3DDevice9* pDevice)
{
onDestroyDevice();
m_pDevice = pDevice;
m_bIsTargetEmpty = true;
if(m_pDevice)
{
return createTargetTexture();
}
return true;
}
코드를 살펴보면 m_bIsTargetEmpty라는 속성이 추가된 것을 알 수 있다. 이 변수는 렌더타겟에 렌더링 결과물이 들어있는지 아닌지를 나타낸다. 이름이 의미하는 것처럼 true이면 내용물이 없다는 것이다. 물론 렌더타겟을 사용하는 측에서 결과물을 채우겠지만, 디바이스가 리셋되는 상황이 발생하면 사용하는 측의 의지와는 상관없이 결과물이 사라져 버리는 상황이 함께 발생된다. 사용하는 측에서는 변동이 발생하거나, m_bIsTargetEmpty를 확인하고 true인 경우에 결과물을 다시 채우게 되는 것이다. 아래의 함수들을 통해서 m_bIsTargetEmpty를 확인하고 변경할 수 있다.
bool getIsTargetEmpty();
void setIsTargetEmpty(bool bFlag = true);
초반에 언급되었던 렌더타겟의 설정 및 복구는 다음과 같이 구현된다.
bool mResTextureRenderTarget::setupTarget()
{
if((m_pDevice == 0)||(m_pTexture == 0)) return false;
if(m_pRenderTarget_Old) {return false;}
if(m_pRenderTarget_New) {return false;}
HRESULT hr;
// 렌더타겟 설정
hr = m_pTexture->GetSurfaceLevel(0, &m_pRenderTarget_New);
if(FAILED(hr) || m_pRenderTarget_New == 0)
{
mASSERT(0);
return false;
}
hr = m_pDevice->GetRenderTarget(0, &m_pRenderTarget_Old);
if(FAILED(hr) || m_pRenderTarget_Old == 0)
{
mASSERT(0);
return false;
}
hr = m_pDevice->SetRenderTarget(0, m_pRenderTarget_New);
if(FAILED(hr))
{
mASSERT(0);
return false;
}
// Z설정저장
if(FAILED(m_pDevice->GetRenderState(D3DRS_ZENABLE, &m_dwUseZ_Old)))
{
mASSERT(0);
return false;
}
if(FAILED(m_pDevice->SetRenderState(D3DRS_ZENABLE, FALSE)))
{
mASSERT(0);
return false;
}
// 렌더타겟 청소
if(m_bRenderTargetClear == true)
{
m_pDevice->Clear(0, NULL, D3DCLEAR_TARGET,
m_dwRenderTargetClearColor, 1.0f, 0);
}
return true;
}
bool mResTextureRenderTarget::restoreTarget()
{
if((m_pDevice == 0)||(m_pTexture == 0)) return false;
if(m_pRenderTarget_Old == 0) return false;
if(m_pRenderTarget_New == 0) return false;
// Z설정복구
if(FAILED(m_pDevice->SetRenderState(D3DRS_ZENABLE, m_dwUseZ_Old)))
{
mASSERT(0);
return false;
}
// 렌더타겟 복구
if(FAILED(m_pDevice->SetRenderTarget(0, m_pRenderTarget_Old)))
{
mASSERT(0);
return false;
}
MRELEASE(m_pRenderTarget_Old);
MRELEASE(m_pRenderTarget_New);
return true;
}
초반의 설명과 마찬가지로, 이전 설정을 저장하고 다시 복구하는 과정을 둘로 나눠놓은 것인데, mResTextureRenderTarget는 Z버퍼를 사용하지 않는 클래스로 설정한 까닭에(Z버퍼를 사용하는 클래스는 여기로부터 한번 더 상속된다), GetRenderState()통해서 ZENABLE상태도 함께 저장한다. 또한, m_bRenderTargetClear, m_dwRenderTargetClearColor 와 아래의 함수들을 추가하여, 렌더타겟 셋업 상황에서 배경을 지울지 말지 선택하고, 배경색을 지정할 수 있다.
void setRenderTargetClear(bool bFlag);
bool getRenderTargetClear();
void setRenderTargetClearColor(D3DCOLOR dwColor);
D3DCOLOR getRenderTargetClearColor();
setRenderTargetClear(), setRenderTargetClearColor()의 내부에서는 setIsTargetEmpty(true)를 호출하여 다시 렌더타겟을 사용하는 측이 결과물을 다시 갱신하도록 상태를 설정해 놓는다.
이제 Depth-Stencil 버퍼(이하 Z버퍼라고……)를 추가한 mResTextureRenderTargetZ를 정의할 것이다. 우선 아래의 변수를 준비한다.
// setup/restore depth-stencil buffer
IDirect3DSurface9* m_pDepthStencil;
IDirect3DSurface9* m_pDepthStencil_Old;
// clear depth-stencil on setupTarget()
bool m_bRenderTargetClearZ;
createTargetTexture()에는 아래와 같이 Z버퍼를 생성하는 부분이 추가되었다.
// DepthStencil버퍼 생성
if(FAILED(m_pDevice->CreateDepthStencilSurface(
m_uWidth,
m_uHeight,
g_museKernel.getDepthStencilFormat(),
D3DMULTISAMPLE_NONE,
D3DMULTISAMPLE_NONE,
TRUE,
&m_pDepthStencil,
0)))
{
onDestroyDevice();
return false;
}
마찬가지로, setupTarget()에서는 아래와 같이 변경되었다.
// 이전 ***********************************************************
// Z설정저장
if(FAILED(m_pDevice->GetRenderState(D3DRS_ZENABLE, &m_dwUseZ_Old)))
{
mASSERT(0);
return false;
}
if(FAILED(m_pDevice->SetRenderState(D3DRS_ZENABLE, FALSE)))
{
mASSERT(0);
return false;
}
// 렌더타겟 청소
if(m_bRenderTargetClear == true)
{
m_pDevice->Clear(0, NULL,
D3DCLEAR_TARGET, m_dwRenderTargetClearColor, 1.0f, 0);
}
// 이후 ***********************************************************
// DepthStencil 설정
hr = m_pDevice->GetDepthStencilSurface(&m_pDepthStencil_Old);
if(FAILED(hr)||(m_pDepthStencil_Old == 0))
{
mASSERT(0);
return false;
}
hr = m_pDevice->SetDepthStencilSurface(m_pDepthStencil);
if(FAILED(hr))
{
mASSERT(0);
return false;
}
if(m_bRenderTargetClearZ == true)
{
if(m_bRenderTargetClear == true)
{
m_pDevice->Clear(0, NULL,
D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
m_dwRenderTargetClearColor, 1.0f, 0L);
}
else
{
m_pDevice->Clear(0, NULL, D3DCLEAR_ZBUFFER,
m_dwRenderTargetClearColor, 1.0f, 0L);
}
}
else
{
if(m_bRenderTargetClear == true)
{
m_pDevice->Clear(0, NULL, D3DCLEAR_TARGET,
m_dwRenderTargetClearColor, 1.0f, 0L);
}
}
restoreTarget()역시 변경되었다.
// 이전 ***********************************************************
// Z설정복구
if(FAILED(m_pDevice->SetRenderState(D3DRS_ZENABLE, m_dwUseZ_Old)))
{
mASSERT(0);
return false;
}
// 이후 ***********************************************************
// DepthStencil 복구
if(FAILED(m_pDevice->SetDepthStencilSurface(m_pDepthStencil_Old)))
{
mASSERT(0);
return false;
}
최근 덧글