mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Add AlphaMask2d phase (#14724)
# Objective - Bevy now supports an opaque phase for mesh2d, but it's very common for 2d textures to have a transparent alpha channel. ## Solution - Add an alpha mask phase identical to the one in 3d. It will do the alpha masking in the shader before drawing the mesh. - Uses the BinnedRenderPhase - Since it's an opaque draw it also correctly writes to depth ## Testing - Tested the mes2d_alpha_mode example and the bevymark example with alpha mask mode enabled --- ## Showcase ![image](https://github.com/user-attachments/assets/9e5e4561-d0a7-4aa3-b049-d4b1247d5ed4) The white logo on the right is rendered with alpha mask enabled. Running the bevymark example I can get 65fps for 120k mesh2d all using alpha mask. ## Notes This is one more step for mesh2d improvements https://github.com/bevyengine/bevy/issues/13265 --------- Co-authored-by: Kristoffer Søholm <k.soeholm@gmail.com>
This commit is contained in:
parent
3bd039e821
commit
9de25ad330
8 changed files with 223 additions and 18 deletions
|
@ -13,7 +13,10 @@ use bevy_utils::tracing::error;
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
use bevy_utils::tracing::info_span;
|
use bevy_utils::tracing::info_span;
|
||||||
|
|
||||||
/// A [`bevy_render::render_graph::Node`] that runs the [`Opaque2d`] [`ViewBinnedRenderPhases`]
|
use super::AlphaMask2d;
|
||||||
|
|
||||||
|
/// A [`bevy_render::render_graph::Node`] that runs the
|
||||||
|
/// [`Opaque2d`] [`ViewBinnedRenderPhases`] and [`AlphaMask2d`] [`ViewBinnedRenderPhases`]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MainOpaquePass2dNode;
|
pub struct MainOpaquePass2dNode;
|
||||||
impl ViewNode for MainOpaquePass2dNode {
|
impl ViewNode for MainOpaquePass2dNode {
|
||||||
|
@ -30,7 +33,10 @@ impl ViewNode for MainOpaquePass2dNode {
|
||||||
(camera, target, depth): QueryItem<'w, Self::ViewQuery>,
|
(camera, target, depth): QueryItem<'w, Self::ViewQuery>,
|
||||||
world: &'w World,
|
world: &'w World,
|
||||||
) -> Result<(), NodeRunError> {
|
) -> Result<(), NodeRunError> {
|
||||||
let Some(opaque_phases) = world.get_resource::<ViewBinnedRenderPhases<Opaque2d>>() else {
|
let (Some(opaque_phases), Some(alpha_mask_phases)) = (
|
||||||
|
world.get_resource::<ViewBinnedRenderPhases<Opaque2d>>(),
|
||||||
|
world.get_resource::<ViewBinnedRenderPhases<AlphaMask2d>>(),
|
||||||
|
) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,7 +46,10 @@ impl ViewNode for MainOpaquePass2dNode {
|
||||||
let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store));
|
let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store));
|
||||||
|
|
||||||
let view_entity = graph.view_entity();
|
let view_entity = graph.view_entity();
|
||||||
let Some(opaque_phase) = opaque_phases.get(&view_entity) else {
|
let (Some(opaque_phase), Some(alpha_mask_phase)) = (
|
||||||
|
opaque_phases.get(&view_entity),
|
||||||
|
alpha_mask_phases.get(&view_entity),
|
||||||
|
) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
render_context.add_command_buffer_generation_task(move |render_device| {
|
render_context.add_command_buffer_generation_task(move |render_device| {
|
||||||
|
@ -77,6 +86,15 @@ impl ViewNode for MainOpaquePass2dNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alpha mask draws
|
||||||
|
if !alpha_mask_phase.is_empty() {
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
let _alpha_mask_main_pass_2d_span = info_span!("alpha_mask_main_pass_2d").entered();
|
||||||
|
if let Err(err) = alpha_mask_phase.render(&mut render_pass, world, view_entity) {
|
||||||
|
error!("Error encountered while rendering the 2d alpha mask phase {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pass_span.end(&mut render_pass);
|
pass_span.end(&mut render_pass);
|
||||||
drop(render_pass);
|
drop(render_pass);
|
||||||
command_encoder.finish()
|
command_encoder.finish()
|
||||||
|
|
|
@ -78,9 +78,11 @@ impl Plugin for Core2dPlugin {
|
||||||
};
|
};
|
||||||
render_app
|
render_app
|
||||||
.init_resource::<DrawFunctions<Opaque2d>>()
|
.init_resource::<DrawFunctions<Opaque2d>>()
|
||||||
|
.init_resource::<DrawFunctions<AlphaMask2d>>()
|
||||||
.init_resource::<DrawFunctions<Transparent2d>>()
|
.init_resource::<DrawFunctions<Transparent2d>>()
|
||||||
.init_resource::<ViewSortedRenderPhases<Transparent2d>>()
|
.init_resource::<ViewSortedRenderPhases<Transparent2d>>()
|
||||||
.init_resource::<ViewBinnedRenderPhases<Opaque2d>>()
|
.init_resource::<ViewBinnedRenderPhases<Opaque2d>>()
|
||||||
|
.init_resource::<ViewBinnedRenderPhases<AlphaMask2d>>()
|
||||||
.add_systems(ExtractSchedule, extract_core_2d_camera_phases)
|
.add_systems(ExtractSchedule, extract_core_2d_camera_phases)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Render,
|
Render,
|
||||||
|
@ -147,8 +149,6 @@ pub struct Opaque2dBinKey {
|
||||||
/// the ID of another type of asset.
|
/// the ID of another type of asset.
|
||||||
pub asset_id: UntypedAssetId,
|
pub asset_id: UntypedAssetId,
|
||||||
/// The ID of a bind group specific to the material.
|
/// The ID of a bind group specific to the material.
|
||||||
///
|
|
||||||
/// In the case of PBR, this is the `MaterialBindGroupId`.
|
|
||||||
pub material_bind_group_id: Option<BindGroupId>,
|
pub material_bind_group_id: Option<BindGroupId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +207,92 @@ impl CachedRenderPipelinePhaseItem for Opaque2d {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alpha mask 2D [`BinnedPhaseItem`]s.
|
||||||
|
pub struct AlphaMask2d {
|
||||||
|
/// The key, which determines which can be batched.
|
||||||
|
pub key: AlphaMask2dBinKey,
|
||||||
|
/// An entity from which data will be fetched, including the mesh if
|
||||||
|
/// applicable.
|
||||||
|
pub representative_entity: Entity,
|
||||||
|
/// The ranges of instances.
|
||||||
|
pub batch_range: Range<u32>,
|
||||||
|
/// An extra index, which is either a dynamic offset or an index in the
|
||||||
|
/// indirect parameters list.
|
||||||
|
pub extra_index: PhaseItemExtraIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data that must be identical in order to batch phase items together.
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct AlphaMask2dBinKey {
|
||||||
|
/// The identifier of the render pipeline.
|
||||||
|
pub pipeline: CachedRenderPipelineId,
|
||||||
|
/// The function used to draw.
|
||||||
|
pub draw_function: DrawFunctionId,
|
||||||
|
/// The asset that this phase item is associated with.
|
||||||
|
///
|
||||||
|
/// Normally, this is the ID of the mesh, but for non-mesh items it might be
|
||||||
|
/// the ID of another type of asset.
|
||||||
|
pub asset_id: UntypedAssetId,
|
||||||
|
/// The ID of a bind group specific to the material.
|
||||||
|
pub material_bind_group_id: Option<BindGroupId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhaseItem for AlphaMask2d {
|
||||||
|
#[inline]
|
||||||
|
fn entity(&self) -> Entity {
|
||||||
|
self.representative_entity
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn draw_function(&self) -> DrawFunctionId {
|
||||||
|
self.key.draw_function
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn batch_range(&self) -> &Range<u32> {
|
||||||
|
&self.batch_range
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn batch_range_mut(&mut self) -> &mut Range<u32> {
|
||||||
|
&mut self.batch_range
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_index(&self) -> PhaseItemExtraIndex {
|
||||||
|
self.extra_index
|
||||||
|
}
|
||||||
|
|
||||||
|
fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
|
||||||
|
(&mut self.batch_range, &mut self.extra_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinnedPhaseItem for AlphaMask2d {
|
||||||
|
type BinKey = AlphaMask2dBinKey;
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Self::BinKey,
|
||||||
|
representative_entity: Entity,
|
||||||
|
batch_range: Range<u32>,
|
||||||
|
extra_index: PhaseItemExtraIndex,
|
||||||
|
) -> Self {
|
||||||
|
AlphaMask2d {
|
||||||
|
key,
|
||||||
|
representative_entity,
|
||||||
|
batch_range,
|
||||||
|
extra_index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CachedRenderPipelinePhaseItem for AlphaMask2d {
|
||||||
|
#[inline]
|
||||||
|
fn cached_pipeline(&self) -> CachedRenderPipelineId {
|
||||||
|
self.key.pipeline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transparent 2D [`SortedPhaseItem`]s.
|
||||||
pub struct Transparent2d {
|
pub struct Transparent2d {
|
||||||
pub sort_key: FloatOrd,
|
pub sort_key: FloatOrd,
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
|
@ -274,6 +360,7 @@ pub fn extract_core_2d_camera_phases(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut transparent_2d_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
mut transparent_2d_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
||||||
mut opaque_2d_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
|
mut opaque_2d_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
|
||||||
|
mut alpha_mask_2d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>,
|
||||||
cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
|
cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
|
||||||
mut live_entities: Local<EntityHashSet>,
|
mut live_entities: Local<EntityHashSet>,
|
||||||
) {
|
) {
|
||||||
|
@ -287,6 +374,7 @@ pub fn extract_core_2d_camera_phases(
|
||||||
commands.get_or_spawn(entity);
|
commands.get_or_spawn(entity);
|
||||||
transparent_2d_phases.insert_or_clear(entity);
|
transparent_2d_phases.insert_or_clear(entity);
|
||||||
opaque_2d_phases.insert_or_clear(entity);
|
opaque_2d_phases.insert_or_clear(entity);
|
||||||
|
alpha_mask_2d_phases.insert_or_clear(entity);
|
||||||
|
|
||||||
live_entities.insert(entity);
|
live_entities.insert(entity);
|
||||||
}
|
}
|
||||||
|
@ -294,6 +382,7 @@ pub fn extract_core_2d_camera_phases(
|
||||||
// Clear out all dead views.
|
// Clear out all dead views.
|
||||||
transparent_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
|
transparent_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
|
||||||
opaque_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
|
opaque_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
|
||||||
|
alpha_mask_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_core_2d_depth_textures(
|
pub fn prepare_core_2d_depth_textures(
|
||||||
|
|
|
@ -98,16 +98,29 @@ bitflags::bitflags! {
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct ColorMaterialFlags: u32 {
|
pub struct ColorMaterialFlags: u32 {
|
||||||
const TEXTURE = 1 << 0;
|
const TEXTURE = 1 << 0;
|
||||||
|
/// Bitmask reserving bits for the [`AlphaMode2d`]
|
||||||
|
/// Values are just sequential values bitshifted into
|
||||||
|
/// the bitmask, and can range from 0 to 3.
|
||||||
|
const ALPHA_MODE_RESERVED_BITS = Self::ALPHA_MODE_MASK_BITS << Self::ALPHA_MODE_SHIFT_BITS;
|
||||||
|
const ALPHA_MODE_OPAQUE = 0 << Self::ALPHA_MODE_SHIFT_BITS;
|
||||||
|
const ALPHA_MODE_MASK = 1 << Self::ALPHA_MODE_SHIFT_BITS;
|
||||||
|
const ALPHA_MODE_BLEND = 2 << Self::ALPHA_MODE_SHIFT_BITS;
|
||||||
const NONE = 0;
|
const NONE = 0;
|
||||||
const UNINITIALIZED = 0xFFFF;
|
const UNINITIALIZED = 0xFFFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ColorMaterialFlags {
|
||||||
|
const ALPHA_MODE_MASK_BITS: u32 = 0b11;
|
||||||
|
const ALPHA_MODE_SHIFT_BITS: u32 = 32 - Self::ALPHA_MODE_MASK_BITS.count_ones();
|
||||||
|
}
|
||||||
|
|
||||||
/// The GPU representation of the uniform data of a [`ColorMaterial`].
|
/// The GPU representation of the uniform data of a [`ColorMaterial`].
|
||||||
#[derive(Clone, Default, ShaderType)]
|
#[derive(Clone, Default, ShaderType)]
|
||||||
pub struct ColorMaterialUniform {
|
pub struct ColorMaterialUniform {
|
||||||
pub color: Vec4,
|
pub color: Vec4,
|
||||||
pub flags: u32,
|
pub flags: u32,
|
||||||
|
pub alpha_cutoff: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsBindGroupShaderType<ColorMaterialUniform> for ColorMaterial {
|
impl AsBindGroupShaderType<ColorMaterialUniform> for ColorMaterial {
|
||||||
|
@ -117,9 +130,20 @@ impl AsBindGroupShaderType<ColorMaterialUniform> for ColorMaterial {
|
||||||
flags |= ColorMaterialFlags::TEXTURE;
|
flags |= ColorMaterialFlags::TEXTURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Defaults to 0.5 like in 3d
|
||||||
|
let mut alpha_cutoff = 0.5;
|
||||||
|
match self.alpha_mode {
|
||||||
|
AlphaMode2d::Opaque => flags |= ColorMaterialFlags::ALPHA_MODE_OPAQUE,
|
||||||
|
AlphaMode2d::Mask(c) => {
|
||||||
|
alpha_cutoff = c;
|
||||||
|
flags |= ColorMaterialFlags::ALPHA_MODE_MASK;
|
||||||
|
}
|
||||||
|
AlphaMode2d::Blend => flags |= ColorMaterialFlags::ALPHA_MODE_BLEND,
|
||||||
|
};
|
||||||
ColorMaterialUniform {
|
ColorMaterialUniform {
|
||||||
color: LinearRgba::from(self.color).to_f32_array().into(),
|
color: LinearRgba::from(self.color).to_f32_array().into(),
|
||||||
flags: flags.bits(),
|
flags: flags.bits(),
|
||||||
|
alpha_cutoff,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,14 @@ struct ColorMaterial {
|
||||||
color: vec4<f32>,
|
color: vec4<f32>,
|
||||||
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
|
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
|
||||||
flags: u32,
|
flags: u32,
|
||||||
|
alpha_cutoff: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLOR_MATERIAL_FLAGS_TEXTURE_BIT: u32 = 1u;
|
const COLOR_MATERIAL_FLAGS_TEXTURE_BIT: u32 = 1u;
|
||||||
|
const COLOR_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS: u32 = 3221225472u; // (0b11u32 << 30)
|
||||||
|
const COLOR_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 0u; // (0u32 << 30)
|
||||||
|
const COLOR_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 1073741824u; // (1u32 << 30)
|
||||||
|
const COLOR_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 2147483648u; // (2u32 << 30)
|
||||||
|
|
||||||
@group(2) @binding(0) var<uniform> material: ColorMaterial;
|
@group(2) @binding(0) var<uniform> material: ColorMaterial;
|
||||||
@group(2) @binding(1) var texture: texture_2d<f32>;
|
@group(2) @binding(1) var texture: texture_2d<f32>;
|
||||||
|
@ -23,14 +29,41 @@ fn fragment(
|
||||||
mesh: VertexOutput,
|
mesh: VertexOutput,
|
||||||
) -> @location(0) vec4<f32> {
|
) -> @location(0) vec4<f32> {
|
||||||
var output_color: vec4<f32> = material.color;
|
var output_color: vec4<f32> = material.color;
|
||||||
|
|
||||||
#ifdef VERTEX_COLORS
|
#ifdef VERTEX_COLORS
|
||||||
output_color = output_color * mesh.color;
|
output_color = output_color * mesh.color;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if ((material.flags & COLOR_MATERIAL_FLAGS_TEXTURE_BIT) != 0u) {
|
if ((material.flags & COLOR_MATERIAL_FLAGS_TEXTURE_BIT) != 0u) {
|
||||||
output_color = output_color * textureSample(texture, texture_sampler, mesh.uv);
|
output_color = output_color * textureSample(texture, texture_sampler, mesh.uv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output_color = alpha_discard(material, output_color);
|
||||||
|
|
||||||
#ifdef TONEMAP_IN_SHADER
|
#ifdef TONEMAP_IN_SHADER
|
||||||
output_color = tonemapping::tone_mapping(output_color, view.color_grading);
|
output_color = tonemapping::tone_mapping(output_color, view.color_grading);
|
||||||
#endif
|
#endif
|
||||||
return output_color;
|
return output_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn alpha_discard(material: ColorMaterial, output_color: vec4<f32>) -> vec4<f32> {
|
||||||
|
var color = output_color;
|
||||||
|
let alpha_mode = material.flags & COLOR_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
|
||||||
|
if alpha_mode == COLOR_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE {
|
||||||
|
// NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
|
||||||
|
color.a = 1.0;
|
||||||
|
}
|
||||||
|
#ifdef MAY_DISCARD
|
||||||
|
else if alpha_mode == COLOR_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;
|
||||||
|
} else {
|
||||||
|
// NOTE: output_color.a < in.material.alpha_cutoff should not be rendered
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // MAY_DISCARD
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_asset::{Asset, AssetApp, AssetId, AssetServer, Handle};
|
use bevy_asset::{Asset, AssetApp, AssetId, AssetServer, Handle};
|
||||||
use bevy_core_pipeline::{
|
use bevy_core_pipeline::{
|
||||||
core_2d::{Opaque2d, Opaque2dBinKey, Transparent2d},
|
core_2d::{AlphaMask2d, AlphaMask2dBinKey, Opaque2d, Opaque2dBinKey, Transparent2d},
|
||||||
tonemapping::{DebandDither, Tonemapping},
|
tonemapping::{DebandDither, Tonemapping},
|
||||||
};
|
};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
@ -149,6 +149,15 @@ pub enum AlphaMode2d {
|
||||||
/// Base color alpha values are overridden to be fully opaque (1.0).
|
/// Base color alpha values are overridden to be fully opaque (1.0).
|
||||||
#[default]
|
#[default]
|
||||||
Opaque,
|
Opaque,
|
||||||
|
/// Reduce transparency to fully opaque or fully transparent
|
||||||
|
/// based on a threshold.
|
||||||
|
///
|
||||||
|
/// Compares the base color alpha value to the specified threshold.
|
||||||
|
/// If the value is below the threshold,
|
||||||
|
/// considers the color to be fully transparent (alpha is set to 0.0).
|
||||||
|
/// If it is equal to or above the threshold,
|
||||||
|
/// considers the color to be fully opaque (alpha is set to 1.0).
|
||||||
|
Mask(f32),
|
||||||
/// The base color alpha value defines the opacity of the color.
|
/// The base color alpha value defines the opacity of the color.
|
||||||
/// 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.
|
||||||
|
@ -176,6 +185,7 @@ where
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app
|
render_app
|
||||||
.add_render_command::<Opaque2d, DrawMaterial2d<M>>()
|
.add_render_command::<Opaque2d, DrawMaterial2d<M>>()
|
||||||
|
.add_render_command::<AlphaMask2d, DrawMaterial2d<M>>()
|
||||||
.add_render_command::<Transparent2d, DrawMaterial2d<M>>()
|
.add_render_command::<Transparent2d, DrawMaterial2d<M>>()
|
||||||
.init_resource::<RenderMaterial2dInstances<M>>()
|
.init_resource::<RenderMaterial2dInstances<M>>()
|
||||||
.init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
|
.init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
|
||||||
|
@ -374,6 +384,7 @@ impl<P: PhaseItem, M: Material2d, const I: usize> RenderCommand<P>
|
||||||
pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode2d) -> Mesh2dPipelineKey {
|
pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode2d) -> Mesh2dPipelineKey {
|
||||||
match alpha_mode {
|
match alpha_mode {
|
||||||
AlphaMode2d::Blend => Mesh2dPipelineKey::BLEND_ALPHA,
|
AlphaMode2d::Blend => Mesh2dPipelineKey::BLEND_ALPHA,
|
||||||
|
AlphaMode2d::Mask(_) => Mesh2dPipelineKey::MAY_DISCARD,
|
||||||
_ => Mesh2dPipelineKey::NONE,
|
_ => Mesh2dPipelineKey::NONE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,6 +407,7 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn queue_material2d_meshes<M: Material2d>(
|
pub fn queue_material2d_meshes<M: Material2d>(
|
||||||
opaque_draw_functions: Res<DrawFunctions<Opaque2d>>,
|
opaque_draw_functions: Res<DrawFunctions<Opaque2d>>,
|
||||||
|
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask2d>>,
|
||||||
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
|
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
|
||||||
material2d_pipeline: Res<Material2dPipeline<M>>,
|
material2d_pipeline: Res<Material2dPipeline<M>>,
|
||||||
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
|
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
|
||||||
|
@ -406,6 +418,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
||||||
render_material_instances: Res<RenderMaterial2dInstances<M>>,
|
render_material_instances: Res<RenderMaterial2dInstances<M>>,
|
||||||
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
||||||
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
|
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
|
||||||
|
mut alpha_mask_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>,
|
||||||
mut views: Query<(
|
mut views: Query<(
|
||||||
Entity,
|
Entity,
|
||||||
&ExtractedView,
|
&ExtractedView,
|
||||||
|
@ -425,13 +438,16 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
||||||
let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else {
|
let Some(transparent_phase) = transparent_render_phases.get_mut(&view_entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(opaque_phase) = opaque_render_phases.get_mut(&view_entity) else {
|
let Some(opaque_phase) = opaque_render_phases.get_mut(&view_entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
let Some(alpha_mask_phase) = alpha_mask_render_phases.get_mut(&view_entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
let draw_transparent_2d = transparent_draw_functions.read().id::<DrawMaterial2d<M>>();
|
let draw_transparent_2d = transparent_draw_functions.read().id::<DrawMaterial2d<M>>();
|
||||||
let draw_opaque_2d = opaque_draw_functions.read().id::<DrawMaterial2d<M>>();
|
let draw_opaque_2d = opaque_draw_functions.read().id::<DrawMaterial2d<M>>();
|
||||||
|
let draw_alpha_mask_2d = alpha_mask_draw_functions.read().id::<DrawMaterial2d<M>>();
|
||||||
|
|
||||||
let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
|
let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
|
||||||
| Mesh2dPipelineKey::from_hdr(view.hdr);
|
| Mesh2dPipelineKey::from_hdr(view.hdr);
|
||||||
|
@ -497,6 +513,19 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
||||||
BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching),
|
BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
AlphaMode2d::Mask(_) => {
|
||||||
|
let bin_key = AlphaMask2dBinKey {
|
||||||
|
pipeline: pipeline_id,
|
||||||
|
draw_function: draw_alpha_mask_2d,
|
||||||
|
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||||
|
material_bind_group_id: material_2d.get_bind_group_id().0,
|
||||||
|
};
|
||||||
|
alpha_mask_phase.add(
|
||||||
|
bin_key,
|
||||||
|
*visible_entity,
|
||||||
|
BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching),
|
||||||
|
);
|
||||||
|
}
|
||||||
AlphaMode2d::Blend => {
|
AlphaMode2d::Blend => {
|
||||||
transparent_phase.add(Transparent2d {
|
transparent_phase.add(Transparent2d {
|
||||||
entity: *visible_entity,
|
entity: *visible_entity,
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use bevy_app::Plugin;
|
use bevy_app::Plugin;
|
||||||
use bevy_asset::{load_internal_asset, AssetId, Handle};
|
use bevy_asset::{load_internal_asset, AssetId, Handle};
|
||||||
|
|
||||||
use bevy_core_pipeline::core_2d::{Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT};
|
use bevy_core_pipeline::{
|
||||||
use bevy_core_pipeline::tonemapping::{
|
core_2d::{AlphaMask2d, Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT},
|
||||||
|
tonemapping::{
|
||||||
get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
|
get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
|
@ -114,6 +116,8 @@ impl Plugin for Mesh2dRenderPlugin {
|
||||||
(
|
(
|
||||||
batch_and_prepare_binned_render_phase::<Opaque2d, Mesh2dPipeline>
|
batch_and_prepare_binned_render_phase::<Opaque2d, Mesh2dPipeline>
|
||||||
.in_set(RenderSet::PrepareResources),
|
.in_set(RenderSet::PrepareResources),
|
||||||
|
batch_and_prepare_binned_render_phase::<AlphaMask2d, Mesh2dPipeline>
|
||||||
|
.in_set(RenderSet::PrepareResources),
|
||||||
batch_and_prepare_sorted_render_phase::<Transparent2d, Mesh2dPipeline>
|
batch_and_prepare_sorted_render_phase::<Transparent2d, Mesh2dPipeline>
|
||||||
.in_set(RenderSet::PrepareResources),
|
.in_set(RenderSet::PrepareResources),
|
||||||
write_batched_instance_buffer::<Mesh2dPipeline>
|
write_batched_instance_buffer::<Mesh2dPipeline>
|
||||||
|
@ -475,6 +479,7 @@ bitflags::bitflags! {
|
||||||
const TONEMAP_IN_SHADER = 1 << 1;
|
const TONEMAP_IN_SHADER = 1 << 1;
|
||||||
const DEBAND_DITHER = 1 << 2;
|
const DEBAND_DITHER = 1 << 2;
|
||||||
const BLEND_ALPHA = 1 << 3;
|
const BLEND_ALPHA = 1 << 3;
|
||||||
|
const MAY_DISCARD = 1 << 4;
|
||||||
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;
|
||||||
const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS;
|
const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||||
|
@ -619,6 +624,10 @@ impl SpecializedMeshPipeline for Mesh2dPipeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key.contains(Mesh2dPipelineKey::MAY_DISCARD) {
|
||||||
|
shader_defs.push("MAY_DISCARD".into());
|
||||||
|
}
|
||||||
|
|
||||||
let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
|
let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
|
||||||
|
|
||||||
let format = match key.contains(Mesh2dPipelineKey::HDR) {
|
let format = match key.contains(Mesh2dPipelineKey::HDR) {
|
||||||
|
|
|
@ -59,16 +59,16 @@ fn setup(
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test the interaction between opaque and transparent meshes
|
// Test the interaction between opaque/mask and transparent meshes
|
||||||
// The white sprite should be:
|
// The white sprite should be:
|
||||||
// - fully opaque
|
// - only the icon is opaque but background is transparent
|
||||||
// - on top of the green sprite
|
// - on top of the green sprite
|
||||||
// - behind the blue sprite
|
// - behind the blue sprite
|
||||||
commands.spawn(MaterialMesh2dBundle {
|
commands.spawn(MaterialMesh2dBundle {
|
||||||
mesh: mesh_handle.clone().into(),
|
mesh: mesh_handle.clone().into(),
|
||||||
material: materials.add(ColorMaterial {
|
material: materials.add(ColorMaterial {
|
||||||
color: WHITE.into(),
|
color: WHITE.into(),
|
||||||
alpha_mode: AlphaMode2d::Opaque,
|
alpha_mode: AlphaMode2d::Mask(0.5),
|
||||||
texture: Some(texture_handle.clone()),
|
texture: Some(texture_handle.clone()),
|
||||||
}),
|
}),
|
||||||
transform: Transform::from_xyz(200.0, 0.0, 0.0),
|
transform: Transform::from_xyz(200.0, 0.0, 0.0),
|
||||||
|
|
|
@ -103,6 +103,7 @@ enum AlphaMode {
|
||||||
Opaque,
|
Opaque,
|
||||||
#[default]
|
#[default]
|
||||||
Blend,
|
Blend,
|
||||||
|
AlphaMask,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for AlphaMode {
|
impl FromStr for AlphaMode {
|
||||||
|
@ -112,8 +113,9 @@ impl FromStr for AlphaMode {
|
||||||
match s {
|
match s {
|
||||||
"opaque" => Ok(Self::Opaque),
|
"opaque" => Ok(Self::Opaque),
|
||||||
"blend" => Ok(Self::Blend),
|
"blend" => Ok(Self::Blend),
|
||||||
|
"alpha_mask" => Ok(Self::AlphaMask),
|
||||||
_ => Err(format!(
|
_ => Err(format!(
|
||||||
"Unknown alpha mode: '{s}', valid modes: 'opaque', 'blend'"
|
"Unknown alpha mode: '{s}', valid modes: 'opaque', 'blend', 'alpha_mask'"
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -601,6 +603,7 @@ fn init_materials(
|
||||||
let alpha_mode = match args.alpha_mode {
|
let alpha_mode = match args.alpha_mode {
|
||||||
AlphaMode::Opaque => AlphaMode2d::Opaque,
|
AlphaMode::Opaque => AlphaMode2d::Opaque,
|
||||||
AlphaMode::Blend => AlphaMode2d::Blend,
|
AlphaMode::Blend => AlphaMode2d::Blend,
|
||||||
|
AlphaMode::AlphaMask => AlphaMode2d::Mask(0.5),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut materials = Vec::with_capacity(capacity);
|
let mut materials = Vec::with_capacity(capacity);
|
||||||
|
|
Loading…
Reference in a new issue