반응형



http://blog.naver.com/sorkelf/40156769867

멀티 패스 렌더링 이란 여러개의 경로를 가지는 렌더링 방법을 말한다. 보통 백 버퍼만으로 렌더링하는것을 싱글 패스 렌더링이라고 하며

멀티 패스 렌더링은 1개의 화면을 만들기 위해 여러개의 서피스에 렌더링하는 기술을 말한다


여기서는 멀티 패스 렌더링을 더욱 좁은뜻으로 다룰 것이다. 

멀티 패스 렌더링에는 [ 한번에 여러개의 서피스에 렌더링 하는 기술] 이라는 의미도 있다. 

예를들면 Tex1과 Tex2라는 서피스를 사용하여 백 버퍼에 렌더링 하는 경우 

Tex1 생성, Tex2 생성, 백 버퍼로의 렌더링이라는 3번(3패스)의 렌더링이 필요하게 된다.


멀티 패스 렌더링을 사용하면 Tex1의 생성과 Tex2의 생성을 1패스에서 한번에 수행할수 있다.

렌더링 회수가 2번이 되기 때문에 퍼포먼스를 큰 폭으로 향상시킬 수 있다. 



① 일단 렌더 타겟이 무엇인가..


   렌더링에 대해서 조금 복습해보자. 우리들이 평소에 아무렇지도 않게 렌더링하고잇는 백버퍼...

왠지 [ 렌더링 = 백버퍼 ]라고 생각하기 쉽지만, 실은 여기에도 조금 생략되어있는 부분이 있다. 


렌더링 디바이스는 디폴트 값으로 [ 렌더 타겟 0번]에 렌더링하도록 설정되어있으며,

백버퍼는 디폴트 값으로 렌더타겟 0번에 설정 되어있다. 


즉 렌더링 디바이스와 백버퍼 사이에는 [ 렌더 타겟 ] 이라는 서피스 저장소가 있는 것이다.

이것을 확실히 알면 멀티 패스 렌더링도 쉽다






② IDirect3DDevice9::SetRenderTarget 함수

    위의 그림처럼 백버퍼는 디폴트로 RT0에 설정되어 있긴 하지만 여기가 백버퍼의 고정석은 아니다.


IDirect3DDevice9::SetRenderTarget 함수를 사용하면 미리 만든 서피스를 RT0이나 RT1등으로 설정할 수 있다.:


 IDirect3Device9::SetRenderTarget 함수

HRESULT SetRenderTarget(
   DWORD RenderTargetIndex,
   IDirect3DSurface9 *pRenderTarget
);


RenderTargetIndex 에는 렌더 타겟의 번호를 설정한다

pRenderTarget에는 서피스를 넘긴다


주의 할 것은 SetRenderTarget 함수를 사용하면 이전에 설정되어있던 서피스의 포인터를 잃게 된다.

예를들어 RT0에 다른 서피스를 설정하면 백버퍼로 되돌아 올 수 없다

따라서 보통은 미리 설정된 서피스는 IDirect3DDevice9::GetRenderTarget으로 미리 저장해두고 그것을 바꾸는 형태로 사용한다



③ 지정한 렌더타겟에 렌더링 하기


   RT0 이외의 서피스에서의 렌더링은 고정 기능 파이프라인에서는 불가능하며(아마도..)프로그래머블 셰이더에서 가능하다


셰이더(HLSL)을 사용하면 픽셀 셰이더에서 아래와같은 형태로 사용한다


float4 Test_PS( float2 texCoord : TEXCOORD0 ) : COLOR0   //<- 여기!!!

{
   float outColor
   // 무언가 처리
   return outColor;
}


COLOR0라고 표시되어 있는 부분에 주목 한다.

이 부분이 실은 렌더타겟 0번에 렌더링 하세요~라는 의미의 시멘틱스이다.

COLOR의 뒷부분의 숫자가 실은 쓰여질 렌더타겟의번호 라는 것이다. 즉 이 말은..


 float4 Test_PS( float2 texCoord : TEXCOORD0 ) : COLOR1 //<- 여기!!!

