mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 14:08:32 +00:00
Add 2d opaque phase with depth buffer (#13069)
This PR is based on top of #12982 # Objective - Mesh2d currently only has an alpha blended phase. Most sprites don't need transparency though. - For some 2d games it can be useful to have a 2d depth buffer ## Solution - Add an opaque phase to render Mesh2d that don't need transparency - This phase currently uses the `SortedRenderPhase` to make it easier to implement based on the already existing transparent phase. A follow up PR will switch this to `BinnedRenderPhase`. - Add a 2d depth buffer - Use that depth buffer in the transparent phase to make sure that sprites and transparent mesh2d are displayed correctly ## Testing I added the mesh2d_transforms example that layers many opaque and transparent mesh2d to make sure they all get displayed correctly. I also confirmed it works with sprites by modifying that example locally. --- ## Changelog - Added `AlphaMode2d` - Added `Opaque2d` render phase - Camera2d now have a `ViewDepthTexture` component ## Migration Guide - `ColorMaterial` now contains `AlphaMode2d`. To keep previous behaviour, use `AlphaMode::BLEND`. If you know your sprite is opaque, use `AlphaMode::OPAQUE` ## Follow up PRs - See tracking issue: #13265 --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
This commit is contained in:
parent
e7d40c9b08
commit
5abc32ceda
12 changed files with 591 additions and 52 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -596,6 +596,17 @@ description = "Demonstrates transparency in 2d"
|
|||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "mesh2d_alpha_mode"
|
||||
path = "examples/2d/mesh2d_alpha_mode.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.mesh2d_alpha_mode]
|
||||
name = "Mesh2d Alpha Mode"
|
||||
description = "Used to test alpha modes with mesh2d"
|
||||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "pixel_grid_snap"
|
||||
path = "examples/2d/pixel_grid_snap.rs"
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
use crate::core_2d::Opaque2d;
|
||||
use bevy_ecs::{prelude::World, query::QueryItem};
|
||||
use bevy_render::{
|
||||
camera::ExtractedCamera,
|
||||
diagnostic::RecordDiagnostics,
|
||||
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
|
||||
render_phase::{TrackedRenderPass, ViewSortedRenderPhases},
|
||||
render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp},
|
||||
renderer::RenderContext,
|
||||
view::{ViewDepthTexture, ViewTarget},
|
||||
};
|
||||
use bevy_utils::tracing::error;
|
||||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
|
||||
/// A [`bevy_render::render_graph::Node`] that runs the [`Opaque2d`] [`ViewSortedRenderPhases`]
|
||||
#[derive(Default)]
|
||||
pub struct MainOpaquePass2dNode;
|
||||
impl ViewNode for MainOpaquePass2dNode {
|
||||
type ViewQuery = (
|
||||
&'static ExtractedCamera,
|
||||
&'static ViewTarget,
|
||||
&'static ViewDepthTexture,
|
||||
);
|
||||
|
||||
fn run<'w>(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext<'w>,
|
||||
(camera, target, depth): QueryItem<'w, Self::ViewQuery>,
|
||||
world: &'w World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let Some(opaque_phases) = world.get_resource::<ViewSortedRenderPhases<Opaque2d>>() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let diagnostics = render_context.diagnostic_recorder();
|
||||
|
||||
let color_attachments = [Some(target.get_color_attachment())];
|
||||
let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store));
|
||||
|
||||
let view_entity = graph.view_entity();
|
||||
let Some(opaque_phase) = opaque_phases.get(&view_entity) else {
|
||||
return Ok(());
|
||||
};
|
||||
render_context.add_command_buffer_generation_task(move |render_device| {
|
||||
#[cfg(feature = "trace")]
|
||||
let _main_opaque_pass_2d_span = info_span!("main_opaque_pass_2d").entered();
|
||||
|
||||
// Command encoder setup
|
||||
let mut command_encoder =
|
||||
render_device.create_command_encoder(&CommandEncoderDescriptor {
|
||||
label: Some("main_opaque_pass_2d_command_encoder"),
|
||||
});
|
||||
|
||||
// Render pass setup
|
||||
let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
|
||||
label: Some("main_opaque_pass_2d"),
|
||||
color_attachments: &color_attachments,
|
||||
depth_stencil_attachment,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
let mut render_pass = TrackedRenderPass::new(&render_device, render_pass);
|
||||
let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_2d");
|
||||
|
||||
if let Some(viewport) = camera.viewport.as_ref() {
|
||||
render_pass.set_camera_viewport(viewport);
|
||||
}
|
||||
|
||||
// Opaque draws
|
||||
if !opaque_phase.items.is_empty() {
|
||||
#[cfg(feature = "trace")]
|
||||
let _opaque_main_pass_2d_span = info_span!("opaque_main_pass_2d").entered();
|
||||
if let Err(err) = opaque_phase.render(&mut render_pass, world, view_entity) {
|
||||
error!("Error encountered while rendering the 2d opaque phase {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
pass_span.end(&mut render_pass);
|
||||
drop(render_pass);
|
||||
command_encoder.finish()
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@ use bevy_render::{
|
|||
diagnostic::RecordDiagnostics,
|
||||
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
|
||||
render_phase::ViewSortedRenderPhases,
|
||||
render_resource::RenderPassDescriptor,
|
||||
render_resource::{RenderPassDescriptor, StoreOp},
|
||||
renderer::RenderContext,
|
||||
view::ViewTarget,
|
||||
view::{ViewDepthTexture, ViewTarget},
|
||||
};
|
||||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
|
@ -16,13 +16,17 @@ use bevy_utils::tracing::info_span;
|
|||
pub struct MainTransparentPass2dNode {}
|
||||
|
||||
impl ViewNode for MainTransparentPass2dNode {
|
||||
type ViewQuery = (&'static ExtractedCamera, &'static ViewTarget);
|
||||
type ViewQuery = (
|
||||
&'static ExtractedCamera,
|
||||
&'static ViewTarget,
|
||||
&'static ViewDepthTexture,
|
||||
);
|
||||
|
||||
fn run<'w>(
|
||||
&self,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext<'w>,
|
||||
(camera, target): bevy_ecs::query::QueryItem<'w, Self::ViewQuery>,
|
||||
(camera, target, depth): bevy_ecs::query::QueryItem<'w, Self::ViewQuery>,
|
||||
world: &'w World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let Some(transparent_phases) =
|
||||
|
@ -46,7 +50,13 @@ impl ViewNode for MainTransparentPass2dNode {
|
|||
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
|
||||
label: Some("main_transparent_pass_2d"),
|
||||
color_attachments: &[Some(target.get_color_attachment())],
|
||||
depth_stencil_attachment: None,
|
||||
// NOTE: For the transparent pass we load the depth buffer. There should be no
|
||||
// need to write to it, but store is set to `true` as a workaround for issue #3776,
|
||||
// https://github.com/bevyengine/bevy/issues/3776
|
||||
// so that wgpu does not clear the depth buffer.
|
||||
// As the opaque and alpha mask passes run first, opaque meshes can occlude
|
||||
// transparent ones.
|
||||
depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
mod camera_2d;
|
||||
mod main_opaque_pass_2d_node;
|
||||
mod main_transparent_pass_2d_node;
|
||||
|
||||
pub mod graph {
|
||||
|
@ -15,6 +16,7 @@ pub mod graph {
|
|||
pub enum Node2d {
|
||||
MsaaWriteback,
|
||||
StartMainPass,
|
||||
MainOpaquePass,
|
||||
MainTransparentPass,
|
||||
EndMainPass,
|
||||
Bloom,
|
||||
|
@ -30,21 +32,29 @@ pub mod graph {
|
|||
|
||||
use std::ops::Range;
|
||||
|
||||
use bevy_utils::HashMap;
|
||||
pub use camera_2d::*;
|
||||
pub use main_opaque_pass_2d_node::*;
|
||||
pub use main_transparent_pass_2d_node::*;
|
||||
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::{entity::EntityHashSet, prelude::*};
|
||||
use bevy_math::FloatOrd;
|
||||
use bevy_render::{
|
||||
camera::Camera,
|
||||
camera::{Camera, ExtractedCamera},
|
||||
extract_component::ExtractComponentPlugin,
|
||||
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
|
||||
render_phase::{
|
||||
sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
|
||||
PhaseItemExtraIndex, SortedPhaseItem, ViewSortedRenderPhases,
|
||||
},
|
||||
render_resource::CachedRenderPipelineId,
|
||||
render_resource::{
|
||||
CachedRenderPipelineId, Extent3d, TextureDescriptor, TextureDimension, TextureFormat,
|
||||
TextureUsages,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
texture::TextureCache,
|
||||
view::{Msaa, ViewDepthTexture},
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
||||
|
@ -52,6 +62,8 @@ use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode};
|
|||
|
||||
use self::graph::{Core2d, Node2d};
|
||||
|
||||
pub const CORE_2D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
|
||||
|
||||
pub struct Core2dPlugin;
|
||||
|
||||
impl Plugin for Core2dPlugin {
|
||||
|
@ -63,17 +75,27 @@ impl Plugin for Core2dPlugin {
|
|||
return;
|
||||
};
|
||||
render_app
|
||||
.init_resource::<DrawFunctions<Opaque2d>>()
|
||||
.init_resource::<DrawFunctions<Transparent2d>>()
|
||||
.init_resource::<ViewSortedRenderPhases<Transparent2d>>()
|
||||
.init_resource::<ViewSortedRenderPhases<Opaque2d>>()
|
||||
.add_systems(ExtractSchedule, extract_core_2d_camera_phases)
|
||||
.add_systems(
|
||||
Render,
|
||||
sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort),
|
||||
(
|
||||
sort_phase_system::<Opaque2d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort),
|
||||
prepare_core_2d_depth_textures.in_set(RenderSet::PrepareResources),
|
||||
),
|
||||
);
|
||||
|
||||
render_app
|
||||
.add_render_sub_graph(Core2d)
|
||||
.add_render_graph_node::<EmptyNode>(Core2d, Node2d::StartMainPass)
|
||||
.add_render_graph_node::<ViewNodeRunner<MainOpaquePass2dNode>>(
|
||||
Core2d,
|
||||
Node2d::MainOpaquePass,
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<MainTransparentPass2dNode>>(
|
||||
Core2d,
|
||||
Node2d::MainTransparentPass,
|
||||
|
@ -86,6 +108,7 @@ impl Plugin for Core2dPlugin {
|
|||
Core2d,
|
||||
(
|
||||
Node2d::StartMainPass,
|
||||
Node2d::MainOpaquePass,
|
||||
Node2d::MainTransparentPass,
|
||||
Node2d::EndMainPass,
|
||||
Node2d::Tonemapping,
|
||||
|
@ -96,6 +119,67 @@ impl Plugin for Core2dPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
/// Opaque 2D [`SortedPhaseItem`]s.
|
||||
pub struct Opaque2d {
|
||||
pub sort_key: FloatOrd,
|
||||
pub entity: Entity,
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
pub batch_range: Range<u32>,
|
||||
pub extra_index: PhaseItemExtraIndex,
|
||||
}
|
||||
impl PhaseItem for Opaque2d {
|
||||
#[inline]
|
||||
fn entity(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw_function(&self) -> DrawFunctionId {
|
||||
self.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 SortedPhaseItem for Opaque2d {
|
||||
type SortKey = FloatOrd;
|
||||
|
||||
#[inline]
|
||||
fn sort_key(&self) -> Self::SortKey {
|
||||
self.sort_key
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sort(items: &mut [Self]) {
|
||||
// radsort is a stable radix sort that performed better than `slice::sort_by_key` or `slice::sort_unstable_by_key`.
|
||||
radsort::sort_by_key(items, |item| item.sort_key().0);
|
||||
}
|
||||
}
|
||||
|
||||
impl CachedRenderPipelinePhaseItem for Opaque2d {
|
||||
#[inline]
|
||||
fn cached_pipeline(&self) -> CachedRenderPipelineId {
|
||||
self.pipeline
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Transparent2d {
|
||||
pub sort_key: FloatOrd,
|
||||
pub entity: Entity,
|
||||
|
@ -162,6 +246,7 @@ impl CachedRenderPipelinePhaseItem for Transparent2d {
|
|||
pub fn extract_core_2d_camera_phases(
|
||||
mut commands: Commands,
|
||||
mut transparent_2d_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
||||
mut opaque_2d_phases: ResMut<ViewSortedRenderPhases<Opaque2d>>,
|
||||
cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
|
||||
mut live_entities: Local<EntityHashSet>,
|
||||
) {
|
||||
|
@ -174,10 +259,61 @@ pub fn extract_core_2d_camera_phases(
|
|||
|
||||
commands.get_or_spawn(entity);
|
||||
transparent_2d_phases.insert_or_clear(entity);
|
||||
opaque_2d_phases.insert_or_clear(entity);
|
||||
|
||||
live_entities.insert(entity);
|
||||
}
|
||||
|
||||
// Clear out all dead views.
|
||||
transparent_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
|
||||
opaque_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
|
||||
}
|
||||
|
||||
pub fn prepare_core_2d_depth_textures(
|
||||
mut commands: Commands,
|
||||
mut texture_cache: ResMut<TextureCache>,
|
||||
render_device: Res<RenderDevice>,
|
||||
transparent_2d_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
||||
opaque_2d_phases: ResMut<ViewSortedRenderPhases<Opaque2d>>,
|
||||
views_2d: Query<(Entity, &ExtractedCamera, &Msaa), (With<Camera2d>,)>,
|
||||
) {
|
||||
let mut textures = HashMap::default();
|
||||
for (entity, camera, msaa) in &views_2d {
|
||||
if !opaque_2d_phases.contains_key(&entity) || !transparent_2d_phases.contains_key(&entity) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(physical_target_size) = camera.physical_target_size else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let cached_texture = textures
|
||||
.entry(camera.target.clone())
|
||||
.or_insert_with(|| {
|
||||
// The size of the depth texture
|
||||
let size = Extent3d {
|
||||
depth_or_array_layers: 1,
|
||||
width: physical_target_size.x,
|
||||
height: physical_target_size.y,
|
||||
};
|
||||
|
||||
let descriptor = TextureDescriptor {
|
||||
label: Some("view_depth_texture"),
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: msaa.samples(),
|
||||
dimension: TextureDimension::D2,
|
||||
format: CORE_2D_DEPTH_FORMAT,
|
||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
};
|
||||
|
||||
texture_cache.get(&render_device, descriptor)
|
||||
})
|
||||
.clone();
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(ViewDepthTexture::new(cached_texture, Some(0.0)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_core_pipeline::core_2d::Transparent2d;
|
||||
use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT};
|
||||
|
||||
use bevy_ecs::{
|
||||
prelude::Entity,
|
||||
|
@ -139,7 +139,22 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
|
|||
}),
|
||||
layout,
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: CORE_2D_DEPTH_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::GreaterEqual,
|
||||
stencil: StencilState {
|
||||
front: StencilFaceState::IGNORE,
|
||||
back: StencilFaceState::IGNORE,
|
||||
read_mask: 0,
|
||||
write_mask: 0,
|
||||
},
|
||||
bias: DepthBiasState {
|
||||
constant: 0,
|
||||
slope_scale: 0.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.mesh_key.msaa_samples(),
|
||||
mask: !0,
|
||||
|
@ -224,7 +239,22 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline {
|
|||
}),
|
||||
layout,
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: CORE_2D_DEPTH_FORMAT,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::GreaterEqual,
|
||||
stencil: StencilState {
|
||||
front: StencilFaceState::IGNORE,
|
||||
back: StencilFaceState::IGNORE,
|
||||
read_mask: 0,
|
||||
write_mask: 0,
|
||||
},
|
||||
bias: DepthBiasState {
|
||||
constant: 0,
|
||||
slope_scale: 0.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.mesh_key.msaa_samples(),
|
||||
mask: !0,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{Material2d, Material2dPlugin, MaterialMesh2dBundle};
|
||||
use crate::{AlphaMode2d, Material2d, Material2dPlugin, MaterialMesh2dBundle};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle};
|
||||
use bevy_color::{Color, ColorToComponents, LinearRgba};
|
||||
use bevy_color::{Alpha, Color, ColorToComponents, LinearRgba};
|
||||
use bevy_math::Vec4;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::{
|
||||
|
@ -46,6 +46,7 @@ impl Plugin for ColorMaterialPlugin {
|
|||
#[uniform(0, ColorMaterialUniform)]
|
||||
pub struct ColorMaterial {
|
||||
pub color: Color,
|
||||
pub alpha_mode: AlphaMode2d,
|
||||
#[texture(1)]
|
||||
#[sampler(2)]
|
||||
pub texture: Option<Handle<Image>>,
|
||||
|
@ -63,6 +64,8 @@ impl Default for ColorMaterial {
|
|||
ColorMaterial {
|
||||
color: Color::WHITE,
|
||||
texture: None,
|
||||
// TODO should probably default to AlphaMask once supported?
|
||||
alpha_mode: AlphaMode2d::Blend,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +74,11 @@ impl From<Color> for ColorMaterial {
|
|||
fn from(color: Color) -> Self {
|
||||
ColorMaterial {
|
||||
color,
|
||||
alpha_mode: if color.alpha() < 1.0 {
|
||||
AlphaMode2d::Blend
|
||||
} else {
|
||||
AlphaMode2d::Opaque
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -89,9 +97,9 @@ impl From<Handle<Image>> for ColorMaterial {
|
|||
bitflags::bitflags! {
|
||||
#[repr(transparent)]
|
||||
pub struct ColorMaterialFlags: u32 {
|
||||
const TEXTURE = 1 << 0;
|
||||
const NONE = 0;
|
||||
const UNINITIALIZED = 0xFFFF;
|
||||
const TEXTURE = 1 << 0;
|
||||
const NONE = 0;
|
||||
const UNINITIALIZED = 0xFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,6 +128,10 @@ impl Material2d for ColorMaterial {
|
|||
fn fragment_shader() -> ShaderRef {
|
||||
COLOR_MATERIAL_SHADER_HANDLE.into()
|
||||
}
|
||||
|
||||
fn alpha_mode(&self) -> AlphaMode2d {
|
||||
self.alpha_mode
|
||||
}
|
||||
}
|
||||
|
||||
/// A component bundle for entities with a [`Mesh2dHandle`](crate::Mesh2dHandle) and a [`ColorMaterial`].
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{Asset, AssetApp, AssetId, AssetServer, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_2d::Transparent2d,
|
||||
core_2d::{Opaque2d, Transparent2d},
|
||||
tonemapping::{DebandDither, Tonemapping},
|
||||
};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::entity::EntityHashMap;
|
||||
use bevy_ecs::{
|
||||
entity::EntityHashMap,
|
||||
prelude::*,
|
||||
system::{lifetimeless::SRes, SystemParamItem},
|
||||
};
|
||||
use bevy_math::FloatOrd;
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
mesh::{MeshVertexBufferLayoutRef, RenderMesh},
|
||||
render_asset::{
|
||||
|
@ -32,8 +33,7 @@ use bevy_render::{
|
|||
};
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
use bevy_utils::tracing::error;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::{hash::Hash, marker::PhantomData};
|
||||
|
||||
use crate::{
|
||||
DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances,
|
||||
|
@ -121,6 +121,10 @@ pub trait Material2d: AsBindGroup + Asset + Clone + Sized {
|
|||
0.0
|
||||
}
|
||||
|
||||
fn alpha_mode(&self) -> AlphaMode2d {
|
||||
AlphaMode2d::Opaque
|
||||
}
|
||||
|
||||
/// Customizes the default [`RenderPipelineDescriptor`].
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
|
@ -133,6 +137,23 @@ pub trait Material2d: AsBindGroup + Asset + Clone + Sized {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets how a 2d material's base color alpha channel is used for transparency.
|
||||
/// Currently, this only works with [`Mesh2d`](crate::mesh2d::Mesh2d). Sprites are always transparent.
|
||||
///
|
||||
/// This is very similar to [`AlphaMode`](bevy_render::alpha::AlphaMode) but this only applies to 2d meshes.
|
||||
/// We use a separate type because 2d doesn't support all the transparency modes that 3d does.
|
||||
#[derive(Debug, Default, Reflect, Copy, Clone, PartialEq)]
|
||||
#[reflect(Default, Debug)]
|
||||
pub enum AlphaMode2d {
|
||||
/// Base color alpha values are overridden to be fully opaque (1.0).
|
||||
#[default]
|
||||
Opaque,
|
||||
/// The base color alpha value defines the opacity of the color.
|
||||
/// Standard alpha-blending is used to blend the fragment's color
|
||||
/// with the color behind it.
|
||||
Blend,
|
||||
}
|
||||
|
||||
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given [`Material2d`]
|
||||
/// asset type (which includes [`Material2d`] types).
|
||||
pub struct Material2dPlugin<M: Material2d>(PhantomData<M>);
|
||||
|
@ -153,6 +174,7 @@ where
|
|||
|
||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.add_render_command::<Opaque2d, DrawMaterial2d<M>>()
|
||||
.add_render_command::<Transparent2d, DrawMaterial2d<M>>()
|
||||
.init_resource::<RenderMaterial2dInstances<M>>()
|
||||
.init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
|
||||
|
@ -348,6 +370,13 @@ impl<P: PhaseItem, M: Material2d, const I: usize> RenderCommand<P>
|
|||
}
|
||||
}
|
||||
|
||||
pub const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode2d) -> Mesh2dPipelineKey {
|
||||
match alpha_mode {
|
||||
AlphaMode2d::Blend => Mesh2dPipelineKey::BLEND_ALPHA,
|
||||
_ => Mesh2dPipelineKey::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelineKey {
|
||||
match tonemapping {
|
||||
Tonemapping::None => Mesh2dPipelineKey::TONEMAP_METHOD_NONE,
|
||||
|
@ -365,6 +394,7 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin
|
|||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn queue_material2d_meshes<M: Material2d>(
|
||||
opaque_draw_functions: Res<DrawFunctions<Opaque2d>>,
|
||||
transparent_draw_functions: Res<DrawFunctions<Transparent2d>>,
|
||||
material2d_pipeline: Res<Material2dPipeline<M>>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<Material2dPipeline<M>>>,
|
||||
|
@ -374,6 +404,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||
mut render_mesh_instances: ResMut<RenderMesh2dInstances>,
|
||||
render_material_instances: Res<RenderMaterial2dInstances<M>>,
|
||||
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
|
||||
mut opaque_render_phases: ResMut<ViewSortedRenderPhases<Opaque2d>>,
|
||||
mut views: Query<(
|
||||
Entity,
|
||||
&ExtractedView,
|
||||
|
@ -394,7 +425,12 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||
continue;
|
||||
};
|
||||
|
||||
let Some(opaque_phase) = opaque_render_phases.get_mut(&view_entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let draw_transparent_2d = transparent_draw_functions.read().id::<DrawMaterial2d<M>>();
|
||||
let draw_opaque_2d = opaque_draw_functions.read().id::<DrawMaterial2d<M>>();
|
||||
|
||||
let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
|
||||
| Mesh2dPipelineKey::from_hdr(view.hdr);
|
||||
|
@ -421,8 +457,9 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||
let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
|
||||
continue;
|
||||
};
|
||||
let mesh_key =
|
||||
view_key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology());
|
||||
let mesh_key = view_key
|
||||
| Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology())
|
||||
| material_2d.properties.mesh_pipeline_key_bits;
|
||||
|
||||
let pipeline_id = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
|
@ -443,21 +480,37 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||
};
|
||||
|
||||
mesh_instance.material_bind_group_id = material_2d.get_bind_group_id();
|
||||
|
||||
let mesh_z = mesh_instance.transforms.world_from_local.translation.z;
|
||||
transparent_phase.add(Transparent2d {
|
||||
entity: *visible_entity,
|
||||
draw_function: draw_transparent_2d,
|
||||
pipeline: pipeline_id,
|
||||
// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the
|
||||
// lowest sort key and getting closer should increase. As we have
|
||||
// -z in front of the camera, the largest distance is -far with values increasing toward the
|
||||
// camera. As such we can just use mesh_z as the distance
|
||||
sort_key: FloatOrd(mesh_z + material_2d.depth_bias),
|
||||
// Batching is done in batch_and_prepare_render_phase
|
||||
batch_range: 0..1,
|
||||
extra_index: PhaseItemExtraIndex::NONE,
|
||||
});
|
||||
|
||||
match material_2d.properties.alpha_mode {
|
||||
AlphaMode2d::Opaque => {
|
||||
opaque_phase.add(Opaque2d {
|
||||
entity: *visible_entity,
|
||||
draw_function: draw_opaque_2d,
|
||||
pipeline: pipeline_id,
|
||||
// Front-to-back ordering
|
||||
sort_key: -FloatOrd(mesh_z + material_2d.properties.depth_bias),
|
||||
// Batching is done in batch_and_prepare_render_phase
|
||||
batch_range: 0..1,
|
||||
extra_index: PhaseItemExtraIndex::NONE,
|
||||
});
|
||||
}
|
||||
AlphaMode2d::Blend => {
|
||||
transparent_phase.add(Transparent2d {
|
||||
entity: *visible_entity,
|
||||
draw_function: draw_transparent_2d,
|
||||
pipeline: pipeline_id,
|
||||
// NOTE: Back-to-front ordering for transparent with ascending sort means far should have the
|
||||
// lowest sort key and getting closer should increase. As we have
|
||||
// -z in front of the camera, the largest distance is -far with values increasing toward the
|
||||
// camera. As such we can just use mesh_z as the distance
|
||||
sort_key: FloatOrd(mesh_z + material_2d.properties.depth_bias),
|
||||
// Batching is done in batch_and_prepare_render_phase
|
||||
batch_range: 0..1,
|
||||
extra_index: PhaseItemExtraIndex::NONE,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -465,12 +518,27 @@ pub fn queue_material2d_meshes<M: Material2d>(
|
|||
#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)]
|
||||
pub struct Material2dBindGroupId(pub Option<BindGroupId>);
|
||||
|
||||
/// Common [`Material2d`] properties, calculated for a specific material instance.
|
||||
pub struct Material2dProperties {
|
||||
/// The [`AlphaMode2d`] of this material.
|
||||
pub alpha_mode: AlphaMode2d,
|
||||
/// Add a bias to the view depth of the mesh which can be used to force a specific render order
|
||||
/// for meshes with equal depth, to avoid z-fighting.
|
||||
/// The bias is in depth-texture units so large values may
|
||||
pub depth_bias: f32,
|
||||
/// The bits in the [`Mesh2dPipelineKey`] for this material.
|
||||
///
|
||||
/// These are precalculated so that we can just "or" them together in
|
||||
/// [`queue_material2d_meshes`].
|
||||
pub mesh_pipeline_key_bits: Mesh2dPipelineKey,
|
||||
}
|
||||
|
||||
/// Data prepared for a [`Material2d`] instance.
|
||||
pub struct PreparedMaterial2d<T: Material2d> {
|
||||
pub bindings: Vec<(u32, OwnedBindingResource)>,
|
||||
pub bind_group: BindGroup,
|
||||
pub key: T::Data,
|
||||
pub depth_bias: f32,
|
||||
pub properties: Material2dProperties,
|
||||
}
|
||||
|
||||
impl<T: Material2d> PreparedMaterial2d<T> {
|
||||
|
@ -492,19 +560,27 @@ impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {
|
|||
fn prepare_asset(
|
||||
material: Self::SourceAsset,
|
||||
(render_device, images, fallback_image, pipeline): &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self, bevy_render::render_asset::PrepareAssetError<Self::SourceAsset>> {
|
||||
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
|
||||
match material.as_bind_group(
|
||||
&pipeline.material2d_layout,
|
||||
render_device,
|
||||
images,
|
||||
fallback_image,
|
||||
) {
|
||||
Ok(prepared) => Ok(PreparedMaterial2d {
|
||||
bindings: prepared.bindings,
|
||||
bind_group: prepared.bind_group,
|
||||
key: prepared.data,
|
||||
depth_bias: material.depth_bias(),
|
||||
}),
|
||||
Ok(prepared) => {
|
||||
let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty();
|
||||
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode()));
|
||||
Ok(PreparedMaterial2d {
|
||||
bindings: prepared.bindings,
|
||||
bind_group: prepared.bind_group,
|
||||
key: prepared.data,
|
||||
properties: Material2dProperties {
|
||||
depth_bias: material.depth_bias(),
|
||||
alpha_mode: material.alpha_mode(),
|
||||
mesh_pipeline_key_bits,
|
||||
},
|
||||
})
|
||||
}
|
||||
Err(AsBindGroupError::RetryNextUpdate) => {
|
||||
Err(PrepareAssetError::RetryNextUpdate(material))
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use bevy_app::Plugin;
|
||||
use bevy_asset::{load_internal_asset, AssetId, Handle};
|
||||
|
||||
use bevy_core_pipeline::core_2d::{Camera2d, Transparent2d};
|
||||
use bevy_core_pipeline::core_2d::{Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT};
|
||||
use bevy_core_pipeline::tonemapping::{
|
||||
get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
|
||||
};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::entity::EntityHashMap;
|
||||
use bevy_ecs::{
|
||||
entity::EntityHashMap,
|
||||
prelude::*,
|
||||
query::ROQueryItem,
|
||||
system::{lifetimeless::*, SystemParamItem, SystemState},
|
||||
|
@ -107,6 +107,8 @@ impl Plugin for Mesh2dRenderPlugin {
|
|||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
batch_and_prepare_sorted_render_phase::<Opaque2d, Mesh2dPipeline>
|
||||
.in_set(RenderSet::PrepareResources),
|
||||
batch_and_prepare_sorted_render_phase::<Transparent2d, Mesh2dPipeline>
|
||||
.in_set(RenderSet::PrepareResources),
|
||||
write_batched_instance_buffer::<Mesh2dPipeline>
|
||||
|
@ -388,6 +390,7 @@ bitflags::bitflags! {
|
|||
const HDR = 1 << 0;
|
||||
const TONEMAP_IN_SHADER = 1 << 1;
|
||||
const DEBAND_DITHER = 1 << 2;
|
||||
const BLEND_ALPHA = 1 << 3;
|
||||
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 TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
|
@ -539,6 +542,17 @@ impl SpecializedMeshPipeline for Mesh2dPipeline {
|
|||
false => TextureFormat::bevy_default(),
|
||||
};
|
||||
|
||||
let (depth_write_enabled, label, blend);
|
||||
if key.contains(Mesh2dPipelineKey::BLEND_ALPHA) {
|
||||
label = "transparent_mesh2d_pipeline";
|
||||
blend = Some(BlendState::ALPHA_BLENDING);
|
||||
depth_write_enabled = false;
|
||||
} else {
|
||||
label = "opaque_mesh2d_pipeline";
|
||||
blend = None;
|
||||
depth_write_enabled = true;
|
||||
}
|
||||
|
||||
Ok(RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: MESH2D_SHADER_HANDLE,
|
||||
|
@ -552,7 +566,7 @@ impl SpecializedMeshPipeline for Mesh2dPipeline {
|
|||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format,
|
||||
blend: Some(BlendState::ALPHA_BLENDING),
|
||||
blend,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
|
@ -567,13 +581,28 @@ impl SpecializedMeshPipeline for Mesh2dPipeline {
|
|||
topology: key.primitive_topology(),
|
||||
strip_index_format: None,
|
||||
},
|
||||
depth_stencil: None,
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: CORE_2D_DEPTH_FORMAT,
|
||||
depth_write_enabled,
|
||||
depth_compare: CompareFunction::GreaterEqual,
|
||||
stencil: StencilState {
|
||||
front: StencilFaceState::IGNORE,
|
||||
back: StencilFaceState::IGNORE,
|
||||
read_mask: 0,
|
||||
write_mask: 0,
|
||||
},
|
||||
bias: DepthBiasState {
|
||||
constant: 0,
|
||||
slope_scale: 0.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
label: Some("transparent_mesh2d_pipeline".into()),
|
||||
label: Some(label.into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
||||
use bevy_color::{ColorToComponents, LinearRgba};
|
||||
use bevy_core_pipeline::{
|
||||
core_2d::Transparent2d,
|
||||
core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
|
||||
tonemapping::{
|
||||
get_lut_bind_group_layout_entries, get_lut_bindings, DebandDither, Tonemapping,
|
||||
TonemappingLuts,
|
||||
|
@ -294,7 +294,25 @@ impl SpecializedRenderPipeline for SpritePipeline {
|
|||
topology: PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
},
|
||||
depth_stencil: None,
|
||||
// Sprites are always alpha blended so they never need to write to depth.
|
||||
// They just need to read it in case an opaque mesh2d
|
||||
// that wrote to depth is present.
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: CORE_2D_DEPTH_FORMAT,
|
||||
depth_write_enabled: false,
|
||||
depth_compare: CompareFunction::GreaterEqual,
|
||||
stencil: StencilState {
|
||||
front: StencilFaceState::IGNORE,
|
||||
back: StencilFaceState::IGNORE,
|
||||
read_mask: 0,
|
||||
write_mask: 0,
|
||||
},
|
||||
bias: DepthBiasState {
|
||||
constant: 0,
|
||||
slope_scale: 0.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
mask: !0,
|
||||
|
|
97
examples/2d/mesh2d_alpha_mode.rs
Normal file
97
examples/2d/mesh2d_alpha_mode.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
//! This example is used to test how transforms interact with alpha modes for [`MaterialMesh2dBundle`] entities.
|
||||
//! This makes sure the depth buffer is correctly being used for opaque and transparent 2d meshes
|
||||
|
||||
use bevy::{
|
||||
color::palettes::css::{BLUE, GREEN, WHITE},
|
||||
prelude::*,
|
||||
sprite::{AlphaMode2d, MaterialMesh2dBundle},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||
) {
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
|
||||
let texture_handle = asset_server.load("branding/icon.png");
|
||||
let mesh_handle = meshes.add(Rectangle::from_size(Vec2::splat(256.0)));
|
||||
|
||||
// opaque
|
||||
// Each sprite should be square with the transparent parts being completely black
|
||||
// The blue sprite should be on top with the white and green one behind it
|
||||
commands.spawn(MaterialMesh2dBundle {
|
||||
mesh: mesh_handle.clone().into(),
|
||||
material: materials.add(ColorMaterial {
|
||||
color: WHITE.into(),
|
||||
alpha_mode: AlphaMode2d::Opaque,
|
||||
texture: Some(texture_handle.clone()),
|
||||
}),
|
||||
transform: Transform::from_xyz(-400.0, 0.0, 0.0),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(MaterialMesh2dBundle {
|
||||
mesh: mesh_handle.clone().into(),
|
||||
material: materials.add(ColorMaterial {
|
||||
color: BLUE.into(),
|
||||
alpha_mode: AlphaMode2d::Opaque,
|
||||
texture: Some(texture_handle.clone()),
|
||||
}),
|
||||
transform: Transform::from_xyz(-300.0, 0.0, 1.0),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(MaterialMesh2dBundle {
|
||||
mesh: mesh_handle.clone().into(),
|
||||
material: materials.add(ColorMaterial {
|
||||
color: GREEN.into(),
|
||||
alpha_mode: AlphaMode2d::Opaque,
|
||||
texture: Some(texture_handle.clone()),
|
||||
}),
|
||||
transform: Transform::from_xyz(-200.0, 0.0, -1.0),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Test the interaction between opaque and transparent meshes
|
||||
// The white sprite should be:
|
||||
// - fully opaque
|
||||
// - on top of the green sprite
|
||||
// - behind the blue sprite
|
||||
commands.spawn(MaterialMesh2dBundle {
|
||||
mesh: mesh_handle.clone().into(),
|
||||
material: materials.add(ColorMaterial {
|
||||
color: WHITE.into(),
|
||||
alpha_mode: AlphaMode2d::Opaque,
|
||||
texture: Some(texture_handle.clone()),
|
||||
}),
|
||||
transform: Transform::from_xyz(200.0, 0.0, 0.0),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(MaterialMesh2dBundle {
|
||||
mesh: mesh_handle.clone().into(),
|
||||
material: materials.add(ColorMaterial {
|
||||
color: BLUE.with_alpha(0.7).into(),
|
||||
alpha_mode: AlphaMode2d::Blend,
|
||||
texture: Some(texture_handle.clone()),
|
||||
}),
|
||||
transform: Transform::from_xyz(300.0, 0.0, 1.0),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(MaterialMesh2dBundle {
|
||||
mesh: mesh_handle.clone().into(),
|
||||
material: materials.add(ColorMaterial {
|
||||
color: GREEN.with_alpha(0.7).into(),
|
||||
alpha_mode: AlphaMode2d::Blend,
|
||||
texture: Some(texture_handle),
|
||||
}),
|
||||
transform: Transform::from_xyz(400.0, 0.0, -1.0),
|
||||
..default()
|
||||
});
|
||||
}
|
|
@ -109,6 +109,7 @@ Example | Description
|
|||
[Manual Mesh 2D](../examples/2d/mesh2d_manual.rs) | Renders a custom mesh "manually" with "mid-level" renderer apis
|
||||
[Mesh 2D](../examples/2d/mesh2d.rs) | Renders a 2d mesh
|
||||
[Mesh 2D With Vertex Colors](../examples/2d/mesh2d_vertex_color_texture.rs) | Renders a 2d mesh with vertex color attributes
|
||||
[Mesh2d Alpha Mode](../examples/2d/mesh2d_alpha_mode.rs) | Used to test alpha modes with mesh2d
|
||||
[Move Sprite](../examples/2d/move_sprite.rs) | Changes the transform of a sprite
|
||||
[Pixel Grid Snapping](../examples/2d/pixel_grid_snap.rs) | Shows how to create graphics that snap to the pixel grid by rendering to a texture in 2D
|
||||
[Sprite](../examples/2d/sprite.rs) | Renders a sprite
|
||||
|
|
|
@ -13,7 +13,7 @@ use bevy::{
|
|||
render_asset::RenderAssetUsages,
|
||||
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
||||
},
|
||||
sprite::{MaterialMesh2dBundle, Mesh2dHandle},
|
||||
sprite::{AlphaMode2d, MaterialMesh2dBundle, Mesh2dHandle},
|
||||
utils::Duration,
|
||||
window::{PresentMode, WindowResolution},
|
||||
winit::{UpdateMode, WinitSettings},
|
||||
|
@ -71,6 +71,10 @@ struct Args {
|
|||
/// generate z values in increasing order rather than randomly
|
||||
#[argh(switch)]
|
||||
ordered_z: bool,
|
||||
|
||||
/// the alpha mode used to spawn the sprites
|
||||
#[argh(option, default = "AlphaMode::Blend")]
|
||||
alpha_mode: AlphaMode,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
|
@ -94,6 +98,27 @@ impl FromStr for Mode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
enum AlphaMode {
|
||||
Opaque,
|
||||
#[default]
|
||||
Blend,
|
||||
}
|
||||
|
||||
impl FromStr for AlphaMode {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"opaque" => Ok(Self::Opaque),
|
||||
"blend" => Ok(Self::Blend),
|
||||
_ => Err(format!(
|
||||
"Unknown alpha mode: '{s}', valid modes: 'opaque', 'blend'"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FIXED_TIMESTEP: f32 = 0.2;
|
||||
|
||||
fn main() {
|
||||
|
@ -573,10 +598,16 @@ fn init_materials(
|
|||
}
|
||||
.max(1);
|
||||
|
||||
let alpha_mode = match args.alpha_mode {
|
||||
AlphaMode::Opaque => AlphaMode2d::Opaque,
|
||||
AlphaMode::Blend => AlphaMode2d::Blend,
|
||||
};
|
||||
|
||||
let mut materials = Vec::with_capacity(capacity);
|
||||
materials.push(assets.add(ColorMaterial {
|
||||
color: Color::WHITE,
|
||||
texture: textures.first().cloned(),
|
||||
alpha_mode,
|
||||
}));
|
||||
|
||||
// We're seeding the PRNG here to make this example deterministic for testing purposes.
|
||||
|
@ -588,6 +619,7 @@ fn init_materials(
|
|||
assets.add(ColorMaterial {
|
||||
color: Color::srgb_u8(color_rng.gen(), color_rng.gen(), color_rng.gen()),
|
||||
texture: textures.choose(&mut texture_rng).cloned(),
|
||||
alpha_mode,
|
||||
})
|
||||
})
|
||||
.take(capacity - materials.len()),
|
||||
|
|
Loading…
Add table
Reference in a new issue