이론에도 나와 있듯이 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 |