Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add support for opaque, alpha mask, and alpha blend modes #3072

Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 87 additions & 4 deletions pipelined/bevy_core_pipeline/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ impl Plugin for CorePipelinePlugin {
let render_app = app.sub_app(RenderApp);
render_app
.init_resource::<DrawFunctions<Transparent2d>>()
.init_resource::<DrawFunctions<Opaque3d>>()
.init_resource::<DrawFunctions<AlphaMask3d>>()
.init_resource::<DrawFunctions<Transparent3d>>()
.add_system_to_stage(RenderStage::Extract, extract_clear_color)
.add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases)
.add_system_to_stage(RenderStage::Prepare, prepare_core_views_system)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent2d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);

let pass_node_2d = MainPass2dNode::new(&mut render_app.world);
Expand Down Expand Up @@ -147,6 +151,76 @@ impl PhaseItem for Transparent2d {
}
}

pub struct Opaque3d {
pub distance: f32,
pub pipeline: CachedPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}

impl PhaseItem for Opaque3d {
type SortKey = FloatOrd;

#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}

#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
}

impl EntityPhaseItem for Opaque3d {
#[inline]
fn entity(&self) -> Entity {
self.entity
}
}

impl CachedPipelinePhaseItem for Opaque3d {
#[inline]
fn cached_pipeline(&self) -> CachedPipelineId {
self.pipeline
}
}

pub struct AlphaMask3d {
pub distance: f32,
pub pipeline: CachedPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}

impl PhaseItem for AlphaMask3d {
type SortKey = FloatOrd;

#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}

#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
}

impl EntityPhaseItem for AlphaMask3d {
#[inline]
fn entity(&self) -> Entity {
self.entity
}
}

impl CachedPipelinePhaseItem for AlphaMask3d {
#[inline]
fn cached_pipeline(&self) -> CachedPipelineId {
self.pipeline
}
}

