Skip to content

Commit c5e2362

Browse files
authored
Implemented a better bloom effect with no threshold (#550)
* Added blending function and equation settings to the pipeline state (now 16 bytes instead of 8 bytes, sad but necessary). * Fixed a bunch of pipeline state values that were using the incorrect number of bits 🫣 * Added `CirlcularIterator` to iterate over things... circularly 😅 * Added `PingPongFramebuffer`, not used atm (was at some point during implementation), but could be useful for other effects! * Added new overload to `FramebufferUtil::SetupFramebuffer` providing more control over the texture desc. * Changed post-processing stack order by adding auto-exposure before bloom.
1 parent 87a6dfd commit c5e2362

File tree

29 files changed

+927
-380
lines changed

29 files changed

+927
-380
lines changed

Resources/Engine/Shaders/PostProcess/Bloom.ovfx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ void main()
1616
#version 450 core
1717

1818
in vec2 TexCoords;
19-
out vec4 FRAGMENT_COLOR;
19+
out vec3 FRAGMENT_COLOR;
2020

2121
uniform sampler2D _InputTexture;
2222
uniform sampler2D _BloomTexture;
23-
uniform float _BloomIntensity;
23+
uniform float _BloomStrength;
2424

2525
void main()
2626
{
27-
const vec3 sceneColor = texture(_InputTexture, TexCoords).rgb;
27+
const vec3 hdrColor = texture(_InputTexture, TexCoords).rgb;
2828
const vec3 bloomColor = texture(_BloomTexture, TexCoords).rgb;
29-
FRAGMENT_COLOR = vec4(sceneColor + _BloomIntensity * bloomColor, 1.0);
29+
FRAGMENT_COLOR = mix(hdrColor, bloomColor, _BloomStrength);
3030
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#feature KARIS_AVERAGE
2+
3+
#shader vertex
4+
#version 450 core
5+
6+
layout(location = 0) in vec2 geo_Pos;
7+
layout(location = 1) in vec2 geo_TexCoords;
8+
9+
out vec2 TexCoords;
10+
11+
void main()
12+
{
13+
TexCoords = geo_TexCoords;
14+
gl_Position = vec4(geo_Pos, 0.0, 1.0);
15+
}
16+
17+
#shader fragment
18+
#version 450 core
19+
20+
in vec2 TexCoords;
21+
out vec3 downsample;
22+
23+
uniform sampler2D _InputTexture;
24+
uniform vec2 _InputResolution;
25+
26+
vec3 PowVec3(vec3 v, float p)
27+
{
28+
return vec3(pow(v.x, p), pow(v.y, p), pow(v.z, p));
29+
}
30+
31+
const float invGamma = 1.0 / 2.2;
32+
vec3 ToSRGB(vec3 v) { return PowVec3(v, invGamma); }
33+
34+
float sRGBToLuma(vec3 col)
35+
{
36+
//return dot(col, vec3(0.2126f, 0.7152f, 0.0722f));
37+
return dot(col, vec3(0.299f, 0.587f, 0.114f));
38+
}
39+
40+
float KarisAverage(vec3 col)
41+
{
42+
// Formula is 1 / (1 + luma)
43+
float luma = sRGBToLuma(ToSRGB(col)) * 0.25f;
44+
return 1.0f / (1.0f + luma);
45+
}
46+
47+
void main()
48+
{
49+
vec2 srcTexelSize = 1.0 / _InputResolution;
50+
float x = srcTexelSize.x;
51+
float y = srcTexelSize.y;
52+
53+
// Take 13 samples around current texel:
54+
// a - b - c
55+
// - j - k -
56+
// d - e - f
57+
// - l - m -
58+
// g - h - i
59+
// === ('e' is the current texel) ===
60+
vec3 a = texture(_InputTexture, vec2(TexCoords.x - 2*x, TexCoords.y + 2*y)).rgb;
61+
vec3 b = texture(_InputTexture, vec2(TexCoords.x, TexCoords.y + 2*y)).rgb;
62+
vec3 c = texture(_InputTexture, vec2(TexCoords.x + 2*x, TexCoords.y + 2*y)).rgb;
63+
64+
vec3 d = texture(_InputTexture, vec2(TexCoords.x - 2*x, TexCoords.y)).rgb;
65+
vec3 e = texture(_InputTexture, vec2(TexCoords.x, TexCoords.y)).rgb;
66+
vec3 f = texture(_InputTexture, vec2(TexCoords.x + 2*x, TexCoords.y)).rgb;
67+
68+
vec3 g = texture(_InputTexture, vec2(TexCoords.x - 2*x, TexCoords.y - 2*y)).rgb;
69+
vec3 h = texture(_InputTexture, vec2(TexCoords.x, TexCoords.y - 2*y)).rgb;
70+
vec3 i = texture(_InputTexture, vec2(TexCoords.x + 2*x, TexCoords.y - 2*y)).rgb;
71+
72+
vec3 j = texture(_InputTexture, vec2(TexCoords.x - x, TexCoords.y + y)).rgb;
73+
vec3 k = texture(_InputTexture, vec2(TexCoords.x + x, TexCoords.y + y)).rgb;
74+
vec3 l = texture(_InputTexture, vec2(TexCoords.x - x, TexCoords.y - y)).rgb;
75+
vec3 m = texture(_InputTexture, vec2(TexCoords.x + x, TexCoords.y - y)).rgb;
76+
77+
// Apply weighted distribution:
78+
// 0.5 + 0.125 + 0.125 + 0.125 + 0.125 = 1
79+
// a,b,d,e * 0.125
80+
// b,c,e,f * 0.125
81+
// d,e,g,h * 0.125
82+
// e,f,h,i * 0.125
83+
// j,k,l,m * 0.5
84+
// This shows 5 square areas that are being sampled. But some of them overlap,
85+
// so to have an energy preserving downsample we need to make some adjustments.
86+
// The weights are the distributed, so that the sum of j,k,l,m (e.g.)
87+
// contribute 0.5 to the final color output. The code below is written
88+
// to effectively yield this sum. We get:
89+
// 0.125*5 + 0.03125*4 + 0.0625*4 = 1
90+
91+
// Check if we need to perform Karis average on each block of 4 samples
92+
#if defined(KARIS_AVERAGE)
93+
vec3 groups[5];
94+
// We are writing to mip 0, so we need to apply Karis average to each block
95+
// of 4 samples to prevent fireflies (very bright subpixels, leads to pulsating
96+
// artifacts).
97+
groups[0] = (a+b+d+e) * (0.125f/4.0f);
98+
groups[1] = (b+c+e+f) * (0.125f/4.0f);
99+
groups[2] = (d+e+g+h) * (0.125f/4.0f);
100+
groups[3] = (e+f+h+i) * (0.125f/4.0f);
101+
groups[4] = (j+k+l+m) * (0.5f/4.0f);
102+
groups[0] *= KarisAverage(groups[0]);
103+
groups[1] *= KarisAverage(groups[1]);
104+
groups[2] *= KarisAverage(groups[2]);
105+
groups[3] *= KarisAverage(groups[3]);
106+
groups[4] *= KarisAverage(groups[4]);
107+
downsample = groups[0]+groups[1]+groups[2]+groups[3]+groups[4];
108+
downsample = max(downsample, 0.0001f);
109+
#else
110+
downsample = e*0.125; // ok
111+
downsample += (a+c+g+i)*0.03125; // ok
112+
downsample += (b+d+f+h)*0.0625; // ok
113+
downsample += (j+k+l+m)*0.125; // ok
114+
#endif
115+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#shader vertex
2+
#version 450 core
3+
4+
layout(location = 0) in vec2 geo_Pos;
5+
layout(location = 1) in vec2 geo_TexCoords;
6+
7+
out vec2 TexCoords;
8+
9+
void main()
10+
{
11+
TexCoords = geo_TexCoords;
12+
gl_Position = vec4(geo_Pos, 0.0, 1.0);
13+
}
14+
15+
#shader fragment
16+
#version 450 core
17+
18+
in vec2 TexCoords;
19+
out vec3 upsample;
20+
21+
uniform sampler2D _InputTexture;
22+
uniform float _FilterRadius;
23+
24+
void main()
25+
{
26+
float x = _FilterRadius;
27+
float y = _FilterRadius;
28+
29+
// Take 9 samples around current texel:
30+
// a - b - c
31+
// d - e - f
32+
// g - h - i
33+
// === ('e' is the current texel) ===
34+
vec3 a = texture(_InputTexture, vec2(TexCoords.x - x, TexCoords.y + y)).rgb;
35+
vec3 b = texture(_InputTexture, vec2(TexCoords.x, TexCoords.y + y)).rgb;
36+
vec3 c = texture(_InputTexture, vec2(TexCoords.x + x, TexCoords.y + y)).rgb;
37+
38+
vec3 d = texture(_InputTexture, vec2(TexCoords.x - x, TexCoords.y)).rgb;
39+
vec3 e = texture(_InputTexture, vec2(TexCoords.x, TexCoords.y)).rgb;
40+
vec3 f = texture(_InputTexture, vec2(TexCoords.x + x, TexCoords.y)).rgb;
41+
42+
vec3 g = texture(_InputTexture, vec2(TexCoords.x - x, TexCoords.y - y)).rgb;
43+
vec3 h = texture(_InputTexture, vec2(TexCoords.x, TexCoords.y - y)).rgb;
44+
vec3 i = texture(_InputTexture, vec2(TexCoords.x + x, TexCoords.y - y)).rgb;
45+
46+
// Apply weighted distribution, by using a 3x3 tent filter:
47+
// 1 | 1 2 1 |
48+
// -- * | 2 4 2 |
49+
// 16 | 1 2 1 |
50+
upsample = e*4.0;
51+
upsample += (b+d+f+h)*2.0;
52+
upsample += (a+c+g+i);
53+
upsample *= 1.0 / 16.0;
54+
}

Resources/Engine/Shaders/PostProcess/Blur.ovfx

Lines changed: 0 additions & 43 deletions
This file was deleted.

Resources/Engine/Shaders/PostProcess/Brightness.ovfx

Lines changed: 0 additions & 35 deletions
This file was deleted.

Sources/Overload/OvCore/include/OvCore/Helpers/Serializer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,4 @@ namespace OvCore::Helpers
105105
static OvAudio::Resources::Sound* DeserializeSound(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name);
106106
#pragma endregion
107107
};
108-
}
108+
}

