From 71412828e5b8fd7b66354a85c715a9edd9582344 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Tue, 15 Nov 2022 23:11:27 -0300 Subject: [PATCH 01/23] Use a more efficient representation for alpha modes in material flags --- crates/bevy_pbr/src/pbr_material.rs | 7 ++++--- crates/bevy_pbr/src/render/pbr_functions.wgsl | 5 +++-- crates/bevy_pbr/src/render/pbr_types.wgsl | 7 ++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 8e48acf5821c6..97554adb22fc5 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -295,9 +295,10 @@ bitflags::bitflags! { const OCCLUSION_TEXTURE = (1 << 3); const DOUBLE_SIDED = (1 << 4); const UNLIT = (1 << 5); - const ALPHA_MODE_OPAQUE = (1 << 6); - const ALPHA_MODE_MASK = (1 << 7); - const ALPHA_MODE_BLEND = (1 << 8); + const ALPHA_MODE_BITS = (0b111 << 6); + const ALPHA_MODE_OPAQUE = (0b000 << 6); + const ALPHA_MODE_MASK = (0b001 << 6); + const ALPHA_MODE_BLEND = (0b010 << 6); const TWO_COMPONENT_NORMAL_MAP = (1 << 9); const FLIP_NORMAL_MAP_Y = (1 << 10); const NONE = 0; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 4f4c5495dbaa3..06b10942e7fdf 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -7,10 +7,11 @@ fn alpha_discard(material: StandardMaterial, output_color: vec4) -> vec4{ var color = output_color; - if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) { + let alpha_mode = (material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BITS); + if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) { // 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) { + } else if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) { if (color.a >= material.alpha_cutoff) { // NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque color.a = 1.0; diff --git a/crates/bevy_pbr/src/render/pbr_types.wgsl b/crates/bevy_pbr/src/render/pbr_types.wgsl index 879cc64e2d744..83e5b0ce2e138 100644 --- a/crates/bevy_pbr/src/render/pbr_types.wgsl +++ b/crates/bevy_pbr/src/render/pbr_types.wgsl @@ -17,9 +17,10 @@ let STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT: u32 = 4u; let STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT: u32 = 8u; let STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u; let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u; -let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 64u; -let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u; -let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u; +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BITS: u32 = 448u; // (0b111 << 6) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 0u; // (0b000 << 6) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 64u; // (0b001 << 6) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 128u; // (0b010 << 6) let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 512u; let STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 1024u; From bbb342050271f61474f1e01820e08506e57fe52b Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 16 Nov 2022 01:20:23 -0300 Subject: [PATCH 02/23] Add `Premultiplied`, `Add` and `Multiply` blend modes --- crates/bevy_pbr/src/alpha.rs | 3 ++ crates/bevy_pbr/src/material.rs | 15 +++++++-- crates/bevy_pbr/src/pbr_material.rs | 6 ++++ crates/bevy_pbr/src/render/mesh.rs | 31 ++++++++++++++----- crates/bevy_pbr/src/render/pbr.wgsl | 3 ++ crates/bevy_pbr/src/render/pbr_functions.wgsl | 13 ++++++++ crates/bevy_pbr/src/render/pbr_types.wgsl | 3 ++ 7 files changed, 64 insertions(+), 10 deletions(-) diff --git a/crates/bevy_pbr/src/alpha.rs b/crates/bevy_pbr/src/alpha.rs index b42a78bd8ad4d..351d2a88fcc39 100644 --- a/crates/bevy_pbr/src/alpha.rs +++ b/crates/bevy_pbr/src/alpha.rs @@ -23,6 +23,9 @@ pub enum AlphaMode { /// Standard alpha-blending is used to blend the fragment's color /// with the color behind it. Blend, + Premultiplied, + Add, + Multiply, } impl Eq for AlphaMode {} diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 2dc0e0f3ed59e..e7cf78f529de3 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -384,8 +384,14 @@ pub fn queue_material_meshes( MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | view_key; let alpha_mode = material.properties.alpha_mode; - if let AlphaMode::Blend = alpha_mode { - mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS; + if let AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add = + alpha_mode + { + // Blend, Premultiplied and Add all share the same pipeline key + // They're made distinct in the PBR shader, via `premultiply_alpha()` + mesh_key |= MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA; + } else if let AlphaMode::Multiply = alpha_mode { + mesh_key |= MeshPipelineKey::BLEND_MULTIPLY; } let pipeline_id = pipelines.specialize( @@ -424,7 +430,10 @@ pub fn queue_material_meshes( distance, }); } - AlphaMode::Blend => { + AlphaMode::Blend + | AlphaMode::Premultiplied + | AlphaMode::Add + | AlphaMode::Multiply => { transparent_phase.add(Transparent3d { entity: *visible_entity, draw_function: draw_transparent_pbr, diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 97554adb22fc5..0aeb74c76cca8 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -299,6 +299,9 @@ bitflags::bitflags! { const ALPHA_MODE_OPAQUE = (0b000 << 6); const ALPHA_MODE_MASK = (0b001 << 6); const ALPHA_MODE_BLEND = (0b010 << 6); + const ALPHA_MODE_PREMULTIPLIED = (0b011 << 6); + const ALPHA_MODE_ADD = (0b100 << 6); + const ALPHA_MODE_MULTIPLY = (0b101 << 6); const TWO_COMPONENT_NORMAL_MAP = (1 << 9); const FLIP_NORMAL_MAP_Y = (1 << 10); const NONE = 0; @@ -378,6 +381,9 @@ impl AsBindGroupShaderType for StandardMaterial { flags |= StandardMaterialFlags::ALPHA_MODE_MASK; } AlphaMode::Blend => flags |= StandardMaterialFlags::ALPHA_MODE_BLEND, + AlphaMode::Premultiplied => flags |= StandardMaterialFlags::ALPHA_MODE_PREMULTIPLIED, + AlphaMode::Add => flags |= StandardMaterialFlags::ALPHA_MODE_ADD, + AlphaMode::Multiply => flags |= StandardMaterialFlags::ALPHA_MODE_MULTIPLY, }; StandardMaterialUniform { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 73e7eea7b0c5f..8c82627bdaaf7 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -515,10 +515,13 @@ bitflags::bitflags! { /// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA. pub struct MeshPipelineKey: u32 { const NONE = 0; - const TRANSPARENT_MAIN_PASS = (1 << 0); - const HDR = (1 << 1); - const TONEMAP_IN_SHADER = (1 << 2); - const DEBAND_DITHER = (1 << 3); + const BLEND_BITS = (0b11 << 0); + const BLEND_OPAQUE = (0b00 << 0); + const BLEND_PREMULTIPLIED_ALPHA = (0b01 << 0); + const BLEND_MULTIPLY = (0b10 << 0); + const HDR = (1 << 2); + const TONEMAP_IN_SHADER = (1 << 3); + const DEBAND_DITHER = (1 << 4); const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } @@ -620,12 +623,26 @@ impl SpecializedMeshPipeline for MeshPipeline { let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; let (label, blend, depth_write_enabled); - if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) { - label = "transparent_mesh_pipeline".into(); - blend = Some(BlendState::ALPHA_BLENDING); + let pass = key.intersection(MeshPipelineKey::BLEND_BITS); + if pass == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA { + label = "premultiplied_alpha_mesh_pipeline".into(); + blend = Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING); // For the transparent pass, fragments that are closer will be alpha blended // but their depth is not written to the depth buffer depth_write_enabled = false; + } else if pass == MeshPipelineKey::BLEND_MULTIPLY { + label = "multiply_mesh_pipeline".into(); + blend = Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::Dst, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::OVER, + }); + // For the multiply pass, fragments that are closer will be alpha blended + // but their depth is not written to the depth buffer + depth_write_enabled = false; } else { label = "opaque_mesh_pipeline".into(); blend = Some(BlendState::REPLACE); diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 6f5d94edfbaf3..afea35f599ce5 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -101,5 +101,8 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { #ifdef DEBAND_DITHER output_color = dither(output_color, in.frag_coord.xy); #endif + + output_color = premultiply_alpha(material.flags, output_color); + return output_color; } diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 06b10942e7fdf..7aa6522792d0e 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -269,3 +269,16 @@ fn dither(color: vec4, pos: vec2) -> vec4 { } #endif +fn premultiply_alpha(standard_material_flags: u32, color: vec4) -> vec4 { + let alpha_mode = standard_material_flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BITS; + if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND) { + return vec4(color.rgb * color.a, color.a); + } else if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED) { + return color.rgba; + } else if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) { + return vec4(color.rgb * color.a, 0.0); + } else if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MULTIPLY) { + return vec4(color.rgb * color.a, color.a); + } + return color.rgba; +} diff --git a/crates/bevy_pbr/src/render/pbr_types.wgsl b/crates/bevy_pbr/src/render/pbr_types.wgsl index 83e5b0ce2e138..f325f58b2b90d 100644 --- a/crates/bevy_pbr/src/render/pbr_types.wgsl +++ b/crates/bevy_pbr/src/render/pbr_types.wgsl @@ -21,6 +21,9 @@ let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BITS: u32 = 448u; // (0b11 let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 0u; // (0b000 << 6) let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 64u; // (0b001 << 6) let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 128u; // (0b010 << 6) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED: u32 = 192u; // (0b011 << 6) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD: u32 = 256u; // (0b100 << 6) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MULTIPLY: u32 = 320u; // (0b101 << 6) let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 512u; let STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 1024u; From e2a0c46040775a43b0c66ac3486f5479f7f314ca Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Wed, 16 Nov 2022 02:52:19 -0300 Subject: [PATCH 03/23] Add blend modes example --- Cargo.toml | 10 ++ examples/3d/blend_modes.rs | 259 +++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 examples/3d/blend_modes.rs diff --git a/Cargo.toml b/Cargo.toml index 251e47609645d..b8d8e2acee20d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -293,6 +293,16 @@ description = "A scene showcasing the built-in 3D shapes" category = "3D Rendering" wasm = true +[[example]] +name = "blend_modes" +path = "examples/3d/blend_modes.rs" + +[package.metadata.example.blend_modes] +name = "Blend Modes" +description = "Showcases different blend modes" +category = "3D Rendering" +wasm = true + [[example]] name = "lighting" path = "examples/3d/lighting.rs" diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs new file mode 100644 index 0000000000000..dc2843caba138 --- /dev/null +++ b/examples/3d/blend_modes.rs @@ -0,0 +1,259 @@ +//! This example showcases different blend modes. +//! +//! ## Controls +//! +//! | Key Binding | Action | +//! |:-------------------|:------------------------------------| +//! | `Up` / `Down` | Increase / Decrease Alpha | +//! | `Left` / `Right` | Rotate Camera | +//! | `Spacebar` | Toggle Unlit | +//! | `C` | Randomize Colors | + +use bevy::prelude::*; +use rand::random; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(example_control_system) + .run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + asset_server: Res, +) { + let base_color = Color::rgba(0.9, 0.2, 0.3, 1.0); + let icosphere_mesh = meshes.add( + Mesh::try_from(shape::Icosphere { + radius: 0.9, + subdivisions: 7, + }) + .unwrap(), + ); + + // Opaque + commands.spawn(( + PbrBundle { + mesh: icosphere_mesh.clone(), + material: materials.add(StandardMaterial { + base_color, + alpha_mode: AlphaMode::Opaque, + ..default() + }), + transform: Transform::from_xyz(-4.0, 0.0, 0.0), + ..default() + }, + ExampleControls { + unlit: true, + color: true, + }, + )); + + // Blend + commands.spawn(( + PbrBundle { + mesh: icosphere_mesh.clone(), + material: materials.add(StandardMaterial { + base_color, + alpha_mode: AlphaMode::Blend, + ..default() + }), + transform: Transform::from_xyz(-2.0, 0.0, 0.0), + ..default() + }, + ExampleControls { + unlit: true, + color: true, + }, + )); + + // Premultiplied + commands.spawn(( + PbrBundle { + mesh: icosphere_mesh.clone(), + material: materials.add(StandardMaterial { + base_color, + alpha_mode: AlphaMode::Premultiplied, + ..default() + }), + transform: Transform::from_xyz(0.0, 0.0, 0.0), + ..default() + }, + ExampleControls { + unlit: true, + color: true, + }, + )); + + // Add + commands.spawn(( + PbrBundle { + mesh: icosphere_mesh.clone(), + material: materials.add(StandardMaterial { + base_color, + alpha_mode: AlphaMode::Add, + ..default() + }), + transform: Transform::from_xyz(2.0, 0.0, 0.0), + ..default() + }, + ExampleControls { + unlit: true, + color: true, + }, + )); + + // Multiply + commands.spawn(( + PbrBundle { + mesh: icosphere_mesh.clone(), + material: materials.add(StandardMaterial { + base_color, + alpha_mode: AlphaMode::Multiply, + ..default() + }), + transform: Transform::from_xyz(4.0, 0.0, 0.0), + ..default() + }, + ExampleControls { + unlit: true, + color: true, + }, + )); + + // Chessboard Plane + let black_material = materials.add(Color::BLACK.into()); + let white_material = materials.add(Color::WHITE.into()); + let plane_mesh = meshes.add(shape::Plane { size: 2.0 }.into()); + + for x in -3..4 { + for z in -3..4 { + commands.spawn(( + PbrBundle { + mesh: plane_mesh.clone(), + material: if (x + z) % 2 == 0 { + black_material.clone() + } else { + white_material.clone() + }, + transform: Transform::from_xyz(x as f32 * 2.0, -1.0, z as f32 * 2.0), + ..default() + }, + ExampleControls { + unlit: false, + color: true, + }, + )); + } + } + + // Light + commands.spawn(PointLightBundle { + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + + // Camera + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(0.0, 2.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + // Controls Text + commands.spawn( + TextBundle::from_section( + "Up / Down — Increase / Decrease Alpha\nLeft / Right — Rotate Camera\nSpacebar — Toggle Unlit\nC — Randomize Colors", + 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() + }), + ); +} + +#[derive(Component)] +struct ExampleControls { + unlit: bool, + color: bool, +} + +struct ExampleState { + alpha: f32, + unlit: bool, +} + +impl Default for ExampleState { + fn default() -> Self { + ExampleState { + alpha: 1.0, + unlit: false, + } + } +} + +fn example_control_system( + mut materials: ResMut>, + controllable: Query<(&Handle, &ExampleControls)>, + mut camera: Query<&mut Transform, With>, + mut state: Local, + time: Res