pub struct Transparent3d {
pub distance: f32,
pub pipeline: CachedPipelineId,
Expand Down Expand Up @@ -203,9 +277,11 @@ pub fn extract_core_pipeline_camera_phases(
}
if let Some(camera_3d) = active_cameras.get(CameraPlugin::CAMERA_3D) {
if let Some(entity) = camera_3d.entity {
commands
.get_or_spawn(entity)
.insert(RenderPhase::<Transparent3d>::default());
commands.get_or_spawn(entity).insert_bundle((
RenderPhase::<Opaque3d>::default(),
RenderPhase::<AlphaMask3d>::default(),
RenderPhase::<Transparent3d>::default(),
));
}
}
}
Expand All @@ -215,7 +291,14 @@ pub fn prepare_core_views_system(
mut texture_cache: ResMut<TextureCache>,
msaa: Res<Msaa>,
render_device: Res<RenderDevice>,
views_3d: Query<(Entity, &ExtractedView), With<RenderPhase<Transparent3d>>>,
views_3d: Query<
(Entity, &ExtractedView),
(
With<RenderPhase<Opaque3d>>,
With<RenderPhase<AlphaMask3d>>,
With<RenderPhase<Transparent3d>>,
),
>,
) {
for (entity, view) in views_3d.iter() {
let cached_texture = texture_cache.get(
Expand Down
187 changes: 146 additions & 41 deletions pipelined/bevy_core_pipeline/src/main_pass_3d.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{ClearColor, Transparent3d};
use crate::{AlphaMask3d, ClearColor, Opaque3d, Transparent3d};
use bevy_ecs::prelude::*;
use bevy_render2::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
Expand All @@ -14,6 +14,8 @@ use bevy_render2::{
pub struct MainPass3dNode {
query: QueryState<
(
&'static RenderPhase<Opaque3d>,
&'static RenderPhase<AlphaMask3d>,
&'static RenderPhase<Transparent3d>,
&'static ViewTarget,
&'static ViewDepthTexture,
Expand Down Expand Up @@ -48,52 +50,155 @@ impl Node for MainPass3dNode {
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let (transparent_phase, target, depth) = self
let (opaque_phase, alpha_mask_phase, transparent_phase, target, depth) = self
.query
.get_manual(world, view_entity)
.expect("view entity should exist");
let clear_color = world.get_resource::<ClearColor>().unwrap();
let pass_descriptor = RenderPassDescriptor {
label: Some("main_pass_3d"),
color_attachments: &[RenderPassColorAttachment {
view: if let Some(sampled_target) = &target.sampled_target {
sampled_target
} else {
&target.view
},
resolve_target: if target.sampled_target.is_some() {
Some(&target.view)
} else {
None
},
ops: Operations {
load: LoadOp::Clear(clear_color.0.into()),
store: true,
},
}],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
depth_ops: Some(Operations {
load: LoadOp::Clear(0.0),
store: true,

{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine for now, but this seems ripe for parallel command encoding (once we start doing that). Breaking this up into 3 separate nodes might make that easier.

// Run the opaque pass, sorted front-to-back
// NOTE: Scoped to drop the mutable borrow of render_context
let pass_descriptor = RenderPassDescriptor {
label: Some("main_opaque_pass_3d"),
color_attachments: &[RenderPassColorAttachment {
view: if let Some(sampled_target) = &target.sampled_target {
sampled_target
} else {
&target.view
},
resolve_target: if target.sampled_target.is_some() {
Some(&target.view)
} else {
None
},
// NOTE: The opaque pass clears and initializes the color
// buffer as well as writing to it.
ops: Operations {
load: LoadOp::Clear(clear_color.0.into()),
store: true,
},
}],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
// NOTE: The opaque main pass clears and writes to the depth buffer.
depth_ops: Some(Operations {
load: LoadOp::Clear(0.0),
store: true,
}),
stencil_ops: None,
}),
};

let draw_functions = world.get_resource::<DrawFunctions<Opaque3d>>().unwrap();

let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in opaque_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}

{
// Run the alpha mask pass, sorted front-to-back
// NOTE: Scoped to drop the mutable borrow of render_context
let pass_descriptor = RenderPassDescriptor {
label: Some("main_alpha_mask_pass_3d"),
color_attachments: &[RenderPassColorAttachment {
view: if let Some(sampled_target) = &target.sampled_target {
sampled_target
} else {
&target.view
},
resolve_target: if target.sampled_target.is_some() {
Some(&target.view)
} else {
None
},
// NOTE: The alpha_mask pass loads the color buffer as well as overwriting it where appropriate.
ops: Operations {
load: LoadOp::Load,
store: true,
},
}],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
// NOTE: The alpha mask pass loads the depth buffer and possibly overwrites it
depth_ops: Some(Operations {
load: LoadOp::Load,
store: true,
}),
stencil_ops: None,
}),
};

let draw_functions = world.get_resource::<DrawFunctions<AlphaMask3d>>().unwrap();

let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in alpha_mask_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}

{
// Run the transparent pass, sorted back-to-front
// NOTE: Scoped to drop the mutable borrow of render_context
let pass_descriptor = RenderPassDescriptor {
label: Some("main_transparent_pass_3d"),
color_attachments: &[RenderPassColorAttachment {
view: if let Some(sampled_target) = &target.sampled_target {
sampled_target
} else {
&target.view
},
resolve_target: if target.sampled_target.is_some() {
Some(&target.view)
} else {
None
},
// NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate.
ops: Operations {
load: LoadOp::Load,
store: true,
},
}],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
// NOTE: For the transparent pass we load the depth buffer but do not write to it.
// As the opaque and alpha mask passes run first, opaque meshes can occlude
// transparent ones.
depth_ops: Some(Operations {
load: LoadOp::Load,
store: false,
}),
stencil_ops: None,
}),
stencil_ops: None,
}),
};

let draw_functions = world
.get_resource::<DrawFunctions<Transparent3d>>()
.unwrap();

let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in transparent_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
};

let draw_functions = world
.get_resource::<DrawFunctions<Transparent3d>>()
.unwrap();

let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in transparent_phase.items.iter() {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}

Ok(())
}
}
11 changes: 10 additions & 1 deletion pipelined/bevy_gltf2/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy_core::Name;
use bevy_ecs::world::World;
use bevy_log::warn;
use bevy_math::{Mat4, Vec3};
use bevy_pbr2::{PbrBundle, StandardMaterial};
use bevy_pbr2::{AlphaMode, PbrBundle, StandardMaterial};
use bevy_render2::{
camera::{
Camera, CameraPlugin, CameraProjection, OrthographicProjection, PerspectiveProjection,
Expand Down Expand Up @@ -438,6 +438,7 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0),
emissive_texture,
unlit: material.unlit(),
alpha_mode: alpha_mode(material),
..Default::default()
}),
)
Expand Down Expand Up @@ -649,6 +650,14 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
}
}

fn alpha_mode(material: &Material) -> AlphaMode {
match material.alpha_mode() {
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
gltf::material::AlphaMode::Mask => AlphaMode::Mask(material.alpha_cutoff().unwrap_or(0.5)),
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
}
}

async fn load_buffers(
gltf: &gltf::Gltf,
load_context: &LoadContext<'_>,
Expand Down
22 changes: 22 additions & 0 deletions pipelined/bevy_pbr2/src/alpha.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use bevy_ecs::reflect::ReflectComponent;
use bevy_reflect::Reflect;

// FIXME: This should probably be part of bevy_render2!
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cart Where do you think this should live?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The set of available values here is specific to the bevy_pbr2 implementation, so I think the current location is good for now.

/// Alpha mode
#[derive(Debug, Reflect, Clone, PartialEq)]
#[reflect(Component)]
pub enum AlphaMode {
Opaque,
/// An alpha cutoff must be supplied where alpha values >= the cutoff
/// will be fully opaque and < will be fully transparent
Mask(f32),
Blend,
}

impl Eq for AlphaMode {}

impl Default for AlphaMode {
fn default() -> Self {
AlphaMode::Opaque
}
}
Loading