{
   float outColor
   // 무언가 처리
   return outColor;
}


이렇게 하면 렌더타겟 1번에 쓰여진다는 것이 된다.

[ 이것은 싱글 패스 렌더링이잖아!!. 그럼 0번과 1번에 동시에 쓰려면 어떻게 해야하지?]

라고 생각할지도 모르지만 이 부분은 반환값을 구조체로 만들어 버리면 된다


 struct OUTPUT_PS 

{

   float4 color0 : COLOR0;
   float4 color1 : COLOR1;
};

OUTPUT_PS  Test_PS( float2 texCoord : TEXCOORD0 )
{
    OUTPUT_PS  outColor
    // 무언가 처리
   return outColor;
}



④ MRT를 서포트 하는가에 주의


   멀티 렌더 타겟은 비교적 최근엔 많이 쓰이지만 조금 옛날 비디오 카드면 서포트 하지 않는 경우도 있다

멀티 렌더 타겟으로써 가질 수 있는 서피스의 최대 수는 IDirect3DDevice9::GetDeviceCaps함수로 얻을수 있는 D3DCAPS9 구조체에서NumSimultaneousRTs의 체크한다


D3DCAPS9 Caps;

pDev->GetDeviceCaps( &Caps );
DWORD MaxRT = Caps.NumSimultaneousRTs;

멀티 렌더 타겟의 최대 수


이것이 1일 경우, 멀티 패스 렌더링은 할 수 없다. ELSA GLADIAC FX746Ultra DDR3(Geforce FX 5700 Ultra)에서는 1, 노트북 그래픽 카드인 (FMV-BIBLO MG70H 2004년 봄 모델 Intel(R) 82852/82855 GM/GME Graphics Controller)에서도 1이었다.

NVIDIA GeForce6600에서는 4였다.


대충 2005년 이후 나온 모델은 사용가능 할 것이라 판단된다


프로그램상에서 이것을 확인하는 것이 귀찮은 사람은 DirectX의 유틸리티 중DirectX Caps Viewer(DXCapsViewer.exe)」를 사용하길 바란다. 이것은 비디오 카드의 능력을 열거해 주는 어플리케이션이다.

자신의 비디오카드 폴더 내에서 [D3D Device Types]→[HAL]→[Caps]안에 위 플래그가 있을 것이다



⑤ 멀티 패스 렌더링으로 무엇을 할 것인가?


  멀티 패스 렌더링을 함으로 뭐가 가능한가? 이것은 딱 이거다 라고 할순 없지만 근래의 복잡하며 여러 장의 텍스처를 겹쳐서 사용하는 렌더링 표현에서는 필수 항목인 기술인 것은 확실하다.

특히 [ 동적으로 텍스처를 만드는 ] 이펙트에서 효과적이다

동적으로 만든다고 하면 포스트 이펙트 (렌더링한 그림에 뭉개기 효과나 세피아톤등으로 가공하는 후처리), 큐브 맵 생성, 거울 효과등등 여러가지 있을 것이다.


멀티 패스 렌더링에서의 또다른 장점은 버텍스 셰이더와의 균형이다.

버텍스 셰이더에서는 매우 복잡한 본 조작, 픽셀 셰이더에서의 라이트 처리를 위한 법선 설정, 

버텍스 변환등 귀찮은 것을 여러가지 수행한다.


싱글 패스 렌더링의 경우 매번 이 작업을 반복해야 하는 반면 멀티 패스 렌더링에서는 대부분의 버텍스 셰이더 작업을 1번으로 끝낼 수 있다. 이것은 퍼포먼스 향상으로 연결 된다.


멀티 패스 렌더링에서 뭐가 가능한가는 셰이더 프로그램을 배워가면 자연히 알게 되겠지만 연습을 위해 RGB 칼라와 알파 정보를 2개의 서피스( 하나는 백 버퍼)에 동시에 렌더링해보자



