DirectX11 Tutorial 36: 블러

강좌번역/DirectX 11 2018. 7. 3. 23:56 by 빠재

원문: Tutorial 36: Blur

블러 효과는 화면 전체 또는 개별 물체를 뿌옇게 만드는 데 사용됩니다. 하지만 더욱 중요한 점으로는 이 블러 효과가 다른 많은 효과들의 기본이 된다는 것입니다. 그런 효과들에는 블룸, 뎁스 오브 필드, 풀스크린 글로우, 글로우 매핑, 헤일로/엣지 글로우, 부드러운 그림자 경계, 흐려진 라이트 트레일, 물속 효과, 그리고 더 많이 있습니다. 하지만 이 예제에서는 간단한 풀스크린 블러를 하는 방법만 설명하도록 하겠습니다.

DirectX 11에서 실시간 블러 효과를 하기 위해서 우선 화면을 텍스쳐에 그린 다음 텍스쳐에 블러를 먹인 뒤 화면에 텍스쳐를 그리는 식으로 진행됩니다. 텍스쳐에 그려진 씬에 2D 이미지 프로세싱을 하는 것을 후처리 또는 포스트 프로세싱(Post processing)라고 합니다. 포스트 프로세싱은 대개 무겁고 셰이더의 많은 최적화가 요구됩니다.

이 튜토리얼에서는 따로 최적화를 하지 않습니다. 대신 코드를 쪼개 놓아 블러 효과가 실제로 어떻게 작동하는지 최대한 명확히 이해할 수 있게 했습니다. 일단 어떻게 동작하는지 알고 나면 용도에 따라 최적화의 방향이 잡히게 될 것입니다. 최적화를 하는 데는 많은 방법들이 있지만(렌더 투 텍스쳐를 적게 한다던지, 셰이더를 멀티패스로 만든다던지, 가중치값을 정규화한다던지 등등), 여러분 스스로 생각해 보고 구현하도록 권해드립니다.

블러 알고리즘

  1. 화면을 텍스쳐에 그립니다.

  2. 텍스쳐를 절반 또는 그 이하로 다운샘플링합니다.

  3. 샘플된 텍스쳐에 수평 블러를 수행합니다.

  4. 수직 블러를 수행합니다.

  5. 원래 화면 사이즈로 텍스쳐를 샘플링합니다.

  6. 화면에 텍스쳐를 그립니다.

각각의 단계에 대해 설명하겠습니다.

첫 번째 단계는 전체 화면을 텍스쳐에 그리는 것입니다. 이것은 매우 직관적이고 Tutorial 22: 텍스쳐에 그리기에서 다룬 내용입니다. 아직 보지 않았다면 한번 살펴보시기 바랍니다.

두 번째 단계는 더 작은 크기로 텍스쳐를 다운샘플링하는 것입니다. 우선 두 개의 삼각형으로 이루어진 2D 사각형 모델을 만듭니다(이 예제에서 이 2D 모델에 대한 클래스를 OrthoWindowClass라고 하겠습니다). 이 2D 사각형 모델을 필요한 크기보다 작게 만듭니다(ex: 256x256 또는 화면 너비/높이의 절반). 그 다음 전체 화면 텍스쳐를 이 작은 2D 사각형 모델에 입히면 셰이더 샘플러의 필터링이 알아서 다운샘플링을 수행할 것입니다. 이 역시 Tutorial 11: 2D 렌더링에서 다루었던 부분입니다.

그렇다면 다운샘플링이 블러링 알고리즘과 무슨 관련이 있는지 궁금해지실지도 모르겠습니다. 다운샘플링을 하는 첫 번째 이유는 큰 텍스쳐에 처리를 하는 것보다 비용이 (많이) 적기 때문입니다. 두 번째 이유는 텍스쳐를 줄이고 다시 크게 늘리는 것 자체가 또 하나의 블러 효과이기 때문에 블러를 두 번 한것만큼의 좋은 효과를 낼 수 있습니다. 사실 옛날에는 실시간 블러를 할 수 있는 방법이 많지 않았습니다. 아마 단순히 텍스쳐를 줄였다 늘리는 것만 가능할 수도 있었습니다. 픽셀이 심각하게 깨지고 보기 좋지 않았지만 프로그래머블 그래픽스 하드웨어가 등장하기 전까지는 다른 방법이 많지 않았습니다.

다운샘플링된 텍스쳐를 만들었으니 블러를 수행합니다. 이번에 사용할 블러링은 인접한 픽셀들의 가중 평균을 구하는 것입니다. 이것이 꽤 무겁기는 하지만 두 번의 패스로 나누어 처리함으로 계산 복잡도를 줄이는 방법을 쓸 수 있습니다. 인접한 픽셀들을 다 찾아 계산하는 원형 패스가 아니라 우선 수평 방향으로 계산을 하고 그 다음 수직 방향으로 계산을 합니다(역자주: 가우시안 필터를 적용하는 방식입니다).

두 방법의 속도 차이를 이해하기 위해 10x10 크기의 이미지를 생각헤 보겠습니다. 두 번의 선형 패스를 10x10 이미지에 적용하는 것은 (10x10) + (10x10) = 200픽셀을 읽어야 합니다. 한 번의 원형 패스를 처리하는 데에는 100x100 = 10,000번의 픽셀 읽기가 필요합니다. 같은 예를 전체 화면과 같은 높은 해상도로 확장하면 왜 두 번의 선형 패스를 선택해야 하는지 이해하실 것입니다.

첫 번째 선형 패스는 수평 블러입니다. 일례로 다음과 같은 픽셀이 있다고 해 봅시다:

그리고 좌우 3개의 인접한 픽셀들의 가중 평균을 구하면 각 픽셀은 아래와 같은 모양처럼 바뀔 것입니다.

다운샘플링한 텍스쳐 전체에 이 연산을 수행합니다. 수평 방향으로 블러된 이미지는 HorizontalBlurTexture이라고 하는 두번째 렌더 투 텍스쳐에 그려집니다. 이 텍스쳐는 다음 수직 블러 패스의 입력으로 사용될 것입니다.

수평 블러 단계에서 사용한 블러 가중치는 여러분이 입맛에 따라 늘이거나 줄일 수 있습니다. 예를 들어 가운데 픽셀이 1.0, 첫 번째 왼쪽/오른쪽 이웃이 0.9, 두 번째 이웃이 0.8 이렇게 둘 수 있습니다. 또는 좀 더 공격적으로 적용하여 1.0, 0.75, 0.5와 같은 값을 쓸 수도 있습니다. 이 값들의 선택은 여러분의 몫이고 그에 따라 매우 다른 결과를 얻을 수 있습니다. 값을 직접 넣는 대신 사인파나 톱날 모양 패턴을 적용할 수도 있습니다. 이 또한 여러분의 몫이며 서로 다른 흥미로운 모양의 블러 효과를 만들 수 있습니다.

또 다른 변수로는 참조할 이웃 픽셀들의 숫자입니다. 이번 예제에서는 인접한 3개의 이웃을 블러합니다. 하지만 원한다면 20개의 인접한 픽셀을 블러할 수도 있습니다. 다시 말하지만 이 숫자를 바꾸게 되면 최종 블러 결과에 상당한 영향을 미치게 됩니다. 이번 예제 셰이더 코드에서는 4개의 인접 픽셀을 사용합니다.

다른 렌더 투 텍스쳐에 수평으로 블러된 이미지가 있으니 수평 블러를 해 보겠습니다. 방법은 수평 블러와 같으나 방향만 수직으로 간다는 것이 다르고 처음 다운샘플된 텍스쳐 대신 수평 블러의 결과를 입력으로 사용하게 됩니다. 수직 블러 또한 VerticalBlurTexture이라는 다른 렌더 투 텍스쳐에 기록됩니다. 각각의 렌더링 결과를 다른 렌더 투 텍스쳐에 쓰는 것은 디버그 용도로 중간 블러 패스 결과를 볼 수 있게 하는 것이 가능하게 해 줍니다. 이전 샘플을 수직 블러까지 적용하면 아래와 같은 결과를 얻을 수 있습니다.

처리가 완료되면 저해상도로 블러된 이미지를 얻게 되었지만 아직 원래 사이즈로 돌리는 작업이 남아 있습니다. 이 역시 처음에 다운 샘플링이 일어난 것과 똑같은 방법으로 이루어집니다. 두 삼각형으로 이루어진 2D 사각형을 화면 해상도 크기로 만들고 블러된 텍스쳐를 사각형에 입히면 셰이더 샘플러의 필터링이 알아서 업샘플링을 해 줍니다. 모든 과정이 완료되었고 업샘플링된 텍스쳐 역시 2D 화면에 그려질 수 있습니다.

다른 고려할 점들

여러분도 아마 예상하셨을지 모르겠지만 업샘플링 과정에서 일부 계단 현상이 발생할 수 있습니다. 다운샘플링 사이즈가 절반 정도였다면 그런 현상이 나타나지 않을 수도 있습니다. 하지만 다운샘플의 크기가 1/4이거나 더 큰 블러를 위해 그보다 더 작게 한다면 다시 크게 돌렸을 때 일부 결점이 보일 것입니다. 이런 결점은 먼 거리에서의 움직임이 있는 경우 깜박이거나 어른거리는 식으로 더욱 두드러지게 나타납니다. 이런 문제를 해결하기 위한 한 가지 방법으로는 직접 업샘플링 셰이더를 작성하여 기본으로 제공되는 빠른 선형 보간 대신 블러에서 썼던 것과 같이 이 픽셀의 값을 정할 때 많은 인접 픽셀들의 정보를 사용하게 하는 것이 있습니다. 그 외에도 계단 현상을 줄일 수 있는 여러 샘플링 필터들이 있습니다.

만약 물체 단위로 블러를 수행한다면 해당 위치에 2D 텍스쳐를 그리기 위한 빌보드가 필요할 것입니다. 그 방법으로는 예전에 제가 썼던 빌보드 예제를 참고하실 수 있습니다.

마지막으로 프레임워크와 코드 작업을 하기 전에 더욱 큰 블러 효과를 내고 싶다면 다운샘플된 이미지에 수직/수평 블러를 한번만 적용하는 것이 아니라 두 번 적용할 수도 있다는 점도 알아 두시면 좋습니다.

프레임워크

