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
10
Cargo.toml
10
Cargo.toml
@ -297,6 +297,16 @@ description = "A scene showcasing the built-in 3D shapes"
|
|||||||
category = "3D Rendering"
|
category = "3D Rendering"
|
||||||
wasm = true
|
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]]
|
[[example]]
|
||||||
name = "lighting"
|
name = "lighting"
|
||||||
path = "examples/3d/lighting.rs"
|
path = "examples/3d/lighting.rs"
|
||||||
|
@ -23,6 +23,30 @@ pub enum AlphaMode {
|
|||||||
/// Standard alpha-blending is used to blend the fragment's color
|
/// Standard alpha-blending is used to blend the fragment's color
|
||||||
/// with the color behind it.
|
/// with the color behind it.
|
||||||
Blend,
|
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 {}
|
impl Eq for AlphaMode {}
|
||||||
|
@ -415,8 +415,14 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
MeshPipelineKey::from_primitive_topology(mesh.primitive_topology)
|
MeshPipelineKey::from_primitive_topology(mesh.primitive_topology)
|
||||||
| view_key;
|
| view_key;
|
||||||
let alpha_mode = material.properties.alpha_mode;
|
let alpha_mode = material.properties.alpha_mode;
|
||||||
if let AlphaMode::Blend = alpha_mode {
|
if let AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add =
|
||||||
mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS;
|
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(
|
let pipeline_id = pipelines.specialize(
|
||||||
@ -455,7 +461,10 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
distance,
|
distance,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AlphaMode::Blend => {
|
AlphaMode::Blend
|
||||||
|
| AlphaMode::Premultiplied
|
||||||
|
| AlphaMode::Add
|
||||||
|
| AlphaMode::Multiply => {
|
||||||
transparent_phase.add(Transparent3d {
|
transparent_phase.add(Transparent3d {
|
||||||
entity: *visible_entity,
|
entity: *visible_entity,
|
||||||
draw_function: draw_transparent_pbr,
|
draw_function: draw_transparent_pbr,
|
||||||
|
@ -298,16 +298,25 @@ bitflags::bitflags! {
|
|||||||
const OCCLUSION_TEXTURE = (1 << 3);
|
const OCCLUSION_TEXTURE = (1 << 3);
|
||||||
const DOUBLE_SIDED = (1 << 4);
|
const DOUBLE_SIDED = (1 << 4);
|
||||||
const UNLIT = (1 << 5);
|
const UNLIT = (1 << 5);
|
||||||
const ALPHA_MODE_OPAQUE = (1 << 6);
|
const TWO_COMPONENT_NORMAL_MAP = (1 << 6);
|
||||||
const ALPHA_MODE_MASK = (1 << 7);
|
const FLIP_NORMAL_MAP_Y = (1 << 7);
|
||||||
const ALPHA_MODE_BLEND = (1 << 8);
|
const ALPHA_MODE_RESERVED_BITS = (Self::ALPHA_MODE_MASK_BITS << Self::ALPHA_MODE_SHIFT_BITS); // ← Bitmask reserving bits for the `AlphaMode`
|
||||||
const TWO_COMPONENT_NORMAL_MAP = (1 << 9);
|
const ALPHA_MODE_OPAQUE = (0 << Self::ALPHA_MODE_SHIFT_BITS); // ← Values are just sequential values bitshifted into
|
||||||
const FLIP_NORMAL_MAP_Y = (1 << 10);
|
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 NONE = 0;
|
||||||
const UNINITIALIZED = 0xFFFF;
|
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`].
|
/// The GPU representation of the uniform data of a [`StandardMaterial`].
|
||||||
#[derive(Clone, Default, ShaderType)]
|
#[derive(Clone, Default, ShaderType)]
|
||||||
pub struct StandardMaterialUniform {
|
pub struct StandardMaterialUniform {
|
||||||
@ -380,6 +389,9 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
|||||||
flags |= StandardMaterialFlags::ALPHA_MODE_MASK;
|
flags |= StandardMaterialFlags::ALPHA_MODE_MASK;
|
||||||
}
|
}
|
||||||
AlphaMode::Blend => flags |= StandardMaterialFlags::ALPHA_MODE_BLEND,
|
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 {
|
StandardMaterialUniform {
|
||||||
|
@ -527,7 +527,10 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||||||
match alpha_mode {
|
match alpha_mode {
|
||||||
AlphaMode::Opaque => {}
|
AlphaMode::Opaque => {}
|
||||||
AlphaMode::Mask(_) => mesh_key |= MeshPipelineKey::ALPHA_MASK,
|
AlphaMode::Mask(_) => mesh_key |= MeshPipelineKey::ALPHA_MASK,
|
||||||
AlphaMode::Blend => continue,
|
AlphaMode::Blend
|
||||||
|
| AlphaMode::Premultiplied
|
||||||
|
| AlphaMode::Add
|
||||||
|
| AlphaMode::Multiply => continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
let pipeline_id = pipelines.specialize(
|
let pipeline_id = pipelines.specialize(
|
||||||
@ -566,7 +569,10 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
|||||||
distance,
|
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.
|
/// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA.
|
||||||
pub struct MeshPipelineKey: u32 {
|
pub struct MeshPipelineKey: u32 {
|
||||||
const NONE = 0;
|
const NONE = 0;
|
||||||
const TRANSPARENT_MAIN_PASS = (1 << 0);
|
const HDR = (1 << 0);
|
||||||
const HDR = (1 << 1);
|
const TONEMAP_IN_SHADER = (1 << 1);
|
||||||
const TONEMAP_IN_SHADER = (1 << 2);
|
const DEBAND_DITHER = (1 << 2);
|
||||||
const DEBAND_DITHER = (1 << 3);
|
const DEPTH_PREPASS = (1 << 3);
|
||||||
const DEPTH_PREPASS = (1 << 4);
|
const NORMAL_PREPASS = (1 << 4);
|
||||||
const NORMAL_PREPASS = (1 << 5);
|
const ALPHA_MASK = (1 << 5);
|
||||||
const ALPHA_MASK = (1 << 6);
|
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 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;
|
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_MASK_BITS: u32 = 0b111;
|
||||||
const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();
|
const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();
|
||||||
const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111;
|
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 {
|
pub fn from_msaa_samples(msaa_samples: u32) -> Self {
|
||||||
let msaa_bits =
|
let msaa_bits =
|
||||||
@ -677,12 +684,30 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||||||
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
|
let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
|
||||||
|
|
||||||
let (label, blend, depth_write_enabled);
|
let (label, blend, depth_write_enabled);
|
||||||
if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) {
|
let pass = key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS);
|
||||||
label = "transparent_mesh_pipeline".into();
|
if pass == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA {
|
||||||
blend = Some(BlendState::ALPHA_BLENDING);
|
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
|
// For the transparent pass, fragments that are closer will be alpha blended
|
||||||
// but their depth is not written to the depth buffer
|
// but their depth is not written to the depth buffer
|
||||||
depth_write_enabled = false;
|
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 {
|
} else {
|
||||||
label = "opaque_mesh_pipeline".into();
|
label = "opaque_mesh_pipeline".into();
|
||||||
blend = Some(BlendState::REPLACE);
|
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.
|
// 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_rgb = pow(output_rgb, vec3<f32>(2.2));
|
||||||
output_color = vec4(output_rgb, output_color.a);
|
output_color = vec4(output_rgb, output_color.a);
|
||||||
|
#endif
|
||||||
|
#ifdef PREMULTIPLY_ALPHA
|
||||||
|
output_color = premultiply_alpha(material.flags, output_color);
|
||||||
#endif
|
#endif
|
||||||
return output_color;
|
return output_color;
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,11 @@
|
|||||||
|
|
||||||
fn alpha_discard(material: StandardMaterial, output_color: vec4<f32>) -> vec4<f32> {
|
fn alpha_discard(material: StandardMaterial, output_color: vec4<f32>) -> vec4<f32> {
|
||||||
var color = output_color;
|
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
|
// NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
|
||||||
color.a = 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 {
|
if color.a >= material.alpha_cutoff {
|
||||||
// NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
|
// NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
|
||||||
color.a = 1.0;
|
color.a = 1.0;
|
||||||
@ -268,3 +269,66 @@ fn dither(color: vec4<f32>, pos: vec2<f32>) -> vec4<f32> {
|
|||||||
}
|
}
|
||||||
#endif // DEBAND_DITHER
|
#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_OCCLUSION_TEXTURE_BIT: u32 = 8u;
|
||||||
let STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u;
|
let STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u;
|
||||||
let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u;
|
let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u;
|
||||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 64u;
|
let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 64u;
|
||||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u;
|
let STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 128u;
|
||||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u;
|
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS: u32 = 3758096384u; // (0b111u32 << 29)
|
||||||
let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 512u;
|
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 0u; // (0u32 << 29)
|
||||||
let STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 1024u;
|
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
|
// Creates a StandardMaterial with default values
|
||||||
fn standard_material_new() -> StandardMaterial {
|
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 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
|
[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
|
[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)
|
[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
|
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
|
||||||
|
Loading…
Reference in New Issue
Block a user