⑥ 멀티 패스 렌더링 예 -1:색과 알파 값을 동시에 렌더링


   이것은 DirectX 부록으로 있는 비행기 모델을 렌더링 한다.

이때 RGB 칼라와 알파 값을 분해 해어 별도의 텍스처로 한번 렌더링 한다.

그리고 이 결과를 교대로 화면에 렌더링 한다.

이것이 어디에 쓰이는가 보다는 멀티 패스 렌더링의 본질을 이해 하는데 집중해주길 바란다.)


○ 셰이더 프로그램

   우선은 셰이더 프로그램을 만든다. 목표는 픽셀 셰이더에서 렌더링 정보를 RGB와 알파값으로 분해하는 것이다


float4x4 WVP;   // WorldViewProjection Matrix

texture Tex;    // Texture
sampler2D TexSampler = sampler_state {  Texture = (Tex); };

struct OUTPUT_VS
{
    float4 pos      : POSITION;
    float2 texCoord : TEXCOORD0;
};

struct OUTPUT_PS
{
    float4 color : COLOR0;
    float4 alpha : COLOR1;
};

OUTPUT_VS SimpleVS( float4 inPos : POSITION, float2 inTexCoord : TEXCOORD0 )
{
    OUTPUT_VS outVS = (OUTPUT_VS)0;
    outVS.pos = mul( inPos, WVP );
    outVS.texCoord = inTexCoord;
    return outVS;
}

OUTPUT_PS RGBAndAlphaMPR_PS( float2 texCoord : TEXCOORD0 )
{
    OUTPUT_PS PSout = (OUTPUT_PS)0;
    PSout.color = tex2D( TexSampler, texCoord );
    PSout.alpha = PSout.color.a;
    PSout.alpha.a = 1.0f;
    return PSout;
}

technique RGBAndAlphaMPR
{
    pass p0
    {
        // 렌더 스테이트 설정
        AlphaBlendEnable = TRUE;
        SrcBlend = SRCALPHA;
        DestBlend = INVSRCALPHA;
        ColorOp[0] = SELECTARG1;
        ColorArg1[0] = TEXTURE;
        ColorArg2[0] = DIFFUSE;
        AlphaOp[0] = SELECTARG1;
        AlphaArg1[0] = TEXTURE;
        AlphaArg2[0] = DIFFUSE;
        ColorOp[1] = DISABLE;
        AlphaOp[1] = DISABLE;

        // 셰이더
        VertexShader = compile vs_2_0 SimpleVS();
        PixelShader = compile ps_2_0 RGBAndAlphaMPR_PS();
    }
}

셰이더 프로그램 (RGBAndAlphaMPR.fx)


월드-뷰-프로젝션 행렬로써 WVP를 선언한다. 

다음으로 Tex는 모델의 입히는 텍스처이다

이것은 픽셀 셰이더에서 사용한다

sampler는 텍스처로부터 색을 취하는 형태이지만 이번엔 Tex로부터 얻을 수 있도록 설정하였다.

원래는 샘플링 방법등을 정의하지만 여기서는 간단하게 하기 위해 생략한다.


다음 OUTPUT_VS는 버텍스 셰이더의 반환값에 대한 구조체이다.

이번엔 변환후의 버텍스 단위와 텍스처 좌표(UV 좌표)가 필요하므로 위와 같이 설정한다.

그 다음에 있는 OUTPUT_PS가 여기에서의 핵심이다. 여기에는 2개의 COLOR가 설정되어있다.

OUTPUT_PS::alpha에 알파 정보가 저장되며, 이것이 렌더타겟 1번에 출력될 값이다.


SimpleVS 버텍스 셰이더는 딱히 무언가 하는 것은 아니다 입력된 로컬 버텍스를 WVP행렬을 사용해 스크린 좌표로 변환한다

UV좌표도 구조체에 복사해서 반환값을 전달 하고 있다


픽셀 셰이더에서는 넘어온 UV 좌표에 해당하는 텍셀(텍스처 좌표상의 픽셀)을 tex2D함수로 얻어온다.

이것은 픽셀 셰이더의 기본이다.