이번에는 세 개의 새로운 클래스가 있습니다. 처음 두 클래스는 HorizontalBlurClassVerticalBlurClass로써, 각각 수평 방향과 수직 방향의 블러 셰이더를 맡고 있습니다. 두 클래스를 BlurClass 같은 이름으로 합칠 수도 있지만 좀 더 깔끔한 설명을 위해 분리하였습니다. 세 번째 클래스는 OrthoWindowClass입니다. 이 클래스는 두 개의 삼각형으로 만든 2D 사각형 모델을 그립니다. 크기를 조절하는 것도 가능하게 되어 있지만 아마 텍스쳐를 표현하는 용도로만 사용하기를 원할 것입니다. 그 외에도 다운샘플링, 업샘플링, 화면에 2D 그리는 용도로도 사용할 수 있습니다.

HLSL 수평 블러 셰이더부터 보도록 하겠습니다.

Horizontalblur.vs

////////////////////////////////////////////////////////////////////////////////
// Filename: horizontalblur.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};

이 셰이더에서는 텍셀이라고도 하는 텍스쳐의 개별 픽셀의 실제 UV 위치를 계산하기 위해 화면의 너비(또는 렌더 텍스쳐의 너비)가 필요합니다. 아래 나오는 상수 버퍼에 그 값을 저장할 것입니다. padding은 상수 버퍼의 크기가 16의 배수가 되도록 하기 위한 것입니다.

cbuffer ScreenSizeBuffer
{
    float screenWidth;
    float3 padding;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

PixelInputStructure은 가운데 픽셀과 양쪽으로 네 개씩 인접한 픽셀의 UV 위치를 가집니다.

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float2 texCoord1 : TEXCOORD1;
    float2 texCoord2 : TEXCOORD2;
    float2 texCoord3 : TEXCOORD3;
    float2 texCoord4 : TEXCOORD4;
    float2 texCoord5 : TEXCOORD5;
    float2 texCoord6 : TEXCOORD6;
    float2 texCoord7 : TEXCOORD7;
    float2 texCoord8 : TEXCOORD8;
    float2 texCoord9 : TEXCOORD9;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType HorizontalBlurVertexShader(VertexInputType input)
{
    PixelInputType output;
    float texelSize;


    // 행렬 연산을 위해 위치 벡터의 원소 수가 4개가 되도록 합니다.
    input.position.w = 1.0f;

    // 정점의 위치를 각각 월드, 뷰, 투영 행렬에서 계산합니다.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);

    // 픽셀 셰이더에서 사용할 텍스쳐 좌표를 저장합니다.
    output.tex = input.tex;

이제 1을 화면의 너비(또는 렌더 텍스쳐의 너비)로 나누어 텍셀의 크기를 구합니다. 이 값을 이용하여 인접한 픽셀의 UV 좌표를 계산할 수 있습니다.

    // 화면 너비에 대한 텍셀의 부동소수점 표현 크기를 구합니다.
    texelSize = 1.0f / screenWidth;

가운데 픽셀과 인접한 각 4개의 픽셀에 대한 UV 좌표를 계산합니다. 현재 텍스쳐의 좌표를 가지고 수평 오프셋을 더해 나감으로 9개 좌표 모두 계산합니다. 수평 오프셋은 텍셀 크기에 이웃 좌표의 거리를 곱하여 구합니다. 예를 들어 왼쪽으로 3픽셀 떨어진 좌표는 texelSize * -3.0f로 계산하는 식입니다. 오프셋의 수직 방향은 0이기 때문에 샘플링은 수평 라인에서만 이루어집니다.

    // 현재 픽셀과 그 양 옆 4개 픽셀의 UV 좌표를 계산합니다.
    output.texCoord1 = input.tex + float2(texelSize * -4.0f, 0.0f);
    output.texCoord2 = input.tex + float2(texelSize * -3.0f, 0.0f);
    output.texCoord3 = input.tex + float2(texelSize * -2.0f, 0.0f);
    output.texCoord4 = input.tex + float2(texelSize * -1.0f, 0.0f);
    output.texCoord5 = input.tex + float2(texelSize *  0.0f, 0.0f);
    output.texCoord6 = input.tex + float2(texelSize *  1.0f, 0.0f);
    output.texCoord7 = input.tex + float2(texelSize *  2.0f, 0.0f);
    output.texCoord8 = input.tex + float2(texelSize *  3.0f, 0.0f);
    output.texCoord9 = input.tex + float2(texelSize *  4.0f, 0.0f);

    return output;
}

Horizontalblur.ps

////////////////////////////////////////////////////////////////////////////////
// Filename: horizontalblur.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////

shaderTexture은 블러 처리가 될 텍스쳐입니다.

Texture2D shaderTexture;
SamplerState SampleType;


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float2 texCoord1 : TEXCOORD1;
    float2 texCoord2 : TEXCOORD2;
    float2 texCoord3 : TEXCOORD3;
    float2 texCoord4 : TEXCOORD4;
    float2 texCoord5 : TEXCOORD5;
    float2 texCoord6 : TEXCOORD6;
    float2 texCoord7 : TEXCOORD7;
    float2 texCoord8 : TEXCOORD8;
    float2 texCoord9 : TEXCOORD9;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 HorizontalBlurPixelShader(PixelInputType input) : SV_TARGET
{
    float weight0, weight1, weight2, weight3, weight4;
    float normalization;
    float4 color;

알고리즘 부분에서 설명했듯이 픽셀의 색상은 현재 픽셀과 인접한 여덟 개의 픽셀들의 평균값으로 정합니다. 또한 인접한 픽셀에는 가중치 값이 사용됩니다. 이 예제에서 사용할 가중치값은 중앙에 가까울수록 더 큰 영향을 미치도록 되어 있습니다.

    // 각 픽셀의 기여도에 해당하는 가중치를 정합니다.
    weight0 = 1.0f;
    weight1 = 0.9f;
    weight2 = 0.55f;
    weight3 = 0.18f;
    weight4 = 0.1f;

이 가중치값들을 정규화하여 블러가 더 부드럽게 보이게 만듭니다.

    // 가중치들을 살짝 평균내어 정규화 값을 만듭니다.
    normalization = (weight0 + 2.0f * (weight1 + weight2 + weight3 + weight4));

    // 가중치들을 정규화합니다.
    weight0 = weight0 / normalization;
    weight1 = weight1 / normalization;
    weight2 = weight2 / normalization;
    weight3 = weight3 / normalization;
    weight4 = weight4 / normalization;

블러처리된 픽셀을 구하기 위해 검정색에서 시작하여 각각 가중치가 가운데 픽셀과 여덟 개의 인접한 픽셀들에 각각 가중치를 곱하고 더합니다.

    // 색깔을 검정색으로 초기화합니다.
    color = float4(0.0f, 0.0f, 0.0f, 0.0f);

    // 수평선상의 아홉 픽셀값들을 가중치를 곱해 더합니다.
    color += shaderTexture.Sample(SampleType, input.texCoord1) * weight4;
    color += shaderTexture.Sample(SampleType, input.texCoord2) * weight3;
    color += shaderTexture.Sample(SampleType, input.texCoord3) * weight2;
    color += shaderTexture.Sample(SampleType, input.texCoord4) * weight1;
    color += shaderTexture.Sample(SampleType, input.texCoord5) * weight0;
    color += shaderTexture.Sample(SampleType, input.texCoord6) * weight1;
    color += shaderTexture.Sample(SampleType, input.texCoord7) * weight2;
    color += shaderTexture.Sample(SampleType, input.texCoord8) * weight3;
    color += shaderTexture.Sample(SampleType, input.texCoord9) * weight4;

마지막으로 알파값을 설정합니다.

    // 알파 채널을 1로 설정합니다.
    color.a = 1.0f;

    return color;
}

Horizontalblurshaderclass.h

HorizontalBlurShaderClassTextureShaderClass에 블러 효과를 처리하도록 수정된 버전입니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: horizontalblurshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _HORIZONTALBLURSHADERCLASS_H_
#define _HORIZONTALBLURSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: HorizontalBlurShaderClass
////////////////////////////////////////////////////////////////////////////////
class HorizontalBlurShaderClass
{
private:
    struct MatrixBufferType
    {
        D3DXMATRIX world;
        D3DXMATRIX view;
        D3DXMATRIX projection;
    };

화면 크기 상수 버퍼를 위한 구조체입니다.

    struct ScreenSizeBufferType
    {
        float screenWidth;
        D3DXVECTOR3 padding;
    };

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

    bool Initialize(ID3D11Device*, HWND);
    void Shutdown();
    bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float);

private:
    bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
    void ShutdownShader();
    void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

    bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float);
    void RenderShader(ID3D11DeviceContext*, int);

private:
    ID3D11VertexShader* m_vertexShader;
    ID3D11PixelShader* m_pixelShader;
    ID3D11InputLayout* m_layout;
    ID3D11SamplerState* m_sampleState;
    ID3D11Buffer* m_matrixBuffer;

HorizontalBlurShaderClassTextureShaderClass와 가장 다른 점은 수평 블러 셰이더를 위한 화면 너비 정보가 담긴 상수 버퍼가 있다는 것입니다.

    ID3D11Buffer* m_screenSizeBuffer;
};

#endif

Horizontalblurshaderclass.cpp

TextureShaderClassHorizontalShaderClass가 별로 다르지 않기 때문에 실제로 다른 부분에만 주석을 달도록 하겠습니다.

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


HorizontalBlurShaderClass::HorizontalBlurShaderClass()
{
    m_vertexShader = 0;
    m_pixelShader = 0;
    m_layout = 0;
    m_sampleState = 0;
    m_matrixBuffer = 0;

화면 너비 상수 버퍼를 생성자에서 null로 초기화합니다.

    m_screenSizeBuffer = 0;
}


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


HorizontalBlurShaderClass::~HorizontalBlurShaderClass()
{
}


bool HorizontalBlurShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
    bool result;

horizontalblur.vshorizontalblur.ps HLSL 셰이더를 불러옵니다.

    // 정점 및 픽셀 셰이더를 초기화합니다.
    result = InitializeShader(device, hwnd, L"../Engine/horizontalblur.vs", L"../Engine/horizontalblur.ps");
    if(!result)
    {
        return false;
    }

    return true;
}


void HorizontalBlurShaderClass::Shutdown()
{
    // 정점/픽셀 셰이더 및 연관된 객체들을 해제합니다.
    ShutdownShader();

    return;
}

