Bloom Artifacts when Scaling Blur pass

Started by
0 comments, last by DiabolicalKitten 4 years, 6 months ago

Hello, brace yourselves for yet another bloom question.

So I've got this bloom stuff going on where:

1. I'm downsampling the brightness of the image 1/2 of the previous with 1/2, 1/4, 1/8, and 1/16

    a. Along the way, I'm filtering out stray bright pixels using the following shader:


#version 430
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
#extension GL_ARB_compute_shader : enable
#extension GL_GOOGLE_include_directive : enable

#define NO_NORMAL_TANGENT
#include "Common/Globals.glsl"

layout (set = 0, binding = 0) uniform sampler2D hiRes;
layout (set = 0, binding = 1, rgba16) uniform image2D loRes;

// Size of lo res texture
// Parameters passed from engine:
// bloomThreshold = 0.0f
// invOutputSz = inverse sz of loResImage.
layout (push_constant) uniform PushConst {
  vec4 invOutputSz;
  vec4 bloomThreshold;
} screen;


// Implemented from Jason Stanard's algorithms. Credits to MiniEngine:
// https://github.com/microsoft/DirectX-Graphics-Samples/blob/master/MiniEngine/Core/Shaders/BloomExtractAndDownsampleLdrCS.hlsl
float ColorRGBtoLuma(vec3 c)
{
  return dot(c, vec3(0.212671, 0.715160, 0.072169));
}

layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;

void main()
{
  vec2 offset = screen.invOutputSz.xy * 0.25; 
  ivec2 iPixCoord = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
  // Inverting to get one pixel sz.
  vec2 tx = (vec2(iPixCoord) + 0.5) * screen.invOutputSz.xy;
  
  vec3 c0 = texture(hiRes, tx + vec2(-offset.x, -offset.y)).rgb;
  vec3 c1 = texture(hiRes, tx + vec2( offset.x, -offset.y)).rgb;
  vec3 c2 = texture(hiRes, tx + vec2(-offset.x,  offset.y)).rgb;
  vec3 c3 = texture(hiRes, tx + vec2( offset.x,  offset.y)).rgb;
  
  const float kEpsilon = 0.0001;
  float threshold = screen.bloomThreshold.x;
  
  float luma0 = ColorRGBtoLuma(c0);
  float luma1 = ColorRGBtoLuma(c1);
  float luma2 = ColorRGBtoLuma(c2);
  float luma3 = ColorRGBtoLuma(c3);
  
  // Apply bloom strength threshold. 
  c0 *= max(kEpsilon, luma0 - threshold) / (luma0 + kEpsilon);
  c1 *= max(kEpsilon, luma1 - threshold) / (luma1 + kEpsilon);
  c2 *= max(kEpsilon, luma2 - threshold) / (luma2 + kEpsilon);
  c3 *= max(kEpsilon, luma3 - threshold) / (luma3 + kEpsilon);
  
  const float kShimmerInvStr = 1.0;
  float w0 = 1.0 / (luma0 + kShimmerInvStr);
  float w1 = 1.0 / (luma1 + kShimmerInvStr);
  float w2 = 1.0 / (luma2 + kShimmerInvStr);
  float w3 = 1.0 / (luma3 + kShimmerInvStr);
  float wSum = w0 + w1 + w2 + w3;
  
  vec3 o = (c0 * w0 + c1 * w1 + c2 * w2 + c3 * w3) / wSum;
  vec4 res = vec4(o.rgb, 1.0);
  
  imageStore(loRes, iPixCoord, res);
}

 

2. Blurring each resulting image horizontally and vertically with gaussian blur algorithm as so:


#version 430 
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable

layout (location = 0) out vec4 outColor;

in FRAG_IN {
  vec2 uv;
} frag_in;


// The surface we are blurring.
layout (set = 0, binding = 0) uniform sampler2D sceneSurface;

