Skip to content

Commit

Permalink
Add support for opaque, alpha mask, and alpha blend modes (#3072)
Browse files Browse the repository at this point in the history
# Objective

Add depth prepass and support for opaque, alpha mask, and alpha blend modes for the 3D PBR target.

## Solution

NOTE: This is based on top of #2861 frustum culling. Just lining it up to keep @cart loaded with the review train. 🚂 

There are a lot of important details here. Big thanks to @cwfitzgerald of wgpu, naga, and rend3 fame for explaining how to do it properly!

* An `AlphaMode` component is added that defines whether a material should be considered opaque, an alpha mask (with a cutoff value that defaults to 0.5, the same as glTF), or transparent and should be alpha blended
* Two depth prepasses are added:
  * Opaque does a plain vertex stage
  * Alpha mask does the vertex stage but also a fragment stage that samples the colour for the fragment and discards if its alpha value is below the cutoff value
  * Both are sorted front to back, not that it matters for these passes. (Maybe there should be a way to skip sorting?)
* Three main passes are added:
  * Opaque and alpha mask passes use a depth comparison function of Equal such that only the geometry that was closest is processed further, due to early-z testing
  * The transparent pass uses the Greater depth comparison function so that only transparent objects that are closer than anything opaque are rendered
  * The opaque fragment shading is as before except that alpha is explicitly set to 1.0
  * Alpha mask fragment shading sets the alpha value to 1.0 if it is equal to or above the cutoff, as defined by glTF
  * Opaque and alpha mask are sorted front to back (again not that it matters as we will skip anything that is not equal... maybe sorting is no longer needed here?)
  * Transparent is sorted back to front. Transparent fragment shading uses the alpha blending over operator

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
  • Loading branch information
superdump and cart committed Nov 16, 2021
1 parent 029a7c0 commit 213839f
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 131 deletions.
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
150 changes: 108 additions & 42 deletions pipelined/bevy_core_pipeline/src/main_pass_3d.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
use crate::{ClearColor, Transparent3d};
use crate::{AlphaMask3d, ClearColor, Opaque3d, Transparent3d};
use bevy_ecs::prelude::*;
use bevy_render2::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::{
LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
RenderPassDescriptor,
},
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},
renderer::RenderContext,
view::{ExtractedView, ViewDepthTexture, ViewTarget},
};

pub struct MainPass3dNode {
query: QueryState<
(
&'static RenderPhase<Opaque3d>,
&'static RenderPhase<AlphaMask3d>,
&'static RenderPhase<Transparent3d>,
&'static ViewTarget,
&'static ViewDepthTexture,
Expand Down Expand Up @@ -48,52 +47,119 @@ 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 {

{
// 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"),
// NOTE: The opaque pass clears and initializes the color
// buffer as well as writing to it.
color_attachments: &[target.get_color_attachment(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),
})],
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"),
// NOTE: The alpha_mask pass loads the color buffer as well as overwriting it where appropriate.
color_attachments: &[target.get_color_attachment(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,
}),
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<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"),
// NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate.
color_attachments: &[target.get_color_attachment(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,
}),
};

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!
/// 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
}
}
6 changes: 5 additions & 1 deletion pipelined/bevy_pbr2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
mod alpha;
mod bundle;
mod light;
mod material;
mod render;

pub use alpha::*;
pub use bundle::*;
pub use light::*;
pub use material::*;
pub use render::*;

use bevy_app::prelude::*;
use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_core_pipeline::Transparent3d;
use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d};
use bevy_ecs::prelude::*;
use bevy_reflect::TypeUuid;
use bevy_render2::{
Expand Down Expand Up @@ -109,6 +111,8 @@ impl Plugin for PbrPlugin {
.init_resource::<SpecializedPipelines<ShadowPipeline>>();

let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
render_app.add_render_command::<Opaque3d, DrawPbr>();
render_app.add_render_command::<AlphaMask3d, DrawPbr>();
render_app.add_render_command::<Transparent3d, DrawPbr>();
render_app.add_render_command::<Shadow, DrawShadowMesh>();
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();
Expand Down
Loading

0 comments on commit 213839f

Please sign in to comment.