Render 함수는 화면 너비(또는 텍스쳐 너비)를 인자로 받아 SetShaderParameters 함수로 셰이더에 그 값을 설정합니다.

bool HorizontalBlurShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                       D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float screenWidth)
{
    bool result;


    // 렌더링에 사용될 셰이더 파라미터들을 설정합니다.
    result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, screenWidth);
    if(!result)
    {
        return false;
    }

    // 준비된 버퍼에 셰이더를 이용하여 그립니다.
    RenderShader(deviceContext, indexCount);

    return true;
}


bool HorizontalBlurShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
    HRESULT result;
    ID3D10Blob* errorMessage;
    ID3D10Blob* vertexShaderBuffer;
    ID3D10Blob* pixelShaderBuffer;
    D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
    unsigned int numElements;
    D3D11_SAMPLER_DESC samplerDesc;
    D3D11_BUFFER_DESC matrixBufferDesc;
    D3D11_BUFFER_DESC screenSizeBufferDesc;


    // 이 함수에서 사용할 포인터들을 null로 초기화합니다.
    errorMessage = 0;
    vertexShaderBuffer = 0;
    pixelShaderBuffer = 0;

수평 블러 정점 셰이더를 불러옵니다.

    // 정점 셰이더 코드를 컴파일합니다.
    result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "HorizontalBlurVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
                       &vertexShaderBuffer, &errorMessage, NULL);
    if(FAILED(result))
    {
        // If the shader failed to compile it should have writen something to the error message.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
        }
        // If there was nothing in the error message then it simply could not find the shader file itself.
        else
        {
            MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
        }

        return false;
    }

수평 블러 픽셀 셰이더를 불러옵니다.

    // 픽셀 셰이더 코드를 컴파일합니다.
    result = D3DX11CompileFromFile(psFilename, NULL, NULL, "HorizontalBlurPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
                       &pixelShaderBuffer, &errorMessage, NULL);
    if(FAILED(result))
    {
        // If the shader failed to compile it should have writen something to the error message.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
        }
        // If there was  nothing in the error message then it simply could not find the file itself.
        else
        {
            MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
        }

        return false;
    }

    // 버퍼에서 정점 셰이더를 생성합니다.
    result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
    if(FAILED(result))
    {
        return false;
    }

    // 버퍼에서 픽셀 셰이더를 컴파일합니다.
    result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
    if(FAILED(result))
    {
        return false;
    }

    // 정점 입력 레이아웃 디스크립션 객체입니다.
    // ModelClass::VertexType 구조체와 셰이더의 구조체와 일치해야 합니다.
    polygonLayout[0].SemanticName = "POSITION";
    polygonLayout[0].SemanticIndex = 0;
    polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    polygonLayout[0].InputSlot = 0;
    polygonLayout[0].AlignedByteOffset = 0;
    polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[0].InstanceDataStepRate = 0;

    polygonLayout[1].SemanticName = "TEXCOORD";
    polygonLayout[1].SemanticIndex = 0;
    polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
    polygonLayout[1].InputSlot = 0;
    polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
    polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[1].InstanceDataStepRate = 0;

    // 레이아웃의 원소 수를 구합니다.
    numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

    // 정접 입력 레이아웃 객체를 생성합니다.
    result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), 
                       &m_layout);
    if(FAILED(result))
    {
        return false;
    }

    // 더 이상 사용되지 않는 정점 셰이더 버퍼와 픽셀 셰이더 버퍼를 해제합니다.
    vertexShaderBuffer->Release();
    vertexShaderBuffer = 0;

    pixelShaderBuffer->Release();
    pixelShaderBuffer = 0;

    // 텍스쳐 샘플러 상태 디스크립션입니다.
    samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.MipLODBias = 0.0f;
    samplerDesc.MaxAnisotropy = 1;
    samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
    samplerDesc.BorderColor[0] = 0;
    samplerDesc.BorderColor[1] = 0;
    samplerDesc.BorderColor[2] = 0;
    samplerDesc.BorderColor[3] = 0;
    samplerDesc.MinLOD = 0;
    samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

    // 텍스쳐 샘플러 상태를 생성합니다.
    result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
    if(FAILED(result))
    {
        return false;
    }

    // 정점 셰이더에 있는 동적 행렬 상수 버퍼의 디스크립션입니다.
    matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
    matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    matrixBufferDesc.MiscFlags = 0;
    matrixBufferDesc.StructureByteStride = 0;

    // 이 클래스에서 정점 셰이더 상수 버퍼에 접근하기 위하여 상수 버퍼 포인터를 생성합니다.
    result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
    if(FAILED(result))
    {
        return false;
    }

화면 크기 상수 버퍼 객체를 만들어 수평 정점 셰이더에 있는 화면 크기 버퍼에 접근할 수 있게 합니다.

    // 정점 셰이더에 있는 동적 화면 크기 상수 버퍼의 디스크립션입니다.
    screenSizeBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    screenSizeBufferDesc.ByteWidth = sizeof(ScreenSizeBufferType);
    screenSizeBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    screenSizeBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    screenSizeBufferDesc.MiscFlags = 0;
    screenSizeBufferDesc.StructureByteStride = 0;

    // 이 클래스에서 정점 셰이더 상수 버퍼에 접근하기 위하여 상수 버퍼 포인터를 생성하비다.
    result = device->CreateBuffer(&screenSizeBufferDesc, NULL, &m_screenSizeBuffer);
    if(FAILED(result))
    {
        return false;
    }

    return true;
}


void HorizontalBlurShaderClass::ShutdownShader()
{

ShutdownShader 함수에서 화면 크기 상수 버퍼를 해제합니다.

    // 화면 크기 상수 버퍼를 해제합니다.
    if(m_screenSizeBuffer)
    {
        m_screenSizeBuffer->Release();
        m_screenSizeBuffer = 0;
    }

    // 행렬 상수 버퍼를 해제합니다.
    if(m_matrixBuffer)
    {
        m_matrixBuffer->Release();
        m_matrixBuffer = 0;
    }

    // 샘플러 상태를 해제합니다.
    if(m_sampleState)
    {
        m_sampleState->Release();
        m_sampleState = 0;
    }

    // 레이아웃 객체를 해제합니다.
    if(m_layout)
    {
        m_layout->Release();
        m_layout = 0;
    }

    // 픽셀 셰이더를 해제합니다.
    if(m_pixelShader)
    {
        m_pixelShader->Release();
        m_pixelShader = 0;
    }

    // 정점 셰이더를 해제합니다.
    if(m_vertexShader)
    {
        m_vertexShader->Release();
        m_vertexShader = 0;
    }

    return;
}


void HorizontalBlurShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
    char* compileErrors;
    unsigned long bufferSize, i;
    ofstream fout;


    // Get a pointer to the error message text buffer.
    compileErrors = (char*)(errorMessage->GetBufferPointer());

    // Get the length of the message.
    bufferSize = errorMessage->GetBufferSize();

    // Open a file to write the error message to.
    fout.open("shader-error.txt");

    // Write out the error message.
    for(i=0; i<bufferSize; i++)
    {
        fout << compileErrors[i];
    }

    // Close the file.
    fout.close();

    // Release the error message.
    errorMessage->Release();
    errorMessage = 0;

    // Pop a message up on the screen to notify the user to check the text file for compile errors.
    MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

    return;
}

SetShaderParameters 함수는 화면 또는 렌더 텍스쳐의 너비를 인자로 받습니다. 그리고 초기화 시점에 생성했던 화면 크기 상수 버퍼를 통해 셰이더에 값을 넣어줍니다.

bool HorizontalBlurShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                            D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float screenWidth)
{
    HRESULT result;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    MatrixBufferType* dataPtr;
    unsigned int bufferNumber;
    ScreenSizeBufferType* dataPtr2;


    // 셰이더에서 사용할 수 있도록 행렬을 전치(transpose)합니다.
    D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
    D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
    D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

    // 상수 버퍼에 쓰기 위하여 락을 겁니다.
    result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // 상수 버퍼의 포인터를 얻어옵니다.
    dataPtr = (MatrixBufferType*)mappedResource.pData;

    // 상수 버퍼에 행렬을 복사해 넣습니다.
    dataPtr->world = worldMatrix;
    dataPtr->view = viewMatrix;
    dataPtr->projection = projectionMatrix;

    // 상수 버퍼의 락을 해제합니다.
    deviceContext->Unmap(m_matrixBuffer, 0);

    // 정점 셰이더에서의 상수 버퍼의 위치입니다.
    bufferNumber = 0;

    // 정점 셰이더의 상수 버퍼를 갱신된 값으로 설정합니다.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

이제 화면 크기 상수 버퍼에 화면 너비를 넣어줍니다.

    // 화면 크기 상수 버퍼에 쓰기 위하여 락을 겁니다.
    result = deviceContext->Map(m_screenSizeBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // 상수 버퍼의 포인터를 얻어옵니다.
    dataPtr2 = (ScreenSizeBufferType*)mappedResource.pData;

    // 상수 버퍼에 데이터를 복사합니다.
    dataPtr2->screenWidth = screenWidth;
    dataPtr2->padding = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

    // 상수 버퍼의 락을 해제합니다.
    deviceContext->Unmap(m_screenSizeBuffer, 0);

    // 정점 셰이더에서의 상수 버퍼의 위치입니다.
    bufferNumber = 1;

    // 정점 셰이더의 상수 버퍼를 갱신된 값으로 설정합니다.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_screenSizeBuffer);

    // 픽셀 셰이더에서의 텍스쳐 리소스를 설정합니다.
    deviceContext->PSSetShaderResources(0, 1, &texture);

    return true;
}


void HorizontalBlurShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
    // Set the vertex input layout.
    deviceContext->IASetInputLayout(m_layout);

    // Set the vertex and pixel shaders that will be used to render this triangle.
    deviceContext->VSSetShader(m_vertexShader, NULL, 0);
    deviceContext->PSSetShader(m_pixelShader, NULL, 0);

    // Set the sampler state in the pixel shader.
    deviceContext->PSSetSamplers(0, 1, &m_sampleState);

    // Render the triangle.
    deviceContext->DrawIndexed(indexCount, 0, 0);

    return;
}

Verticalblur.vs

수직 블러 HLSL 프로그램은 너비 대신 높이를 처리하는 것을 빼면 수평 블러 셰이더와 동일합니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: verticalblur.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};

