diff --git a/Cargo.toml b/Cargo.toml index 53db32d150de6..547846e5fc45d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -297,6 +297,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/crates/bevy_pbr/src/alpha.rs b/crates/bevy_pbr/src/alpha.rs index b42a78bd8ad4d..8aff7db46d6e8 100644 --- a/crates/bevy_pbr/src/alpha.rs +++ b/crates/bevy_pbr/src/alpha.rs @@ -23,6 +23,30 @@ pub enum AlphaMode { /// Standard alpha-blending is used to blend the fragment's color /// with the color behind it. 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. + Premultiplied, + /// 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, } impl Eq for AlphaMode {} diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 379058b52adff..a3039f08cd151 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -415,8 +415,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( @@ -455,7 +461,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 629fee943c4d0..2e65e3e835413 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -298,16 +298,25 @@ 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 TWO_COMPONENT_NORMAL_MAP = (1 << 9); - const FLIP_NORMAL_MAP_Y = (1 << 10); + const TWO_COMPONENT_NORMAL_MAP = (1 << 6); + const FLIP_NORMAL_MAP_Y = (1 << 7); + const ALPHA_MODE_RESERVED_BITS = (Self::ALPHA_MODE_MASK_BITS << Self::ALPHA_MODE_SHIFT_BITS); // ← Bitmask reserving bits for the `AlphaMode` + const ALPHA_MODE_OPAQUE = (0 << Self::ALPHA_MODE_SHIFT_BITS); // ← Values are just sequential values bitshifted into + const ALPHA_MODE_MASK = (1 << Self::ALPHA_MODE_SHIFT_BITS); // the bitmask, and can range from 0 to 7. + const ALPHA_MODE_BLEND = (2 << Self::ALPHA_MODE_SHIFT_BITS); // + const ALPHA_MODE_PREMULTIPLIED = (3 << Self::ALPHA_MODE_SHIFT_BITS); // + const ALPHA_MODE_ADD = (4 << Self::ALPHA_MODE_SHIFT_BITS); // Right now only values 0–5 are used, which still gives + const ALPHA_MODE_MULTIPLY = (5 << Self::ALPHA_MODE_SHIFT_BITS); // ← us "room" for two more modes without adding more bits const NONE = 0; const UNINITIALIZED = 0xFFFF; } } +impl StandardMaterialFlags { + const ALPHA_MODE_MASK_BITS: u32 = 0b111; + const ALPHA_MODE_SHIFT_BITS: u32 = 32 - Self::ALPHA_MODE_MASK_BITS.count_ones(); +} + /// The GPU representation of the uniform data of a [`StandardMaterial`]. #[derive(Clone, Default, ShaderType)] pub struct StandardMaterialUniform { @@ -380,6 +389,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/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 8c0af74df1c86..8202409d4aa99 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -527,7 +527,10 @@ pub fn queue_prepass_material_meshes( match alpha_mode { AlphaMode::Opaque => {} AlphaMode::Mask(_) => mesh_key |= MeshPipelineKey::ALPHA_MASK, - AlphaMode::Blend => continue, + AlphaMode::Blend + | AlphaMode::Premultiplied + | AlphaMode::Add + | AlphaMode::Multiply => continue, } let pipeline_id = pipelines.specialize( @@ -566,7 +569,10 @@ pub fn queue_prepass_material_meshes( distance, }); } - AlphaMode::Blend => {} + AlphaMode::Blend + | AlphaMode::Premultiplied + | AlphaMode::Add + | AlphaMode::Multiply => {} } } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index bb828137ffa9c..d1fb3a3c62162 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -557,13 +557,16 @@ 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 DEPTH_PREPASS = (1 << 4); - const NORMAL_PREPASS = (1 << 5); - const ALPHA_MASK = (1 << 6); + const HDR = (1 << 0); + const TONEMAP_IN_SHADER = (1 << 1); + const DEBAND_DITHER = (1 << 2); + const DEPTH_PREPASS = (1 << 3); + const NORMAL_PREPASS = (1 << 4); + const ALPHA_MASK = (1 << 5); + const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state + const BLEND_OPAQUE = (0 << Self::BLEND_SHIFT_BITS); // ← Values are just sequential within the mask, and can range from 0 to 3 + const BLEND_PREMULTIPLIED_ALPHA = (1 << Self::BLEND_SHIFT_BITS); // + const BLEND_MULTIPLY = (2 << Self::BLEND_SHIFT_BITS); // ← We still have room for one more value without adding more bits 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; } @@ -573,7 +576,11 @@ impl MeshPipelineKey { const MSAA_MASK_BITS: u32 = 0b111; const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111; - const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = Self::MSAA_SHIFT_BITS - 3; + const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = + Self::MSAA_SHIFT_BITS - Self::PRIMITIVE_TOPOLOGY_MASK_BITS.count_ones(); + const BLEND_MASK_BITS: u32 = 0b11; + const BLEND_SHIFT_BITS: u32 = + Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::BLEND_MASK_BITS.count_ones(); pub fn from_msaa_samples(msaa_samples: u32) -> Self { let msaa_bits = @@ -677,12 +684,30 @@ 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_RESERVED_BITS); + if pass == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA { + label = "premultiplied_alpha_mesh_pipeline".into(); + blend = Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING); + shader_defs.push("PREMULTIPLY_ALPHA".into()); + shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into()); // 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, + }); + shader_defs.push("PREMULTIPLY_ALPHA".into()); + shader_defs.push("BLEND_MULTIPLY".into()); + // 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 cfe825190d2a5..3f5774551535a 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -106,6 +106,9 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { // SRGB; the GPU will assume our output is linear and will apply an SRGB conversion. output_rgb = pow(output_rgb, vec3(2.2)); output_color = vec4(output_rgb, output_color.a); +#endif +#ifdef PREMULTIPLY_ALPHA + output_color = premultiply_alpha(material.flags, output_color); #endif return output_color; } diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 5da8ca9030d23..bb638e6ea2979 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_RESERVED_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; @@ -268,3 +269,66 @@ fn dither(color: vec4, pos: vec2) -> vec4 { } #endif // DEBAND_DITHER +#ifdef PREMULTIPLY_ALPHA +fn premultiply_alpha(standard_material_flags: u32, color: vec4) -> vec4 { +// `Blend`, `Premultiplied` and `Alpha` all share the same `BlendState`. Depending +// on the alpha mode, we premultiply the color channels by the alpha channel value, +// (and also optionally replace the alpha value with 0.0) so that the result produces +// the desired blend mode when sent to the blending operation. +#ifdef BLEND_PREMULTIPLIED_ALPHA + // For `BlendState::PREMULTIPLIED_ALPHA_BLENDING` the blend function is: + // + // result = 1 * src_color + (1 - src_alpha) * dst_color + let alpha_mode = standard_material_flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; + if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND) { + // Here, we premultiply `src_color` by `src_alpha` (ahead of time, here in the shader) + // + // src_color *= src_alpha + // + // We end up with: + // + // result = 1 * (src_alpha * src_color) + (1 - src_alpha) * dst_color + // result = src_alpha * src_color + (1 - src_alpha) * dst_color + // + // Which is the blend operation for regular alpha blending `BlendState::ALPHA_BLENDING` + return vec4(color.rgb * color.a, color.a); + } else if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) { + // Here, we premultiply `src_color` by `src_alpha`, and replace `src_alpha` with 0.0: + // + // src_color *= src_alpha + // src_alpha = 0.0 + // + // We end up with: + // + // result = 1 * (src_alpha * src_color) + (1 - 0) * dst_color + // result = src_alpha * src_color + 1 * dst_color + // + // Which is the blend operation for additive blending + return vec4(color.rgb * color.a, 0.0); + } else { + // Here, we don't do anything, so that we get premultiplied alpha blending. (As expected) + return color.rgba; + } +#endif +// `Multiply` uses its own `BlendState`, but we still need to premultiply here in the +// shader so that we get correct results as we tweak the alpha channel +#ifdef BLEND_MULTIPLY + // The blend function is: + // + // result = dst_color * src_color + (1 - src_alpha) * dst_color + // + // We premultiply `src_color` by `src_alpha`: + // + // src_color *= src_alpha + // + // We end up with: + // + // result = dst_color * (src_color * src_alpha) + (1 - src_alpha) * dst_color + // result = src_alpha * (src_color * dst_color) + (1 - src_alpha) * dst_color + // + // Which is the blend operation for multiplicative blending with arbitrary mixing + // controlled by the source alpha channel + return vec4(color.rgb * color.a, color.a); +#endif +} +#endif diff --git a/crates/bevy_pbr/src/render/pbr_types.wgsl b/crates/bevy_pbr/src/render/pbr_types.wgsl index 879cc64e2d744..8b9eb3297b292 100644 --- a/crates/bevy_pbr/src/render/pbr_types.wgsl +++ b/crates/bevy_pbr/src/render/pbr_types.wgsl @@ -17,11 +17,17 @@ 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_TWO_COMPONENT_NORMAL_MAP: u32 = 512u; -let STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 1024u; +let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 64u; +let STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 128u; +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS: u32 = 3758096384u; // (0b111u32 << 29) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 0u; // (0u32 << 29) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 536870912u; // (1u32 << 29) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 1073741824u; // (2u32 << 29) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED: u32 = 1610612736u; // (3u32 << 29) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD: u32 = 2147483648u; // (4u32 << 29) +let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MULTIPLY: u32 = 2684354560u; // (5u32 << 29) +// ↑ To calculate/verify the values above, use the following playground: +// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7792f8dd6fc6a8d4d0b6b1776898a7f4 // Creates a StandardMaterial with default values fn standard_material_new() -> StandardMaterial { diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs new file mode 100644 index 0000000000000..30076743e5fb1 --- /dev/null +++ b/examples/3d/blend_modes.rs @@ -0,0 +1,378 @@ +//! This example showcases different blend modes. +//! +//! ## Controls +//! +//! | Key Binding | Action | +//! |:-------------------|:------------------------------------| +//! | `Up` / `Down` | Increase / Decrease Alpha | +//! | `Left` / `Right` | Rotate Camera | +//! | `H` | Toggle HDR | +//! | `Spacebar` | Toggle Unlit | +//! | `C` | Randomize Colors | + +use bevy::prelude::*; +use rand::random; + +fn main() { + let mut app = App::new(); + + app.add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(example_control_system); + + // Unfortunately, MSAA and HDR are not supported simultaneously under WebGL. + // Since this example uses HDR, we must disable MSAA for WASM builds, at least + // until WebGPU is ready and no longer behind a feature flag in Web browsers. + #[cfg(target_arch = "wasm32")] + app.insert_resource(Msaa { samples: 1 }); // Default is 4 samples (MSAA on) + + app.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 + let 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, + }, + )) + .id(); + + // Blend + let 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, + }, + )) + .id(); + + // Premultiplied + let 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, + }, + )) + .id(); + + // Add + let 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, + }, + )) + .id(); + + // Multiply + let multiply = commands + .spawn(( + PbrBundle { + mesh: icosphere_mesh, + 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, + }, + )) + .id(); + + // 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.5, 10.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + // Controls Text + let text_style = TextStyle { + font: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: 18.0, + color: Color::BLACK, + }; + + let label_text_style = TextStyle { + font: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: 25.0, + color: Color::ORANGE, + }; + + commands.spawn( + TextBundle::from_section( + "Up / Down — Increase / Decrease Alpha\nLeft / Right — Rotate Camera\nH - Toggle HDR\nSpacebar — Toggle Unlit\nC — Randomize Colors", + text_style.clone(), + ) + .with_style(Style { + position_type: PositionType::Absolute, + position: UiRect { + top: Val::Px(10.0), + left: Val::Px(10.0), + ..default() + }, + ..default() + }), + ); + + commands.spawn(( + TextBundle::from_section("", text_style).with_style(Style { + position_type: PositionType::Absolute, + position: UiRect { + top: Val::Px(10.0), + right: Val::Px(10.0), + ..default() + }, + ..default() + }), + ExampleDisplay, + )); + + commands.spawn(( + TextBundle::from_section("┌─ Opaque\n│\n│\n│\n│", label_text_style.clone()).with_style( + Style { + position_type: PositionType::Absolute, + ..default() + }, + ), + ExampleLabel { entity: opaque }, + )); + + commands.spawn(( + TextBundle::from_section("┌─ Blend\n│\n│\n│", label_text_style.clone()).with_style(Style { + position_type: PositionType::Absolute, + ..default() + }), + ExampleLabel { entity: blend }, + )); + + commands.spawn(( + TextBundle::from_section("┌─ Premultiplied\n│\n│", label_text_style.clone()).with_style( + Style { + position_type: PositionType::Absolute, + ..default() + }, + ), + ExampleLabel { + entity: premultiplied, + }, + )); + + commands.spawn(( + TextBundle::from_section("┌─ Add\n│", label_text_style.clone()).with_style(Style { + position_type: PositionType::Absolute, + ..default() + }), + ExampleLabel { entity: add }, + )); + + commands.spawn(( + TextBundle::from_section("┌─ Multiply", label_text_style).with_style(Style { + position_type: PositionType::Absolute, + ..default() + }), + ExampleLabel { entity: multiply }, + )); +} + +#[derive(Component)] +struct ExampleControls { + unlit: bool, + color: bool, +} + +#[derive(Component)] +struct ExampleLabel { + entity: Entity, +} + +struct ExampleState { + alpha: f32, + unlit: bool, +} + +#[derive(Component)] +struct ExampleDisplay; + +impl Default for ExampleState { + fn default() -> Self { + ExampleState { + alpha: 0.9, + unlit: false, + } + } +} + +#[allow(clippy::too_many_arguments)] +fn example_control_system( + mut materials: ResMut>, + controllable: Query<(&Handle, &ExampleControls)>, + mut camera: Query<(&mut Camera, &mut Transform, &GlobalTransform), With>, + mut labels: Query<(&mut Style, &ExampleLabel)>, + mut display: Query<&mut Text, With>, + labelled: Query<&GlobalTransform>, + mut state: Local, + time: Res