얻어온 색으로부터 알파값을 PSout.alpha에 대입하고 있다.

마지막으로 return에서 구조체를 반환한다.

이것으로 한번에 2개의 렌더 타겟에서 렌더링된다


○ 테크닉



technique RGBAndAlphaMPR

{
    pass p0
    {
        // 렌더 스테이트 설정
        AlphaBlendEnable = TRUE;
        SrcBlend = SRCALPHA;
        DestBlend = INVSRCALPHA;
        ColorOp[0] = SELECTARG1;
        ColorArg1[0] = TEXTURE;
        ColorArg2[0] = DIFFUSE;
        AlphaOp[0] = SELECTARG1;
        AlphaArg1[0] = TEXTURE;
        AlphaArg2[0] = DIFFUSE;
        ColorOp[1] = DISABLE;
        AlphaOp[1] = DISABLE;

        // 셰이더
        VertexShader = compile vs_2_0 SimpleVS();
        PixelShader = compile ps_2_0 RGBAndAlphaMPR_PS();
    }
}

테크닉 (RGBAndAlphaMPR.fx)


알파 블렌드를 유효하게 해야 하므로 AlphaBlendEnable을 TRUE로 이하 알파 블렌드를 구현하는 스탠다드한 설정을한다


○ 프로그램


 // 멀티 렌더 타겟 테스트


#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")

#include <windows.h>
#include <tchar.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <math.h>

TCHAR gName[100] = _T("멀티 렌더 타겟");

#define SAFERELEASE(x) if(x){x->Release();}
#define FULLRELEASE \
       SAFERELEASE(pAlphaSurf);   \
       SAFERELEASE(pBackBuffer);  \
       SAFERELEASE(pEffect);      \
       SAFERELEASE(pErrorBuffer); \
       SAFERELEASE(pAirPlaneTex); \
       SAFERELEASE(pAirPlane);    \
       SAFERELEASE(pAlphaTex);    \
       SAFERELEASE(g_pD3DDev);    \
       SAFERELEASE(g_pD3D);

#define FAILEDCHECK(x) \
if(FAILED(x)) {        \
        FULLRELEASE;   \
       return 0;       \
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT mes, WPARAM wParam, LPARAM lParam){
   if(mes == WM_DESTROY || mes == WM_CLOSE ) {PostQuitMessage(0); return 0;}
   return DefWindowProc(hWnd, mes, wParam, lParam);
}