수직 블러 셰이더는 수평 블러 셰이더에서 했던 것처럼 화면 또는 렌더 텍스쳐의 너비를 받는 대신 높이를 입력으로 받습니다. 버퍼 구조체의 이름은 같지만 첫 번째 변수 이름이 width대신 height라는 점에 유의하십시오.

cbuffer ScreenSizeBuffer
{
    float screenHeight;
    float3 padding;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float2 texCoord1 : TEXCOORD1;
    float2 texCoord2 : TEXCOORD2;
    float2 texCoord3 : TEXCOORD3;
    float2 texCoord4 : TEXCOORD4;
    float2 texCoord5 : TEXCOORD5;
    float2 texCoord6 : TEXCOORD6;
    float2 texCoord7 : TEXCOORD7;
    float2 texCoord8 : TEXCOORD8;
    float2 texCoord9 : TEXCOORD9;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType VerticalBlurVertexShader(VertexInputType input)
{
    PixelInputType output;
    float texelSize;


    // 행렬 연산을 위해 위치 벡터의 원소 수가 4개가 되도록 합니다.
    input.position.w = 1.0f;

    // 정점의 위치를 각각 월드, 뷰, 투영 행렬에서 계산합니다.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // 픽셀 셰이더에서 사용할 텍스쳐 좌표를 저장합니다.
    output.tex = input.tex;

텍셀 크기는 높이를 이용하여 계산합니다.

    // 화면 높이에 대한 텍셀의 부동소수점 표현 크기를 구합니다.
    texelSize = 1.0f / screenHeight;

오프셋은 높이값으로만 조절되며 너비는 현재 값으로 유지됩니다.

    // 현재 픽셀과 그 위아래 4개 픽셀의 UV좌표를 계산합니다.
    output.texCoord1 = input.tex + float2(0.0f, texelSize * -4.0f);
    output.texCoord2 = input.tex + float2(0.0f, texelSize * -3.0f);
    output.texCoord3 = input.tex + float2(0.0f, texelSize * -2.0f);
    output.texCoord4 = input.tex + float2(0.0f, texelSize * -1.0f);
    output.texCoord5 = input.tex + float2(0.0f, texelSize *  0.0f);
    output.texCoord6 = input.tex + float2(0.0f, texelSize *  1.0f);
    output.texCoord7 = input.tex + float2(0.0f, texelSize *  2.0f);
    output.texCoord8 = input.tex + float2(0.0f, texelSize *  3.0f);
    output.texCoord9 = input.tex + float2(0.0f, texelSize *  4.0f);

    return output;
}

Verticalblur.ps

////////////////////////////////////////////////////////////////////////////////
// Filename: verticalblur.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
Texture2D shaderTexture;
SamplerState SampleType;


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float2 texCoord1 : TEXCOORD1;
    float2 texCoord2 : TEXCOORD2;
    float2 texCoord3 : TEXCOORD3;
    float2 texCoord4 : TEXCOORD4;
    float2 texCoord5 : TEXCOORD5;
    float2 texCoord6 : TEXCOORD6;
    float2 texCoord7 : TEXCOORD7;
    float2 texCoord8 : TEXCOORD8;
    float2 texCoord9 : TEXCOORD9;
};


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 VerticalBlurPixelShader(PixelInputType input) : SV_TARGET
{
    float weight0, weight1, weight2, weight3, weight4;
    float normalization;
    float4 color;

수평 블러 셰이더에서 사용했던 것과 같은 가중치 시스템과 값들을 사용합니다.

    // 각 픽셀에 기여도에 해당하는 가중치를 정합니다.
    weight0 = 1.0f;
    weight1 = 0.9f;
    weight2 = 0.55f;
    weight3 = 0.18f;
    weight4 = 0.1f;

    // 가중치들을 살짝 평균내어 정규화 값을 만듭니다.
    normalization = (weight0 + 2.0f * (weight1 + weight2 + weight3 + weight4));

    // 가중치들을 정규화합니다.
    weight0 = weight0 / normalization;
    weight1 = weight1 / normalization;
    weight2 = weight2 / normalization;
    weight3 = weight3 / normalization;
    weight4 = weight4 / normalization;

    // 색깔을 검정색으로 초기화합니다.
    color = float4(0.0f, 0.0f, 0.0f, 0.0f);

    // 수직선상의 아홉 픽셀값들을 가중치를 곱해 더합니다.
    color += shaderTexture.Sample(SampleType, input.texCoord1) * weight4;
    color += shaderTexture.Sample(SampleType, input.texCoord2) * weight3;
    color += shaderTexture.Sample(SampleType, input.texCoord3) * weight2;
    color += shaderTexture.Sample(SampleType, input.texCoord4) * weight1;
    color += shaderTexture.Sample(SampleType, input.texCoord5) * weight0;
    color += shaderTexture.Sample(SampleType, input.texCoord6) * weight1;
    color += shaderTexture.Sample(SampleType, input.texCoord7) * weight2;
    color += shaderTexture.Sample(SampleType, input.texCoord8) * weight3;
    color += shaderTexture.Sample(SampleType, input.texCoord9) * weight4;

    // 알파 채널을 1로 설정합니다.
    color.a = 1.0f;

    return color;
}

Verticalblurshaderclass.h

VerticalBlurShaderClass는 높이 대신 너비를 사용하는 HorizontalBlurShaderClass와 동일합니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: verticalblurshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _VERTICALBLURSHADERCLASS_H_
#define _VERTICALBLURSHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: VerticalBlurShaderClass
////////////////////////////////////////////////////////////////////////////////
class VerticalBlurShaderClass
{
private:
    struct MatrixBufferType
    {
        D3DXMATRIX world;
        D3DXMATRIX view;
        D3DXMATRIX projection;
    };

ScreenSizeBufferType은 HLSL 수직 블러 셰이더에서 텍셀 사이즈를 계산하는데, 이 때 너비 대신 높이 정보를 사용합니다.

    struct ScreenSizeBufferType
    {
        float screenHeight;
        D3DXVECTOR3 padding;
    };

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

    bool Initialize(ID3D11Device*, HWND);
    void Shutdown();
    bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float);

private:
    bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
    void ShutdownShader();
    void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

    bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, float);
    void RenderShader(ID3D11DeviceContext*, int);

private:
    ID3D11VertexShader* m_vertexShader;
    ID3D11PixelShader* m_pixelShader;
    ID3D11InputLayout* m_layout;
    ID3D11SamplerState* m_sampleState;
    ID3D11Buffer* m_matrixBuffer;

horizontalBlurShaderClass에서처럼 화면 크기 상수 버퍼를 만들어 사용합니다.

    ID3D11Buffer* m_screenSizeBuffer;
};

#endif

Verticalblurshaderclass.cpp

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


VerticalBlurShaderClass::VerticalBlurShaderClass()
{
    m_vertexShader = 0;
    m_pixelShader = 0;
    m_layout = 0;
    m_sampleState = 0;
    m_matrixBuffer = 0;

화면 크기 상수 버퍼를 생성자에서 null로 초기화합니다.

    m_screenSizeBuffer = 0;
}


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


VerticalBlurShaderClass::~VerticalBlurShaderClass()
{
}


bool VerticalBlurShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
    bool result;

수직 블러 HLSL 프로그램 파일을 불러옵니다.

    // 정점 및 픽셀 셰이더를 초기화합니다.
    result = InitializeShader(device, hwnd, L"../Engine/verticalblur.vs", L"../Engine/verticalblur.ps");
    if(!result)
    {
        return false;
    }

    return true;
}


void VerticalBlurShaderClass::Shutdown()
{
    // 정점/픽셀 셰이더 및 연관된 객체들을 정리합니다.
    ShutdownShader();

    return;
}

Render 함수는 화면 또는 렌더 텍스쳐의 높이를 인자로 받아 수직 블러 셰이더에 전달하여 수직 텍셀 크기를 정할 수 있게 합니다.

bool VerticalBlurShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                     D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float screenHeight)
{
    bool result;


    // 렌더링에 사용할 셰이더 파라미터를 설정합니다.
    result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, screenHeight);
    if(!result)
    {
        return false;
    }

    // 준비된 버퍼를 셰이더로 그립니다.
    RenderShader(deviceContext, indexCount);

    return true;
}


bool VerticalBlurShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
    HRESULT result;
    ID3D10Blob* errorMessage;
    ID3D10Blob* vertexShaderBuffer;
    ID3D10Blob* pixelShaderBuffer;
    D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
    unsigned int numElements;
    D3D11_SAMPLER_DESC samplerDesc;
    D3D11_BUFFER_DESC matrixBufferDesc;
    D3D11_BUFFER_DESC screenSizeBufferDesc;


    // 이 함수에서 사용할 포인터를 null로 초기화합니다.
    errorMessage = 0;
    vertexShaderBuffer = 0;
    pixelShaderBuffer = 0;

수직 블러 정점 셰이더를 불러옵니다.

    // 정점 셰이더 코드를 컴파일합니다.
    result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "VerticalBlurVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
                       &vertexShaderBuffer, &errorMessage, NULL);
    if(FAILED(result))
    {
        // If the shader failed to compile it should have writen something to the error message.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
        }
        // If there was nothing in the error message then it simply could not find the shader file itself.
        else
        {
            MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
        }

        return false;
    }

