mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
Bloom (#6397)
# Objective - Adds a bloom pass for HDR-enabled Camera3ds. - Supersedes (and all credit due to!) https://github.com/bevyengine/bevy/pull/3430 and https://github.com/bevyengine/bevy/pull/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 <47158642+JMS55@users.noreply.github.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com> Co-authored-by: DGriffin91 <github@dgdigital.net>
This commit is contained in:
parent
2e653e5774
commit
4c4f47697c
8 changed files with 1129 additions and 4 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -313,6 +313,16 @@ description = "Illustrates spot lights"
|
||||||
category = "3D Rendering"
|
category = "3D Rendering"
|
||||||
wasm = true
|
wasm = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "bloom"
|
||||||
|
path = "examples/3d/bloom.rs"
|
||||||
|
|
||||||
|
[package.metadata.example.bloom]
|
||||||
|
name = "Bloom"
|
||||||
|
description = "Illustrates bloom configuration using HDR and emissive materials"
|
||||||
|
category = "3D Rendering"
|
||||||
|
wasm = false
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "load_gltf"
|
name = "load_gltf"
|
||||||
path = "examples/3d/load_gltf.rs"
|
path = "examples/3d/load_gltf.rs"
|
||||||
|
|
|
@ -25,6 +25,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.9.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.9.0-dev" }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.9.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.9.0-dev" }
|
||||||
|
bevy_math = { path = "../bevy_math", version = "0.9.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" }
|
||||||
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
136
crates/bevy_core_pipeline/src/bloom/bloom.wgsl
Normal file
136
crates/bevy_core_pipeline/src/bloom/bloom.wgsl
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
#import bevy_core_pipeline::fullscreen_vertex_shader
|
||||||
|
|
||||||
|
struct BloomUniforms {
|
||||||
|
threshold: f32,
|
||||||
|
knee: f32,
|
||||||
|
scale: f32,
|
||||||
|
intensity: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var original: 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>;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn downsample_prefilter(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
|
||||||
|
let texel_size = 1.0 / vec2<f32>(textureDimensions(original));
|
||||||
|
|
||||||
|
let scale = texel_size;
|
||||||
|
|
||||||
|
let curve = vec3<f32>(
|
||||||
|
uniforms.threshold - uniforms.knee,
|
||||||
|
uniforms.knee * 2.0,
|
||||||
|
0.25 / uniforms.knee,
|
||||||
|
);
|
||||||
|
|
||||||
|
var o: vec4<f32> = sample_13_tap(uv, scale);
|
||||||
|
|
||||||
|
o = quadratic_threshold(o, uniforms.threshold, curve);
|
||||||
|
o = max(o, vec4<f32>(0.00001));
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
800
crates/bevy_core_pipeline/src/bloom/mod.rs
Normal file
800
crates/bevy_core_pipeline/src/bloom/mod.rs
Normal file
|
@ -0,0 +1,800 @@
|
||||||
|
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
||||||
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_asset::{load_internal_asset, HandleUntyped};
|
||||||
|
use bevy_ecs::{
|
||||||
|
prelude::{Component, Entity},
|
||||||
|
query::{QueryState, With},
|
||||||
|
system::{Commands, Query, Res, ResMut, Resource},
|
||||||
|
world::{FromWorld, World},
|
||||||
|
};
|
||||||
|
use bevy_math::UVec2;
|
||||||
|
use bevy_reflect::{Reflect, TypeUuid};
|
||||||
|
use bevy_render::{
|
||||||
|
camera::ExtractedCamera,
|
||||||
|
prelude::Camera,
|
||||||
|
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType},
|
||||||
|
render_phase::TrackedRenderPass,
|
||||||
|
render_resource::*,
|
||||||
|
renderer::{RenderContext, RenderDevice, RenderQueue},
|
||||||
|
texture::{CachedTexture, TextureCache},
|
||||||
|
view::ViewTarget,
|
||||||
|
Extract, RenderApp, RenderStage,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
use bevy_utils::tracing::info_span;
|
||||||
|
use bevy_utils::HashMap;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
|
pub mod draw_3d_graph {
|
||||||
|
pub mod node {
|
||||||
|
/// Label for the bloom render node.
|
||||||
|
pub const BLOOM: &str = "bloom_3d";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub mod draw_2d_graph {
|
||||||
|
pub mod node {
|
||||||
|
/// Label for the bloom render node.
|
||||||
|
pub const BLOOM: &str = "bloom_2d";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLOOM_SHADER_HANDLE: HandleUntyped =
|
||||||
|
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 929599476923908);
|
||||||
|
|
||||||
|
pub struct BloomPlugin;
|
||||||
|
|
||||||
|
impl Plugin for BloomPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
load_internal_asset!(app, BLOOM_SHADER_HANDLE, "bloom.wgsl", Shader::from_wgsl);
|
||||||
|
|
||||||
|
app.register_type::<BloomSettings>();
|
||||||
|
|
||||||
|
let render_app = match app.get_sub_app_mut(RenderApp) {
|
||||||
|
Ok(render_app) => render_app,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
render_app
|
||||||
|
.init_resource::<BloomPipelines>()
|
||||||
|
.init_resource::<BloomUniforms>()
|
||||||
|
.add_system_to_stage(RenderStage::Extract, extract_bloom_settings)
|
||||||
|
.add_system_to_stage(RenderStage::Prepare, prepare_bloom_textures)
|
||||||
|
.add_system_to_stage(RenderStage::Prepare, prepare_bloom_uniforms)
|
||||||
|
.add_system_to_stage(RenderStage::Queue, queue_bloom_bind_groups);
|
||||||
|
|
||||||
|
{
|
||||||
|
let bloom_node = BloomNode::new(&mut render_app.world);
|
||||||
|
let mut graph = render_app.world.resource_mut::<RenderGraph>();
|
||||||
|
let draw_3d_graph = graph
|
||||||
|
.get_sub_graph_mut(crate::core_3d::graph::NAME)
|
||||||
|
.unwrap();
|
||||||
|
draw_3d_graph.add_node(draw_3d_graph::node::BLOOM, bloom_node);
|
||||||
|
draw_3d_graph
|
||||||
|
.add_slot_edge(
|
||||||
|
draw_3d_graph.input_node().unwrap().id,
|
||||||
|
crate::core_3d::graph::input::VIEW_ENTITY,
|
||||||
|
draw_3d_graph::node::BLOOM,
|
||||||
|
BloomNode::IN_VIEW,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// MAIN_PASS -> BLOOM -> TONEMAPPING
|
||||||
|
draw_3d_graph
|
||||||
|
.add_node_edge(
|
||||||
|
crate::core_3d::graph::node::MAIN_PASS,
|
||||||
|
draw_3d_graph::node::BLOOM,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
draw_3d_graph
|
||||||
|
.add_node_edge(
|
||||||
|
draw_3d_graph::node::BLOOM,
|
||||||
|
crate::core_3d::graph::node::TONEMAPPING,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let bloom_node = BloomNode::new(&mut render_app.world);
|
||||||
|
let mut graph = render_app.world.resource_mut::<RenderGraph>();
|
||||||
|
let draw_2d_graph = graph
|
||||||
|
.get_sub_graph_mut(crate::core_2d::graph::NAME)
|
||||||
|
.unwrap();
|
||||||
|
draw_2d_graph.add_node(draw_2d_graph::node::BLOOM, bloom_node);
|
||||||
|
draw_2d_graph
|
||||||
|
.add_slot_edge(
|
||||||
|
draw_2d_graph.input_node().unwrap().id,
|
||||||
|
crate::core_2d::graph::input::VIEW_ENTITY,
|
||||||
|
draw_2d_graph::node::BLOOM,
|
||||||
|
BloomNode::IN_VIEW,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// MAIN_PASS -> BLOOM -> TONEMAPPING
|
||||||
|
draw_2d_graph
|
||||||
|
.add_node_edge(
|
||||||
|
crate::core_2d::graph::node::MAIN_PASS,
|
||||||
|
draw_2d_graph::node::BLOOM,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
draw_2d_graph
|
||||||
|
.add_node_edge(
|
||||||
|
draw_2d_graph::node::BLOOM,
|
||||||
|
crate::core_2d::graph::node::TONEMAPPING,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a bloom effect to a HDR-enabled 2d or 3d camera.
|
||||||
|
///
|
||||||
|
/// See also <https://en.wikipedia.org/wiki/Bloom_(shader_effect)>.
|
||||||
|
#[derive(Component, Reflect, Clone)]
|
||||||
|
pub struct BloomSettings {
|
||||||
|
/// Baseline of the threshold curve (default: 1.0).
|
||||||
|
///
|
||||||
|
/// RGB values under the threshold curve will not have bloom applied.
|
||||||
|
pub threshold: f32,
|
||||||
|
|
||||||
|
/// Knee of the threshold curve (default: 0.1).
|
||||||
|
pub knee: f32,
|
||||||
|
|
||||||
|
/// Scale used when upsampling (default: 1.0).
|
||||||
|
pub scale: f32,
|
||||||
|
|
||||||
|
/// Intensity of the bloom effect (default: 1.0).
|
||||||
|
pub intensity: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BloomSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
threshold: 1.0,
|
||||||
|
knee: 0.1,
|
||||||
|
scale: 1.0,
|
||||||
|
intensity: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BloomNode {
|
||||||
|
view_query: QueryState<(
|
||||||
|
&'static ExtractedCamera,
|
||||||
|
&'static ViewTarget,
|
||||||
|
&'static BloomTextures,
|
||||||
|
&'static BloomBindGroups,
|
||||||
|
&'static BloomUniformIndex,
|
||||||
|
)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BloomNode {
|
||||||
|
const IN_VIEW: &'static str = "view";
|
||||||
|
|
||||||
|
fn new(world: &mut World) -> Self {
|
||||||
|
Self {
|
||||||
|
view_query: QueryState::new(world),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node for BloomNode {
|
||||||
|
fn input(&self) -> Vec<SlotInfo> {
|
||||||
|
vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, world: &mut World) {
|
||||||
|
self.view_query.update_archetypes(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
graph: &mut RenderGraphContext,
|
||||||
|
render_context: &mut RenderContext,
|
||||||
|
world: &World,
|
||||||
|
) -> Result<(), NodeRunError> {
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
let _bloom_span = info_span!("bloom").entered();
|
||||||
|
|
||||||
|
let pipelines = world.resource::<BloomPipelines>();
|
||||||
|
let pipeline_cache = world.resource::<PipelineCache>();
|
||||||
|
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||||
|
let (camera, view_target, textures, bind_groups, uniform_index) =
|
||||||
|
match self.view_query.get_manual(world, view_entity) {
|
||||||
|
Ok(result) => result,
|
||||||
|
_ => return Ok(()),
|
||||||
|
};
|
||||||
|
let (
|
||||||
|
downsampling_prefilter_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_pipeline),
|
||||||
|
pipeline_cache.get_render_pipeline(pipelines.upsampling_pipeline),
|
||||||
|
pipeline_cache.get_render_pipeline(pipelines.upsampling_final_pipeline),
|
||||||
|
) {
|
||||||
|
(Some(p1), Some(p2), Some(p3), Some(p4)) => (p1, p2, p3, p4),
|
||||||
|
_ => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let view = &BloomTextures::texture_view(&textures.texture_a, 0);
|
||||||
|
let mut prefilter_pass =
|
||||||
|
TrackedRenderPass::new(render_context.command_encoder.begin_render_pass(
|
||||||
|
&RenderPassDescriptor {
|
||||||
|
label: Some("bloom_prefilter_pass"),
|
||||||
|
color_attachments: &[Some(RenderPassColorAttachment {
|
||||||
|
view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: Operations::default(),
|
||||||
|
})],
|
||||||
|
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]);
|
||||||
|
if let Some(viewport) = camera.viewport.as_ref() {
|
||||||
|
prefilter_pass.set_camera_viewport(viewport);
|
||||||
|
}
|
||||||
|
prefilter_pass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for mip in 1..textures.mip_count {
|
||||||
|
let view = &BloomTextures::texture_view(&textures.texture_a, mip);
|
||||||
|
let mut downsampling_pass =
|
||||||
|
TrackedRenderPass::new(render_context.command_encoder.begin_render_pass(
|
||||||
|
&RenderPassDescriptor {
|
||||||
|
label: Some("bloom_downsampling_pass"),
|
||||||
|
color_attachments: &[Some(RenderPassColorAttachment {
|
||||||
|
view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: Operations::default(),
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
downsampling_pass.set_render_pipeline(downsampling_pipeline);
|
||||||
|
downsampling_pass.set_bind_group(
|
||||||
|
0,
|
||||||
|
&bind_groups.downsampling_bind_groups[mip as usize - 1],
|
||||||
|
&[uniform_index.0],
|
||||||
|
);
|
||||||
|
if let Some(viewport) = camera.viewport.as_ref() {
|
||||||
|
downsampling_pass.set_camera_viewport(viewport);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
let mut upsampling_pass =
|
||||||
|
TrackedRenderPass::new(render_context.command_encoder.begin_render_pass(
|
||||||
|
&RenderPassDescriptor {
|
||||||
|
label: Some("bloom_upsampling_pass"),
|
||||||
|
color_attachments: &[Some(RenderPassColorAttachment {
|
||||||
|
view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: Operations::default(),
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
upsampling_pass.set_render_pipeline(upsampling_pipeline);
|
||||||
|
upsampling_pass.set_bind_group(
|
||||||
|
0,
|
||||||
|
&bind_groups.upsampling_bind_groups[mip as usize - 1],
|
||||||
|
&[uniform_index.0],
|
||||||
|
);
|
||||||
|
if let Some(viewport) = camera.viewport.as_ref() {
|
||||||
|
upsampling_pass.set_camera_viewport(viewport);
|
||||||
|
}
|
||||||
|
upsampling_pass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut upsampling_final_pass =
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
))],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
upsampling_final_pass.set_render_pipeline(upsampling_final_pipeline);
|
||||||
|
upsampling_final_pass.set_bind_group(
|
||||||
|
0,
|
||||||
|
&bind_groups.upsampling_final_bind_group,
|
||||||
|
&[uniform_index.0],
|
||||||
|
);
|
||||||
|
if let Some(viewport) = camera.viewport.as_ref() {
|
||||||
|
upsampling_final_pass.set_camera_viewport(viewport);
|
||||||
|
}
|
||||||
|
upsampling_final_pass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct BloomPipelines {
|
||||||
|
downsampling_prefilter_pipeline: CachedRenderPipelineId,
|
||||||
|
downsampling_pipeline: CachedRenderPipelineId,
|
||||||
|
upsampling_pipeline: CachedRenderPipelineId,
|
||||||
|
upsampling_final_pipeline: CachedRenderPipelineId,
|
||||||
|
sampler: Sampler,
|
||||||
|
downsampling_bind_group_layout: BindGroupLayout,
|
||||||
|
upsampling_bind_group_layout: BindGroupLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for BloomPipelines {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
let render_device = world.resource::<RenderDevice>();
|
||||||
|
|
||||||
|
let sampler = render_device.create_sampler(&SamplerDescriptor {
|
||||||
|
min_filter: FilterMode::Linear,
|
||||||
|
mag_filter: FilterMode::Linear,
|
||||||
|
address_mode_u: AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: AddressMode::ClampToEdge,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let downsampling_bind_group_layout =
|
||||||
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
label: Some("bloom_downsampling_bind_group_layout"),
|
||||||
|
entries: &[
|
||||||
|
// Upsampled input texture (downsampled for final upsample)
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
ty: BindingType::Texture {
|
||||||
|
sample_type: TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
// Sampler
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
// Bloom settings
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: true,
|
||||||
|
min_binding_size: Some(BloomUniform::min_size()),
|
||||||
|
},
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let upsampling_bind_group_layout =
|
||||||
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
label: Some("bloom_upsampling_bind_group_layout"),
|
||||||
|
entries: &[
|
||||||
|
// Downsampled input texture
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
ty: BindingType::Texture {
|
||||||
|
sample_type: TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
// Sampler
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
ty: BindingType::Sampler(SamplerBindingType::Filtering),
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
// Bloom settings
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: true,
|
||||||
|
min_binding_size: Some(BloomUniform::min_size()),
|
||||||
|
},
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
// Upsampled input texture
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 3,
|
||||||
|
ty: BindingType::Texture {
|
||||||
|
sample_type: TextureSampleType::Float { filterable: true },
|
||||||
|
view_dimension: TextureViewDimension::D2,
|
||||||
|
multisampled: false,
|
||||||
|
},
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut pipeline_cache = world.resource_mut::<PipelineCache>();
|
||||||
|
|
||||||
|
let downsampling_prefilter_pipeline =
|
||||||
|
pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
|
||||||
|
label: Some("bloom_downsampling_prefilter_pipeline".into()),
|
||||||
|
layout: Some(vec![downsampling_bind_group_layout.clone()]),
|
||||||
|
vertex: fullscreen_shader_vertex_state(),
|
||||||
|
fragment: Some(FragmentState {
|
||||||
|
shader: BLOOM_SHADER_HANDLE.typed::<Shader>(),
|
||||||
|
shader_defs: vec![],
|
||||||
|
entry_point: "downsample_prefilter".into(),
|
||||||
|
targets: vec![Some(ColorTargetState {
|
||||||
|
format: ViewTarget::TEXTURE_FORMAT_HDR,
|
||||||
|
blend: None,
|
||||||
|
write_mask: ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let downsampling_pipeline =
|
||||||
|
pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
|
||||||
|
label: Some("bloom_downsampling_pipeline".into()),
|
||||||
|
layout: Some(vec![downsampling_bind_group_layout.clone()]),
|
||||||
|
vertex: fullscreen_shader_vertex_state(),
|
||||||
|
fragment: Some(FragmentState {
|
||||||
|
shader: BLOOM_SHADER_HANDLE.typed::<Shader>(),
|
||||||
|
shader_defs: vec![],
|
||||||
|
entry_point: "downsample".into(),
|
||||||
|
targets: vec![Some(ColorTargetState {
|
||||||
|
format: ViewTarget::TEXTURE_FORMAT_HDR,
|
||||||
|
blend: None,
|
||||||
|
write_mask: ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let upsampling_pipeline = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
|
||||||
|
label: Some("bloom_upsampling_pipeline".into()),
|
||||||
|
layout: Some(vec![upsampling_bind_group_layout.clone()]),
|
||||||
|
vertex: fullscreen_shader_vertex_state(),
|
||||||
|
fragment: Some(FragmentState {
|
||||||
|
shader: BLOOM_SHADER_HANDLE.typed::<Shader>(),
|
||||||
|
shader_defs: vec![],
|
||||||
|
entry_point: "upsample".into(),
|
||||||
|
targets: vec![Some(ColorTargetState {
|
||||||
|
format: ViewTarget::TEXTURE_FORMAT_HDR,
|
||||||
|
blend: None,
|
||||||
|
write_mask: ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
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()]),
|
||||||
|
vertex: fullscreen_shader_vertex_state(),
|
||||||
|
fragment: Some(FragmentState {
|
||||||
|
shader: BLOOM_SHADER_HANDLE.typed::<Shader>(),
|
||||||
|
shader_defs: vec![],
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
write_mask: ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
}),
|
||||||
|
primitive: PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: MultisampleState::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
BloomPipelines {
|
||||||
|
downsampling_prefilter_pipeline,
|
||||||
|
downsampling_pipeline,
|
||||||
|
upsampling_pipeline,
|
||||||
|
upsampling_final_pipeline,
|
||||||
|
sampler,
|
||||||
|
downsampling_bind_group_layout,
|
||||||
|
upsampling_bind_group_layout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_bloom_settings(
|
||||||
|
mut commands: Commands,
|
||||||
|
cameras: Extract<Query<(Entity, &Camera, &BloomSettings), With<Camera>>>,
|
||||||
|
) {
|
||||||
|
for (entity, camera, bloom_settings) in &cameras {
|
||||||
|
if camera.is_active && camera.hdr {
|
||||||
|
commands.get_or_spawn(entity).insert(bloom_settings.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct BloomTextures {
|
||||||
|
texture_a: CachedTexture,
|
||||||
|
texture_b: CachedTexture,
|
||||||
|
mip_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BloomTextures {
|
||||||
|
fn texture_view(texture: &CachedTexture, base_mip_level: u32) -> TextureView {
|
||||||
|
texture.texture.create_view(&TextureViewDescriptor {
|
||||||
|
base_mip_level,
|
||||||
|
mip_level_count: Some(unsafe { NonZeroU32::new_unchecked(1) }),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_bloom_textures(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut texture_cache: ResMut<TextureCache>,
|
||||||
|
render_device: Res<RenderDevice>,
|
||||||
|
views: Query<(Entity, &ExtractedCamera), With<BloomSettings>>,
|
||||||
|
) {
|
||||||
|
let mut texture_as = HashMap::default();
|
||||||
|
let mut texture_bs = 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 mut texture_descriptor = TextureDescriptor {
|
||||||
|
label: None,
|
||||||
|
size: Extent3d {
|
||||||
|
width: (width / 2).max(1),
|
||||||
|
height: (height / 2).max(1),
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: mip_count,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
|
format: ViewTarget::TEXTURE_FORMAT_HDR,
|
||||||
|
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
|
||||||
|
.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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ShaderType)]
|
||||||
|
struct BloomUniform {
|
||||||
|
threshold: f32,
|
||||||
|
knee: f32,
|
||||||
|
scale: f32,
|
||||||
|
intensity: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct BloomUniforms {
|
||||||
|
uniforms: DynamicUniformBuffer<BloomUniform>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct BloomUniformIndex(u32);
|
||||||
|
|
||||||
|
fn prepare_bloom_uniforms(
|
||||||
|
mut commands: Commands,
|
||||||
|
render_device: Res<RenderDevice>,
|
||||||
|
render_queue: Res<RenderQueue>,
|
||||||
|
mut bloom_uniforms: ResMut<BloomUniforms>,
|
||||||
|
bloom_query: Query<(Entity, &ExtractedCamera, &BloomSettings)>,
|
||||||
|
) {
|
||||||
|
bloom_uniforms.uniforms.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))))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
commands.insert_or_spawn_batch(entities);
|
||||||
|
|
||||||
|
bloom_uniforms
|
||||||
|
.uniforms
|
||||||
|
.write_buffer(&render_device, &render_queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct BloomBindGroups {
|
||||||
|
prefilter_bind_group: BindGroup,
|
||||||
|
downsampling_bind_groups: Box<[BindGroup]>,
|
||||||
|
upsampling_bind_groups: Box<[BindGroup]>,
|
||||||
|
upsampling_final_bind_group: BindGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_bloom_bind_groups(
|
||||||
|
mut commands: Commands,
|
||||||
|
render_device: Res<RenderDevice>,
|
||||||
|
pipelines: Res<BloomPipelines>,
|
||||||
|
uniforms: Res<BloomUniforms>,
|
||||||
|
views: Query<(Entity, &ViewTarget, &BloomTextures)>,
|
||||||
|
) {
|
||||||
|
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(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group_count = textures.mip_count as usize - 1;
|
||||||
|
|
||||||
|
let mut downsampling_bind_groups = Vec::with_capacity(bind_group_count);
|
||||||
|
for mip in 1..textures.mip_count {
|
||||||
|
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
label: Some("bloom_downsampling_bind_group"),
|
||||||
|
layout: &pipelines.downsampling_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: BindingResource::TextureView(&BloomTextures::texture_view(
|
||||||
|
&textures.texture_a,
|
||||||
|
mip - 1,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: BindingResource::Sampler(&pipelines.sampler),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: uniforms.clone(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
downsampling_bind_groups.push(bind_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
label: Some("bloom_upsampling_bind_group"),
|
||||||
|
layout: &pipelines.upsampling_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: BindingResource::TextureView(&org),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: BindingResource::Sampler(&pipelines.sampler),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: uniforms.clone(),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 3,
|
||||||
|
resource: BindingResource::TextureView(&up),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
upsampling_bind_groups.push(bind_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
let upsampling_final_bind_group =
|
||||||
|
render_device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
label: Some("bloom_upsampling_final_bind_group"),
|
||||||
|
layout: &pipelines.downsampling_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: BindingResource::TextureView(&BloomTextures::texture_view(
|
||||||
|
&textures.texture_b,
|
||||||
|
0,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: BindingResource::Sampler(&pipelines.sampler),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: uniforms.clone(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.entity(entity).insert(BloomBindGroups {
|
||||||
|
prefilter_bind_group,
|
||||||
|
downsampling_bind_groups: downsampling_bind_groups.into_boxed_slice(),
|
||||||
|
upsampling_bind_groups: upsampling_bind_groups.into_boxed_slice(),
|
||||||
|
upsampling_final_bind_group,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_mip_count(min_view: u32) -> u32 {
|
||||||
|
((min_view as f32).log2().round() as u32 - 1).max(1)
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod bloom;
|
||||||
pub mod clear_color;
|
pub mod clear_color;
|
||||||
pub mod core_2d;
|
pub mod core_2d;
|
||||||
pub mod core_3d;
|
pub mod core_3d;
|
||||||
|
@ -16,6 +17,7 @@ pub mod prelude {
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
bloom::BloomPlugin,
|
||||||
clear_color::{ClearColor, ClearColorConfig},
|
clear_color::{ClearColor, ClearColorConfig},
|
||||||
core_2d::Core2dPlugin,
|
core_2d::Core2dPlugin,
|
||||||
core_3d::Core3dPlugin,
|
core_3d::Core3dPlugin,
|
||||||
|
@ -44,10 +46,11 @@ impl Plugin for CorePipelinePlugin {
|
||||||
.register_type::<ClearColorConfig>()
|
.register_type::<ClearColorConfig>()
|
||||||
.init_resource::<ClearColor>()
|
.init_resource::<ClearColor>()
|
||||||
.add_plugin(ExtractResourcePlugin::<ClearColor>::default())
|
.add_plugin(ExtractResourcePlugin::<ClearColor>::default())
|
||||||
.add_plugin(TonemappingPlugin)
|
|
||||||
.add_plugin(UpscalingPlugin)
|
|
||||||
.add_plugin(Core2dPlugin)
|
.add_plugin(Core2dPlugin)
|
||||||
.add_plugin(Core3dPlugin)
|
.add_plugin(Core3dPlugin)
|
||||||
|
.add_plugin(TonemappingPlugin)
|
||||||
|
.add_plugin(UpscalingPlugin)
|
||||||
|
.add_plugin(BloomPlugin)
|
||||||
.add_plugin(FxaaPlugin);
|
.add_plugin(FxaaPlugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,12 +127,12 @@ impl Plugin for PbrPlugin {
|
||||||
.register_type::<DirectionalLight>()
|
.register_type::<DirectionalLight>()
|
||||||
.register_type::<PointLight>()
|
.register_type::<PointLight>()
|
||||||
.register_type::<SpotLight>()
|
.register_type::<SpotLight>()
|
||||||
.add_plugin(MeshRenderPlugin)
|
|
||||||
.add_plugin(MaterialPlugin::<StandardMaterial>::default())
|
|
||||||
.register_asset_reflect::<StandardMaterial>()
|
.register_asset_reflect::<StandardMaterial>()
|
||||||
.register_type::<AmbientLight>()
|
.register_type::<AmbientLight>()
|
||||||
.register_type::<DirectionalLightShadowMap>()
|
.register_type::<DirectionalLightShadowMap>()
|
||||||
.register_type::<PointLightShadowMap>()
|
.register_type::<PointLightShadowMap>()
|
||||||
|
.add_plugin(MeshRenderPlugin)
|
||||||
|
.add_plugin(MaterialPlugin::<StandardMaterial>::default())
|
||||||
.init_resource::<AmbientLight>()
|
.init_resource::<AmbientLight>()
|
||||||
.init_resource::<GlobalVisiblePointLights>()
|
.init_resource::<GlobalVisiblePointLights>()
|
||||||
.init_resource::<DirectionalLightShadowMap>()
|
.init_resource::<DirectionalLightShadowMap>()
|
||||||
|
|
174
examples/3d/bloom.rs
Normal file
174
examples/3d/bloom.rs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
//! Illustrates bloom configuration using HDR and emissive materials.
|
||||||
|
|
||||||
|
use bevy::{core_pipeline::bloom::BloomSettings, prelude::*};
|
||||||
|
use std::{
|
||||||
|
collections::hash_map::DefaultHasher,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_startup_system(setup_scene)
|
||||||
|
.add_system(update_bloom_settings)
|
||||||
|
.add_system(bounce_spheres)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_scene(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
commands.spawn((
|
||||||
|
Camera3dBundle {
|
||||||
|
camera: Camera {
|
||||||
|
hdr: true, // 1. HDR must be enabled on the camera
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BloomSettings::default(), // 2. Enable bloom for the camera
|
||||||
|
));
|
||||||
|
|
||||||
|
let material_emissive = materials.add(StandardMaterial {
|
||||||
|
emissive: Color::rgb_linear(5.2, 1.2, 0.8), // 3. Set StandardMaterial::emissive using Color::rgb_linear, for entities we want to apply bloom to
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let material_non_emissive = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::GRAY,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mesh = meshes.add(
|
||||||
|
shape::Icosphere {
|
||||||
|
radius: 0.5,
|
||||||
|
subdivisions: 5,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for x in -10..10 {
|
||||||
|
for z in -10..10 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
(x, z).hash(&mut hasher);
|
||||||
|
let rand = hasher.finish() % 2 == 0;
|
||||||
|
|
||||||
|
let material = if rand {
|
||||||
|
material_emissive.clone()
|
||||||
|
} else {
|
||||||
|
material_non_emissive.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
PbrBundle {
|
||||||
|
mesh: mesh.clone(),
|
||||||
|
material,
|
||||||
|
transform: Transform::from_xyz(x as f32 * 2.0, 0.0, z as f32 * 2.0),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Bouncing,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI camera
|
||||||
|
commands.spawn(Camera2dBundle {
|
||||||
|
camera: Camera {
|
||||||
|
priority: -1,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.spawn(
|
||||||
|
TextBundle::from_section(
|
||||||
|
"",
|
||||||
|
TextStyle {
|
||||||
|
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||||
|
font_size: 18.0,
|
||||||
|
color: Color::BLACK,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_style(Style {
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
position: UiRect {
|
||||||
|
top: Val::Px(10.0),
|
||||||
|
left: Val::Px(10.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn update_bloom_settings(
|
||||||
|
mut camera: Query<&mut BloomSettings>,
|
||||||
|
mut text: Query<&mut Text>,
|
||||||
|
keycode: Res<Input<KeyCode>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
let mut bloom_settings = camera.single_mut();
|
||||||
|
let mut text = text.single_mut();
|
||||||
|
let text = &mut text.sections[0].value;
|
||||||
|
|
||||||
|
*text = "BloomSettings\n".to_string();
|
||||||
|
text.push_str("-------------\n");
|
||||||
|
text.push_str(&format!("Threshold: {}\n", bloom_settings.threshold));
|
||||||
|
text.push_str(&format!("Knee: {}\n", bloom_settings.knee));
|
||||||
|
text.push_str(&format!("Scale: {}\n", bloom_settings.scale));
|
||||||
|
text.push_str(&format!("Intensity: {}\n", bloom_settings.intensity));
|
||||||
|
|
||||||
|
text.push_str("\n\n");
|
||||||
|
|
||||||
|
text.push_str("Controls (-/+)\n");
|
||||||
|
text.push_str("---------------\n");
|
||||||
|
text.push_str("Q/W - Threshold\n");
|
||||||
|
text.push_str("E/R - Knee\n");
|
||||||
|
text.push_str("A/S - Scale\n");
|
||||||
|
text.push_str("D/F - Intensity\n");
|
||||||
|
|
||||||
|
let dt = time.delta_seconds();
|
||||||
|
|
||||||
|
if keycode.pressed(KeyCode::Q) {
|
||||||
|
bloom_settings.threshold -= dt;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::W) {
|
||||||
|
bloom_settings.threshold += dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.pressed(KeyCode::E) {
|
||||||
|
bloom_settings.knee -= dt;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::R) {
|
||||||
|
bloom_settings.knee += dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.pressed(KeyCode::A) {
|
||||||
|
bloom_settings.scale -= dt;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::S) {
|
||||||
|
bloom_settings.scale += dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if keycode.pressed(KeyCode::D) {
|
||||||
|
bloom_settings.intensity -= dt;
|
||||||
|
}
|
||||||
|
if keycode.pressed(KeyCode::F) {
|
||||||
|
bloom_settings.intensity += dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Bouncing;
|
||||||
|
|
||||||
|
fn bounce_spheres(time: Res<Time>, mut query: Query<&mut Transform, With<Bouncing>>) {
|
||||||
|
for mut transform in query.iter_mut() {
|
||||||
|
transform.translation.y =
|
||||||
|
(transform.translation.x + transform.translation.z + time.elapsed_seconds()).sin();
|
||||||
|
}
|
||||||
|
}
|
|
@ -106,6 +106,7 @@ Example | Description
|
||||||
--- | ---
|
--- | ---
|
||||||
[3D Scene](../examples/3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting
|
[3D Scene](../examples/3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting
|
||||||
[3D Shapes](../examples/3d/3d_shapes.rs) | A scene showcasing the built-in 3D shapes
|
[3D Shapes](../examples/3d/3d_shapes.rs) | A scene showcasing the built-in 3D shapes
|
||||||
|
[Bloom](../examples/3d/bloom.rs) | Illustrates bloom configuration using HDR and emissive materials
|
||||||
[FXAA](../examples/3d/fxaa.rs) | Compares MSAA (Multi-Sample Anti-Aliasing) and FXAA (Fast Approximate Anti-Aliasing)
|
[FXAA](../examples/3d/fxaa.rs) | Compares MSAA (Multi-Sample Anti-Aliasing) and FXAA (Fast Approximate Anti-Aliasing)
|
||||||
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
|
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
|
||||||
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
|
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
|
||||||
|
|
Loading…
Reference in a new issue