From 425c7ae65ef3206ea17aca7c1b9cbc08e5e7d1ba Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Tue, 15 Nov 2022 14:25:19 -0500 Subject: [PATCH 001/127] Fix bloom transparency --- crates/bevy_core_pipeline/src/bloom/bloom.wgsl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/bloom.wgsl b/crates/bevy_core_pipeline/src/bloom/bloom.wgsl index 0e6addeef3ee3..04fe8c5759d84 100644 --- a/crates/bevy_core_pipeline/src/bloom/bloom.wgsl +++ b/crates/bevy_core_pipeline/src/bloom/bloom.wgsl @@ -16,7 +16,7 @@ var uniforms: BloomUniforms; @group(0) @binding(3) var up: texture_2d; -fn quadratic_threshold(color: vec4, threshold: f32, curve: vec3) -> vec4 { +fn quadratic_threshold(color: vec3, threshold: f32, curve: vec3) -> vec3 { let br = max(max(color.r, color.g), color.b); var rq: f32 = clamp(br - curve.x, 0.0, curve.y); @@ -62,7 +62,7 @@ fn sample_13_tap(uv: vec2, scale: vec2) -> vec4 { o = o + (f + g + l + k) * div.y; o = o + (g + h + m + l) * div.y; - return o; + return vec4(o.rgb, g.a); } // Samples original using a 3x3 tent filter. @@ -71,19 +71,23 @@ fn sample_13_tap(uv: vec2, scale: vec2) -> vec4 { fn sample_original_3x3_tent(uv: vec2, scale: vec2) -> vec4 { let d = vec4(1.0, 1.0, -1.0, 0.0); + let o = textureSample(original, original_sampler, uv); + var s: vec4 = 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); s = s + textureSample(original, original_sampler, uv + d.zw * scale) * 2.0; - s = s + textureSample(original, original_sampler, uv) * 4.0; + s = s + o * 4.0; s = s + textureSample(original, original_sampler, uv + d.xw * scale) * 2.0; 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); - return s / 16.0; + s /= 16.0; + + return vec4(s.rgb, o.a); } @fragment @@ -100,10 +104,10 @@ fn downsample_prefilter(@location(0) uv: vec2) -> @location(0) vec4 { var o: vec4 = sample_13_tap(uv, scale); - o = quadratic_threshold(o, uniforms.threshold, curve); - o = max(o, vec4(0.00001)); + var q = quadratic_threshold(o.rgb, uniforms.threshold, curve); + q = max(q, vec3(0.00001)); - return o; + return vec4(q, o.a); } @fragment From 1dd39be7a50659b01a84e449c7bf5a3a9f7b7406 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Thu, 17 Nov 2022 16:38:04 -0500 Subject: [PATCH 002/127] Default to ACES tonemapping --- .../src/tonemapping/tonemapping.wgsl | 2 +- .../src/tonemapping/tonemapping_shared.wgsl | 12 ++++++- crates/bevy_pbr/src/render/pbr_functions.wgsl | 33 ++++++++----------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl index a4bc5d4be4364..01bfe2c3d9708 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -10,7 +10,7 @@ var hdr_sampler: sampler; fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv); - var output_rgb = reinhard_luminance(hdr_color.rgb); + var output_rgb = aces_filmic(hdr_color.rgb); #ifdef DEBAND_DITHER output_rgb = pow(output_rgb.rgb, vec3(1.0 / 2.2)); diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl index deafac0750d84..7fd96b7680c88 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl @@ -1,5 +1,15 @@ #define_import_path bevy_core_pipeline::tonemapping +// from https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve +fn aces_filmic(color: vec3) -> vec3 { + let a = 2.51; + let b = 0.03; + let c = 2.43; + let d = 0.59; + let e = 0.14; + return saturate((color * (a * color + b)) / (color * (c * color + d) + e)); +} + // from https://64.github.io/tonemapping/ // reinhard on RGB oversaturates colors fn tonemapping_reinhard(color: vec3) -> vec3 { @@ -34,4 +44,4 @@ fn screen_space_dither(frag_coord: vec2) -> vec3 { var dither = vec3(dot(vec2(171.0, 231.0), frag_coord)).xxx; dither = fract(dither.rgb / vec3(103.0, 71.0, 97.0)); return (dither - 0.5) / 255.0; -} \ No newline at end of file +} diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 4f4c5495dbaa3..261dfe9564711 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -5,13 +5,13 @@ #endif -fn alpha_discard(material: StandardMaterial, output_color: vec4) -> vec4{ +fn alpha_discard(material: StandardMaterial, output_color: vec4) -> vec4 { var color = output_color; - if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) { + if (material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u { // NOTE: If rendering as opaque, alpha should be ignored so set to 1.0 color.a = 1.0; - } else if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) { - if (color.a >= material.alpha_cutoff) { + } else if (material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u { + if color.a >= material.alpha_cutoff { // NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque color.a = 1.0; } else { @@ -75,7 +75,7 @@ fn apply_normal_mapping( #ifdef STANDARDMATERIAL_NORMAL_MAP // Nt is the tangent-space normal. var Nt = textureSample(normal_map_texture, normal_map_sampler, uv).rgb; - if ((standard_material_flags & STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u) { + if (standard_material_flags & STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u { // Only use the xy components and derive z for 2-component normal maps. Nt = vec3(Nt.rg * 2.0 - 1.0, 0.0); Nt.z = sqrt(1.0 - Nt.x * Nt.x - Nt.y * Nt.y); @@ -83,7 +83,7 @@ fn apply_normal_mapping( Nt = Nt * 2.0 - 1.0; } // Normal maps authored for DirectX require flipping the y component - if ((standard_material_flags & STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u) { + if (standard_material_flags & STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u { Nt.y = -Nt.y; } // NOTE: The mikktspace method of normal mapping applies maps the tangent-space normal from @@ -106,7 +106,7 @@ fn calculate_view( is_orthographic: bool, ) -> vec3 { var V: vec3; - if (is_orthographic) { + if is_orthographic { // Orthographic view vector V = normalize(vec3(view.view_proj[0].z, view.view_proj[1].z, view.view_proj[2].z)); } else { @@ -198,8 +198,7 @@ fn pbr( let light_id = get_light_id(i); let light = point_lights.data[light_id]; var shadow: f32 = 1.0; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + if (mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u { shadow = fetch_point_shadow(light_id, in.world_position, in.world_normal); } let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); @@ -211,8 +210,7 @@ fn pbr( let light_id = get_light_id(i); let light = point_lights.data[light_id]; var shadow: f32 = 1.0; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + if (mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u { shadow = fetch_spot_shadow(light_id, in.world_position, in.world_normal); } let light_contrib = spot_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); @@ -223,8 +221,7 @@ fn pbr( for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) { let light = lights.directional_lights[i]; var shadow: f32 = 1.0; - if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u - && (light.flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { + if (mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (light.flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u { shadow = fetch_directional_shadow(i, in.world_position, in.world_normal); } let light_contrib = directional_light(light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); @@ -235,10 +232,9 @@ fn pbr( let specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV); output_color = vec4( - light_accum + - (diffuse_ambient + specular_ambient) * lights.ambient_color.rgb * occlusion + - emissive.rgb * output_color.a, - output_color.a); + light_accum + (diffuse_ambient + specular_ambient) * lights.ambient_color.rgb * occlusion + emissive.rgb * output_color.a, + output_color.a + ); output_color = cluster_debug_visualization( output_color, @@ -254,7 +250,7 @@ fn pbr( #ifdef TONEMAP_IN_SHADER fn tone_mapping(in: vec4) -> vec4 { // tone_mapping - return vec4(reinhard_luminance(in.rgb), in.a); + return vec4(aces_filmic(in.rgb), in.a); // Gamma correction. // Not needed with sRGB buffer @@ -267,4 +263,3 @@ fn dither(color: vec4, pos: vec2) -> vec4 { return vec4(color.rgb + screen_space_dither(pos.xy), color.a); } #endif - From 7704e12a67ba846dd1619ab6d029bf99edd7b680 Mon Sep 17 00:00:00 2001 From: JMS55 <47158642+JMS55@users.noreply.github.com> Date: Thu, 17 Nov 2022 21:32:03 -0500 Subject: [PATCH 003/127] WIP: Fix bloom --- .../bevy_core_pipeline/src/bloom/bloom.wgsl | 158 ++++----- crates/bevy_core_pipeline/src/bloom/mod.rs | 326 ++++++++---------- examples/3d/bloom.rs | 116 +++---- 3 files changed, 276 insertions(+), 324 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/bloom.wgsl b/crates/bevy_core_pipeline/src/bloom/bloom.wgsl index 04fe8c5759d84..d583a0be8e00e 100644 --- a/crates/bevy_core_pipeline/src/bloom/bloom.wgsl +++ b/crates/bevy_core_pipeline/src/bloom/bloom.wgsl @@ -1,20 +1,21 @@ +// Reference: https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom + #import bevy_core_pipeline::fullscreen_vertex_shader -struct BloomUniforms { +struct BloomSettings { + intensity: f32, threshold: f32, knee: f32, - scale: f32, - intensity: f32, }; @group(0) @binding(0) -var original: texture_2d; +var input_texture: texture_2d; @group(0) @binding(1) -var original_sampler: sampler; +var s: sampler; @group(0) @binding(2) -var uniforms: BloomUniforms; +var settings: BloomSettings; @group(0) @binding(3) -var up: texture_2d; +var main_pass_texture: texture_2d; fn quadratic_threshold(color: vec3, threshold: f32, curve: vec3) -> vec3 { let br = max(max(color.r, color.g), color.b); @@ -25,116 +26,97 @@ fn quadratic_threshold(color: vec3, threshold: f32, curve: vec3) -> ve return color * max(rq, br - threshold) / max(br, 0.0001); } -// 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, scale: vec2) -> vec4 { - let a = textureSample(original, original_sampler, uv + vec2(-1.0, -1.0) * scale); - let b = textureSample(original, original_sampler, uv + vec2(0.0, -1.0) * scale); - let c = textureSample(original, original_sampler, uv + vec2(1.0, -1.0) * scale); - let d = textureSample(original, original_sampler, uv + vec2(-0.5, -0.5) * scale); - let e = textureSample(original, original_sampler, uv + vec2(0.5, -0.5) * scale); - let f = textureSample(original, original_sampler, uv + vec2(-1.0, 0.0) * scale); - let g = textureSample(original, original_sampler, uv + vec2(0.0, 0.0) * scale); - let h = textureSample(original, original_sampler, uv + vec2(1.0, 0.0) * scale); - let i = textureSample(original, original_sampler, uv + vec2(-0.5, 0.5) * scale); - let j = textureSample(original, original_sampler, uv + vec2(0.5, 0.5) * scale); - let k = textureSample(original, original_sampler, uv + vec2(-1.0, 1.0) * scale); - let l = textureSample(original, original_sampler, uv + vec2(0.0, 1.0) * scale); - let m = textureSample(original, original_sampler, uv + vec2(1.0, 1.0) * scale); - - let div = (1.0 / 4.0) * vec2(0.5, 0.125); - - var o: vec4 = (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 vec4(o.rgb, g.a); +fn sample_input_13_tap(uv: vec2) -> vec3 { + let texel_size = 1.0 / vec2(textureDimensions(input_texture)); + let x = texel_size.x; + let y = texel_size.y; + + let a = textureSample(input_texture, s, vec2(uv.x - 2.0 * x, uv.y + 2.0 * y)).rgb; + let b = textureSample(input_texture, s, vec2(uv.x, uv.y + 2.0 * y)).rgb; + let c = textureSample(input_texture, s, vec2(uv.x + 2.0 * x, uv.y + 2.0 * y)).rgb; + + let d = textureSample(input_texture, s, vec2(uv.x - 2.0 * x, uv.y)).rgb; + let e = textureSample(input_texture, s, vec2(uv.x, uv.y)).rgb; + let f = textureSample(input_texture, s, vec2(uv.x + 2.0 * x, uv.y)).rgb; + + let g = textureSample(input_texture, s, vec2(uv.x - 2.0 * x, uv.y - 2.0 * y)).rgb; + let h = textureSample(input_texture, s, vec2(uv.x, uv.y - 2.0 * y)).rgb; + let i = textureSample(input_texture, s, vec2(uv.x + 2.0 * x, uv.y - 2.0 * y)).rgb; + + let j = textureSample(input_texture, s, vec2(uv.x - x, uv.y + y)).rgb; + let k = textureSample(input_texture, s, vec2(uv.x + x, uv.y + y)).rgb; + let l = textureSample(input_texture, s, vec2(uv.x - x, uv.y - y)).rgb; + let m = textureSample(input_texture, s, vec2(uv.x + x, uv.y - y)).rgb; + + var sample = e * 0.125; + sample += (a + c + g + i) * 0.03125; + sample += (b + d + f + h) * 0.0625; + sample += (j + k + l + m) * 0.125; + return sample; } -// 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, scale: vec2) -> vec4 { +fn sample_input_3x3_tent(uv: vec2) -> vec3 { let d = vec4(1.0, 1.0, -1.0, 0.0); - let o = textureSample(original, original_sampler, uv); - - var s: vec4 = 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); + var sample: vec3 = textureSample(input_texture, s, uv - d.xy).rgb; + sample += textureSample(input_texture, s, uv - d.wy).rgb * 2.0; + sample += textureSample(input_texture, s, uv - d.zy).rgb; - s = s + textureSample(original, original_sampler, uv + d.zw * scale) * 2.0; - s = s + o * 4.0; - s = s + textureSample(original, original_sampler, uv + d.xw * scale) * 2.0; + sample += textureSample(input_texture, s, uv + d.zw).rgb * 2.0; + sample += textureSample(input_texture, s, uv).rgb * 4.0; + sample += textureSample(input_texture, s, uv + d.xw).rgb * 2.0; - 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); + sample += textureSample(input_texture, s, uv + d.zy).rgb; + sample += textureSample(input_texture, s, uv + d.wy).rgb * 2.0; + sample += textureSample(input_texture, s, uv + d.xy).rgb; - s /= 16.0; - - return vec4(s.rgb, o.a); + return sample / 16.0; } @fragment -fn downsample_prefilter(@location(0) uv: vec2) -> @location(0) vec4 { - let texel_size = 1.0 / vec2(textureDimensions(original)); +fn downsample_first(@location(0) uv: vec2) -> @location(0) vec4 { + // let texel_size = 1.0 / vec2(textureDimensions(original)); + + // let scale = texel_size; - let scale = texel_size; + // let curve = vec3( + // settings.threshold - settings.knee, + // settings.knee * 2.0, + // 0.25 / settings.knee, + // ); - let curve = vec3( - uniforms.threshold - uniforms.knee, - uniforms.knee * 2.0, - 0.25 / uniforms.knee, - ); + // var o: vec3 = sample_13_tap(uv, scale); - var o: vec4 = sample_13_tap(uv, scale); + // var q = quadratic_threshold(o.rgb, settings.threshold, curve); + // q = max(q, vec3(0.00001)); - var q = quadratic_threshold(o.rgb, uniforms.threshold, curve); - q = max(q, vec3(0.00001)); + // return vec4(q, 0.0); - return vec4(q, o.a); + return vec4(sample_input_13_tap(uv), 1.0); } @fragment fn downsample(@location(0) uv: vec2) -> @location(0) vec4 { - let texel_size = 1.0 / vec2(textureDimensions(original)); - - let scale = texel_size; - - return sample_13_tap(uv, scale); + return vec4(sample_input_13_tap(uv), 1.0); } @fragment fn upsample(@location(0) uv: vec2) -> @location(0) vec4 { - let texel_size = 1.0 / vec2(textureDimensions(original)); + // let texel_size = 1.0 / vec2(textureDimensions(original)); - let upsample = sample_original_3x3_tent(uv, texel_size * uniforms.scale); - var color: vec4 = textureSample(up, original_sampler, uv); - color = vec4(color.rgb + upsample.rgb, upsample.a); + // let upsample = sample_original_3x3_tent(uv, texel_size); + // var color: vec4 = textureSample(up, original_sampler, uv); + // color = vec4(color.rgb + upsample.rgb, 0.0); - return color; + return vec4(sample_input_3x3_tent(uv), 1.0); } @fragment fn upsample_final(@location(0) uv: vec2) -> @location(0) vec4 { - let texel_size = 1.0 / vec2(textureDimensions(original)); + let main_pass_sample = textureSample(main_pass_texture, s, uv); + let bloom_sample = sample_input_13_tap(uv); - let upsample = sample_original_3x3_tent(uv, texel_size * uniforms.scale); + let sample = mix(main_pass_sample.rgb, bloom_sample, settings.intensity); - return vec4(upsample.rgb * uniforms.intensity, upsample.a); + return vec4(sample, main_pass_sample.a); } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index f94232ec156e0..ea5796bd4a21e 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -62,6 +62,7 @@ impl Plugin for BloomPlugin { .add_system_to_stage(RenderStage::Prepare, prepare_bloom_uniforms) .add_system_to_stage(RenderStage::Queue, queue_bloom_bind_groups); + // Add bloom to the 3d render graph { let bloom_node = BloomNode::new(&mut render_app.world); let mut graph = render_app.world.resource_mut::(); @@ -92,6 +93,7 @@ impl Plugin for BloomPlugin { .unwrap(); } + // Add bloom to the 2d render graph { let bloom_node = BloomNode::new(&mut render_app.world); let mut graph = render_app.world.resource_mut::(); @@ -124,42 +126,45 @@ impl Plugin for BloomPlugin { } } +/// ## Usage +/// /// Applies a bloom effect to a HDR-enabled 2d or 3d camera. /// /// Bloom causes bright objects to "glow", emitting a halo of light around them. /// /// Often used in conjunction with `bevy_pbr::StandardMaterial::emissive`. /// -/// Note: This light is not "real" in the way directional or point lights are. +/// Should be used along with a tonemapping function that maps each RGB component separately, such as ACES Filmic. +/// +/// ## Note +/// +/// This light is not "real" in the way directional or point lights are. /// /// Bloom will not cast shadows or bend around other objects - it is purely a post-processing /// effect overlaid on top of the already-rendered scene. /// /// See also . -#[derive(Component, Reflect, Clone)] +#[derive(Component, ShaderType, Reflect, Clone)] pub struct BloomSettings { - /// Baseline of the threshold curve (default: 1.0). + /// Intensity of the bloom effect (default: 1.0). + pub intensity: f32, + + /// Baseline of the threshold curve (default: 0.0). /// /// RGB values under the threshold curve will not have bloom applied. + /// Using a threshold is not physically accurate, but may fit better with your artistic direction. pub threshold: f32, - /// Knee of the threshold curve (default: 0.1). + /// Knee of the threshold curve (default: 0.0). pub knee: f32, - - /// Scale used when upsampling (default: 1.0). - pub scale: f32, - - /// Intensity of the bloom effect (default: 0.3). - pub intensity: f32, } impl Default for BloomSettings { fn default() -> Self { Self { - threshold: 1.0, - knee: 0.1, - scale: 1.0, intensity: 0.3, + threshold: 0.0, + knee: 0.0, } } } @@ -167,8 +172,7 @@ impl Default for BloomSettings { struct BloomNode { view_query: QueryState<( &'static ExtractedCamera, - &'static ViewTarget, - &'static BloomTextures, + &'static BloomTexture, &'static BloomBindGroups, &'static BloomUniformIndex, )>, @@ -205,18 +209,18 @@ impl Node for BloomNode { let pipelines = world.resource::(); let pipeline_cache = world.resource::(); let view_entity = graph.get_input_entity(Self::IN_VIEW)?; - let (camera, view_target, textures, bind_groups, uniform_index) = + let (camera, bloom_texture, bind_groups, uniform_index) = match self.view_query.get_manual(world, view_entity) { Ok(result) => result, _ => return Ok(()), }; let ( - downsampling_prefilter_pipeline, + downsampling_first_pipeline, downsampling_pipeline, upsampling_pipeline, upsampling_final_pipeline, ) = match ( - pipeline_cache.get_render_pipeline(pipelines.downsampling_prefilter_pipeline), + pipeline_cache.get_render_pipeline(pipelines.downsampling_first_pipeline), pipeline_cache.get_render_pipeline(pipelines.downsampling_pipeline), pipeline_cache.get_render_pipeline(pipelines.upsampling_pipeline), pipeline_cache.get_render_pipeline(pipelines.upsampling_final_pipeline), @@ -226,11 +230,11 @@ impl Node for BloomNode { }; { - let view = &BloomTextures::texture_view(&textures.texture_a, 0); - let mut prefilter_pass = + let view = &bloom_texture.view(0); + let mut downsampling_first_pass = TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( &RenderPassDescriptor { - label: Some("bloom_prefilter_pass"), + label: Some("bloom_downsampling_first_pass"), color_attachments: &[Some(RenderPassColorAttachment { view, resolve_target: None, @@ -239,16 +243,20 @@ impl Node for BloomNode { depth_stencil_attachment: None, }, )); - prefilter_pass.set_render_pipeline(downsampling_prefilter_pipeline); - prefilter_pass.set_bind_group(0, &bind_groups.prefilter_bind_group, &[uniform_index.0]); + downsampling_first_pass.set_render_pipeline(downsampling_first_pipeline); + downsampling_first_pass.set_bind_group( + 0, + &bind_groups.downsampling_first_bind_group, + &[uniform_index.0], + ); if let Some(viewport) = camera.viewport.as_ref() { - prefilter_pass.set_camera_viewport(viewport); + downsampling_first_pass.set_camera_viewport(viewport); } - prefilter_pass.draw(0..3, 0..1); + downsampling_first_pass.draw(0..3, 0..1); } - for mip in 1..textures.mip_count { - let view = &BloomTextures::texture_view(&textures.texture_a, mip); + for mip in 1..bloom_texture.mip_count { + let view = &bloom_texture.view(mip); let mut downsampling_pass = TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( &RenderPassDescriptor { @@ -273,8 +281,8 @@ impl Node for BloomNode { downsampling_pass.draw(0..3, 0..1); } - for mip in (1..textures.mip_count).rev() { - let view = &BloomTextures::texture_view(&textures.texture_b, mip - 1); + for mip in (1..bloom_texture.mip_count).rev() { + let view = &bloom_texture.view(mip - 1); let mut upsampling_pass = TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( &RenderPassDescriptor { @@ -282,7 +290,10 @@ impl Node for BloomNode { color_attachments: &[Some(RenderPassColorAttachment { view, resolve_target: None, - ops: Operations::default(), + ops: Operations { + load: LoadOp::Load, + store: true, + }, })], depth_stencil_attachment: None, }, @@ -290,7 +301,7 @@ impl Node for BloomNode { upsampling_pass.set_render_pipeline(upsampling_pipeline); upsampling_pass.set_bind_group( 0, - &bind_groups.upsampling_bind_groups[mip as usize - 1], + &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - mip - 1) as usize], &[uniform_index.0], ); if let Some(viewport) = camera.viewport.as_ref() { @@ -304,12 +315,11 @@ impl Node for BloomNode { TrackedRenderPass::new(render_context.command_encoder.begin_render_pass( &RenderPassDescriptor { label: Some("bloom_upsampling_final_pass"), - color_attachments: &[Some(view_target.get_unsampled_color_attachment( - Operations { - load: LoadOp::Load, - store: true, - }, - ))], + color_attachments: &[Some(RenderPassColorAttachment { + view: &bind_groups.view_target, + resolve_target: None, + ops: Operations::default(), + })], depth_stencil_attachment: None, }, )); @@ -331,13 +341,15 @@ impl Node for BloomNode { #[derive(Resource)] struct BloomPipelines { - downsampling_prefilter_pipeline: CachedRenderPipelineId, + downsampling_first_pipeline: CachedRenderPipelineId, downsampling_pipeline: CachedRenderPipelineId, upsampling_pipeline: CachedRenderPipelineId, upsampling_final_pipeline: CachedRenderPipelineId, + + main_bind_group_layout: BindGroupLayout, + upsampling_final_bind_group_layout: BindGroupLayout, + sampler: Sampler, - downsampling_bind_group_layout: BindGroupLayout, - upsampling_bind_group_layout: BindGroupLayout, } impl FromWorld for BloomPipelines { @@ -352,11 +364,11 @@ impl FromWorld for BloomPipelines { ..Default::default() }); - let downsampling_bind_group_layout = + let main_bind_group_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("bloom_downsampling_bind_group_layout"), + label: Some("bloom_main_bind_group_layout"), entries: &[ - // Upsampled input texture (downsampled for final upsample) + // Input texture (higher resolution for downsample, lower resolution for upsample) BindGroupLayoutEntry { binding: 0, ty: BindingType::Texture { @@ -380,7 +392,7 @@ impl FromWorld for BloomPipelines { ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, - min_binding_size: Some(BloomUniform::min_size()), + min_binding_size: Some(BloomSettings::min_size()), }, visibility: ShaderStages::FRAGMENT, count: None, @@ -388,11 +400,11 @@ impl FromWorld for BloomPipelines { ], }); - let upsampling_bind_group_layout = + let upsampling_final_bind_group_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("bloom_upsampling_bind_group_layout"), + label: Some("bloom_upsampling_final_bind_group_layout"), entries: &[ - // Downsampled input texture + // Main pass texture BindGroupLayoutEntry { binding: 0, ty: BindingType::Texture { @@ -416,12 +428,12 @@ impl FromWorld for BloomPipelines { ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, - min_binding_size: Some(BloomUniform::min_size()), + min_binding_size: Some(BloomSettings::min_size()), }, visibility: ShaderStages::FRAGMENT, count: None, }, - // Upsampled input texture + // Second to last upsample result texture (BloomTexture mip 0) BindGroupLayoutEntry { binding: 3, ty: BindingType::Texture { @@ -437,15 +449,15 @@ impl FromWorld for BloomPipelines { let mut pipeline_cache = world.resource_mut::(); - let downsampling_prefilter_pipeline = + let downsampling_first_pipeline = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { - label: Some("bloom_downsampling_prefilter_pipeline".into()), - layout: Some(vec![downsampling_bind_group_layout.clone()]), + label: Some("bloom_downsampling_first_pipeline".into()), + layout: Some(vec![main_bind_group_layout.clone()]), vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { shader: BLOOM_SHADER_HANDLE.typed::(), shader_defs: vec![], - entry_point: "downsample_prefilter".into(), + entry_point: "downsample_first".into(), targets: vec![Some(ColorTargetState { format: ViewTarget::TEXTURE_FORMAT_HDR, blend: None, @@ -460,7 +472,7 @@ impl FromWorld for BloomPipelines { let downsampling_pipeline = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { label: Some("bloom_downsampling_pipeline".into()), - layout: Some(vec![downsampling_bind_group_layout.clone()]), + layout: Some(vec![main_bind_group_layout.clone()]), vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { shader: BLOOM_SHADER_HANDLE.typed::(), @@ -479,7 +491,7 @@ impl FromWorld for BloomPipelines { let upsampling_pipeline = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { label: Some("bloom_upsampling_pipeline".into()), - layout: Some(vec![upsampling_bind_group_layout.clone()]), + layout: Some(vec![main_bind_group_layout.clone()]), vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { shader: BLOOM_SHADER_HANDLE.typed::(), @@ -487,7 +499,14 @@ impl FromWorld for BloomPipelines { entry_point: "upsample".into(), targets: vec![Some(ColorTargetState { format: ViewTarget::TEXTURE_FORMAT_HDR, - blend: None, + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::REPLACE, + }), write_mask: ColorWrites::ALL, })], }), @@ -499,7 +518,7 @@ impl FromWorld for BloomPipelines { let upsampling_final_pipeline = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { label: Some("bloom_upsampling_final_pipeline".into()), - layout: Some(vec![downsampling_bind_group_layout.clone()]), + layout: Some(vec![upsampling_final_bind_group_layout.clone()]), vertex: fullscreen_shader_vertex_state(), fragment: Some(FragmentState { shader: BLOOM_SHADER_HANDLE.typed::(), @@ -507,14 +526,7 @@ impl FromWorld for BloomPipelines { entry_point: "upsample_final".into(), targets: vec![Some(ColorTargetState { format: ViewTarget::TEXTURE_FORMAT_HDR, - blend: Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, - }, - alpha: BlendComponent::REPLACE, - }), + blend: None, write_mask: ColorWrites::ALL, })], }), @@ -524,13 +536,15 @@ impl FromWorld for BloomPipelines { }); BloomPipelines { - downsampling_prefilter_pipeline, + downsampling_first_pipeline, downsampling_pipeline, upsampling_pipeline, upsampling_final_pipeline, + sampler, - downsampling_bind_group_layout, - upsampling_bind_group_layout, + + main_bind_group_layout, + upsampling_final_bind_group_layout, } } } @@ -547,15 +561,15 @@ fn extract_bloom_settings( } #[derive(Component)] -struct BloomTextures { - texture_a: CachedTexture, - texture_b: CachedTexture, +struct BloomTexture { + // First mip is half the screen resolution, successive mips are half the previous + texture: CachedTexture, mip_count: u32, } -impl BloomTextures { - fn texture_view(texture: &CachedTexture, base_mip_level: u32) -> TextureView { - texture.texture.create_view(&TextureViewDescriptor { +impl BloomTexture { + fn view(&self, base_mip_level: u32) -> TextureView { + self.texture.texture.create_view(&TextureViewDescriptor { base_mip_level, mip_level_count: Some(unsafe { NonZeroU32::new_unchecked(1) }), ..Default::default() @@ -569,19 +583,19 @@ fn prepare_bloom_textures( render_device: Res, views: Query<(Entity, &ExtractedCamera), With>, ) { - let mut texture_as = HashMap::default(); - let mut texture_bs = HashMap::default(); + let mut textures = HashMap::default(); for (entity, camera) in &views { if let Some(UVec2 { x: width, y: height, }) = camera.physical_viewport_size { - let min_view = width.min(height) / 2; - let mip_count = calculate_mip_count(min_view); + let min_view = width.min(height) as f32; + // How many times we can halve the resolution, minus 3 to avoid tiny mips + let mip_count = (min_view.log2().round() as i32 - 3).max(1) as u32; - let mut texture_descriptor = TextureDescriptor { - label: None, + let texture_descriptor = TextureDescriptor { + label: Some("bloom_texture"), size: Extent3d { width: (width / 2).max(1), height: (height / 2).max(1), @@ -594,38 +608,21 @@ fn prepare_bloom_textures( usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, }; - texture_descriptor.label = Some("bloom_texture_a"); - let texture_a = texture_as - .entry(camera.target.clone()) - .or_insert_with(|| texture_cache.get(&render_device, texture_descriptor.clone())) - .clone(); - - texture_descriptor.label = Some("bloom_texture_b"); - let texture_b = texture_bs + let texture = textures .entry(camera.target.clone()) .or_insert_with(|| texture_cache.get(&render_device, texture_descriptor)) .clone(); - commands.entity(entity).insert(BloomTextures { - texture_a, - texture_b, - mip_count, - }); + commands + .entity(entity) + .insert(BloomTexture { texture, mip_count }); } } } -#[derive(ShaderType)] -struct BloomUniform { - threshold: f32, - knee: f32, - scale: f32, - intensity: f32, -} - #[derive(Resource, Default)] struct BloomUniforms { - uniforms: DynamicUniformBuffer, + buffer: DynamicUniformBuffer, } #[derive(Component)] @@ -636,44 +633,32 @@ fn prepare_bloom_uniforms( render_device: Res, render_queue: Res, mut bloom_uniforms: ResMut, - bloom_query: Query<(Entity, &ExtractedCamera, &BloomSettings)>, + bloom_query: Query<(Entity, &BloomSettings)>, ) { - bloom_uniforms.uniforms.clear(); + bloom_uniforms.buffer.clear(); let entities = bloom_query .iter() - .filter_map(|(entity, camera, settings)| { - let size = match camera.physical_viewport_size { - Some(size) => size, - None => return None, - }; - let min_view = size.x.min(size.y) / 2; - let mip_count = calculate_mip_count(min_view); - let scale = (min_view / 2u32.pow(mip_count)) as f32 / 8.0; - - let uniform = BloomUniform { - threshold: settings.threshold, - knee: settings.knee, - scale: settings.scale * scale, - intensity: settings.intensity, - }; - let index = bloom_uniforms.uniforms.push(uniform); - Some((entity, (BloomUniformIndex(index)))) + .map(|(entity, settings)| { + let index = bloom_uniforms.buffer.push(settings.clone()); + (entity, (BloomUniformIndex(index))) }) .collect::>(); commands.insert_or_spawn_batch(entities); bloom_uniforms - .uniforms + .buffer .write_buffer(&render_device, &render_queue); } #[derive(Component)] struct BloomBindGroups { - prefilter_bind_group: BindGroup, + downsampling_first_bind_group: BindGroup, downsampling_bind_groups: Box<[BindGroup]>, upsampling_bind_groups: Box<[BindGroup]>, upsampling_final_bind_group: BindGroup, + + view_target: TextureView, } fn queue_bloom_bind_groups( @@ -681,43 +666,43 @@ fn queue_bloom_bind_groups( render_device: Res, pipelines: Res, uniforms: Res, - views: Query<(Entity, &ViewTarget, &BloomTextures)>, + views: Query<(Entity, &ViewTarget, &BloomTexture)>, ) { - if let Some(uniforms) = uniforms.uniforms.binding() { - for (entity, view_target, textures) in &views { - let prefilter_bind_group = render_device.create_bind_group(&BindGroupDescriptor { - label: Some("bloom_prefilter_bind_group"), - layout: &pipelines.downsampling_bind_group_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(view_target.main_texture()), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&pipelines.sampler), - }, - BindGroupEntry { - binding: 2, - resource: uniforms.clone(), - }, - ], - }); + if let Some(uniforms) = uniforms.buffer.binding() { + for (entity, view_target, bloom_texture) in &views { + let view_target = view_target.post_process_write(); - let bind_group_count = textures.mip_count as usize - 1; + let downsampling_first_bind_group = + render_device.create_bind_group(&BindGroupDescriptor { + label: Some("bloom_downsampling_first_bind_group"), + layout: &pipelines.main_bind_group_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&view_target.source), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&pipelines.sampler), + }, + BindGroupEntry { + binding: 2, + resource: uniforms.clone(), + }, + ], + }); + + let bind_group_count = bloom_texture.mip_count as usize - 1; let mut downsampling_bind_groups = Vec::with_capacity(bind_group_count); - for mip in 1..textures.mip_count { + for mip in 1..bloom_texture.mip_count { let bind_group = render_device.create_bind_group(&BindGroupDescriptor { label: Some("bloom_downsampling_bind_group"), - layout: &pipelines.downsampling_bind_group_layout, + layout: &pipelines.main_bind_group_layout, entries: &[ BindGroupEntry { binding: 0, - resource: BindingResource::TextureView(&BloomTextures::texture_view( - &textures.texture_a, - mip - 1, - )), + resource: BindingResource::TextureView(&bloom_texture.view(mip - 1)), }, BindGroupEntry { binding: 1, @@ -734,24 +719,14 @@ fn queue_bloom_bind_groups( } let mut upsampling_bind_groups = Vec::with_capacity(bind_group_count); - for mip in 1..textures.mip_count { - let up = BloomTextures::texture_view(&textures.texture_a, mip - 1); - let org = BloomTextures::texture_view( - if mip == textures.mip_count - 1 { - &textures.texture_a - } else { - &textures.texture_b - }, - mip, - ); - + for mip in (1..bloom_texture.mip_count).rev() { let bind_group = render_device.create_bind_group(&BindGroupDescriptor { label: Some("bloom_upsampling_bind_group"), - layout: &pipelines.upsampling_bind_group_layout, + layout: &pipelines.main_bind_group_layout, entries: &[ BindGroupEntry { binding: 0, - resource: BindingResource::TextureView(&org), + resource: BindingResource::TextureView(&bloom_texture.view(mip)), }, BindGroupEntry { binding: 1, @@ -761,10 +736,6 @@ fn queue_bloom_bind_groups( binding: 2, resource: uniforms.clone(), }, - BindGroupEntry { - binding: 3, - resource: BindingResource::TextureView(&up), - }, ], }); @@ -774,14 +745,11 @@ fn queue_bloom_bind_groups( let upsampling_final_bind_group = render_device.create_bind_group(&BindGroupDescriptor { label: Some("bloom_upsampling_final_bind_group"), - layout: &pipelines.downsampling_bind_group_layout, + layout: &pipelines.upsampling_final_bind_group_layout, entries: &[ BindGroupEntry { binding: 0, - resource: BindingResource::TextureView(&BloomTextures::texture_view( - &textures.texture_b, - 0, - )), + resource: BindingResource::TextureView(&bloom_texture.view(0)), }, BindGroupEntry { binding: 1, @@ -791,19 +759,21 @@ fn queue_bloom_bind_groups( binding: 2, resource: uniforms.clone(), }, + BindGroupEntry { + binding: 3, + resource: BindingResource::TextureView(&view_target.source), + }, ], }); commands.entity(entity).insert(BloomBindGroups { - prefilter_bind_group, + downsampling_first_bind_group, downsampling_bind_groups: downsampling_bind_groups.into_boxed_slice(), upsampling_bind_groups: upsampling_bind_groups.into_boxed_slice(), upsampling_final_bind_group, + + view_target: view_target.destination.clone(), }); } } } - -fn calculate_mip_count(min_view: u32) -> u32 { - ((min_view as f32).log2().round() as i32 - 3).max(1) as u32 -} diff --git a/examples/3d/bloom.rs b/examples/3d/bloom.rs index c112ffa9cda40..e3f0e694f65d7 100644 --- a/examples/3d/bloom.rs +++ b/examples/3d/bloom.rs @@ -1,4 +1,4 @@ -//! Illustrates bloom configuration using HDR and emissive materials. +//! Illustrates bloom post-processing using HDR and emissive materials. use bevy::{core_pipeline::bloom::BloomSettings, prelude::*}; use std::{ @@ -10,7 +10,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .add_startup_system(setup_scene) - .add_system(update_bloom_settings) + // .add_system(update_bloom_settings) .add_system(bounce_spheres) .run(); } @@ -98,62 +98,62 @@ fn setup_scene( // ------------------------------------------------------------------------------------------------ -fn update_bloom_settings( - mut camera: Query<&mut BloomSettings>, - mut text: Query<&mut Text>, - keycode: Res>, - time: Res