수직 블러 픽셀 셰이더를 불러옵니다.

    // 픽셀 셰이더 코드를 컴파일합니다.
    result = D3DX11CompileFromFile(psFilename, NULL, NULL, "VerticalBlurPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL, 
                       &pixelShaderBuffer, &errorMessage, NULL);
    if(FAILED(result))
    {
        // If the shader failed to compile it should have writen something to the error message.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
        }
        // If there was  nothing in the error message then it simply could not find the file itself.
        else
        {
            MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
        }

        return false;
    }

    // 버퍼에서 정점 셰이더를 생성합니다.
    result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
    if(FAILED(result))
    {
        return false;
    }

    // 버퍼에서 픽셀 셰이더를 생성합니다.
    result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
    if(FAILED(result))
    {
        return false;
    }

    // 정접 입력 레이아웃 디스크립션입니다.
    // ModelClass::VertexType 구조체 및 셰이더의 구조체와 일치해야 합니다.
    polygonLayout[0].SemanticName = "POSITION";
    polygonLayout[0].SemanticIndex = 0;
    polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    polygonLayout[0].InputSlot = 0;
    polygonLayout[0].AlignedByteOffset = 0;
    polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[0].InstanceDataStepRate = 0;

    polygonLayout[1].SemanticName = "TEXCOORD";
    polygonLayout[1].SemanticIndex = 0;
    polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
    polygonLayout[1].InputSlot = 0;
    polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
    polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[1].InstanceDataStepRate = 0;

    // 레이아웃의 원소 수를 구합니다.
    numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

    // 정접 입력 레이아웃을 생성합니다.
    result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), 
                       &m_layout);
    if(FAILED(result))
    {
        return false;
    }

    // 더 이상 사용하지 않는 정점 및 픽셀 셰이더 버퍼를 정리합니다.
    vertexShaderBuffer->Release();
    vertexShaderBuffer = 0;

    pixelShaderBuffer->Release();
    pixelShaderBuffer = 0;

    // 텍스쳐 샘플러 상태 디스크립션입니다.
    samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    samplerDesc.MipLODBias = 0.0f;
    samplerDesc.MaxAnisotropy = 1;
    samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
    samplerDesc.BorderColor[0] = 0;
    samplerDesc.BorderColor[1] = 0;
    samplerDesc.BorderColor[2] = 0;
    samplerDesc.BorderColor[3] = 0;
    samplerDesc.MinLOD = 0;
    samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

    // 텍스쳐 샘플러 상태 객체를 생성합니다.
    result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
    if(FAILED(result))
    {
        return false;
    }

    // 정점 셰이더에 있는 동적 행렬 상수 버퍼의 디스크립션입니다.
    matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
    matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    matrixBufferDesc.MiscFlags = 0;
    matrixBufferDesc.StructureByteStride = 0;

    // 이 클래스에서 정점 셰이더 상수 버퍼에 접근할 수 있도록 상수 버퍼 포인터를 생성합니다.
    result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
    if(FAILED(result))
    {
        return false;
    }

여기에서 수직 블러 셰이더가 화면 전체 높이값에 접근할 수 있도록 버퍼를 설정합니다.

    // 정점 셰이더에 있는 동적 화면 크기 상수 버퍼의 디스크립션입니다.
    screenSizeBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    screenSizeBufferDesc.ByteWidth = sizeof(ScreenSizeBufferType);
    screenSizeBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    screenSizeBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    screenSizeBufferDesc.MiscFlags = 0;
    screenSizeBufferDesc.StructureByteStride = 0;

    // 이 클래스에서 정점 셰이더 상수 버퍼에 접근할 수 있도록 상수 버퍼 포인터를 생성합니다.
    result = device->CreateBuffer(&screenSizeBufferDesc, NULL, &m_screenSizeBuffer);
    if(FAILED(result))
    {
        return false;
    }

    return true;
}


void VerticalBlurShaderClass::ShutdownShader()
{

ShutdownShader 함수에서 화면 크기 상수 버퍼를 정리합니다.

    // 화면 크기 상수 버퍼를 해제합니다.
    if(m_screenSizeBuffer)
    {
        m_screenSizeBuffer->Release();
        m_screenSizeBuffer = 0;
    }

    // 행렬 상수 버퍼를 해제합니다.
    if(m_matrixBuffer)
    {
        m_matrixBuffer->Release();
        m_matrixBuffer = 0;
    }

    // 샘플러 상태 객체를 해제합니다.
    if(m_sampleState)
    {
        m_sampleState->Release();
        m_sampleState = 0;
    }

    // 레이아웃 객체를 해제합니다.
    if(m_layout)
    {
        m_layout->Release();
        m_layout = 0;
    }

    // 픽셀 셰이더를 해제합니다.
    if(m_pixelShader)
    {
        m_pixelShader->Release();
        m_pixelShader = 0;
    }

    // 정점 셰이더를 해제합니다.
    if(m_vertexShader)
    {
        m_vertexShader->Release();
        m_vertexShader = 0;
    }

    return;
}


void VerticalBlurShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
    char* compileErrors;
    unsigned long bufferSize, i;
    ofstream fout;


    // Get a pointer to the error message text buffer.
    compileErrors = (char*)(errorMessage->GetBufferPointer());

    // Get the length of the message.
    bufferSize = errorMessage->GetBufferSize();

    // Open a file to write the error message to.
    fout.open("shader-error.txt");

    // Write out the error message.
    for(i=0; i<bufferSize; i++)
    {
        fout << compileErrors[i];
    }

    // Close the file.
    fout.close();

    // Release the error message.
    errorMessage->Release();
    errorMessage = 0;

    // Pop a message up on the screen to notify the user to check the text file for compile errors.
    MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

    return;
}

SetShaderParameters 함수를 이용하여 HLSL 셰이더에 화면 높이를 비롯한 변수들을 설정합니다.

bool VerticalBlurShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix, 
                          D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, float screenHeight)
{
    HRESULT result;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    MatrixBufferType* dataPtr;
    unsigned int bufferNumber;
    ScreenSizeBufferType* dataPtr2;


    // 셰이더에서 사용할 행렬을 전치(transpose)하여 준비합니다.
    D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
    D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
    D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

    // 행렬 상수 버퍼에 쓰기 위하여 락을 겁니다.
    result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // 상수 버퍼의 포인터를 얻어옵니다.
    dataPtr = (MatrixBufferType*)mappedResource.pData;

    // 상수 버퍼에 행렬을 복사해 넣습니다.
    dataPtr->world = worldMatrix;
    dataPtr->view = viewMatrix;
    dataPtr->projection = projectionMatrix;

    // 상수 버퍼의 락을 해제합니다.
    deviceContext->Unmap(m_matrixBuffer, 0);

    // 정점 셰이더에서 상수 버퍼의 위치입니다.
    bufferNumber = 0;

    // 정점 셰이더의 상수 버퍼를 갱신된 값으로 설정합니다.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

여기에 화면의 높이를 상수 버퍼에 설정합니다.

    // 화면 크기 상수 버퍼에 쓰기 위하여 락을 겁니다.
    result = deviceContext->Map(m_screenSizeBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // 상수 버퍼의 포인터를 얻어옵니다.
    dataPtr2 = (ScreenSizeBufferType*)mappedResource.pData;

    // 상수 버퍼에 값을 복사합니다.
    dataPtr2->screenHeight = screenHeight;
    dataPtr2->padding = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

    // 상수 버퍼의 락을 해제합니다.
    deviceContext->Unmap(m_screenSizeBuffer, 0);

    // 정점 셰이더에서 상수 버퍼의 위치입니다.
    bufferNumber = 1;

    // 정점 셰이더의 상수 버퍼를 갱신된 값으로 설정합니다.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_screenSizeBuffer);

    // 픽셀 셰이더에 텍스쳐 리소스를 설정합니다.
    deviceContext->PSSetShaderResources(0, 1, &texture);

    return true;
}


void VerticalBlurShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
    // Set the vertex input layout.
    deviceContext->IASetInputLayout(m_layout);

    // Set the vertex and pixel shaders that will be used to render this triangle.
    deviceContext->VSSetShader(m_vertexShader, NULL, 0);
    deviceContext->PSSetShader(m_pixelShader, NULL, 0);

    // Set the sampler state in the pixel shader.
    deviceContext->PSSetSamplers(0, 1, &m_sampleState);

    // Render the triangle.
    deviceContext->DrawIndexed(indexCount, 0, 0);

    return;
}

Orthowindowclass.h

OrthoWindowClass는 렌더 텍스쳐 또는 2D 그래픽을 그리는 2D 렌더링을 하기 위하여 두 개의 삼각형으로 만든 평평한 사각형 모델입니다. 여기서는 3D공간의 사각형을 2차원으로(2D 화면) 투영시키기 때문에 미리 고정된 직교 좌표를 사용합니다. 언제 초기화되느냐에 따라 전체 화면의 윈도우 또는 작은 윈도우를 구성하는 데 사용됩니다. 거의 대부분의 코드가 평소 사용하던 ModelClass와 동일합니다.

////////////////////////////////////////////////////////////////////////////////
// Filename: orthowindowclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _ORTHOWINDOWCLASS_H_
#define _ORTHOWINDOWCLASS_H_


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


////////////////////////////////////////////////////////////////////////////////
// Class name: OrthoWindowClass
////////////////////////////////////////////////////////////////////////////////
class OrthoWindowClass
{
private:

VertexType 구조체는 위치와 텍스쳐 좌표만 있으면 됩니다. 노말 벡터는 필요하지 않습니다.

    struct VertexType
    {
        D3DXVECTOR3 position;
        D3DXVECTOR2 texture;
    };

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

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

    int GetIndexCount();

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

private:

OrthoWindowClass는 일반적인 3D 모델과 동일하게 정점/인덱스 버퍼를 사용합니다.

    ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
    int m_vertexCount, m_indexCount;
};

#endif

Orthowindowclass.cpp

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

정점 및 인덱스 버퍼의 포인터를 생성자에서 null로 초기화합니다.

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


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


OrthoWindowClass::~OrthoWindowClass()
{
}

Initialize 함수는 높이와 너비를 인자로 받아 그 크기대로 2D 윈도우를 만들고 그 인자로 InitializeBuffers 함수도 호출합니다.

bool OrthoWindowClass::Initialize(ID3D11Device* device, int windowWidth, int windowHeight)
{
    bool result;


    // 직교 윈도우 모델의 공간 구성을 저장하기 위한 정점 및 인덱스 버퍼를 초기화합니다.
    result = InitializeBuffers(device, windowWidth, windowHeight);
    if(!result)
    {
        return false;
    }

    return true;
}

Shutdown 함수는 ShutdownBuffers 함수를 호출하여 사용이 끝난 정점 및 인덱스 버퍼를 정리합니다.

void OrthoWindowClass::Shutdown()
{
    // 정점 및 인덱스 버퍼를 정리합니다.
    ShutdownBuffers();

    return;
}

Render 함수는 RenderBuffers 함수를 호출하여 2D 윈도우를 화면에 그립니다.

void OrthoWindowClass::Render(ID3D11DeviceContext* deviceContext)
{
    // 정점 및 인덱스 버퍼를 그래픽스 파이프라인에 넣어 그릴 수 있도록 준비합니다.
    RenderBuffers(deviceContext);

    return;
}

GetIndexCount 함수는 2D 윈도우 모델을 그리기 위한 인덱스 개수를 전달합니다.

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

