Skip to content

Commit 4c4f476

Browse files
jakobhellermannJMS55cartDGriffin91
committed
Bloom (#6397)
# Objective - Adds a bloom pass for HDR-enabled Camera3ds. - Supersedes (and all credit due to!) #3430 and #2876 ![image](https://user-images.githubusercontent.com/47158642/198698783-228edc00-20b5-4218-a613-331ccd474f38.png) ## Solution - A threshold is applied to isolate emissive samples, and then a series of downscale and upscaling passes are applied and composited together. - Bloom is applied to 2d or 3d Cameras with hdr: true and a BloomSettings component. --- ## Changelog - Added a `core_pipeline::bloom::BloomSettings` component. - Added `BloomNode` that runs between the main pass and tonemapping. - Added a `BloomPlugin` that is loaded as part of CorePipelinePlugin. - Added a bloom example project. Co-authored-by: JMS55 <[email protected]> Co-authored-by: Carter Anderson <[email protected]> Co-authored-by: DGriffin91 <[email protected]>
1 parent 2e653e5 commit 4c4f476

File tree

8 files changed

+1129
-4
lines changed

8 files changed

+1129
-4
lines changed

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,16 @@ description = "Illustrates spot lights"
313313
category = "3D Rendering"
314314
wasm = true
315315

316+
[[example]]
317+
name = "bloom"
318+
path = "examples/3d/bloom.rs"
319+
320+
[package.metadata.example.bloom]
321+
name = "Bloom"
322+
description = "Illustrates bloom configuration using HDR and emissive materials"
323+
category = "3D Rendering"
324+
wasm = false
325+
316326
[[example]]
317327
name = "load_gltf"
318328
path = "examples/3d/load_gltf.rs"

crates/bevy_core_pipeline/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev" }
2525
bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev" }
2626
bevy_render = { path = "../bevy_render", version = "0.9.0-dev" }
2727
bevy_transform = { path = "../bevy_transform", version = "0.9.0-dev" }
28+
bevy_math = { path = "../bevy_math", version = "0.9.0-dev" }
2829
bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" }
2930

3031
serde = { version = "1", features = ["derive"] }
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#import bevy_core_pipeline::fullscreen_vertex_shader
2+
3+
struct BloomUniforms {
4+
threshold: f32,
5+
knee: f32,
6+
scale: f32,
7+
intensity: f32,
8+
};
9+
10+
@group(0) @binding(0)
11+
var original: texture_2d<f32>;
12+
@group(0) @binding(1)
13+
var original_sampler: sampler;
14+
@group(0) @binding(2)
15+
var<uniform> uniforms: BloomUniforms;
16+
@group(0) @binding(3)
17+
var up: texture_2d<f32>;
18+
19+
fn quadratic_threshold(color: vec4<f32>, threshold: f32, curve: vec3<f32>) -> vec4<f32> {
20+
let br = max(max(color.r, color.g), color.b);
21+
22+
var rq: f32 = clamp(br - curve.x, 0.0, curve.y);
23+
rq = curve.z * rq * rq;
24+
25+
return color * max(rq, br - threshold) / max(br, 0.0001);
26+
}
27+
28+
// Samples original around the supplied uv using a filter.
29+
//
30+
// o o o
31+
// o o
32+
// o o o
33+
// o o
34+
// o o o
35+
//
36+
// This is used because it has a number of advantages that
37+
// outweigh the cost of 13 samples that basically boil down
38+
// to it looking better.
39+
//
40+
// These advantages are outlined in a youtube video by the Cherno:
41+
// https://www.youtube.com/watch?v=tI70-HIc5ro
42+
fn sample_13_tap(uv: vec2<f32>, scale: vec2<f32>) -> vec4<f32> {
43+
let a = textureSample(original, original_sampler, uv + vec2<f32>(-1.0, -1.0) * scale);
44+
let b = textureSample(original, original_sampler, uv + vec2<f32>(0.0, -1.0) * scale);
45+
let c = textureSample(original, original_sampler, uv + vec2<f32>(1.0, -1.0) * scale);
46+
let d = textureSample(original, original_sampler, uv + vec2<f32>(-0.5, -0.5) * scale);
47+
let e = textureSample(original, original_sampler, uv + vec2<f32>(0.5, -0.5) * scale);
48+
let f = textureSample(original, original_sampler, uv + vec2<f32>(-1.0, 0.0) * scale);
49+
let g = textureSample(original, original_sampler, uv + vec2<f32>(0.0, 0.0) * scale);
50+
let h = textureSample(original, original_sampler, uv + vec2<f32>(1.0, 0.0) * scale);
51+
let i = textureSample(original, original_sampler, uv + vec2<f32>(-0.5, 0.5) * scale);
52+
let j = textureSample(original, original_sampler, uv + vec2<f32>(0.5, 0.5) * scale);
53+
let k = textureSample(original, original_sampler, uv + vec2<f32>(-1.0, 1.0) * scale);
54+
let l = textureSample(original, original_sampler, uv + vec2<f32>(0.0, 1.0) * scale);
55+
let m = textureSample(original, original_sampler, uv + vec2<f32>(1.0, 1.0) * scale);
56+
57+
let div = (1.0 / 4.0) * vec2<f32>(0.5, 0.125);
58+
59+
var o: vec4<f32> = (d + e + i + j) * div.x;
60+
o = o + (a + b + g + f) * div.y;
61+
o = o + (b + c + h + g) * div.y;
62+
o = o + (f + g + l + k) * div.y;
63+
o = o + (g + h + m + l) * div.y;
64+
65+
return o;
66+
}
67+
68+
// Samples original using a 3x3 tent filter.
69+
//
70+
// NOTE: Use a 2x2 filter for better perf, but 3x3 looks better.
71+
fn sample_original_3x3_tent(uv: vec2<f32>, scale: vec2<f32>) -> vec4<f32> {
72+
let d = vec4<f32>(1.0, 1.0, -1.0, 0.0);
73+
74+
var s: vec4<f32> = textureSample(original, original_sampler, uv - d.xy * scale);
75+
s = s + textureSample(original, original_sampler, uv - d.wy * scale) * 2.0;
76+
s = s + textureSample(original, original_sampler, uv - d.zy * scale);
77+
78+
s = s + textureSample(original, original_sampler, uv + d.zw * scale) * 2.0;
79+
s = s + textureSample(original, original_sampler, uv) * 4.0;
80+
s = s + textureSample(original, original_sampler, uv + d.xw * scale) * 2.0;
81+
82+
s = s + textureSample(original, original_sampler, uv + d.zy * scale);
83+
s = s + textureSample(original, original_sampler, uv + d.wy * scale) * 2.0;
84+
s = s + textureSample(original, original_sampler, uv + d.xy * scale);
85+
86+
return s / 16.0;
87+
}
88+
89+
@fragment
90+
fn downsample_prefilter(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
91+
let texel_size = 1.0 / vec2<f32>(textureDimensions(original));
92+
93+
let scale = texel_size;
94+
95+
let curve = vec3<f32>(
96+
uniforms.threshold - uniforms.knee,
97+
uniforms.knee * 2.0,
98+
0.25 / uniforms.knee,
99+
);
100+
101+
var o: vec4<f32> = sample_13_tap(uv, scale);
102+
103+
o = quadratic_threshold(o, uniforms.threshold, curve);
104+
o = max(o, vec4<f32>(0.00001));
105+
106+
return o;
107+
}
108+
109+
@fragment
110+
fn downsample(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
111+
let texel_size = 1.0 / vec2<f32>(textureDimensions(original));
112+
113+
let scale = texel_size;
114+
115+
return sample_13_tap(uv, scale);
116+
}
117+
118+
@fragment
119+
fn upsample(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
120+
let texel_size = 1.0 / vec2<f32>(textureDimensions(original));
121+
122+
let upsample = sample_original_3x3_tent(uv, texel_size * uniforms.scale);
123+
var color: vec4<f32> = textureSample(up, original_sampler, uv);
124+
color = vec4<f32>(color.rgb + upsample.rgb, upsample.a);
125+
126+
return color;
127+
}
128+
129+
@fragment
130+
fn upsample_final(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
131+
let texel_size = 1.0 / vec2<f32>(textureDimensions(original));
132+
133+
let upsample = sample_original_3x3_tent(uv, texel_size * uniforms.scale);
134+
135+
return vec4<f32>(upsample.rgb * uniforms.intensity, upsample.a);
136+
}

0 commit comments

Comments
 (0)