Sources/Overload/OvCore/include/OvCore/Rendering/FramebufferUtil.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@
1212

1313
namespace OvCore::Rendering::FramebufferUtil
1414
{
15+
/**
16+
* Prepare the given framebuffer for rendering by attaching
17+
* the necessary textures and renderbuffers.
18+
* @note This overload gives more control over the texture desc.
19+
* @param p_framebuffer
20+
* @param p_textureDesc
21+
* @param p_useDepth
22+
* @param p_useStencil
23+
*/
24+
void SetupFramebuffer(
25+
OvRendering::HAL::Framebuffer& p_framebuffer,
26+
const OvRendering::Settings::TextureDesc& p_textureDesc,
27+
bool p_useDepth = true,
28+
bool p_useStencil = false
29+
);
30+
1531
/**
1632
* Prepare the given framebuffer for rendering by attaching
1733
* the necessary textures and renderbuffers
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @project: Overload
3+
* @author: Overload Tech.
4+
* @licence: MIT
5+
*/
6+
7+
#pragma once
8+
9+
#include <array>
10+
11+
#include <OvRendering/HAL/Framebuffer.h>
12+
#include <OvTools/Utils/CircularIterator.h>
13+
14+
namespace OvCore::Rendering
15+
{
16+
/**
17+
* Convenient ping-pong buffer holding two framebuffers
18+
*/
19+
class PingPongFramebuffer : public OvTools::Utils::CircularIterator<OvRendering::HAL::Framebuffer, 2>
20+
{
21+
public:
22+
/**
23+
* Create a ping pong buffer
24+
* @param p_debugName
25+
*/
26+
PingPongFramebuffer(std::string_view p_debugName = std::string_view{});
27+
28+
/**
29+
* Return the two framebuffers
30+
*/
31+
std::array<OvRendering::HAL::Framebuffer, 2>& GetFramebuffers();
32+
33+
private:
34+
std::array<OvRendering::HAL::Framebuffer, 2> m_framebuffers;
35+
};
36+
}

Sources/Overload/OvCore/include/OvCore/Rendering/PostProcess/BloomEffect.h

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,27 @@
66

77
#pragma once
88

9+
#include <OvCore/Rendering/PingPongFramebuffer.h>
910
#include <OvCore/Rendering/PostProcess/AEffect.h>
1011
#include <OvRendering/Data/Material.h>
1112
#include <OvRendering/HAL/Framebuffer.h>
1213

1314
namespace OvCore::Rendering::PostProcess
1415
{
16+
namespace BloomConstants
17+
{
18+
constexpr uint32_t kMinPassCount = 1;
19+
constexpr uint32_t kMaxPassCount = 10;
20+
constexpr float kMaxBloomIntensity = 1.0f / 0.04f; // 0.04f being the default internal interpolation factor.
21+
}
22+
1523
/**
1624
* Settings for the BloomEffect
1725
*/
1826
struct BloomSettings : public EffectSettings
1927
{
20-
float threshold = 1.0f;
21-
float radius = 5.0f;
22-
int kernelSize = 6;
23-
float intensity = 0.6f;
24-
int passes = 10;
28+
float intensity = 1.0f;
29+
int passes = 6;
2530
};
2631

2732
/**
@@ -60,9 +65,11 @@ namespace OvCore::Rendering::PostProcess
6065
) override;
6166

6267
private:
63-
std::array<OvRendering::HAL::Framebuffer, 2> m_bloomPingPong;
64-
OvRendering::Data::Material m_brightnessMaterial;
65-
OvRendering::Data::Material m_blurMaterial;
68+
std::array<OvRendering::HAL::Framebuffer, BloomConstants::kMaxPassCount> m_bloomSamplingBuffers;
69+
OvRendering::HAL::Framebuffer m_bloomOutputBuffer;
70+
OvRendering::Data::Material m_downsamplingMaterial;
71+
OvRendering::Data::Material m_upsamplingMaterial;
6672
OvRendering::Data::Material m_bloomMaterial;
73+
OvRendering::Data::Material m_blitMaterial;
6774
};
6875
}

0 commit comments

Comments
 (0)