Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Revamp Bloom #6677

Closed
wants to merge 136 commits into from
Closed
Show file tree
Hide file tree
Changes from 126 commits
Commits
Show all changes
136 commits
Select commit Hold shift + click to select a range
425c7ae
Fix bloom transparency
JMS55 Nov 15, 2022
1dd39be
Default to ACES tonemapping
JMS55 Nov 17, 2022
7704e12
WIP: Fix bloom
JMS55 Nov 18, 2022
bb465b5
Revamp bloom
JMS55 Nov 18, 2022
82e0f2f
Appease clippy
JMS55 Nov 18, 2022
fa4fccb
Doc fixes
JMS55 Nov 18, 2022
d7a1ae7
Improve bloom docs
JMS55 Nov 18, 2022
9a94a40
Bloom add karis average
JMS55 Nov 18, 2022
bb06589
Reword bloom docs
JMS55 Nov 18, 2022
892fb5e
Add soft threshold
JMS55 Nov 18, 2022
eb27945
Fix shader error
JMS55 Nov 18, 2022
2e3f428
Misc changes
JMS55 Nov 18, 2022
dabf32a
Cleanup
JMS55 Nov 20, 2022
5fd4116
Clamp bloom threshold
JMS55 Nov 20, 2022
38df5ad
Clamp in shader
JMS55 Nov 20, 2022
7de822b
Misc rename
JMS55 Nov 20, 2022
cff860e
Rename threshold setting
JMS55 Nov 20, 2022
e58aeac
Rename uniform struct in shader
JMS55 Nov 20, 2022
af760a7
Slightly lower filter radius
JMS55 Nov 20, 2022
1719571
Small doc change
JMS55 Nov 20, 2022
d6b642a
Change example colors to more saturated ones
StarLederer Nov 22, 2022
3f4ecb8
Make example colors brighter
StarLederer Nov 22, 2022
4398515
Switch to energy conserving blending
StarLederer Nov 22, 2022
d172215
Revert default intensity to 0.3
StarLederer Nov 22, 2022
5fbdc01
Fix possible crash
StarLederer Nov 22, 2022
4b63ea3
Fix outdated comment
StarLederer Nov 22, 2022
aea919a
Fix unnecessary conversions
StarLederer Nov 29, 2022
0e481ff
Remove the use of view_target.post_process_write()
StarLederer Dec 2, 2022
47f2e3d
Clean redundant parts of "upsample final" up
StarLederer Dec 2, 2022
d4f491c
Add comments for future generations
StarLederer Dec 2, 2022
e64900e
Use WGPU native blending constants to control bloom intensity (instea…
StarLederer Dec 2, 2022
193932f
Add a comment about blend factors
StarLederer Dec 2, 2022
8972fd3
Remove unused uniform value
StarLederer Dec 2, 2022
ec53b37
Clean an unnecessary bind group up
StarLederer Dec 2, 2022
4daff1b
Remove rouge line
StarLederer Dec 2, 2022
5fb4806
Mark unideal code with TODOs
StarLederer Dec 2, 2022
2ab6225
Move BloomSettings to its own module
StarLederer Dec 2, 2022
bf8e301
Implement parametric bloom blending curve
StarLederer Dec 14, 2022
a574388
Convert builder functions to constants
StarLederer Dec 16, 2022
85af8f9
Implement additive bloom mode
StarLederer Dec 16, 2022
1dc0d41
Clean up bloom blending
StarLederer Dec 16, 2022
774f008
Add a sugestion for future improvement
StarLederer Dec 16, 2022
2f35c92
Revamp bloom blend function
StarLederer Dec 22, 2022
896ee46
Change order to BloomSettings -> PrefilterSettings -> BloomMode
StarLederer Dec 22, 2022
9f1c6cc
Rename BloomMode to BloomCompositeMode
StarLederer Dec 22, 2022
79bc5b9
Fix outdated names
StarLederer Dec 22, 2022
ec3c569
Rename OLDSCHOOL to OLD_SCHOOL
StarLederer Dec 22, 2022
9bef787
Change decate format in comment
StarLederer Dec 22, 2022
f849c4e
Update doc comment
StarLederer Dec 22, 2022
9298bc6
Rename PrefilterSettings to BloomPrefilterSettings
StarLederer Dec 22, 2022
3452b32
Register new Reflect types with the app
StarLederer Dec 22, 2022
c74f301
Remove unused use
StarLederer Dec 22, 2022
3d0a912
Add a doc comment to compute_blend_factor and move it higher
StarLederer Dec 28, 2022
9d5c544
Add a link to desmos
StarLederer Dec 28, 2022
dc257d4
Fix an outdated comment
StarLederer Dec 28, 2022
df53631
Update example comments
StarLederer Dec 28, 2022
ad1f11c
Fix bad/outdated comments
StarLederer Dec 28, 2022
3f51432
Implement SDR bloom
StarLederer Dec 28, 2022
8971a88
Remove unused uniforms from upsampling stage
StarLederer Jan 2, 2023
8a0d1d1
Specialize downsampling pipeline
StarLederer Jan 2, 2023
dac7680
Remove outdated line
StarLederer Jan 2, 2023
82a553d
Update example
StarLederer Jan 2, 2023
4ff46bf
Fix incorrect operator
StarLederer Jan 2, 2023
0214e39
Speed up threshold controls
StarLederer Jan 2, 2023
1cbe98a
Add descriptions to bloom parameters
StarLederer Jan 2, 2023
ebe2cff
Improve bloom calculation in additive mode
StarLederer Jan 2, 2023
3e8d9fa
Link to my parametric curve visualizer
StarLederer Jan 2, 2023
c0a7f2f
Partly implement the fix from #6802
StarLederer Jan 3, 2023
cc57711
Merge remote-tracking branch 'upstream/main' into bloom-transparent-f…
StarLederer Jan 3, 2023
9377e14
Remove error print
StarLederer Jan 3, 2023
12e4b10
Slighly limit bloom texture count
StarLederer Jan 3, 2023
75f59f8
Implement fix from #6802
StarLederer Jan 3, 2023
a495344
Fix transparency
StarLederer Jan 3, 2023
38d7c45
Merge pull request #4 from StarLederer/bloom-transparent-fix
StarLederer Jan 3, 2023
4a2587b
Update examples/3d/bloom.rs
StarLederer Jan 5, 2023
c515fc5
Update crates/bevy_core_pipeline/src/bloom/mod.rs
StarLederer Jan 5, 2023
c7faf67
Update crates/bevy_core_pipeline/src/bloom/mod.rs
StarLederer Jan 5, 2023
f87bf01
Update crates/bevy_core_pipeline/src/bloom/mod.rs
StarLederer Jan 5, 2023
895d626
Update crates/bevy_core_pipeline/src/bloom/mod.rs
StarLederer Jan 9, 2023
b2dba3a
Update crates/bevy_core_pipeline/src/bloom/mod.rs
StarLederer Jan 9, 2023
1536709
Update crates/bevy_core_pipeline/src/bloom/settings.rs
StarLederer Jan 9, 2023
8ae71e6
Update crates/bevy_core_pipeline/src/bloom/settings.rs
StarLederer Jan 9, 2023
7a321f3
Update crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs
StarLederer Jan 9, 2023
677b758
BloomSettings doc tweaks
JMS55 Jan 9, 2023
d4b61ac
Note WebGL2 incompabillity
JMS55 Jan 9, 2023
f315d78
Update crates/bevy_core_pipeline/src/bloom/mod.rs
StarLederer Jan 9, 2023
aa2a597
Add 2d bloom example
JMS55 Jan 10, 2023
a297ce9
Merge commit '0af8e1c2117fa5b36e084848edf0a173207d6f4e' into bloom-tr…
JMS55 Jan 13, 2023
c2955b3
Update crates/bevy_core_pipeline/src/bloom/mod.rs
JMS55 Jan 13, 2023
09ae245
Cleanup bloom
JMS55 Jan 30, 2023
a96ad6e
Merge commit 'a61bf35c9769eb35d993ec34e927015fe78f085d' into bloom-tr…
JMS55 Jan 30, 2023
870435c
Fix comment
JMS55 Feb 2, 2023
a187c4b
Misc fix
JMS55 Feb 2, 2023
95ad4d7
Add bloom_2d example controls
JMS55 Feb 2, 2023
c041374
Merge commit 'e03982583da83e6559c6a8fee75be59b95c607af' into bloom-tr…
JMS55 Feb 2, 2023
6dd011b
Fix pipeline cache mut
JMS55 Feb 2, 2023
e1485f1
Update crates/bevy_core_pipeline/src/bloom/settings.rs
StarLederer Feb 3, 2023
48fedbf
Rename BloomDownsamplingUniform to BloomDownsamplingUniforms
StarLederer Feb 3, 2023
1c11c9d
Update crates/bevy_core_pipeline/src/bloom/bloom.wgsl
StarLederer Feb 3, 2023
c296a45
Update crates/bevy_core_pipeline/src/bloom/settings.rs
StarLederer Feb 3, 2023
860effc
Update crates/bevy_core_pipeline/src/bloom/settings.rs
StarLederer Feb 3, 2023
f99a027
Fix comment
StarLederer Feb 3, 2023
6d4ee2b
Fix comment
StarLederer Feb 3, 2023
e8fb506
Fix comment
StarLederer Feb 3, 2023
3339655
Finish renaming BloomDownsamplingUniforms
StarLederer Feb 3, 2023
fe5aa43
Simplify id names
StarLederer Feb 3, 2023
70b398e
Stageless rebase: Merge commit 'ea2ecd4f75da476849e9cdcd9f77c8696d009…
JMS55 Feb 7, 2023
62c885c
Change default bloom intensity
JMS55 Feb 9, 2023
7b7db8a
Fix docs
JMS55 Feb 9, 2023
ddfe45e
More doc fixes
JMS55 Feb 9, 2023
6cb8b0c
Fix clippy ignore
JMS55 Feb 9, 2023
adedc79
Try fixing docs again
JMS55 Feb 9, 2023
8981638
Doc attribute fixes... again...
JMS55 Feb 9, 2023
13d131c
Actual attribute fixes, finally
JMS55 Feb 9, 2023
93d9042
Address some PR feedback
JMS55 Feb 9, 2023
86372b1
Only create one sampler
JMS55 Feb 9, 2023
80b7ce2
Misc refactor
JMS55 Feb 9, 2023
b7e0951
Add bloom present descriptions.
JMS55 Feb 9, 2023
9b1cdad
Misc doc fix
JMS55 Feb 9, 2023
98060e8
Update crates/bevy_core_pipeline/src/bloom/bloom.wgsl
JMS55 Feb 12, 2023
24af9c6
Update crates/bevy_core_pipeline/src/bloom/bloom.wgsl
JMS55 Feb 12, 2023
26a503b
Apply PR feedback
JMS55 Feb 13, 2023
63630e8
Update crates/bevy_core_pipeline/src/bloom/bloom.wgsl
JMS55 Feb 14, 2023
c624b06
Remove ACES tonemapping
JMS55 Feb 16, 2023
61391d4
Merge commit 'b24ed8bb0cf5567fd2467751d673754d102b47e4' into bloom-tr…
JMS55 Feb 17, 2023
129f334
Update crates/bevy_core_pipeline/src/bloom/settings.rs
JMS55 Feb 18, 2023
7a02aae
Merge commit 'e2c77fee039f72e1230c5d2083a26d5f4e0f13df' into bloom-tr…
JMS55 Feb 20, 2023
e047f41
Rebase
JMS55 Feb 20, 2023
0e2d92f
Fixes
JMS55 Feb 20, 2023
f480488
Simplify shader
JMS55 Feb 20, 2023
c7d722a
Update examples/2d/bloom_2d.rs
JMS55 Feb 22, 2023
c28ad59
Update examples/3d/bloom_3d.rs
JMS55 Feb 22, 2023
5c40029
Add bloom render debug group
JMS55 Feb 27, 2023
bcc6994
Reduce bloom artifacts
robtfm Mar 3, 2023
6cb4c99
Merge commit '73c1ab1d42965e84a1748af24de61eb768d98848' into bloom-tr…
JMS55 Mar 3, 2023
e72f201
Update crates/bevy_core_pipeline/src/bloom/mod.rs
JMS55 Mar 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ path = "examples/hello_world.rs"
hidden = true

# 2D Rendering
[[example]]
name = "bloom_2d"
path = "examples/2d/bloom_2d.rs"

[package.metadata.example.bloom_2d]
name = "2D Bloom"
description = "Illustrates bloom post-processing in 2d"
category = "2D Rendering"
wasm = false

[[example]]
name = "move_sprite"
path = "examples/2d/move_sprite.rs"
Expand Down Expand Up @@ -370,11 +380,11 @@ category = "3D Rendering"
wasm = true

[[example]]
name = "bloom"
path = "examples/3d/bloom.rs"
name = "bloom_3d"
path = "examples/3d/bloom_3d.rs"

[package.metadata.example.bloom]
name = "Bloom"
[package.metadata.example.bloom_3d]
name = "3D Bloom"
description = "Illustrates bloom configuration using HDR and emissive materials"
category = "3D Rendering"
wasm = false
Expand Down
224 changes: 117 additions & 107 deletions crates/bevy_core_pipeline/src/bloom/bloom.wgsl
Original file line number Diff line number Diff line change
@@ -1,138 +1,148 @@
// Bloom works by creating an intermediate texture with a bunch of mip levels, each half the size of the previous.
// You then downsample each mip (starting with the original texture) to the lower resolution mip under it, going in order.
// You then upsample each mip (starting from the smallest mip) and blend with the higher resolution mip above it (ending on the original texture).
//
// References:
// * [COD] - Next Generation Post Processing in Call of Duty - http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
// * [PBB] - Physically Based Bloom - https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom

#import bevy_core_pipeline::fullscreen_vertex_shader
#import bevy_core_pipeline::tonemapping

struct BloomUniforms {
threshold: f32,
knee: f32,
scale: f32,
intensity: f32,
struct BloomDownsamplingUniforms {
threshold_precomputations: vec4<f32>,
viewport: vec4<f32>,
};

@group(0) @binding(0)
var original: texture_2d<f32>;
var input_texture: texture_2d<f32>;
@group(0) @binding(1)
var original_sampler: sampler;
@group(0) @binding(2)
var<uniform> uniforms: BloomUniforms;
@group(0) @binding(3)
var up: texture_2d<f32>;
var s: sampler;

fn quadratic_threshold(color: vec4<f32>, threshold: f32, curve: vec3<f32>) -> vec4<f32> {
let br = max(max(color.r, color.g), color.b);

var rq: f32 = clamp(br - curve.x, 0.0, curve.y);
rq = curve.z * rq * rq;

return color * max(rq, br - threshold) / max(br, 0.0001);
#ifdef FIRST_DOWNSAMPLE
@group(0) @binding(2)
var<uniform> uniforms: BloomDownsamplingUniforms;

// https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/#3.4
fn soft_threshold(color: vec3<f32>) -> vec3<f32> {
let brightness = max(color.r, max(color.g, color.b));
var softness = brightness - uniforms.threshold_precomputations.y;
softness = clamp(softness, 0.0, uniforms.threshold_precomputations.z);
softness = softness * softness * uniforms.threshold_precomputations.w;
var contribution = max(brightness - uniforms.threshold_precomputations.x, softness);
contribution /= max(brightness, 0.00001); // Prevent division by 0
return color * contribution;
}
#endif

// http://graphicrants.blogspot.com/2013/12/tone-mapping.html
fn karis_average(color: vec3<f32>) -> f32 {
// Luminance calculated by gamma-correcting linear RGB to non-linear sRGB using pow(color, 1.0 / 2.2)
// and then calculating luminance based on Rec. 709 color primaries.
let luma = tonemapping_luminance(rgb_to_srgb_simple(color)) / 4.0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what luminance mapping to use here, but this is based on Rec.709. Apparently the values in tonemapping_luminance are based on the Rec.709 colour primaries. The article says that if RGB need to be mapped into the range, then the max colour component should be used. I'm not sure this is correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure either.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is faithful to the source material. Input signal is in linearized sRGB and luma is a weighted sum or sRGB components. We convert our image to sRGB and perform the luminance function (which is the same as luma but with gamma-corrected input). I don't know why this works considering we are trying to process physical light and avoid any human-based measurements like luminance and, even more so, luma, but this function seems to do the trick somehow. I am pretty positive this will lead to artifacts if the input color space changes (for example to ACES 2065-1) but at the moment it works. I think the approach the original developers took here was somewhat similar to the one described in this video (15:47 - 16:15)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clarity, and because I can't resolve these discussions, I consider this discussion resolved well enough for now.

JMS55 marked this conversation as resolved.
Show resolved Hide resolved
return 1.0 / (1.0 + luma);
}

// Samples original around the supplied uv using a filter.
//
// o o o
// o o
// o o o
// o o
// o o o
//
// This is used because it has a number of advantages that
// outweigh the cost of 13 samples that basically boil down
// to it looking better.
//
// These advantages are outlined in a youtube video by the Cherno:
// https://www.youtube.com/watch?v=tI70-HIc5ro
fn sample_13_tap(uv: vec2<f32>, scale: vec2<f32>) -> vec4<f32> {
let a = textureSample(original, original_sampler, uv + vec2<f32>(-1.0, -1.0) * scale);
let b = textureSample(original, original_sampler, uv + vec2<f32>(0.0, -1.0) * scale);
let c = textureSample(original, original_sampler, uv + vec2<f32>(1.0, -1.0) * scale);
let d = textureSample(original, original_sampler, uv + vec2<f32>(-0.5, -0.5) * scale);
let e = textureSample(original, original_sampler, uv + vec2<f32>(0.5, -0.5) * scale);
let f = textureSample(original, original_sampler, uv + vec2<f32>(-1.0, 0.0) * scale);
let g = textureSample(original, original_sampler, uv + vec2<f32>(0.0, 0.0) * scale);
let h = textureSample(original, original_sampler, uv + vec2<f32>(1.0, 0.0) * scale);
let i = textureSample(original, original_sampler, uv + vec2<f32>(-0.5, 0.5) * scale);
let j = textureSample(original, original_sampler, uv + vec2<f32>(0.5, 0.5) * scale);
let k = textureSample(original, original_sampler, uv + vec2<f32>(-1.0, 1.0) * scale);
let l = textureSample(original, original_sampler, uv + vec2<f32>(0.0, 1.0) * scale);
let m = textureSample(original, original_sampler, uv + vec2<f32>(1.0, 1.0) * scale);

let div = (1.0 / 4.0) * vec2<f32>(0.5, 0.125);

var o: vec4<f32> = (d + e + i + j) * div.x;
o = o + (a + b + g + f) * div.y;
o = o + (b + c + h + g) * div.y;
o = o + (f + g + l + k) * div.y;
o = o + (g + h + m + l) * div.y;

return o;
// [COD] slide 153
fn sample_input_13_tap(uv: vec2<f32>) -> vec3<f32> {
let texel_size = 1.0 / vec2<f32>(textureDimensions(input_texture));
let x = texel_size.x;
let y = texel_size.y;

let a = textureSample(input_texture, s, vec2<f32>(uv.x - 2.0 * x, uv.y + 2.0 * y)).rgb;
JMS55 marked this conversation as resolved.
Show resolved Hide resolved
let b = textureSample(input_texture, s, vec2<f32>(uv.x, uv.y + 2.0 * y)).rgb;
let c = textureSample(input_texture, s, vec2<f32>(uv.x + 2.0 * x, uv.y + 2.0 * y)).rgb;

let d = textureSample(input_texture, s, vec2<f32>(uv.x - 2.0 * x, uv.y)).rgb;
let e = textureSample(input_texture, s, vec2<f32>(uv.x, uv.y)).rgb;
let f = textureSample(input_texture, s, vec2<f32>(uv.x + 2.0 * x, uv.y)).rgb;

let g = textureSample(input_texture, s, vec2<f32>(uv.x - 2.0 * x, uv.y - 2.0 * y)).rgb;
let h = textureSample(input_texture, s, vec2<f32>(uv.x, uv.y - 2.0 * y)).rgb;
let i = textureSample(input_texture, s, vec2<f32>(uv.x + 2.0 * x, uv.y - 2.0 * y)).rgb;

let j = textureSample(input_texture, s, vec2<f32>(uv.x - x, uv.y + y)).rgb;
let k = textureSample(input_texture, s, vec2<f32>(uv.x + x, uv.y + y)).rgb;
let l = textureSample(input_texture, s, vec2<f32>(uv.x - x, uv.y - y)).rgb;
let m = textureSample(input_texture, s, vec2<f32>(uv.x + x, uv.y - y)).rgb;

#ifdef FIRST_DOWNSAMPLE
// [COD] slide 168
//
// The first downsample pass reads from the rendered frame which may exhibit
// 'fireflies' (individual very bright pixels) that should not cause the bloom effect.
//
// The first downsample uses a firefly-reduction method proposed by Brian Karis
// which takes a weighted-average of the samples to limit their luma range to [0, 1].
// This implementation matches the LearnOpenGL article [PBB].
var group0 = (a + b + d + e) * (0.125f / 4.0f);
var group1 = (b + c + e + f) * (0.125f / 4.0f);
var group2 = (d + e + g + h) * (0.125f / 4.0f);
var group3 = (e + f + h + i) * (0.125f / 4.0f);
var group4 = (j + k + l + m) * (0.5f / 4.0f);
group0 *= karis_average(group0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The article says:

This operation, when used to reduce fireflies, can also be thought of as a weighting function for each sample:
weight = 1 / (1 + luma)

For a weighted average, sum all samples and divide by the summed weights. The result will be the same as if the samples were tone mapped using T with range of 1, averaged, then inverse tone mapped using Tinverse.

I am not convinced the implementation in this PR is a valid simplification of that. For one, I don't think calculating the weight of a sum of samples will be the same as summing the weights of samples, when the formula for the weight is 1 / (1 + luma). (1 / a) + (1 / b) = (b / (a * b)) + (a / (a * b)) = (a + b) / (a * b).

I would say that I think to be correct, the firefly reduction would need to be applied to each sample individually. But then I realised that each sample is a bilinear interpolation of samples around it, so it is already in some sense a weighted-average value.

So, what is the goal here? To reduce fireflies. The PR considers each group, which are groups of four samples in various configurations at least 2 pixels apart, and then tries to eliminate fireflies in the average of those four. It seems incorrect regardless.

I'm not yet sure what a correct and/or acceptable solution is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems I missed part of the slides that discuss this approach. It was intentional to apply the firefly reduction to the averaged groups of 4 samples, so this is OK.

We should add a reference for it though. Had it been there I'd have looked at it. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The firefly reduction method used in this PR is a well tested one that is extensively described and, as far as I see, correctly implemented one. There is another article (Bonus material 2: Karis average) that shows code very similar to what we have

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that reference. We should probably add a link to it too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now there is a link at the top of the file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarity: I'm happy to consider this resolved.

group1 *= karis_average(group1);
group2 *= karis_average(group2);
group3 *= karis_average(group3);
group4 *= karis_average(group4);
return group0 + group1 + group2 + group3 + group4;
Comment on lines +81 to +91
Copy link
Contributor

@superdump superdump Feb 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the full filter weights should be applied after the luminance averaging. So each 4x4 box filter just needs the 4 samples averaging (i.e. / 4 or * 0.25), then the luminance adjustment is applied, then the 13-tap weighting is applied.

Suggested change
var group0 = (a + b + d + e) * (0.125f / 4.0f);
var group1 = (b + c + e + f) * (0.125f / 4.0f);
var group2 = (d + e + g + h) * (0.125f / 4.0f);
var group3 = (e + f + h + i) * (0.125f / 4.0f);
var group4 = (j + k + l + m) * (0.5f / 4.0f);
group0 *= karis_average(group0);
group1 *= karis_average(group1);
group2 *= karis_average(group2);
group3 *= karis_average(group3);
group4 *= karis_average(group4);
return group0 + group1 + group2 + group3 + group4;
var group0 = (a + b + d + e) * 0.25;
var group1 = (b + c + e + f) * 0.25;
var group2 = (d + e + g + h) * 0.25;
var group3 = (e + f + h + i) * 0.25;
var group4 = (j + k + l + m) * 0.25;
// Reduce fireflies using 'partial Karis averaging' that limits the luma to [0,1] as noted in slide 168
// of Next Generation Post-Processing in Call of Duty: Advanced Warfare
group0 *= karis_average(group0);
group1 *= karis_average(group1);
group2 *= karis_average(group2);
group3 *= karis_average(group3);
group4 *= karis_average(group4);
return (group0 + group1 + group2 + group3) * 0.125 + group4 * 0.5;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this thinks otherwise: https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom But maybe they did it wrong?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarity: given this matches the learnopengl source material, I'm happy to consider this resolved, pending feedback from Jimenez.

#else
var sample = (a + c + g + i) * 0.03125;
sample += (b + d + f + h) * 0.0625;
sample += (e + j + k + l + m) * 0.125;
return sample;
#endif
}

// Samples original using a 3x3 tent filter.
//
// NOTE: Use a 2x2 filter for better perf, but 3x3 looks better.
fn sample_original_3x3_tent(uv: vec2<f32>, scale: vec2<f32>) -> vec4<f32> {
let d = vec4<f32>(1.0, 1.0, -1.0, 0.0);
// [COD] slide 162
fn sample_input_3x3_tent(uv: vec2<f32>) -> vec3<f32> {
// Radius. Empirically chosen by and tweaked from the LearnOpenGL article.
let x = 0.004;
let y = 0.004;
JMS55 marked this conversation as resolved.
Show resolved Hide resolved
JMS55 marked this conversation as resolved.
Show resolved Hide resolved

let a = textureSample(input_texture, s, vec2<f32>(uv.x - x, uv.y + y)).rgb;
let b = textureSample(input_texture, s, vec2<f32>(uv.x, uv.y + y)).rgb;
let c = textureSample(input_texture, s, vec2<f32>(uv.x + x, uv.y + y)).rgb;

var s: vec4<f32> = textureSample(original, original_sampler, uv - d.xy * scale);
s = s + textureSample(original, original_sampler, uv - d.wy * scale) * 2.0;
s = s + textureSample(original, original_sampler, uv - d.zy * scale);
let d = textureSample(input_texture, s, vec2<f32>(uv.x - x, uv.y)).rgb;
let e = textureSample(input_texture, s, vec2<f32>(uv.x, uv.y)).rgb;
let f = textureSample(input_texture, s, vec2<f32>(uv.x + x, uv.y)).rgb;

s = s + textureSample(original, original_sampler, uv + d.zw * scale) * 2.0;
s = s + textureSample(original, original_sampler, uv) * 4.0;
s = s + textureSample(original, original_sampler, uv + d.xw * scale) * 2.0;
let g = textureSample(input_texture, s, vec2<f32>(uv.x - x, uv.y - y)).rgb;
let h = textureSample(input_texture, s, vec2<f32>(uv.x, uv.y - y)).rgb;
let i = textureSample(input_texture, s, vec2<f32>(uv.x + x, uv.y - y)).rgb;

s = s + textureSample(original, original_sampler, uv + d.zy * scale);
s = s + textureSample(original, original_sampler, uv + d.wy * scale) * 2.0;
s = s + textureSample(original, original_sampler, uv + d.xy * scale);
var sample = e * 0.25;
sample += (b + d + f + h) * 0.125;
sample += (a + c + g + i) * 0.0625;

return s / 16.0;
return sample;
}

#ifdef FIRST_DOWNSAMPLE
@fragment
fn downsample_prefilter(@location(0) output_uv: vec2<f32>) -> @location(0) vec4<f32> {
fn downsample_first(@location(0) output_uv: vec2<f32>) -> @location(0) vec4<f32> {
let sample_uv = uniforms.viewport.xy + output_uv * uniforms.viewport.zw;
let texel_size = 1.0 / vec2<f32>(textureDimensions(original));
var sample = sample_input_13_tap(sample_uv);
// Lower bound of 0.0001 is to avoid propagating multiplying by 0.0 through the
// downscaling and upscaling which would result in black boxes.
// The upper bound is to prevent NaNs.
sample = clamp(sample, vec3<f32>(0.0001), vec3<f32>(3.40282347E+38));

let scale = texel_size;
#ifdef USE_THRESHOLD
sample = soft_threshold(sample);
#endif

let curve = vec3<f32>(
uniforms.threshold - uniforms.knee,
uniforms.knee * 2.0,
0.25 / uniforms.knee,
);

var o: vec4<f32> = sample_13_tap(sample_uv, scale);

o = quadratic_threshold(o, uniforms.threshold, curve);
o = max(o, vec4<f32>(0.00001));

return o;
return vec4<f32>(sample, 1.0);
}
#endif

@fragment
fn downsample(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let texel_size = 1.0 / vec2<f32>(textureDimensions(original));

let scale = texel_size;

return sample_13_tap(uv, scale);
return vec4<f32>(sample_input_13_tap(uv), 1.0);
}

@fragment
fn upsample(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let texel_size = 1.0 / vec2<f32>(textureDimensions(original));

let upsample = sample_original_3x3_tent(uv, texel_size * uniforms.scale);
var color: vec4<f32> = textureSample(up, original_sampler, uv);
color = vec4<f32>(color.rgb + upsample.rgb, upsample.a);

return color;
}

@fragment
fn upsample_final(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let texel_size = 1.0 / vec2<f32>(textureDimensions(original));

let upsample = sample_original_3x3_tent(uv, texel_size * uniforms.scale);

return vec4<f32>(upsample.rgb * uniforms.intensity, upsample.a);
return vec4<f32>(sample_input_3x3_tent(uv), 1.0);
}
Loading