InitializeBuffers 함수에서는 너비와 높이 인자를 사용하여 2D 윈도우에 쓸 정점 및 인덱스 버퍼를 설정합니다.

bool OrthoWindowClass::InitializeBuffers(ID3D11Device* device, int windowWidth, int windowHeight)
{
    float left, right, top, bottom;
    VertexType* vertices;
    unsigned long* indices;
    D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
    D3D11_SUBRESOURCE_DATA vertexData, indexData;
    HRESULT result;
    int i;

모든 2D 렌더링에서는 2D 윈도우의 왼쪽, 오른쪽, 위, 아래 좌표가 필요하기 때문에 화면 크기를 이용하여 2D 윈도우의 좌표를 계산합니다. 참고로 화면의 정중앙의 좌표는 (0, 0)입니다.

    // 윈도우의 왼쪽 화면 좌표를 계산합니다.
    left = (float)((windowWidth / 2) * -1);

    // 윈도우의 오른쪽 화면 좌표를 계산합니다.
    right = left + (float)windowWidth;

    // 윈도우의 위쪽 화면 좌표를 계산합니다.
    top = (float)(windowHeight / 2);

    // 윈도우의 아래쪽 화면 좌표를 계산합니다.
    bottom = top - (float)windowHeight;

그 다음으로 정점과 인덱스 수를 설정합니다. 2D 윈도우는 두 개의 삼각형으로 이루어져 있으므로 6개의 정점과 6개의 인덱슬르 가집니다.

    // 정점 배열에 있는 정점 수입니다.
    m_vertexCount = 6;

    // 인덱스 배열에 있는 인덱스 수입니다.
    m_indexCount = m_vertexCount;

2D 윈도우 모델 데이터를 위한 임시 정점, 인덱스 배열을 만듭니다.

    // 정점 배열을 생성합니다.
    vertices = new VertexType[m_vertexCount];
    if(!vertices)
    {
        return false;
    }

    // 인덱스 배열을 생성합니다.
    indices = new unsigned long[m_indexCount];
    if(!indices)
    {
        return false;
    }

2D 윈도우의 정점과 인덱스들을 각 배열에 저장합니다.

    // 정점 배열에 데이터를 넣습니다.
    // 첫 번째 삼각형
    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);

    // 두 번째 삼각형
    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);

    // 인덱스 배열에 데이터를 넣습니다.
    for(i=0; i<m_indexCount; i++)
    {
        indices[i] = i;
    }

방금 만든 정점과 인덱스 배열을 이용하여 각 버퍼를 생성합니다. 크기가 바뀌지 않을 것이기 때문에 dynamic으로 만들지 않습니다.

    // 정점 버퍼의 디스크립션입니다.
    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;
    vertexBufferDesc.StructureByteStride = 0;

    // 정점 데이터에 서브리소스 정보를 제공합니다.
    vertexData.pSysMem = vertices;
    vertexData.SysMemPitch = 0;
    vertexData.SysMemSlicePitch = 0;

    // 정점 버퍼를 생성합니다.
    result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // 인덱스 버퍼의 디스크립션입니다.
    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;

    // 인덱스 데이터에 서브리소스 정보를 제공합니다.
    indexData.pSysMem = indices;
    indexData.SysMemPitch = 0;
    indexData.SysMemSlicePitch = 0;

    // 인덱스 버퍼를 생성합니다.
    result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
    if(FAILED(result))
    {
        return false;
    }

버퍼들이 생성되었으므로 정점과 인덱스 배열을 정리합니다.

    // 정점 및 인덱스 버퍼가 생성되고 데이터가 들어갔으므로 배열들을 해제합니다.
    delete [] vertices;
    vertices = 0;

    delete [] indices;
    indices = 0;

    return true;
}

ShutdownBuffers 함수는 사용이 끝난 정점 및 인덱스 버퍼를 해제하는 역할을 합니다.

void OrthoWindowClass::ShutdownBuffers()
{
    // 인덱스 버퍼를 해제합니다.
    if(m_indexBuffer)
    {
        m_indexBuffer->Release();
        m_indexBuffer = 0;
    }

    // 정점 버퍼를 해제합니다.
    if(m_vertexBuffer)
    {
        m_vertexBuffer->Release();
        m_vertexBuffer = 0;
    }

    return;
}

RenderBuffers에서는 OrthoWindowClass의 정점과 인덱스를 셰이더가 그려낼 데이터로 설정합니다.

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


    // 버퍼의 스트라이드(stride)와 오프셋을 설정합니다.
    stride = sizeof(VertexType); 
    offset = 0;

    // 정점 버퍼를 입력 어셈블러에 넣어 렌더링되도록 합니다.
    deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

    // 인덱스 버퍼를 입력 어셈블러에 넣어 렌더링되도록 합니다.
    deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

    // 이 정점 버퍼로 그릴 도형의 타입을 설정합니다. 여기서는 삼각형입니다.
    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    return;
}

Graphicsclass.h

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


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "textureshaderclass.h"

두 개의 블러 셰이더와 렌더 텍스쳐, OrthoWindowClass의 헤더를 GraphisClass 헤더파일에 인클루드합니다.

#include "horizontalblurshaderclass.h"
#include "verticalblurshaderclass.h"
#include "rendertextureclass.h"
#include "orthowindowclass.h"


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


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

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

private:
    bool Render(float);

GraphicsClass의 아주 일부분이 고쳐졌다고 생각하실지도 모르겠습니다. 그것은 이 튜토리얼을 쉽게 이해하실 수 있도록 알고리즘 수행 단계를 분리하여 보고 싶었기 때문입니다. 여러분의 프로젝트에서는 이 단계를 더 최적화할 수 있을 겁니다.

    bool RenderSceneToTexture(float);
    bool DownSampleTexture();
    bool RenderHorizontalBlurToTexture();
    bool RenderVerticalBlurToTexture();
    bool UpSampleTexture();
    bool Render2DTextureScene();

private:
    D3DClass* m_D3D;
    CameraClass* m_Camera;
    ModelClass* m_Model;
    TextureShaderClass* m_TextureShader;

private 클래스 멤버로 두 셰이더를 선언합니다.

    HorizontalBlurShaderClass* m_HorizontalBlurShader;
    VerticalBlurShaderClass* m_VerticalBlurShader;

많은 렌더 텍스쳐들이 선언되어 있습니다. 다시 말하지만 알고리즘의 각 단계별로 텍스쳐에 쓰여지게 하도록 하기 위함입니다.

    RenderTextureClass *m_RenderTexture, *m_DownSampleTexure, *m_HorizontalBlurTexture, *m_VerticalBlurTexture, *m_UpSampleTexure;

이 예제에서는 두 개의 2D 윈도우가 있습니다. m_FullScreenWindow는 전체 화면을 그리기 위한 것입니다. m_SmallWindow는 더 작은 윈도우/텍스쳐로 다운샘플링하기 위한 것입니다.

    OrthoWindowClass *m_SmallWindow, *m_FullScreenWindow;
};

#endif

Graphicsclass.cpp

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


GraphicsClass::GraphicsClass()
{
    m_D3D = 0;
    m_Camera = 0;
    m_Model = 0;
    m_TextureShader = 0;

생성자에서 셰이더와 렌더 텍스쳐, 윈도우들을 null로 초기화합니다.

    m_HorizontalBlurShader = 0;
    m_VerticalBlurShader = 0;
    m_RenderTexture = 0;
    m_DownSampleTexure = 0;
    m_HorizontalBlurTexture = 0;
    m_VerticalBlurTexture = 0;
    m_UpSampleTexure = 0;
    m_SmallWindow = 0;
    m_FullScreenWindow = 0;
}


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


GraphicsClass::~GraphicsClass()
{
}


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

초기화를 시작하기 전에 먼저 다운 샘플 사이즈를 정합니다. 다운 샘플링에 사용되는 렌더 텍스쳐와 윈도우, 블러링은 이 값을들 입력으로 받게 될 것입니다. 여기서는 화면 크기의 절반으로 했지만 여러분이 값을 바꾸면 또 다른 결과를 얻을 수 있습니다. 또한 이 값들은 블러 처리에 들어가는 시간에도 크게 영향을 미칩니다.

    // 다운 샘플링할 크기를 정합니다.
    downSampleWidth = screenWidth / 2;
    downSampleHeight = screenHeight / 2;

    // Direct3D 객체를 생성합니다.
    m_D3D = new D3DClass;
    if(!m_D3D)
    {
        return false;
    }

    // Direct3D 객체를 초기화합니다.
    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;
    }

    // 카메라 객체를 생성합니다.
    m_Camera = new CameraClass;
    if(!m_Camera)
    {
        return false;
    }

    // 카메라의 초기 위치입니다.
    m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

    // 모델 객체를 생성합니다.
    m_Model = new ModelClass;
    if(!m_Model)
    {
        return false;
    }

    // 모델 객체를 초기화합니다.
    result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds");
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the model 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;
    }

수평 블러 셰이더를 생성 및 초기화합니다.

    // 수평 블러 셰이더 객체를 생성합니다.
    m_HorizontalBlurShader = new HorizontalBlurShaderClass;
    if(!m_HorizontalBlurShader)
    {
        return false;
    }

    // 수평 블러 셰이더 객체를 초기화합니다.
    result = m_HorizontalBlurShader->Initialize(m_D3D->GetDevice(), hwnd);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the horizontal blur shader object.", L"Error", MB_OK);
        return false;
    }

수직 블러 셰이더를 생성 및 초기화합니다.

    // 수직 블러 셰이더 객체를 생성합니다.
    m_VerticalBlurShader = new VerticalBlurShaderClass;
    if(!m_VerticalBlurShader)
    {
        return false;
    }

    // 수직 블러 셰이더 객체를 초기화합니다.
    result = m_VerticalBlurShader->Initialize(m_D3D->GetDevice(), hwnd);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the vertical blur shader object.", L"Error", MB_OK);
        return false;
    }

전체 화면 렌더 텍스쳐를 생성 및 초기화합니다. 전체 화면 크기를 입력으로 사용합니다.

    // 렌더 텍스쳐 객체를 생성합니다.
    m_RenderTexture = new RenderTextureClass;
    if(!m_RenderTexture)
    {
        return false;
    }

    // 렌더 텍스쳐 객체를 초기화합니다.
    result = m_RenderTexture->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the render to texture object.", L"Error", MB_OK);
        return false;
    }

