포스트이펙트 프레임

그림자매핑을 떠 올리면서 포스트이펙트를 작성해 보자.

이전장의 tutorial10장의 ColorConversion3.rfx 파일을 NoEffect.rfx, Grayscale.rfx, Sepia.rfx 파일로 저장한다.

NoEffect.rfx, Grayscale.rfx, Sepia.rfx는 동일한 이름의 패스만 남기고 나머지 패스는 삭제한다.

그림자매핑에서는 사각형 메쉬를 사용했는데, 여기서는 C++ 코드에서 직접 만들겠다.

사각형 메쉬 만들기

정점 위치와 UV 좌표 만으로 사각형 메쉬를 만든다.

정점선언, 정점버퍼, 색인버퍼를 선언한 구조체를 만든다.
전역변수 gScreenQuadMesh를 선언한다.

//스크린 쿼드 메쉬
struct ScreenQuadMesh
{
    int size;
    LPDIRECT3DVERTEXDECLARATION9    decl;
    LPDIRECT3DVERTEXBUFFER9            vb;
    LPDIRECT3DINDEXBUFFER9            ib;
};
ScreenQuadMesh gScreenQuadMesh = {0, NULL, NULL, NULL};

스크린 쿼드 메쉬를 해제한다.

void ReleaseScreenMesh()
{
    _RELEASE_(gScreenQuadMesh.decl);
    _RELEASE_(gScreenQuadMesh.vb);
    _RELEASE_(gScreenQuadMesh.ib);
}

스크린 쿼드 메쉬를 생성한다.

void InitScreenMesh()
{
    ScreenQuadMesh_CreateVertexDeclaration( gScreenQuadMesh);
    ScreenQuadMesh_CreateVertexBuffer( gScreenQuadMesh);
    ScreenQuadMesh_CreateIndexBuffer( gScreenQuadMesh);
}

메모리 구조를 다이렉트 X 디바이스에 알려주기 위해 정점 선언을 생성한다.

HRESULT ScreenQuadMesh_CreateVertexDeclaration( ScreenQuadMesh& quadMesh)
{
    // 정점 선언을 만든다
    D3DVERTEXELEMENT9 vtxDesc[3];
    int offset = 0;
    int i = 0;

    // 위치
    vtxDesc[i].Stream = 0;
    vtxDesc[i].Offset = offset;
    vtxDesc[i].Type = D3DDECLTYPE_FLOAT3;
    vtxDesc[i].Method = D3DDECLMETHOD_DEFAULT;
    vtxDesc[i].Usage = D3DDECLUSAGE_POSITION;
    vtxDesc[i].UsageIndex = 0;

    offset += sizeof(float)* 3;
    ++i;

    // UV좌표 0
    vtxDesc[i].Stream = 0;
    vtxDesc[i].Offset = offset;
    vtxDesc[i].Type = D3DDECLTYPE_FLOAT2;
    vtxDesc[i].Method = D3DDECLMETHOD_DEFAULT;
    vtxDesc[i].Usage = D3DDECLUSAGE_TEXCOORD;
    vtxDesc[i].UsageIndex = 0;

    offset += sizeof(float)* 2;
    ++i;

    // 정점포맷의 끝임을 표현 (D3DDECL_END())
    vtxDesc[i].Stream = 0xFF;
    vtxDesc[i].Offset = 0;
    vtxDesc[i].Type = D3DDECLTYPE_UNUSED;
    vtxDesc[i].Method = 0;
    vtxDesc[i].Usage = 0;
    vtxDesc[i].UsageIndex = 0;

    quadMesh.size = 0;
    return gD3DDevice->CreateVertexDeclaration(vtxDesc, &quadMesh.decl);    
}

 

    vtxDesc[i].Stream = 0;
    vtxDesc[i].Offset = offset;
    vtxDesc[i].Type = D3DDECLTYPE_FLOAT3;
    vtxDesc[i].Method = D3DDECLMETHOD_DEFAULT;
    vtxDesc[i].Usage = D3DDECLUSAGE_POSITION;
    vtxDesc[i].UsageIndex = 0;

Stream : 스트림 인덱스
Offset : 정점 정보가 시작하는 위치
type : 데이터 형
Method : D3DDECLMETHOD_DEFAULT 디폴트 설정
Usage : 이 요소의 사용용도
UsageIndex : TEXCOORD의 인덱스로, 텍스쳐를 사용하지 않으므로 0이다.

셰이더 자원 처리

포스트프로세싱에 사용할 셰이더 리소스를 만든다.

struct ScreenEffect
{
    LPD3DXEFFECT noEffect;
    LPD3DXEFFECT grayScale;
    LPD3DXEFFECT sepia;
};
ScreenEffect gScreenEffect = { NULL, NULL, NULL};

스크린용 렌더타겟 변수 선언

//스크린 렌더타겟
LPDIRECT3DTEXTURE9 gScreenRenderTarget = NULL;

리소스들을 해제한다.

void ReleaseScreenEffect()
{
    _RELEASE_(gScreenEffect.noEffect);
    _RELEASE_(gScreenEffect.grayScale);
    _RELEASE_(gScreenEffect.sepia);    

    _RELEASE_(gScreenRenderTarget);
}