int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
   // 어플리케이션 초기화
   MSG msg; HWND hWnd;
   WNDCLASSEX wcex ={sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInstance, NULL, NULL,
                                    (HBRUSH)(COLOR_WINDOW+1), NULL, (TCHAR*)gName, NULL};
   if(!RegisterClassEx(&wcex))
      return 0;

   if(!(hWnd = CreateWindow(gName, gName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 640, 480,
                                    NULL, NULL, hInstance, NULL)))
      return 0;

   // Direct3D 초기화
   LPDIRECT3D9 g_pD3D;
   LPDIRECT3DDEVICE9 g_pD3DDev;
   if( !(g_pD3D = Direct3DCreate9( D3D_SDK_VERSION )) ) return 0;

   D3DPRESENT_PARAMETERS d3dpp = {640,480,D3DFMT_UNKNOWN,0,D3DMULTISAMPLE_NONE,0,
                                                      D3DSWAPEFFECT_DISCARD,NULL,TRUE,TRUE,D3DFMT_D24S8,0,D3DPRESENT_RATE_DEFAULT,D3DPRESENT_INTERVAL_DEFAULT};

   if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDev ) ) )
   if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDev ) ) )
   {
      g_pD3D->Release();
      return 0;
   }

   D3DCAPS9 Caps;
   g_pD3DDev->GetDeviceCaps( &Caps );
   DWORD RT = Caps.NumSimultaneousRTs;
   if ( RT <= 1 ) {
       MessageBox( hWnd, _T("멀티 렌더 타겟을 지원하지 않습니다 종료합니다"), _T("그 외 에러"),0);
       g_pD3DDev->Release();
       g_pD3D->Release();
       return 0;
   }

   // 변수 정의
   ID3DXMesh *pAirPlane = 0;
   IDirect3DTexture9 *pAirPlaneTex = 0;
   IDirect3DTexture9 *pAlphaTex = 0;
   ID3DXEffect *pEffect = 0;
   ID3DXBuffer *pErrorBuffer = 0;
   IDirect3DSurface9 *pAlphaSurf = 0;
   IDirect3DSurface9 *pBackBuffer = 0;

   // 비행기 오브젝트 생성
   DWORD NumMaterials;
   FAILEDCHECK( D3DXLoadMeshFromX( _T("airplane 2.x"), D3DXMESH_MANAGED, g_pD3DDev, NULL, NULL, NULL, &NumMaterials, &pAirPlane) );
   FAILEDCHECK( D3DXCreateTextureFromFile( g_pD3DDev, _T("AirPlaneTex.png"), &pAirPlaneTex ) );

   // 이펙트 생성
   FAILEDCHECK( D3DXCreateEffectFromFile( g_pD3DDev, _T("RGBAndAlphaMPR.fx"), 0, 0, 0, 0, &pEffect, &pErrorBuffer) );

   // 알파용 텍스처 생성
   FAILEDCHECK( g_pD3DDev->CreateTexture( 640, 480, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &pAlphaTex, 0 ) );

   // 렌더 타겟 세팅
   pAlphaTex->GetSurfaceLevel( 0, &pAlphaSurf );
   g_pD3DDev->SetRenderTarget( 1, pAlphaSurf );
   g_pD3DDev->GetRenderTarget( 0, &pBackBuffer );

   // 각 행렬 생성
   D3DXMATRIX WorldMat, ViewMat, ProjMat, WVP;
   D3DXMatrixIdentity( &WorldMat );
   D3DXMatrixPerspectiveFovLH( &ProjMat, D3DXToRadian(45), 640.0f/480.0f, 0.1f, 100.0f);

   ShowWindow(hWnd, nCmdShow);

   // 메시지 루프
   int count = 0;
   double angle = 0.0;
   do{
      Sleep(1);
      if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ){ DispatchMessage(&msg);}

         g_pD3DDev->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(40,40,80), 1.0f, 0 );
         g_pD3DDev->BeginScene();

         // 뷰 회전
         angle += 0.01f;
         D3DXMatrixLookAtLH(
             &ViewMat,
             &D3DXVECTOR3((float)(10.0f*cos(angle)),5,(float)(10.0f*sin(angle))),
             &D3DXVECTOR3(0,0,0),
             &D3DXVECTOR3(0,1,0)
             );
         WVP = WorldMat * ViewMat * ProjMat;

         // 렌더 타겟 교환
        if ( count++ % 60 == 0 ) {
            IDirect3DSurface9 *pTmp = pAlphaSurf;
            pAlphaSurf = pBackBuffer;
            pBackBuffer = pTmp;
            g_pD3DDev->SetRenderTarget( 1, NULL );      // 이부분
            g_pD3DDev->SetRenderTarget( 0, pBackBuffer );
            g_pD3DDev->SetRenderTarget( 1, pAlphaSurf );
        }

         // 렌더 디바이스에 이펙트 세팅
         pEffect->SetMatrix("WVP", &WVP );
         pEffect->SetTexture("Tex", pAirPlaneTex );
         pEffect->SetTechnique( "RGBAndAlphaMPR" );
         unsigned int numPass;
         pEffect->Begin( &numPass, 0 );
         pEffect->BeginPass( 0 );
        unsigned int i;
        for( i = 0; i < NumMaterials; i++ )
             pAirPlane->DrawSubset( i );
        pEffect->EndPass();


         g_pD3DDev->EndScene();
         g_pD3DDev->Present( NULL, NULL, NULL, NULL );
   }while(msg.message != WM_QUIT);

   FULLRELEASE;

   return 0;
}

메인 프로그램(main.cpp)




반응형

+ Recent posts