다운 샘플에 사용할 렌더 텍스쳐를 생성 및 초기화합니다. 다운샘플링 크기를 입력으로 사용합니다.

    // 다운 샘플 렌더 텍스쳐 객체를 생성합니다.
    m_DownSampleTexure = new RenderTextureClass;
    if(!m_DownSampleTexure)
    {
        return false;
    }

    // 다운 샘플 렌더 텍스쳐 객체를 초기화합니다.
    result = m_DownSampleTexure->Initialize(m_D3D->GetDevice(), downSampleWidth, downSampleHeight, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the down sample render to texture object.", L"Error", MB_OK);
        return false;
    }

수평 블러가 그려질 렌더 텍스쳐를 생성 및 초기화합니다. 다운 샘플 크기를 입력으로 사용합니다.

    // 수평 블러 렌더 텍스쳐를 생성합니다.
    m_HorizontalBlurTexture = new RenderTextureClass;
    if(!m_HorizontalBlurTexture)
    {
        return false;
    }

    // 수평 블러 텍스쳐 객첼르 초기화합니다.
    result = m_HorizontalBlurTexture->Initialize(m_D3D->GetDevice(), downSampleWidth, downSampleHeight, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the horizontal blur render to texture object.", L"Error", MB_OK);
        return false;
    }

수직 블러가 그려질 렌더 텍스쳐를 생성 및 초기화합니다. 다운 샘플 크기를 입력으로 사용합니다.

    // 수직 블러 렌더 텍스쳐를 생성합니다.
    m_VerticalBlurTexture = new RenderTextureClass;
    if(!m_VerticalBlurTexture)
    {
        return false;
    }

    // 수직 블러 렌더 텍스쳐 객체를 초기화합니다.
    result = m_VerticalBlurTexture->Initialize(m_D3D->GetDevice(), downSampleWidth, downSampleHeight, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the vertical blur render to texture object.", L"Error", MB_OK);
        return false;
    }

업샘플링에 사용할 렌더 텍스쳐 객체를 생성 및 초기화합니다. 전체 화면 크기를 입력으로 사용합니다.

    // 업샘플 렌더 텍스쳐를 생성합니다.
    m_UpSampleTexure = new RenderTextureClass;
    if(!m_UpSampleTexure)
    {
        return false;
    }

    // 업샘플 렌더 텍스쳐 객체를 초기화합니다.
    result = m_UpSampleTexure->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the up sample render to texture object.", L"Error", MB_OK);
        return false;
    }

다운 샘플링에 사용할 작은 2D 윈도우를 생성 및 초기화합니다. 그리고 전체 화면 렌더 텍스쳐를 그곳에 그립니다. 다운 샘플 크기를 입력으로 사용합니다.

    // 작은 직사각형 윈도우를 생성합니다.
    m_SmallWindow = new OrthoWindowClass;
    if(!m_SmallWindow)
    {
        return false;
    }

    // 작은 직사각형 윈도우를 초기화합니다.
    result = m_SmallWindow->Initialize(m_D3D->GetDevice(), downSampleWidth, downSampleHeight);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the small ortho window object.", L"Error", MB_OK);
        return false;
    }

렌더 텍스쳐를 전체 화면에 그릴 전체 화면 2D 윈도우 모델을 생성 및 초기화합니다. 전체 화면 크기를 입력으로 사용합니다.

    // 전체 화면 직사각형 윈도우를 생성합니다.
    m_FullScreenWindow = new OrthoWindowClass;
    if(!m_FullScreenWindow)
    {
        return false;
    }

    // 전체 화면 직사각형 윈도우 객체를 초기화합니다.
    result = m_FullScreenWindow->Initialize(m_D3D->GetDevice(), screenWidth, screenHeight);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the full screen ortho window object.", L"Error", MB_OK);
        return false;
    }

    return true;
}


void GraphicsClass::Shutdown()
{

어플리케이션이 종료되는 시점에서 Shutdown함수에서는 Initialize 함수에서 만들었던 모든 셰이더, 렌더 텍스쳐, 직사각형 윈도우 객체들을 정리하는 역할을 합니다.

    // 전체 화면 직사각형 윈도우를 해제합니다.
    if(m_FullScreenWindow)
    {
        m_FullScreenWindow->Shutdown();
        delete m_FullScreenWindow;
        m_FullScreenWindow = 0;
    }

    // 작은 직사각형 윈도우를 해제합니다.
    if(m_SmallWindow)
    {
        m_SmallWindow->Shutdown();
        delete m_SmallWindow;
        m_SmallWindow = 0;
    }
    
    // 업샘플 렌더 텍스쳐를 해제합니다.
    if(m_UpSampleTexure)
    {
        m_UpSampleTexure->Shutdown();
        delete m_UpSampleTexure;
        m_UpSampleTexure = 0;
    }

    // 수직 블러 렌더 텍스쳐를 해제합니다.
    if(m_VerticalBlurTexture)
    {
        m_VerticalBlurTexture->Shutdown();
        delete m_VerticalBlurTexture;
        m_VerticalBlurTexture = 0;
    }

    // 수평 블러 렌더 텍스쳐를 해제합니다.
    if(m_HorizontalBlurTexture)
    {
        m_HorizontalBlurTexture->Shutdown();
        delete m_HorizontalBlurTexture;
        m_HorizontalBlurTexture = 0;
    }

    // 다운샘플 렌더 텍스쳐를 해제합니다.
    if(m_DownSampleTexure)
    {
        m_DownSampleTexure->Shutdown();
        delete m_DownSampleTexure;
        m_DownSampleTexure = 0;
    }
    
    // 렌더 텍스쳐 객체를 해제합니다.
    if(m_RenderTexture)
    {
        m_RenderTexture->Shutdown();
        delete m_RenderTexture;
        m_RenderTexture = 0;
    }
    
    // 수직 블러 셰이더를 해제합니다.
    if(m_VerticalBlurShader)
    {
        m_VerticalBlurShader->Shutdown();
        delete m_VerticalBlurShader;
        m_VerticalBlurShader = 0;
    }

    // 수평 블러 셰이더를 해제합니다.
    if(m_HorizontalBlurShader)
    {
        m_HorizontalBlurShader->Shutdown();
        delete m_HorizontalBlurShader;
        m_HorizontalBlurShader = 0;
    }

    // 텍스쳐 셰이더를 해제합니다.
    if(m_TextureShader)
    {
        m_TextureShader->Shutdown();
        delete m_TextureShader;
        m_TextureShader = 0;
    }

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

    // 카메라를 해제합니다.
    if(m_Camera)
    {
        delete m_Camera;
        m_Camera = 0;
    }

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

    return;
}


bool GraphicsClass::Frame()
{
    bool result;
    static float rotation = 0.0f;


    // Update the rotation variable each frame.
    rotation += (float)D3DX_PI * 0.005f;
    if(rotation > 360.0f)
    {
        rotation -= 360.0f;
    }

    // Render the graphics scene.
    result = Render(rotation);
    if(!result)
    {
        return false;
    }

    return true;
}

Render 함수에서 이번 예제의 모든 작업들이 이루어집니다. 앞서 설명했던 알고리즘의 각 단계대로 화면을 그립니다. 각 단계는 함수로 분리되어 있고 각 함수마다 자신들의 렌더 텍스쳐를 가지고 있습니다. 각 단계마다 렌더 텍스쳐를 가짐으로 디버그에 도움이 되는 중간 결과들을 볼 수 있게 됩니다.

bool GraphicsClass::Render(float rotation)
{
    bool result;


    // 우선 장면을 렌더 텍스쳐에 그립니다.
    result = RenderSceneToTexture(rotation);
    if(!result)
    {
        return false;
    }

    // 렌더 텍스쳐를 더 작은 사이즈의 텍스쳐로 다운샘플링합니다.
    result = DownSampleTexture();
    if(!result)
    {
        return false;
    }

    // 다운 샘플 렌더 텍스쳐에 수평 블러를 수행합니다.
    result = RenderHorizontalBlurToTexture();
    if(!result)
    {
        return false;
    }

    // 수평 블러 렌더 텍스쳐에 수직 블러를 수행합니다.
    result = RenderVerticalBlurToTexture();
    if(!result)
    {
        return false;
    }

    // 블러링된 텍스쳐를 원래 크기로 업샘플링합니다.
    result = UpSampleTexture();
    if(!result)
    {
        return false;
    }

    // 업샘플링된 블러 텍스쳐를 화면에 그립니다.
    result = Render2DTextureScene();
    if(!result)
    {
        return false;
    }

    return true;
}

첫 번째 함수는 회전하는 육면체를 전체 화면 크기의 렌더 텍스쳐에 그리는 알고리즘의 첫 번째 단계를 수행합니다.

bool GraphicsClass::RenderSceneToTexture(float rotation)
{
    D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
    bool result;


    // 렌더 타겟을 설정합니다.
    m_RenderTexture->SetRenderTarget(m_D3D->GetDeviceContext());

    // 렌더 텍스쳐를 깨끗이 지웁니다.
    m_RenderTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f);

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

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

    // 월드 행렬을 회전값만큼 돌려 육면체가 계속 회전하도록 합니다.
    D3DXMatrixRotationY(&worldMatrix, rotation);

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

    // 텍스쳐 셰이더로 모델을 그립니다.
    result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix, 
                     m_Model->GetTexture());
    if(!result)
    {
        return false;
    }

    // 렌더 텍스쳐가 아닌 원래 백버퍼로 렌더 타겟을 돌립니다.
    m_D3D->SetBackBufferRenderTarget();

    // 뷰포트를 원래대로 돌립니다.
    m_D3D->ResetViewport();

    return true;
}

