그래픽스 기술

[PBR] HDRI - postprocess

doyyy_0 2025. 1. 15. 21:00

우리가 후처리를 할 때 예를들어 가우시안 블러를 적용할 때, LDR(Low Dynamic Range)이미지(unorm)이미지로 적용을 하였습니다. 하지만, HDR(High Dynamic Range)이미지를 적용할 때는 방법이 다릅니다. 더 사실적으로 표현하기 위함이죠.

 

HDR이미지는 unorm(R8G8B8A8)과 다르게 float16(R16G16B16A16)입니다. 더 넓은 범위의 빛의 강도를 조절할 수 있지요. 가우시안 블러를 적용하려면 다음과 같은 단계를 따릅니다.

 

1. 해상도 줄이기(빛 번짐 표현 하기 위함. 이거 없으면 가우시안 블러를 많이 적용해야돼서 속도에 영향을 미침)

=> Down 샘플링과 Up샘플링으로 표현

2. 가우시안 블러(주위 값을 Convolusion을리가 후처리를 할 때 예를들어 가우시안 블러를 적용할 때, LDR이미지(unorm)이미지로 적용을 하였습니다.

 

하지만, HDR이미지를 적용할 때는 방법이 다릅니다. 더 사실적으로 표현하기 위함이죠.

 

 

 

HDR이미지는 unorm(R8G8B8A8)과 다르게 float16(R16G16B16A16)입니다. 더 넓은 범위의 빛의 강도를 조절할 수 있지요. 가우시안 블러를 적용하려면 다음과 같은 단계를 따릅니다.

 

 

 

1. 해상도 줄이기(빛 번짐 표현 하기 위함. 이거 없으면 가우시안 블러를 많이 적용해야돼서 속도에 영향을 미침)

=> Down 샘플링과 Up샘플링으로 표현

2. 가우시안 블러(convolution을 통해 주위 값 계산)

3. original target과 블러된 값 합침

 

여기서 LDR방식과 HDR방식의 블러 처리 방식이 다릅니다.

1. HDR방식은 맨 마지막 Combine(두 타겟 합치는 과정)에서 최종적으로 float형식을 모니터로 내보내야 하기 때문에 Unorm으로 바꿔줍니다. 이 때, Tone Mapping과정이 필요합니다.(gamma correction포함) 

 

2. HDR은 LDR과 다르게 해상도 Down, Up과정에서 모두 블러 처리를 합니다. X축 Y축 따로 하는게 아니라 그냥 한번에 다 다같이 합니다. 

공식은 다음과 같습니다.

 

DownSampling

=>

float4 main(SamplingPixelShaderInput input) : SV_TARGET
{
    float3 a = g_texture0.Sample(g_sampler, input.texcoord + float2(-2*dx,2*dy)).rgb;
    float3 b = g_texture0.Sample(g_sampler, input.texcoord + float2(0.0, 2 * dy)).rgb;
    float3 c = g_texture0.Sample(g_sampler, input.texcoord + float2(2 * dx, 2 * dy)).rgb;
    
    float3 d = g_texture0.Sample(g_sampler, input.texcoord + float2(-2 * dx,0.0)).rgb;
    float3 e = g_texture0.Sample(g_sampler, input.texcoord + float2(0.0, 0.0)).rgb;
    float3 f = g_texture0.Sample(g_sampler, input.texcoord + float2(2 * dx, 0.0)).rgb;
    
    float3 g = g_texture0.Sample(g_sampler, input.texcoord + float2(-2 * dx, -2 * dy)).rgb;
    float3 h = g_texture0.Sample(g_sampler, input.texcoord + float2(0.0, -2 * dy)).rgb;
    float3 i = g_texture0.Sample(g_sampler, input.texcoord + float2(2 * dx, -2 * dy)).rgb;
    
    float3 j = g_texture0.Sample(g_sampler, input.texcoord + float2(-dx, dy)).rgb;
    float3 k = g_texture0.Sample(g_sampler, input.texcoord + float2(dx, dy)).rgb;
    float3 l = g_texture0.Sample(g_sampler, input.texcoord + float2(-dx, - dy)).rgb;
    float3 m = g_texture0.Sample(g_sampler, input.texcoord + float2(dx, -dy)).rgb;
    
    float3 downSample = e * 0.125;
    downSample += (a + c + g + i) * 0.03125;
    downSample += (b + d + f + h) * 0.0625;
    downSample += (j + k + l + m) * 0.125;
    
    return float4(downSample, 1.0);
}

 

UpSampling 

=>

float4 main(SamplingPixelShaderInput input) : SV_TARGET
{
    float3 a = g_texture0.Sample(g_sampler, input.texcoord + float2(-dx, dy)).rgb;
    float3 b = g_texture0.Sample(g_sampler, input.texcoord + float2(0.0, dy)).rgb;
    float3 c = g_texture0.Sample(g_sampler, input.texcoord + float2(dx, dy)).rgb;
    
    float3 d = g_texture0.Sample(g_sampler, input.texcoord + float2(-dx, 0.0)).rgb;
    float3 e = g_texture0.Sample(g_sampler, input.texcoord + float2(0.0, 0.0)).rgb;
    float3 f = g_texture0.Sample(g_sampler, input.texcoord + float2(dx, 0.0)).rgb;
    
    float3 g = g_texture0.Sample(g_sampler, input.texcoord + float2(-dx, -dy)).rgb;
    float3 h = g_texture0.Sample(g_sampler, input.texcoord + float2(0.0, -dy)).rgb;
    float3 i = g_texture0.Sample(g_sampler, input.texcoord + float2(dx, -dy)).rgb;
    
    float3 upSample = e * 4.0;
    upSample += (b + d + f + h) * 2.0;
    upSample += (a + c + g + i);
    upSample *= 1.0 / 16.0;
    
    return float4(upSample, 1.0);

}

 

3. 두 Target합칠 때 코드 비교입니다.

//LDR
float4 main(SamplingPixelShaderInput input) : SV_TARGET
{
    return g_texture0.Sample(g_sampler, input.texcoord) + strength * g_texture1.Sample(g_sampler, input.texcoord);

}

//HDR
float4 main(SamplingPixelShaderInput input) : SV_TARGET
{
    float3 color0 = g_texture0.Sample(g_sampler, input.texcoord).rgb;
    float3 color1 = g_texture1.Sample(g_sampler, input.texcoord).rgb;
    
    float3 combined = (1.0 - strength) * color0 + strength * color1;

    // Tone Mapping  
    combined = LinearToneMapping(combined);
    //여기까진 float16형식이었다가, 반환은 백버퍼에 하기때문에 unorm으로 됨
    return float4(combined, 1.0f);
}

즉, LDR은 블러 Target에 strength를 곱해서 Original이미지에 더해주는 반면, HDR은 각 값을 interpolation합니다. 빛나는 부분은 약간의 빛번짐이 있다는 걸 고려해서, 살짝 뿌옇게 만들어주는 방식입니다. LDR에 interpolation을 하면 전체적으로 이미지가 뿌해지기만 하는 효과가 있어서 방식이 다릅니다.

 

참고 자료: phys. Based Bloom

https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom

 

강의 출처 : 홍정모 그래픽스 새싹코드 Part3

https://www.honglab.ai/courses/take/graphicspt3