mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Standard Material Blend Modes (#6644)
# Objective - This PR adds support for blend modes to the PBR `StandardMaterial`. <img width="1392" alt="Screenshot 2022-11-18 at 20 00 56" src="https://user-images.githubusercontent.com/418473/202820627-0636219a-a1e5-437a-b08b-b08c6856bf9c.png"> <img width="1392" alt="Screenshot 2022-11-18 at 20 01 01" src="https://user-images.githubusercontent.com/418473/202820615-c8d43301-9a57-49c4-bd21-4ae343c3e9ec.png"> ## Solution - The existing `AlphaMode` enum is extended, adding three more modes: `AlphaMode::Premultiplied`, `AlphaMode::Add` and `AlphaMode::Multiply`; - All new modes are rendered in the existing `Transparent3d` phase; - The existing mesh flags for alpha mode are reorganized for a more compact/efficient representation, and new values are added; - `MeshPipelineKey::TRANSPARENT_MAIN_PASS` is refactored into `MeshPipelineKey::BLEND_BITS`. - `AlphaMode::Opaque` and `AlphaMode::Mask(f32)` share a single opaque pipeline key: `MeshPipelineKey::BLEND_OPAQUE`; - `Blend`, `Premultiplied` and `Add` share a single premultiplied alpha pipeline key, `MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA`. In the shader, color values are premultiplied accordingly (or not) depending on the blend mode to produce the three different results after PBR/tone mapping/dithering; - `Multiply` uses its own independent pipeline key, `MeshPipelineKey::BLEND_MULTIPLY`; - Example and documentation are provided. --- ## Changelog ### Added - Added support for additive and multiplicative blend modes in the PBR `StandardMaterial`, via `AlphaMode::Add` and `AlphaMode::Multiply`; - Added support for premultiplied alpha in the PBR `StandardMaterial`, via `AlphaMode::Premultiplied`;
This commit is contained in:
parent
ff5e4fd1ec
commit
603cb439d9
11 changed files with 566 additions and 28 deletions
10
Cargo.toml
10
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"
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -415,8 +415,14 @@ pub fn queue_material_meshes<M: Material>(
|
|||
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<M: Material>(
|
|||
distance,
|
||||
});
|
||||
}
|
||||
AlphaMode::Blend => {
|
||||
AlphaMode::Blend
|
||||
| AlphaMode::Premultiplied
|
||||
| AlphaMode::Add
|
||||
| AlphaMode::Multiply => {
|
||||
transparent_phase.add(Transparent3d {
|
||||
entity: *visible_entity,
|
||||
draw_function: draw_transparent_pbr,
|
||||
|
|
|
@ -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<StandardMaterialUniform> 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 {
|
||||
|
|
|
@ -527,7 +527,10 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||
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<M: Material>(
|
|||
distance,
|
||||
});
|
||||
}
|
||||
AlphaMode::Blend => {}
|
||||
AlphaMode::Blend
|
||||
| AlphaMode::Premultiplied
|
||||
| AlphaMode::Add
|
||||
| AlphaMode::Multiply => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -106,6 +106,9 @@ fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
|
|||
// SRGB; the GPU will assume our output is linear and will apply an SRGB conversion.
|
||||
output_rgb = pow(output_rgb, vec3<f32>(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;
|
||||
}
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
|
||||
fn alpha_discard(material: StandardMaterial, output_color: vec4<f32>) -> vec4<f32> {
|
||||
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<f32>, pos: vec2<f32>) -> vec4<f32> {
|
|||
}
|
||||
#endif // DEBAND_DITHER
|
||||
|
||||
#ifdef PREMULTIPLY_ALPHA
|
||||
fn premultiply_alpha(standard_material_flags: u32, color: vec4<f32>) -> vec4<f32> {
|
||||
// `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<f32>(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<f32>(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<f32>(color.rgb * color.a, color.a);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -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 {
|
||||
|
|
378
examples/3d/blend_modes.rs
Normal file
378
examples/3d/blend_modes.rs
Normal file
|
@ -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<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
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<Assets<StandardMaterial>>,
|
||||
controllable: Query<(&Handle<StandardMaterial>, &ExampleControls)>,
|
||||
mut camera: Query<(&mut Camera, &mut Transform, &GlobalTransform), With<Camera3d>>,
|
||||
mut labels: Query<(&mut Style, &ExampleLabel)>,
|
||||
mut display: Query<&mut Text, With<ExampleDisplay>>,
|
||||
labelled: Query<&GlobalTransform>,
|
||||
mut state: Local<ExampleState>,
|
||||
time: Res<Time>,
|
||||
input: Res<Input<KeyCode>>,
|
||||
) {
|
||||
if input.pressed(KeyCode::Up) {
|
||||
state.alpha = (state.alpha + time.delta_seconds()).min(1.0);
|
||||
} else if input.pressed(KeyCode::Down) {
|
||||
state.alpha = (state.alpha - time.delta_seconds()).max(0.0);
|
||||
}
|
||||
|
||||
if input.just_pressed(KeyCode::Space) {
|
||||
state.unlit = !state.unlit;
|
||||
}
|
||||
|
||||
let randomize_colors = input.just_pressed(KeyCode::C);
|
||||
|
||||
for (material_handle, controls) in &controllable {
|
||||
let mut material = materials.get_mut(material_handle).unwrap();
|
||||
material.base_color.set_a(state.alpha);
|
||||
|
||||
if controls.color && randomize_colors {
|
||||
material.base_color.set_r(random());
|
||||
material.base_color.set_g(random());
|
||||
material.base_color.set_b(random());
|
||||
}
|
||||
if controls.unlit {
|
||||
material.unlit = state.unlit;
|
||||
}
|
||||
}
|
||||
|
||||
let (mut camera, mut camera_transform, camera_global_transform) = camera.single_mut();
|
||||
|
||||
if input.just_pressed(KeyCode::H) {
|
||||
camera.hdr = !camera.hdr;
|
||||
}
|
||||
|
||||
let rotation = if input.pressed(KeyCode::Left) {
|
||||
time.delta_seconds()
|
||||
} else if input.pressed(KeyCode::Right) {
|
||||
-time.delta_seconds()
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
camera_transform.rotate_around(
|
||||
Vec3::ZERO,
|
||||
Quat::from_euler(EulerRot::XYZ, 0.0, rotation, 0.0),
|
||||
);
|
||||
|
||||
for (mut style, label) in &mut labels {
|
||||
let world_position =
|
||||
labelled.get(label.entity).unwrap().translation() + Vec3::new(0.0, 1.0, 0.0);
|
||||
|
||||
let viewport_position = camera
|
||||
.world_to_viewport(camera_global_transform, world_position)
|
||||
.unwrap();
|
||||
|
||||
style.position.bottom = Val::Px(viewport_position.y);
|
||||
style.position.left = Val::Px(viewport_position.x);
|
||||
}
|
||||
|
||||
let mut display = display.single_mut();
|
||||
display.sections[0].value = format!(
|
||||
" HDR: {}\nAlpha: {:.2}",
|
||||
if camera.hdr { "ON " } else { "OFF" },
|
||||
state.alpha
|
||||
);
|
||||
}
|
|
@ -107,6 +107,7 @@ Example | Description
|
|||
--- | ---
|
||||
[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
|
||||
[Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes
|
||||
[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)
|
||||
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
|
||||
|
|
Loading…
Reference in a new issue