두 번째 함수는 Initialize 함수에서 화면에 절반 크기로 만들었던 작은 윈도우에 전체 화면 렌더 텍스쳐를 그림으로 다운 샘플링을 수행합니다. 또한 투영 행렬을 가져올 때 작은 렌더 텍스쳐에서 직교 행렬을 가져온다는 것에 주목하시기 바랍니다.

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


    // 렌더 타겟을 이 렌더 텍스쳐로 설정합니다.
    m_DownSampleTexure->SetRenderTarget(m_D3D->GetDeviceContext());

    // 렌더 텍스쳐를 깨끗이 지웁니다.
    m_DownSampleTexure->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 1.0f, 0.0f, 1.0f);

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

    // 카메라와 d3d 객체에서 월드 및 뷰 행렬을 가져옵니다.
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetWorldMatrix(worldMatrix);
    
    // 렌더 텍스쳐는 더 작은 크기이기 때문에 거기에서 직교 행렬을 가져옵니다.
    m_DownSampleTexure->GetOrthoMatrix(orthoMatrix);

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

    // 작은 직사각형 윈도우의 정점과 인덱스 버퍼를 그래픽스 파이프라인에 넣어 렌더링할 준비를 합니다.
    m_SmallWindow->Render(m_D3D->GetDeviceContext());

    // 작은 직사각형 윈도우를 텍스쳐 셰이더로 그리는데 화면의 렌더 텍스쳐를 리소스로 사용합니다.
    result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_SmallWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                     m_RenderTexture->GetShaderResourceView());
    if(!result)
    {
        return false;
    }

    // 2D 렌더링이 끝났으므로 Z버퍼를 다시 켭니다.
    m_D3D->TurnZBufferOn();
    
    // 렌더 타겟이 아닌 백버퍼를 원래 렌더 타겟으로 돌립니다.
    m_D3D->SetBackBufferRenderTarget();

    // 뷰포트를 원래대로 돌립니다.
    m_D3D->ResetViewport();

    return true;
}

세 번째 함수는 다운 샘플 텍스쳐에 수평 블러를 하고 그 결과를 수직 블러 셰이더의 입력으로 사용할 렌더 텍스쳐에 저장합니다.

bool GraphicsClass::RenderHorizontalBlurToTexture()
{
    D3DXMATRIX worldMatrix, viewMatrix, orthoMatrix;
    float screenSizeX;
    bool result;


    // 수평 블러 셰이더에서 사용할 화면의 너비값을 부동소수점 표현으로 저장합니다.
    screenSizeX = (float)m_HorizontalBlurTexture->GetTextureWidth();
    
    // 렌더 타겟을 설정합니다.
    m_HorizontalBlurTexture->SetRenderTarget(m_D3D->GetDeviceContext());

    // 렌더 텍스쳐를 깨끗이 지웁니다.
    m_HorizontalBlurTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f);

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

    // 카메라와 d3d 객체에서 월드와 뷰 행렬을 가져옵니다.
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetWorldMatrix(worldMatrix);

    // 텍스쳐가 다른 크기를 가지고 있으므로 렌더 텍스쳐의 직교 행렬을 가져옵니다.
    m_HorizontalBlurTexture->GetOrthoMatrix(orthoMatrix);

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

    // 작은 직사각형 윈도우의 정점과 인덱스 버퍼를 그래픽스 파이프라인에 넣어 렌더링할 준비를 합니다.
    m_SmallWindow->Render(m_D3D->GetDeviceContext());
    
    // 작은 직사각형 윈도우를 수평 블러 셰이더로 그립니다. 다운 샘플 렌더 텍스쳐를 리소스로 사용합니다.
    result = m_HorizontalBlurShader->Render(m_D3D->GetDeviceContext(), m_SmallWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                        m_DownSampleTexure->GetShaderResourceView(), screenSizeX);
    if(!result)
    {
        return false;
    }

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

    // 렌더 텍스쳐가 아닌 백버퍼를 원래 렌더 타겟으로 돌립니다.
    m_D3D->SetBackBufferRenderTarget();

    // 뷰포트를 원래대로 돌립니다.
    m_D3D->ResetViewport();

    return true;
}

네 번째 함수는 수평 블러된 렌더 텍스쳐에 수직 블러를 수행합니다. 결과는 또 다른 렌더 텍스쳐에 저장됩니다.

bool GraphicsClass::RenderVerticalBlurToTexture()
{
    D3DXMATRIX worldMatrix, viewMatrix, orthoMatrix;
    float screenSizeY;
    bool result;


    // 수직 블러 셰이더에서 사용할 화면 높이값을 부동소수점 표현으로 저장합니다.
    screenSizeY = (float)m_VerticalBlurTexture->GetTextureHeight();

    // 렌더 타겟을 설정합니다.
    m_VerticalBlurTexture->SetRenderTarget(m_D3D->GetDeviceContext());

    // 렌더 텍스쳐를 깨끗이 지웁니다.
    m_VerticalBlurTexture->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f);

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

    // 카메라와 d3d 객체에서 월드와 뷰 행렬을 가져옵니다.
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetWorldMatrix(worldMatrix);

    // 텍스쳐가 다른 크기이기 때문에 렌더 텍스쳐에서 직교 행렬을 가져옵니다.
    m_VerticalBlurTexture->GetOrthoMatrix(orthoMatrix);

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

    // 작은 직사각형 윈도우의 정점과 인덱스 버퍼를 그래픽스 파이프라인에 넣어 렌더링할 준비를 합니다.
    m_SmallWindow->Render(m_D3D->GetDeviceContext());
    
    // 작은 직사각형 윈도우를 수직 블러 셰이더를 이용하여 그립니다. 수평 블러 렌더 텍스쳐를 리소스로 사용합니다.
    result = m_VerticalBlurShader->Render(m_D3D->GetDeviceContext(), m_SmallWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                          m_HorizontalBlurTexture->GetShaderResourceView(), screenSizeY);
    if(!result)
    {
        return false;
    }

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

    // 렌더 타겟을 렌더 텍스쳐가 아닌 원래 백 버퍼로 돌립니다.
    m_D3D->SetBackBufferRenderTarget();

    // 뷰포트를 원래대로 돌립니다.
    m_D3D->ResetViewport();

    return true;
}

다섯번째 함수는 작은 수직/수평 블러가 행해젠 텍스쳐를 업샘플링합니다. 업샘플링은 단지 작은 블러 텍스쳐를 전체 화면 크기의 2D 윈도우 모델에 그리는 것입니다. 결과물은 m_UpSampleTexture라는 렌더 텍스쳐에 그려집니다.

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


    // 렌더 타겟을 설정합니다.
    m_UpSampleTexure->SetRenderTarget(m_D3D->GetDeviceContext());

    // 렌더 텍스쳐를 깨끗이 지웁니다.
    m_UpSampleTexure->ClearRenderTarget(m_D3D->GetDeviceContext(), 0.0f, 0.0f, 0.0f, 1.0f);

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

    // 카메라와 d3d 객체에서 월드와 뷰 행렬을 가져옵니다.
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetWorldMatrix(worldMatrix);

    // 텍스쳐가 다른 크기를 가지고 있으므로 렌더 텍스쳐에서 직교 행렬을 구합니다.
    m_UpSampleTexure->GetOrthoMatrix(orthoMatrix);

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

    // 전체 화면 직사각형 윈도우의 정점과 인덱스 버퍼를 그래픽스 파이프라인에 넣어 렌더링할 준비를 합니다.
    m_FullScreenWindow->Render(m_D3D->GetDeviceContext());

    // 전체 화면 직사각형 윈도우를 텍스쳐 셰이더로 그립니다. 작은 크기의 블러 렌더 텍스쳐가 리소스로 사용됩니다.
    result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_FullScreenWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                     m_VerticalBlurTexture->GetShaderResourceView());
    if(!result)
    {
        return false;
    }

    // 2D 렌더링이 끝났으므로 Z버퍼를 다시 켭니다.
    m_D3D->TurnZBufferOn();
    
    // 렌더 타겟을 렌더 텍스쳐가 아닌 원래의 백버퍼로 돌립니다.
    m_D3D->SetBackBufferRenderTarget();

    // 뷰포트를 원래대로 돌립니다.
    m_D3D->ResetViewport();

    return true;
}

마지막 여섯 번째 함수는 업샘플링된 블러 텍스쳐를 전체 화면에 그려 블러 효과를 완성합니다.

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


    // 장면을 시작하기 위해 버퍼를 깨끗이 합니다.
    m_D3D->BeginScene(1.0f, 0.0f, 0.0f, 0.0f);

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

    // 카메라와 d3d 객체에서 월드, 뷰, 직교 행렬을 가져옵니다.
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetWorldMatrix(worldMatrix);
    m_D3D->GetOrthoMatrix(orthoMatrix);

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

    // 전체 화면 직사각형의 정점과 인덱스 버퍼를 그래픽스 파이프라인에 넣어 렌더링할 준비를 합니다.
    m_FullScreenWindow->Render(m_D3D->GetDeviceContext());

    // 전체 화면 직사각형을 텍스쳐 셰이더로 그립니다. 전체 화면 블러 텍스쳐가 리소스로 사용됩니다.
    result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_FullScreenWindow->GetIndexCount(), worldMatrix, viewMatrix, orthoMatrix, 
                     m_UpSampleTexure->GetShaderResourceView());
    if(!result)
    {
        return false;
    }

    // 2D 렌더링이 끝났으므로 Z버퍼를 다시 켭니다.
    m_D3D->TurnZBufferOn();
    
    // 렌더링된 장면을 화면에 표시합니다.
    m_D3D->EndScene();

    return true;
}

마치면서

이제 전체 화면 블러 효과를 할 수 있게 되었습니다. 이를 통해 블러링을 기본으로 하는 많은 복잡한 효과들로 통하는 문이 열린 것입니다.

연습 문제

  1. 프로그램을 다시 컴파일하여 실행해 보십시오. 회전하는 육면체 전체 화면에 뿌연 것을 볼 것입니다. ESC키로 종료합니다.

  2. 다운 샘플 크기를 조절해 보면서 블러의 정도와 어플리케이션의 스피드에 어떤 영향을 미치는지 확인해 보십시오. 다운샘플링을 아예 하지 않는 것도 시도해 보십시오.

  3. 수직 및 수평 블러 HLSL 파일에서 이웃의 숫자와 그 가중치들을 수정하여 블러에 어떤 영향을 미치는지 확인해 보십시오.

  4. 불필요한 과정들을 생략하여 예제를 최적화해 보십시오.

  5. 이 효과를 확장하여 풀스크린 글로우 효과를 만들어 보십시오(그냥 일반 화면에 블러 텍스쳐를 더하면 됩니다).

  6. 선형 샘플러를 이용한 것이 아닌 다른 방법으로 업샘플링을 해 보십시오.

  7. 수평 및 수직 블러를 이중 패스로 사용하여 더 적극적인 블러 효과를 만들어 보십시오.

  8. 전체 화면이 아닌 개별 오브젝트들에 블러를 해 보십시오.

소스 코드

소스 코드 및 데이터 파일: dx11src36.zip

실행 파일: dx11exe36.zip

Nav