본문 바로가기

프로그래밍/Shader

Bloom 구현 과정

이론에도 나와 있듯이 Bloom은 밝은 픽셀을 주변으로 흘리는 것이다.

쉽게 말하면 뽀샤시기능.

 

처음 해야 할 일은 Bright Pass를 통해서 주변으로 흘릴 픽셀들을 찾는 것이다.

 

평균 휘도 값과 HDR 처리된 이미지를 사용하기 때문에 HDR을 먼저 구현해야한다.

// Bloom Compute Shader
Texture2D<float4> HDRDownScaleTex : register(t0);
StructuredBuffer<float> AvgLum : register(t1);

RWTexture2D<float4> Bloom : register(u1);

[numthreads(1024, 1, 1)]
void BloomReveal(uint3 dispatchThreadID : SV_DispatchThreadID)
{
    uint2 vCurPixel = uint2(dispatchThreadID.x % g_Res.x,
    dispatchThreadID.x / g_Res.x);

    if (vCurPixel.y < g_Res.y)
    {
        float4 vColor = HDRDownScaleTex.Load(int3(vCurPixel, 0));
        float fLum = dot(vColor, LUM_ADAPT_FACTOR);
        float fAvgLum = AvgLum[0];

        float fColorScale = saturate(fLum - fAvgLum * g_fBloomThreshold);

        Bloom[vCurPixel.xy] = vColor * fColorScale;
    }
}

이렇게 하면 주변에 흘릴 픽셀을 구할 수 있다.

상수 버퍼로 BloomThreshold를 받는데 이것은 어느정도 밝기 이상의 픽셀만 흘릴 건지 지정해준다.

 

UnorderedAccesView로 출력된 값을 갖고 있다가 이것을 Blur 처리를 해준다.

 

3D 프로젝트를 할 때 Blur를 만들었지만 Bloom에 들어가는 Blur는 아주 간단하기 때문에 상수버퍼 필요 없이  Weight값을 지정해주고 ShaderResourceView만 받아서 출력을 하는 식으로 하였다.

Texture2D<float4> Input : register(t0);
RWTexture2D<float4> Output : register(u0);

static const float SampleWeights[13] =
{
    0.002216,
    0.008764,
    0.026995,
    0.064759,
    0.120985,
    0.176033,
    0.199471,
    0.176033,
    0.120985,
    0.064759,
    0.026995,
    0.008764,
    0.002216,
};

#define kernelhalf 6
#define groupthreads 128
groupshared float4 SharedInput[groupthreads];

[numthreads(groupthreads, 1, 1)]
void VerticalFilter(uint3 GroupID : SV_GroupID, uint GroupIndex : SV_GroupIndex)
{
    int2 vCoord = int2(GroupID.x, GroupIndex - kernelhalf + (groupthreads - kernelhalf * 2) * GroupID.y);
    vCoord = clamp(vCoord, int2(0, 0), int2(g_Res.x - 1, g_Res.y - 1));
    SharedInput[GroupIndex] = Input.Load(int3(vCoord, 0));

    GroupMemoryBarrierWithGroupSync();

    // Vertical blur
    if (GroupIndex >= kernelhalf && GroupIndex < (groupthreads - kernelhalf) &&
         ((GroupIndex - kernelhalf + (groupthreads - kernelhalf * 2) * GroupID.y) < g_Res.y))
    {
        float4 vOut = 0;
        
        [unroll]
        for (int i = -kernelhalf; i <= kernelhalf; ++i)
        {
            vOut += SharedInput[GroupIndex + i] * SampleWeights[i + kernelhalf];
        }

        Output[vCoord] = float4(vOut.rgb, 1.0f);
    }
}

[numthreads(groupthreads, 1, 1)]
void HorizFilter(uint3 GroupID : SV_GroupID, uint GroupIndex : SV_GroupIndex)
{
    int2 vCoord = int2(GroupIndex - kernelhalf + (groupthreads - kernelhalf * 2) * GroupID.x, GroupID.y);
    vCoord = clamp(vCoord, int2(0, 0), int2(g_Res.x - 1, g_Res.y - 1));
    SharedInput[GroupIndex] = Input.Load(int3(vCoord, 0));

    GroupMemoryBarrierWithGroupSync();

    // Horizontal blur
    if (GroupIndex >= kernelhalf && GroupIndex < (groupthreads - kernelhalf) &&
         ((GroupID.x * (groupthreads - 2 * kernelhalf) + GroupIndex - kernelhalf) < g_Res.x))
    {
        float4 vOut = 0;
        
        [unroll]
        for (int i = -kernelhalf; i <= kernelhalf; ++i)
            vOut += SharedInput[GroupIndex + i] * SampleWeights[i + kernelhalf];

        Output[vCoord] = float4(vOut.rgb, 1.0f);
    }
}

이 과정을 거쳐 나온 출력 값을 UnorderedAccessView로 받은 다음 픽셀 셰이더에 넣어준다.

 

// Bloom Contribution 을 추가한다
vColor += fBloomScale * BloomTex.Sample(g_DiffuseSmp, vUV).xyz;

BloomScale을 사용하여 빛을 흘릴 Scale을 지정해준다.

 

출력된 화면은 다음과 같다.

 

 

'프로그래밍 > Shader' 카테고리의 다른 글

HDR 구현과정 2부  (0) 2019.06.11
HDR 구현 과정 1부  (0) 2019.06.10
Rim Light 이론과 구현  (0) 2019.04.04
Adaptation 구현 과정  (0) 2019.04.03
기하 셰이더 (Geometry Shader)  (0) 2019.03.29