From 7d1fc2922231f55d88cfbd2bb2f16dae2d1ecd60 Mon Sep 17 00:00:00 2001 From: Soulghost Date: Wed, 29 May 2024 21:11:40 +0800 Subject: [PATCH 1/7] Add support for setting blending mode --- src/asset.rs | 26 ++++++++++++++++++++++++++ src/lib.rs | 6 +++++- src/render/batch.rs | 7 ++++++- src/render/mod.rs | 30 +++++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/asset.rs b/src/asset.rs index 526849e4..5723ab2b 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -163,6 +163,24 @@ pub enum AlphaMode { Mask(ExprHandle), } +/// Blending mode for rendering an effect. +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Reflect, Serialize, Deserialize, Hash)] +#[non_exhaustive] +pub enum BlendingMode { + #[default] + /// The base color alpha value defines the opacity of the color. + /// Standard alpha-blending is used to blend the fragment’s color with the color behind it. + Alpha, + + /// Similar to AlphaMode::Blend, however assumes RGB channel values are premultiplied. + Premultiply, + + /// This blend mode simply adds pixel values of one layer with the other. + /// In case of values above 1 (in the case of RGB), white is displayed. + /// This is useful for glow effects, like those you might use for fire or magic spells. + Additive, +} + /// Asset describing a visual effect. /// /// The effect can be instanciated with a [`ParticleEffect`] component, or a @@ -220,6 +238,8 @@ pub struct EffectAsset { module: Module, /// Alpha mode. pub alpha_mode: AlphaMode, + /// Blending mode. + pub blending_mode: BlendingMode, } impl EffectAsset { @@ -341,6 +361,12 @@ impl EffectAsset { self } + /// Set the blending mode. + pub fn with_blending_mode(mut self, blending_mode: BlendingMode) -> Self { + self.blending_mode = blending_mode; + self + } + /// Get the list of existing properties. /// /// This is a shortcut for `self.module().properties()`. diff --git a/src/lib.rs b/src/lib.rs index cb89330a..7f70e663 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,7 +190,7 @@ mod time; #[cfg(test)] mod test_utils; -pub use asset::{AlphaMode, EffectAsset, MotionIntegration, SimulationCondition}; +pub use asset::{AlphaMode, BlendingMode, EffectAsset, MotionIntegration, SimulationCondition}; pub use attributes::*; pub use bundle::ParticleEffectBundle; pub use gradient::{Gradient, GradientKey}; @@ -1109,6 +1109,8 @@ pub struct CompiledParticleEffect { z_layer_2d: FloatOrd, /// Layout flags. layout_flags: LayoutFlags, + /// Blending mode. + blending_mode: BlendingMode, } impl Default for CompiledParticleEffect { @@ -1121,6 +1123,7 @@ impl Default for CompiledParticleEffect { #[cfg(feature = "2d")] z_layer_2d: FloatOrd(0.0), layout_flags: LayoutFlags::NONE, + blending_mode: default(), } } } @@ -1196,6 +1199,7 @@ impl CompiledParticleEffect { }; self.layout_flags = shader_source.layout_flags; + self.blending_mode = asset.blending_mode; let init_shader = shader_cache.get_or_insert(&asset.name, &shader_source.init, shaders); let update_shaders: Vec<_> = shader_source diff --git a/src/render/batch.rs b/src/render/batch.rs index 93a056d6..35643cd3 100644 --- a/src/render/batch.rs +++ b/src/render/batch.rs @@ -11,7 +11,7 @@ use super::{ effect_cache::{DispatchBufferIndices, EffectSlices}, EffectCacheId, GpuCompressedTransform, LayoutFlags, }; -use crate::{EffectAsset, EffectShader, ParticleLayout, PropertyLayout}; +use crate::{BlendingMode, EffectAsset, EffectShader, ParticleLayout, PropertyLayout}; /// Data needed to render all batches pertaining to a specific effect. #[derive(Debug, Component)] @@ -39,6 +39,8 @@ pub(crate) struct EffectBatches { pub particle_layout: ParticleLayout, /// Flags describing the render layout. pub layout_flags: LayoutFlags, + /// Blending mode. + pub blending_mode: BlendingMode, /// Entities holding the source [`ParticleEffect`] instances which were /// batched into this single batch. Used to determine visibility per view. /// @@ -124,6 +126,7 @@ impl EffectBatches { .collect(), handle: input.handle, layout_flags: input.layout_flags, + blending_mode: input.blending_mode, image_handle: input.image_handle, render_shaders: input.effect_shader.render, init_pipeline_id, @@ -149,6 +152,8 @@ pub(crate) struct BatchesInput { pub effect_shader: EffectShader, /// Various flags related to the effect. pub layout_flags: LayoutFlags, + /// Blending mode for particle renderer. + pub blending_mode: BlendingMode, /// Texture to modulate the particle color. pub image_handle: Handle, /// Number of particles to spawn for this effect. diff --git a/src/render/mod.rs b/src/render/mod.rs index 7dd7765b..76c63ba3 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -41,6 +41,7 @@ use rand::random; use crate::{ asset::EffectAsset, next_multiple_of, + prelude::BlendingMode, render::{ batch::{BatchesInput, EffectDrawBatch}, effect_cache::DispatchBufferIndices, @@ -1016,6 +1017,8 @@ pub(crate) struct ParticleRenderPipelineKey { msaa_samples: u32, /// Is the camera using an HDR render target? hdr: bool, + /// Controls how the color of the particle blends with the background. + blending_mode: BlendingMode, } impl Default for ParticleRenderPipelineKey { @@ -1032,6 +1035,7 @@ impl Default for ParticleRenderPipelineKey { pipeline_mode: PipelineMode::Camera3d, msaa_samples: Msaa::default().samples(), hdr: false, + blending_mode: BlendingMode::Alpha, } } } @@ -1213,6 +1217,23 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline { TextureFormat::bevy_default() }; + let blend_state = match key.blending_mode { + BlendingMode::Alpha => BlendState::ALPHA_BLENDING, + BlendingMode::Premultiply => BlendState::PREMULTIPLIED_ALPHA_BLENDING, + BlendingMode::Additive => BlendState { + color: BlendComponent { + src_factor: BlendFactor::SrcAlpha, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + alpha: BlendComponent { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }, + }; + RenderPipelineDescriptor { vertex: VertexState { shader: key.shader.clone(), @@ -1226,7 +1247,7 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline { entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { format, - blend: Some(BlendState::ALPHA_BLENDING), + blend: Some(blend_state), write_mask: ColorWrites::ALL, })], }), @@ -1290,6 +1311,8 @@ pub(crate) struct ExtractedEffect { pub inverse_transform: Mat4, /// Layout flags. pub layout_flags: LayoutFlags, + /// Blending mode. + pub blending_mode: BlendingMode, /// Texture to modulate the particle color. pub image_handle: Handle, /// Effect shader. @@ -1518,6 +1541,8 @@ pub(crate) fn extract_effects( layout_flags |= LayoutFlags::PARTICLE_TEXTURE; } + let blending_mode = effect.blending_mode; + trace!( "Extracted instance of effect '{}' on entity {:?}: image_handle={:?} has_image={} layout_flags={:?}", asset.name, @@ -1539,6 +1564,7 @@ pub(crate) fn extract_effects( // TODO - more efficient/correct way than inverse()? inverse_transform: transform.compute_matrix().inverse(), layout_flags, + blending_mode, image_handle, effect_shader, #[cfg(feature = "2d")] @@ -2073,6 +2099,7 @@ pub(crate) fn prepare_effects( property_layout: extracted_effect.property_layout.clone(), effect_shader: extracted_effect.effect_shader.clone(), layout_flags: extracted_effect.layout_flags, + blending_mode: extracted_effect.blending_mode, image_handle: extracted_effect.image_handle, spawn_count: extracted_effect.spawn_count, transform: extracted_effect.transform.into(), @@ -2483,6 +2510,7 @@ fn emit_draw( pipeline_mode, msaa_samples, hdr: view.hdr, + blending_mode: batches.blending_mode, }, ); #[cfg(feature = "trace")] From d6ffddbb1a91a4440061494d9c46120d9df99c57 Mon Sep 17 00:00:00 2001 From: Soulghost Date: Wed, 29 May 2024 21:13:11 +0800 Subject: [PATCH 2/7] Add example for additive blending mode --- assets/orange_circle.png | Bin 0 -> 3038 bytes examples/additive.rs | 123 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 assets/orange_circle.png create mode 100644 examples/additive.rs diff --git a/assets/orange_circle.png b/assets/orange_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..c066f32e254ad38814cf843c2ff307df6d032698 GIT binary patch literal 3038 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 literal 0 HcmV?d00001 diff --git a/examples/additive.rs b/examples/additive.rs new file mode 100644 index 00000000..0865421a --- /dev/null +++ b/examples/additive.rs @@ -0,0 +1,123 @@ +//! Example of additive blend mode. +//! +//! This example demonstrate how to change the blend mode for the particle renderer. + +use bevy::{ + core_pipeline::tonemapping::Tonemapping, + log::LogPlugin, + prelude::*, + render::{ + camera::Projection, render_resource::WgpuFeatures, settings::WgpuSettings, RenderPlugin, + }, +}; +#[cfg(feature = "examples_world_inspector")] +use bevy_inspector_egui::quick::WorldInspectorPlugin; + +use bevy_hanabi::prelude::*; + +fn main() -> Result<(), Box> { + let mut wgpu_settings = WgpuSettings::default(); + wgpu_settings + .features + .set(WgpuFeatures::VERTEX_WRITABLE_STORAGE, true); + + let mut app = App::default(); + app.insert_resource(ClearColor(Color::DARK_GRAY)) + .add_plugins( + DefaultPlugins + .set(LogPlugin { + level: bevy::log::Level::WARN, + filter: "bevy_hanabi=warn,additive=trace".to_string(), + update_subscriber: None, + }) + .set(RenderPlugin { + render_creation: wgpu_settings.into(), + synchronous_pipeline_compilation: false, + }) + .set(WindowPlugin { + primary_window: Some(Window { + title: "🎆 Hanabi — additive blending".to_string(), + ..default() + }), + ..default() + }), + ) + .add_plugins(HanabiPlugin); + + #[cfg(feature = "examples_world_inspector")] + app.add_plugins(WorldInspectorPlugin::default()); + + app.add_systems(Startup, setup) + .add_systems(Update, bevy::window::close_on_esc) + .run(); + + Ok(()) +} + +fn setup( + asset_server: Res, + mut commands: Commands, + mut effects: ResMut>, +) { + let camera = Camera3dBundle { + transform: Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y), + projection: Projection::Perspective(PerspectiveProjection { + fov: 90.0, + ..Default::default() + }), + tonemapping: Tonemapping::None, + ..Default::default() + }; + + commands.spawn(camera); + + let texture_handle: Handle = asset_server.load("orange_circle.png"); + + let writer = ExprWriter::new(); + + let age = writer.lit(0.).expr(); + let init_age = SetAttributeModifier::new(Attribute::AGE, age); + + let lifetime = writer.lit(5.).expr(); + let init_lifetime = SetAttributeModifier::new(Attribute::LIFETIME, lifetime); + + let init_pos = SetPositionCircleModifier { + center: writer.lit(0.).expr(), + axis: writer.lit(Vec3::Z).expr(), + radius: writer.lit(0.2).expr(), + dimension: ShapeDimension::Volume, + }; + + let init_vel = SetVelocityCircleModifier { + center: writer.lit(Vec3::ZERO).expr(), + axis: writer.lit(Vec3::Z).expr(), + speed: (writer.lit(0.2) + writer.lit(0.2) * writer.rand(ScalarType::Float)).expr(), + }; + + // Use the F32_0 attribute as a per-particle rotation value, initialized on + // spawn and constant after. The rotation angle is in radians, here randomly + // selected in [0:2*PI]. + let rotation = (writer.rand(ScalarType::Float) * writer.lit(std::f32::consts::TAU)).expr(); + let init_rotation = SetAttributeModifier::new(Attribute::F32_0, rotation); + + let size = Vec2::splat(0.8); + let effect = effects.add( + EffectAsset::new(vec![32768], Spawner::rate(5.0.into()), writer.finish()) + .with_name("additive") + .with_blending_mode(BlendingMode::Additive) + .init(init_pos) + .init(init_vel) + .init(init_age) + .init(init_lifetime) + .init(init_rotation) + .render(ParticleTextureModifier { + texture: texture_handle, + sample_mapping: ImageSampleMapping::Modulate, + }) + .render(SetSizeModifier { size: size.into() }), + ); + + commands + .spawn(ParticleEffectBundle::new(effect)) + .insert(Name::new("effect")); +} From 1634915300446a9cf3a2352da40b0f4e44a55958 Mon Sep 17 00:00:00 2001 From: Soulghost Date: Fri, 31 May 2024 10:40:39 +0800 Subject: [PATCH 3/7] merge blend_mode into bevy_hanabi::AlphaMode and add Multiply blending mode --- examples/additive.rs | 2 +- src/asset.rs | 53 ++++++++++++++++++++-------------------- src/lib.rs | 10 ++++---- src/render/batch.rs | 10 ++++---- src/render/mod.rs | 57 +++++++++++++++++++++++++++++++------------- 5 files changed, 79 insertions(+), 53 deletions(-) diff --git a/examples/additive.rs b/examples/additive.rs index 0865421a..c8ec0e85 100644 --- a/examples/additive.rs +++ b/examples/additive.rs @@ -104,7 +104,7 @@ fn setup( let effect = effects.add( EffectAsset::new(vec![32768], Spawner::rate(5.0.into()), writer.finish()) .with_name("additive") - .with_blending_mode(BlendingMode::Additive) + .with_alpha_mode(bevy_hanabi::AlphaMode::Blend) .init(init_pos) .init(init_vel) .init(init_age) diff --git a/src/asset.rs b/src/asset.rs index 5723ab2b..d3e3cf33 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -127,6 +127,33 @@ pub enum AlphaMode { #[default] Blend, + /// Similar to [`AlphaMode::Blend`], however assumes RGB channel values are + /// [premultiplied](https://en.wikipedia.org/wiki/Alpha_compositing#Straight_versus_premultiplied). + /// + /// For otherwise constant RGB values, behaves more like [`AlphaMode::Blend`] for + /// alpha values closer to 1.0, and more like [`AlphaMode::Add`] for + /// alpha values closer to 0.0. + /// + /// Can be used to avoid “border” or “outline” artifacts that can occur + /// when using plain alpha-blended textures. + Premultiply, + + /// Combines the color of the fragments with the colors behind them in an + /// additive process, (i.e. like light) producing lighter results. + /// + /// Black produces no effect. Alpha values can be used to modulate the result. + /// + /// Useful for effects like holograms, ghosts, lasers and other energy beams. + Add, + + /// Combines the color of the fragments with the colors behind them in a + /// multiplicative process, (i.e. like pigments) producing darker results. + /// + /// White produces no effect. Alpha values can be used to modulate the result. + /// + /// Useful for effects like stained glass, window tint film and some colored liquids. + Multiply, + /// Render the effect with alpha masking. /// /// With this mode, the final alpha value computed per particle fragment is @@ -163,24 +190,6 @@ pub enum AlphaMode { Mask(ExprHandle), } -/// Blending mode for rendering an effect. -#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Reflect, Serialize, Deserialize, Hash)] -#[non_exhaustive] -pub enum BlendingMode { - #[default] - /// The base color alpha value defines the opacity of the color. - /// Standard alpha-blending is used to blend the fragment’s color with the color behind it. - Alpha, - - /// Similar to AlphaMode::Blend, however assumes RGB channel values are premultiplied. - Premultiply, - - /// This blend mode simply adds pixel values of one layer with the other. - /// In case of values above 1 (in the case of RGB), white is displayed. - /// This is useful for glow effects, like those you might use for fire or magic spells. - Additive, -} - /// Asset describing a visual effect. /// /// The effect can be instanciated with a [`ParticleEffect`] component, or a @@ -238,8 +247,6 @@ pub struct EffectAsset { module: Module, /// Alpha mode. pub alpha_mode: AlphaMode, - /// Blending mode. - pub blending_mode: BlendingMode, } impl EffectAsset { @@ -361,12 +368,6 @@ impl EffectAsset { self } - /// Set the blending mode. - pub fn with_blending_mode(mut self, blending_mode: BlendingMode) -> Self { - self.blending_mode = blending_mode; - self - } - /// Get the list of existing properties. /// /// This is a shortcut for `self.module().properties()`. diff --git a/src/lib.rs b/src/lib.rs index 7f70e663..a5c6be18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,7 +190,7 @@ mod time; #[cfg(test)] mod test_utils; -pub use asset::{AlphaMode, BlendingMode, EffectAsset, MotionIntegration, SimulationCondition}; +pub use asset::{AlphaMode, EffectAsset, MotionIntegration, SimulationCondition}; pub use attributes::*; pub use bundle::ParticleEffectBundle; pub use gradient::{Gradient, GradientKey}; @@ -1109,8 +1109,8 @@ pub struct CompiledParticleEffect { z_layer_2d: FloatOrd, /// Layout flags. layout_flags: LayoutFlags, - /// Blending mode. - blending_mode: BlendingMode, + /// Alpha mode. + alpha_mode: AlphaMode, } impl Default for CompiledParticleEffect { @@ -1123,7 +1123,7 @@ impl Default for CompiledParticleEffect { #[cfg(feature = "2d")] z_layer_2d: FloatOrd(0.0), layout_flags: LayoutFlags::NONE, - blending_mode: default(), + alpha_mode: default(), } } } @@ -1199,7 +1199,7 @@ impl CompiledParticleEffect { }; self.layout_flags = shader_source.layout_flags; - self.blending_mode = asset.blending_mode; + self.alpha_mode = asset.alpha_mode; let init_shader = shader_cache.get_or_insert(&asset.name, &shader_source.init, shaders); let update_shaders: Vec<_> = shader_source diff --git a/src/render/batch.rs b/src/render/batch.rs index 35643cd3..709b0c7d 100644 --- a/src/render/batch.rs +++ b/src/render/batch.rs @@ -39,8 +39,8 @@ pub(crate) struct EffectBatches { pub particle_layout: ParticleLayout, /// Flags describing the render layout. pub layout_flags: LayoutFlags, - /// Blending mode. - pub blending_mode: BlendingMode, + /// Alpha mode. + pub alpha_mode: AlphaMode, /// Entities holding the source [`ParticleEffect`] instances which were /// batched into this single batch. Used to determine visibility per view. /// @@ -126,7 +126,7 @@ impl EffectBatches { .collect(), handle: input.handle, layout_flags: input.layout_flags, - blending_mode: input.blending_mode, + alpha_mode: input.alpha_mode, image_handle: input.image_handle, render_shaders: input.effect_shader.render, init_pipeline_id, @@ -152,8 +152,8 @@ pub(crate) struct BatchesInput { pub effect_shader: EffectShader, /// Various flags related to the effect. pub layout_flags: LayoutFlags, - /// Blending mode for particle renderer. - pub blending_mode: BlendingMode, + /// Alpha mode. + pub alpha_mode: AlphaMode, /// Texture to modulate the particle color. pub image_handle: Handle, /// Number of particles to spawn for this effect. diff --git a/src/render/mod.rs b/src/render/mod.rs index 76c63ba3..299b39c4 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -41,14 +41,14 @@ use rand::random; use crate::{ asset::EffectAsset, next_multiple_of, - prelude::BlendingMode, render::{ batch::{BatchesInput, EffectDrawBatch}, effect_cache::DispatchBufferIndices, }, spawn::EffectSpawner, - CompiledParticleEffect, EffectProperties, EffectShader, EffectSimulation, HanabiPlugin, - ParticleLayout, PropertyLayout, RemovedEffectsEvent, SimulationCondition, ToWgslString, + AlphaMode, CompiledParticleEffect, EffectProperties, EffectShader, EffectSimulation, + HanabiPlugin, ParticleLayout, PropertyLayout, RemovedEffectsEvent, SimulationCondition, + ToWgslString, }; mod aligned_buffer_vec; @@ -983,6 +983,15 @@ enum PipelineMode { Camera3d, } +#[cfg(all(feature = "2d", feature = "3d"))] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +enum AlphaBlendMode { + Alpha, + Premultiply, + Add, + Multiply, +} + #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) struct ParticleRenderPipelineKey { /// Render shader, with snippets applied, but not preprocessed yet. @@ -1001,6 +1010,8 @@ pub(crate) struct ParticleRenderPipelineKey { /// Key: USE_ALPHA_MASK /// The effect is rendered with alpha masking. use_alpha_mask: bool, + /// The effect needs Alpha blend. + alpha_blend_mode: AlphaBlendMode, /// Key: FLIPBOOK /// The effect is rendered with flipbook texture animation based on the /// sprite index of each particle. @@ -1017,8 +1028,6 @@ pub(crate) struct ParticleRenderPipelineKey { msaa_samples: u32, /// Is the camera using an HDR render target? hdr: bool, - /// Controls how the color of the particle blends with the background. - blending_mode: BlendingMode, } impl Default for ParticleRenderPipelineKey { @@ -1029,13 +1038,13 @@ impl Default for ParticleRenderPipelineKey { has_image: false, local_space_simulation: false, use_alpha_mask: false, + alpha_blend_mode: AlphaBlendMode::Alpha, flipbook: false, needs_uv: false, #[cfg(all(feature = "2d", feature = "3d"))] pipeline_mode: PipelineMode::Camera3d, msaa_samples: Msaa::default().samples(), hdr: false, - blending_mode: BlendingMode::Alpha, } } } @@ -1217,10 +1226,10 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline { TextureFormat::bevy_default() }; - let blend_state = match key.blending_mode { - BlendingMode::Alpha => BlendState::ALPHA_BLENDING, - BlendingMode::Premultiply => BlendState::PREMULTIPLIED_ALPHA_BLENDING, - BlendingMode::Additive => BlendState { + let blend_state = match key.alpha_blend_mode { + AlphaBlendMode::Alpha => BlendState::ALPHA_BLENDING, + AlphaBlendMode::Premultiply => BlendState::PREMULTIPLIED_ALPHA_BLENDING, + AlphaBlendMode::Add => BlendState { color: BlendComponent { src_factor: BlendFactor::SrcAlpha, dst_factor: BlendFactor::One, @@ -1232,6 +1241,14 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline { operation: BlendOperation::Add, }, }, + AlphaBlendMode::Multiply => BlendState { + color: BlendComponent { + src_factor: BlendFactor::Dst, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::OVER, + }, }; RenderPipelineDescriptor { @@ -1311,8 +1328,8 @@ pub(crate) struct ExtractedEffect { pub inverse_transform: Mat4, /// Layout flags. pub layout_flags: LayoutFlags, - /// Blending mode. - pub blending_mode: BlendingMode, + /// Alpha mode. + pub alpha_mode: AlphaMode, /// Texture to modulate the particle color. pub image_handle: Handle, /// Effect shader. @@ -1541,7 +1558,7 @@ pub(crate) fn extract_effects( layout_flags |= LayoutFlags::PARTICLE_TEXTURE; } - let blending_mode = effect.blending_mode; + let alpha_mode = effect.alpha_mode; trace!( "Extracted instance of effect '{}' on entity {:?}: image_handle={:?} has_image={} layout_flags={:?}", @@ -1564,7 +1581,7 @@ pub(crate) fn extract_effects( // TODO - more efficient/correct way than inverse()? inverse_transform: transform.compute_matrix().inverse(), layout_flags, - blending_mode, + alpha_mode, image_handle, effect_shader, #[cfg(feature = "2d")] @@ -2099,7 +2116,7 @@ pub(crate) fn prepare_effects( property_layout: extracted_effect.property_layout.clone(), effect_shader: extracted_effect.effect_shader.clone(), layout_flags: extracted_effect.layout_flags, - blending_mode: extracted_effect.blending_mode, + alpha_mode: extracted_effect.alpha_mode, image_handle: extracted_effect.image_handle, spawn_count: extracted_effect.spawn_count, transform: extracted_effect.transform.into(), @@ -2493,6 +2510,14 @@ fn emit_draw( let render_shader_source = &batches.render_shaders[draw_batch.group_index as usize]; trace!("Emit for group index #{}", draw_batch.group_index); + let alpha_blend_mode = match batches.alpha_mode { + AlphaMode::Blend => AlphaBlendMode::Alpha, + AlphaMode::Premultiply => AlphaBlendMode::Premultiply, + AlphaMode::Add => AlphaBlendMode::Add, + AlphaMode::Multiply => AlphaBlendMode::Multiply, + _ => AlphaBlendMode::Alpha, + }; + #[cfg(feature = "trace")] let _span_specialize = bevy::utils::tracing::info_span!("specialize").entered(); let render_pipeline_id = specialized_render_pipelines.specialize( @@ -2504,13 +2529,13 @@ fn emit_draw( has_image, local_space_simulation, use_alpha_mask, + alpha_blend_mode, flipbook, needs_uv, #[cfg(all(feature = "2d", feature = "3d"))] pipeline_mode, msaa_samples, hdr: view.hdr, - blending_mode: batches.blending_mode, }, ); #[cfg(feature = "trace")] From 5420bba5b71deb85437a3420a071225458ed6cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E7=BA=A7=E9=A1=B5=E9=9D=A2=E4=BB=94?= Date: Fri, 31 May 2024 23:59:57 +0800 Subject: [PATCH 4/7] Update examples/additive.rs Co-authored-by: Sludge <96552222+SludgePhD@users.noreply.github.com> --- examples/additive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/additive.rs b/examples/additive.rs index c8ec0e85..f19ab8cb 100644 --- a/examples/additive.rs +++ b/examples/additive.rs @@ -104,7 +104,7 @@ fn setup( let effect = effects.add( EffectAsset::new(vec![32768], Spawner::rate(5.0.into()), writer.finish()) .with_name("additive") - .with_alpha_mode(bevy_hanabi::AlphaMode::Blend) + .with_alpha_mode(bevy_hanabi::AlphaMode::Add) .init(init_pos) .init(init_vel) .init(init_age) From 63bb8f0aec96e1d896d78be7fde947ec91f87bf7 Mon Sep 17 00:00:00 2001 From: Soulghost Date: Sat, 1 Jun 2024 01:01:10 +0800 Subject: [PATCH 5/7] aligning the additive blending formula with Bevy --- src/render/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/mod.rs b/src/render/mod.rs index 299b39c4..0be08942 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1236,7 +1236,7 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline { operation: BlendOperation::Add, }, alpha: BlendComponent { - src_factor: BlendFactor::One, + src_factor: BlendFactor::Zero, dst_factor: BlendFactor::One, operation: BlendOperation::Add, }, From d1d31ba189cce45048116113cddb05ce67738076 Mon Sep 17 00:00:00 2001 From: Soulghost Date: Sun, 9 Jun 2024 10:06:46 +0800 Subject: [PATCH 6/7] simplify the alpha blending code --- assets/orange_circle.png | Bin 3038 -> 0 bytes examples/additive.rs | 123 --------------------------------------- src/asset.rs | 2 +- src/render/mod.rs | 34 ++++------- 4 files changed, 11 insertions(+), 148 deletions(-) delete mode 100644 assets/orange_circle.png delete mode 100644 examples/additive.rs diff --git a/assets/orange_circle.png b/assets/orange_circle.png deleted file mode 100644 index c066f32e254ad38814cf843c2ff307df6d032698..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3038 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 diff --git a/examples/additive.rs b/examples/additive.rs deleted file mode 100644 index f19ab8cb..00000000 --- a/examples/additive.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Example of additive blend mode. -//! -//! This example demonstrate how to change the blend mode for the particle renderer. - -use bevy::{ - core_pipeline::tonemapping::Tonemapping, - log::LogPlugin, - prelude::*, - render::{ - camera::Projection, render_resource::WgpuFeatures, settings::WgpuSettings, RenderPlugin, - }, -}; -#[cfg(feature = "examples_world_inspector")] -use bevy_inspector_egui::quick::WorldInspectorPlugin; - -use bevy_hanabi::prelude::*; - -fn main() -> Result<(), Box> { - let mut wgpu_settings = WgpuSettings::default(); - wgpu_settings - .features - .set(WgpuFeatures::VERTEX_WRITABLE_STORAGE, true); - - let mut app = App::default(); - app.insert_resource(ClearColor(Color::DARK_GRAY)) - .add_plugins( - DefaultPlugins - .set(LogPlugin { - level: bevy::log::Level::WARN, - filter: "bevy_hanabi=warn,additive=trace".to_string(), - update_subscriber: None, - }) - .set(RenderPlugin { - render_creation: wgpu_settings.into(), - synchronous_pipeline_compilation: false, - }) - .set(WindowPlugin { - primary_window: Some(Window { - title: "🎆 Hanabi — additive blending".to_string(), - ..default() - }), - ..default() - }), - ) - .add_plugins(HanabiPlugin); - - #[cfg(feature = "examples_world_inspector")] - app.add_plugins(WorldInspectorPlugin::default()); - - app.add_systems(Startup, setup) - .add_systems(Update, bevy::window::close_on_esc) - .run(); - - Ok(()) -} - -fn setup( - asset_server: Res, - mut commands: Commands, - mut effects: ResMut>, -) { - let camera = Camera3dBundle { - transform: Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y), - projection: Projection::Perspective(PerspectiveProjection { - fov: 90.0, - ..Default::default() - }), - tonemapping: Tonemapping::None, - ..Default::default() - }; - - commands.spawn(camera); - - let texture_handle: Handle = asset_server.load("orange_circle.png"); - - let writer = ExprWriter::new(); - - let age = writer.lit(0.).expr(); - let init_age = SetAttributeModifier::new(Attribute::AGE, age); - - let lifetime = writer.lit(5.).expr(); - let init_lifetime = SetAttributeModifier::new(Attribute::LIFETIME, lifetime); - - let init_pos = SetPositionCircleModifier { - center: writer.lit(0.).expr(), - axis: writer.lit(Vec3::Z).expr(), - radius: writer.lit(0.2).expr(), - dimension: ShapeDimension::Volume, - }; - - let init_vel = SetVelocityCircleModifier { - center: writer.lit(Vec3::ZERO).expr(), - axis: writer.lit(Vec3::Z).expr(), - speed: (writer.lit(0.2) + writer.lit(0.2) * writer.rand(ScalarType::Float)).expr(), - }; - - // Use the F32_0 attribute as a per-particle rotation value, initialized on - // spawn and constant after. The rotation angle is in radians, here randomly - // selected in [0:2*PI]. - let rotation = (writer.rand(ScalarType::Float) * writer.lit(std::f32::consts::TAU)).expr(); - let init_rotation = SetAttributeModifier::new(Attribute::F32_0, rotation); - - let size = Vec2::splat(0.8); - let effect = effects.add( - EffectAsset::new(vec![32768], Spawner::rate(5.0.into()), writer.finish()) - .with_name("additive") - .with_alpha_mode(bevy_hanabi::AlphaMode::Add) - .init(init_pos) - .init(init_vel) - .init(init_age) - .init(init_lifetime) - .init(init_rotation) - .render(ParticleTextureModifier { - texture: texture_handle, - sample_mapping: ImageSampleMapping::Modulate, - }) - .render(SetSizeModifier { size: size.into() }), - ); - - commands - .spawn(ParticleEffectBundle::new(effect)) - .insert(Name::new("effect")); -} diff --git a/src/asset.rs b/src/asset.rs index d3e3cf33..2ccdf3d3 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -103,7 +103,7 @@ pub enum SimulationCondition { /// rendered during the [`Transparent2d`] render phase. /// /// [`Transparent2d`]: bevy::core_pipeline::core_2d::Transparent2d -#[derive(Debug, Default, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Reflect, Serialize, Deserialize, Hash)] #[non_exhaustive] pub enum AlphaMode { /// Render the effect with alpha blending. diff --git a/src/render/mod.rs b/src/render/mod.rs index 0be08942..bf2ca37a 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -983,15 +983,6 @@ enum PipelineMode { Camera3d, } -#[cfg(all(feature = "2d", feature = "3d"))] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -enum AlphaBlendMode { - Alpha, - Premultiply, - Add, - Multiply, -} - #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) struct ParticleRenderPipelineKey { /// Render shader, with snippets applied, but not preprocessed yet. @@ -1011,7 +1002,7 @@ pub(crate) struct ParticleRenderPipelineKey { /// The effect is rendered with alpha masking. use_alpha_mask: bool, /// The effect needs Alpha blend. - alpha_blend_mode: AlphaBlendMode, + alpha_mode: AlphaMode, /// Key: FLIPBOOK /// The effect is rendered with flipbook texture animation based on the /// sprite index of each particle. @@ -1038,7 +1029,7 @@ impl Default for ParticleRenderPipelineKey { has_image: false, local_space_simulation: false, use_alpha_mask: false, - alpha_blend_mode: AlphaBlendMode::Alpha, + alpha_mode: AlphaMode::Blend, flipbook: false, needs_uv: false, #[cfg(all(feature = "2d", feature = "3d"))] @@ -1226,10 +1217,10 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline { TextureFormat::bevy_default() }; - let blend_state = match key.alpha_blend_mode { - AlphaBlendMode::Alpha => BlendState::ALPHA_BLENDING, - AlphaBlendMode::Premultiply => BlendState::PREMULTIPLIED_ALPHA_BLENDING, - AlphaBlendMode::Add => BlendState { + let blend_state = match key.alpha_mode { + AlphaMode::Blend => BlendState::ALPHA_BLENDING, + AlphaMode::Premultiply => BlendState::PREMULTIPLIED_ALPHA_BLENDING, + AlphaMode::Add => BlendState { color: BlendComponent { src_factor: BlendFactor::SrcAlpha, dst_factor: BlendFactor::One, @@ -1241,7 +1232,7 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline { operation: BlendOperation::Add, }, }, - AlphaBlendMode::Multiply => BlendState { + AlphaMode::Multiply => BlendState { color: BlendComponent { src_factor: BlendFactor::Dst, dst_factor: BlendFactor::OneMinusSrcAlpha, @@ -1249,6 +1240,7 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline { }, alpha: BlendComponent::OVER, }, + _ => BlendState::ALPHA_BLENDING, }; RenderPipelineDescriptor { @@ -2510,13 +2502,7 @@ fn emit_draw( let render_shader_source = &batches.render_shaders[draw_batch.group_index as usize]; trace!("Emit for group index #{}", draw_batch.group_index); - let alpha_blend_mode = match batches.alpha_mode { - AlphaMode::Blend => AlphaBlendMode::Alpha, - AlphaMode::Premultiply => AlphaBlendMode::Premultiply, - AlphaMode::Add => AlphaBlendMode::Add, - AlphaMode::Multiply => AlphaBlendMode::Multiply, - _ => AlphaBlendMode::Alpha, - }; + let alpha_mode = batches.alpha_mode; #[cfg(feature = "trace")] let _span_specialize = bevy::utils::tracing::info_span!("specialize").entered(); @@ -2529,7 +2515,7 @@ fn emit_draw( has_image, local_space_simulation, use_alpha_mask, - alpha_blend_mode, + alpha_mode, flipbook, needs_uv, #[cfg(all(feature = "2d", feature = "3d"))] From de747192883fe2e0c617383b963f226ee20d98a8 Mon Sep 17 00:00:00 2001 From: Soulghost Date: Sun, 23 Jun 2024 11:33:20 +0800 Subject: [PATCH 7/7] fix: compile and CI issues --- src/render/batch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/batch.rs b/src/render/batch.rs index 709b0c7d..57750029 100644 --- a/src/render/batch.rs +++ b/src/render/batch.rs @@ -11,7 +11,7 @@ use super::{ effect_cache::{DispatchBufferIndices, EffectSlices}, EffectCacheId, GpuCompressedTransform, LayoutFlags, }; -use crate::{BlendingMode, EffectAsset, EffectShader, ParticleLayout, PropertyLayout}; +use crate::{AlphaMode, EffectAsset, EffectShader, ParticleLayout, PropertyLayout}; /// Data needed to render all batches pertaining to a specific effect. #[derive(Debug, Component)]