// Push constants to determine direction of blur.
//Parameters passed from engine where -> Value (per image):
//Blur.scale = 1.0f (1/16), 1.5f (1/8), 1.6 (1/4), 1.6 (1/2).
layout (push_constant) uniform Consts {
  int   horizontal;
  float strength;
  float scale;
} Blur;


void main()
{
  // Gaussian weights
  float weight[5];
  weight[0] = 70.0 / 256.0;
  weight[1] = 56.0 / 256.0;
  weight[2] = 28.0 / 256.0;
  weight[3] =  8.0 / 256.0;
  weight[4] =  1.0 / 256.0;
  
  vec2 offset = 1.0 / textureSize(sceneSurface, 0) * Blur.scale;
  vec3 blurColor = texture(sceneSurface, frag_in.uv).rgb * weight[0];
  
  // TODO(): Perform blur. May want to do a linear sample instead?
  // <-------- coefficients --------->  for horizontal.
  if (Blur.horizontal == 1) {
    for (int i = 1; i < 5; ++i) {
      blurColor += texture(sceneSurface, frag_in.uv + vec2(offset.x * i, 0.0)).rgb * weight[i] * Blur.strength;
      blurColor += texture(sceneSurface, frag_in.uv - vec2(offset.x * i, 0.0)).rgb * weight[i] * Blur.strength;
    }
  } else {
    for (int i = 1; i < 5; ++i) {
      blurColor += texture(sceneSurface, frag_in.uv + vec2(0.0, offset.y * i)).rgb * weight[i] * Blur.strength;
      blurColor += texture(sceneSurface, frag_in.uv - vec2(0.0, offset.y * i)).rgb * weight[i] * Blur.strength;
    }
  }
  
  outColor = vec4(blurColor, 1.0);
}

 

3. Finally, accumulating each result back up the chain. ex. 1/16 back to 1/8. Then repeating this process for 1/8 to 1/4, 1/4 to 1/2, and 1/2 to full image, while repeating 2 and 3 in the process.

  a. Code for accumulation:


#version 430
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
#extension GL_ARB_compute_shader : enable
#extension GL_GOOGLE_include_directive : enable

#define NO_NORMAL_TANGENT
#include "Common/Globals.glsl"

layout (set = 0, binding = 0) uniform sampler2D loRes;
layout (set = 0, binding = 1, rgba16) uniform image2D hiRes;

// sz is the (width, height) of the hiRes image.
layout (push_constant) uniform PushConst {
  ivec4 sz;
} screen;

layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;

void main()
{
  ivec2 iPixCoord = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
  vec2 tx = (vec2(iPixCoord) / vec2(screen.sz.xy));
  vec3 lo = texture(loRes, tx).rgb;
  vec3 hi = imageLoad(hiRes, iPixCoord).rgb;
  vec4 res = vec4(hi + lo, 1);
  imageStore(hiRes, iPixCoord, res);
}

 

And the result of this process is as so:

https://gph.is/g/ZyznzwW

Although the problem is that I am trying to control the effect to get a slightly wider spread on the brightness as the reference below:

oOzuvzy.png

 

Although when trying to spread the effect with the Blur.scale value (say 3.1) on all passes I get decent results, but because of stray or tiny clusters of pixels, it ends up with artifacts:

https://imgur.com/a/hSoOfhP

Where it looks more like dithering. I'm guessing it has to do with my gaussian blur pass scaling, since looking at renderdoc, each pass creates this weird pattern:

https://imgur.com/a/wrN6NzL

I've been looking at links like this one: 

 

Which looks a little like what I'm doing, while not doing so much as the energy conservation and HDR that is expected (I'm not really looking for super realism, just some way to control the value of the effect.)

I am a noob when it comes to this kind of stuff, but was hoping to seek advice from the people who have an idea on how to mitigate this issue? All advice is welcomed, experienced or not :D 

 

kittenz kittenz kittenz

This topic is closed to new replies.

Advertisement