렌더타겟을 창의 폭과 높이로 생성한다.
텍스처 포맷은 하드웨어 백버퍼 포맷과 마찬가지로 8비트 RGB 채널 D3DFMT_X8R8G8B8로 한다.
gRenderScrWidth, gRenderScrHeight는 백버퍼의 폭과 높이다.
다른 수치를 넣게 되면 화면이 정상적으로 보이지 않을것이다.

bool InitScreenRenderTarget()
{
    //렌더타겟 생성
    if( FAILED(gD3DDevice->CreateTexture(
            gRenderScrWidth, gRenderScrHeight,
            1,
            D3DUSAGE_RENDERTARGET,
            D3DFMT_X8R8G8B8,D3DPOOL_DEFAULT,
            &gScreenRenderTarget, NULL)))
    {
        return false;
    }

    return true;
}

LoadAsset( )에서 셰이더를 로딩한다.

// 셰이더 로딩
gScreenEffect.noEffect = LoadShader("NoEffect.fx");
if(gScreenEffect.noEffect == NULL)
    return false;

gScreenEffect.grayScale = LoadShader("GrayScale.fx");
if(gScreenEffect.grayScale == NULL)
    return false;

gScreenEffect.sepia = LoadShader("Sepia.fx");
if(gScreenEffect.sepia == NULL)
    return false;

렌더링 - RenderScene( ) - 메시 그리기

하드웨어 백 버퍼를 저장 후, 렌더러텍스쳐를 렌더타겟으로 지정한다.
하드웨어 깊이버퍼를 사용하지 않는 이유는 렌더타겟의 크기가 하드웨어 깊이 버퍼의 크기와 동일하기 때문이다.

//1. 장면을 그린다

//백버퍼 저장
LPDIRECT3DSURFACE9 pHWBackBuffer = NULL;
gD3DDevice->GetRenderTarget(0, &pHWBackBuffer);

//렌더러텍스쳐를 렌더타겟으로 설정
LPDIRECT3DSURFACE9 pSceneSurface = NULL;
if (SUCCEEDED(gScreenRenderTarget->GetSurfaceLevel(0, &pSceneSurface)))
{
    gD3DDevice->SetRenderTarget(0, pSceneSurface);
    pSceneSurface->Release();
    pSceneSurface = NULL;
}

렌더타겟을 새롭게 설정했기 때문에 장면을 지운다.

gD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, 0xFF000000, 1.0f, 0);

행렬를 설정 후 전역변수를 설정한다.

// 쉐이더 전역변수들을 설정
gShader->SetMatrix("gWorldMat", &matWorld);
gShader->SetMatrix("gWorldViewProjMat", &matWorldViewProjection);

gShader->SetVector("gLightPos", &gWorldLightPos);
gShader->SetVector("gViewPos", &gWorldCameraPos);

gShader->SetVector("gLightColor", &gLightColor);
gShader->SetTexture("DiffuseMap_Tex", gStoneDM);
gShader->SetTexture("SpecularMap_Tex", gStoneSM);
gShader->SetTexture("NormalMap_Tex", gStoneNM);
gShader->SetTexture("EnvironmentMap_Tex", gSnowENV);

렌더러 텍스쳐에 그린다.

// 쉐이더를 시작한다.
UINT numPasses = 0;
gShader->Begin(&numPasses, NULL);
{
    for (UINT i = 0; i < numPasses; ++i)
    {
        gShader->BeginPass(i);
        {
            gMesh->DrawSubset(0);
        }
        gShader->EndPass();
    }
}
gShader->End();

렌더링 - RenderScene( ) - 포스트 프로세싱

렌더러 텍스쳐에 그린 이미지를 포스트 프로세싱 처리를 하여 스크린 평면에 그려보자.

저장해 두었던 하드웨어 백 버퍼를 렌더타겟으로 설정한다.

//2. 포스트프로세싱을 적용한다.

// 하드웨어 백버퍼를 사용한다.
gD3DDevice->SetRenderTarget(0, pHWBackBuffer);
pHWBackBuffer->Release();
pHWBackBuffer = NULL;

렌더러 텍스쳐를 렌더타겟으로 설정한 뒤 패스를 시작한다.

useShader->SetTexture("SceneTexture_Tex", gScreenRenderTarget);
useShader->Begin(&numPasses, NULL);
{
    for (UINT i = 0; i < numPasses; ++i)
    {
        useShader->BeginPass(i);
        {

화면에 스크린 평면에 렌더러텍스쳐를 그리고 패스를 끝낸다

            // 화면가득 사각형을 그린다.
            gD3DDevice->SetStreamSource(0, gScreenQuadMesh.vb, 0, sizeof(float)* 5);
            gD3DDevice->SetIndices(gScreenQuadMesh.ib);
            gD3DDevice->SetVertexDeclaration(gScreenQuadMesh.decl);
            gD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 6, 0, 2);
        }
        useShader->EndPass();
    }
}
useShader->End();

Key 1, 2, 3에 따라 NoEffect, Grayscale, Sepia 셰이더를 실행한다.

void main_OnKeydown(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
    switch (vk)
    {
    case '1':
    case '2':
    case '3':
        gPostProcessIndex = vk - '0' - 1;
        break;
    }
}

다운로드 : PostProcssingShader.zip       DxDemo_PostProcessing.zip