diff --git a/src/asset.rs b/src/asset.rs index 526849e4..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. @@ -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 diff --git a/src/lib.rs b/src/lib.rs index cb89330a..a5c6be18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1109,6 +1109,8 @@ pub struct CompiledParticleEffect { z_layer_2d: FloatOrd, /// Layout flags. layout_flags: LayoutFlags, + /// Alpha mode. + alpha_mode: AlphaMode, } 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, + alpha_mode: default(), } } } @@ -1196,6 +1199,7 @@ impl CompiledParticleEffect { }; self.layout_flags = shader_source.layout_flags; + 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 93a056d6..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::{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)] @@ -39,6 +39,8 @@ pub(crate) struct EffectBatches { pub particle_layout: ParticleLayout, /// Flags describing the render layout. pub layout_flags: LayoutFlags, + /// 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. /// @@ -124,6 +126,7 @@ impl EffectBatches { .collect(), handle: input.handle, layout_flags: input.layout_flags, + alpha_mode: input.alpha_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, + /// 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 7dd7765b..bf2ca37a 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -46,8 +46,9 @@ use crate::{ 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; @@ -1000,6 +1001,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_mode: AlphaMode, /// Key: FLIPBOOK /// The effect is rendered with flipbook texture animation based on the /// sprite index of each particle. @@ -1026,6 +1029,7 @@ impl Default for ParticleRenderPipelineKey { has_image: false, local_space_simulation: false, use_alpha_mask: false, + alpha_mode: AlphaMode::Blend, flipbook: false, needs_uv: false, #[cfg(all(feature = "2d", feature = "3d"))] @@ -1213,6 +1217,32 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline { TextureFormat::bevy_default() }; + 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, + operation: BlendOperation::Add, + }, + alpha: BlendComponent { + src_factor: BlendFactor::Zero, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }, + AlphaMode::Multiply => BlendState { + color: BlendComponent { + src_factor: BlendFactor::Dst, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::OVER, + }, + _ => BlendState::ALPHA_BLENDING, + }; + RenderPipelineDescriptor { vertex: VertexState { shader: key.shader.clone(), @@ -1226,7 +1256,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 +1320,8 @@ pub(crate) struct ExtractedEffect { pub inverse_transform: Mat4, /// Layout flags. pub layout_flags: LayoutFlags, + /// Alpha mode. + pub alpha_mode: AlphaMode, /// Texture to modulate the particle color. pub image_handle: Handle, /// Effect shader. @@ -1518,6 +1550,8 @@ pub(crate) fn extract_effects( layout_flags |= LayoutFlags::PARTICLE_TEXTURE; } + let alpha_mode = effect.alpha_mode; + trace!( "Extracted instance of effect '{}' on entity {:?}: image_handle={:?} has_image={} layout_flags={:?}", asset.name, @@ -1539,6 +1573,7 @@ pub(crate) fn extract_effects( // TODO - more efficient/correct way than inverse()? inverse_transform: transform.compute_matrix().inverse(), layout_flags, + alpha_mode, image_handle, effect_shader, #[cfg(feature = "2d")] @@ -2073,6 +2108,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, + alpha_mode: extracted_effect.alpha_mode, image_handle: extracted_effect.image_handle, spawn_count: extracted_effect.spawn_count, transform: extracted_effect.transform.into(), @@ -2466,6 +2502,8 @@ 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_mode = batches.alpha_mode; + #[cfg(feature = "trace")] let _span_specialize = bevy::utils::tracing::info_span!("specialize").entered(); let render_pipeline_id = specialized_render_pipelines.specialize( @@ -2477,6 +2515,7 @@ fn emit_draw( has_image, local_space_simulation, use_alpha_mask, + alpha_mode, flipbook, needs_uv, #[cfg(all(feature = "2d", feature = "3d"))]