Skip to content

Commit 213839f

Browse files
superdumpcart
andcommitted
Add support for opaque, alpha mask, and alpha blend modes (#3072)
# 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 <[email protected]>
1 parent 029a7c0 commit 213839f

File tree

10 files changed

+421
-131
lines changed

10 files changed

+421
-131
lines changed

pipelined/bevy_core_pipeline/src/lib.rs

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,15 @@ impl Plugin for CorePipelinePlugin {
7575
let render_app = app.sub_app(RenderApp);
7676
render_app
7777
.init_resource::<DrawFunctions<Transparent2d>>()
78+
.init_resource::<DrawFunctions<Opaque3d>>()
79+
.init_resource::<DrawFunctions<AlphaMask3d>>()
7880
.init_resource::<DrawFunctions<Transparent3d>>()
7981
.add_system_to_stage(RenderStage::Extract, extract_clear_color)
8082
.add_system_to_stage(RenderStage::Extract, extract_core_pipeline_camera_phases)
8183
.add_system_to_stage(RenderStage::Prepare, prepare_core_views_system)
8284
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent2d>)
85+
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Opaque3d>)
86+
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
8387
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);
8488

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

154+
pub struct Opaque3d {
155+
pub distance: f32,
156+
pub pipeline: CachedPipelineId,
157+
pub entity: Entity,
158+
pub draw_function: DrawFunctionId,
159+
}
160+
161+
impl PhaseItem for Opaque3d {
162+
type SortKey = FloatOrd;
163+
164+
#[inline]
165+
fn sort_key(&self) -> Self::SortKey {
166+
FloatOrd(self.distance)
167+
}
168+
169+
#[inline]
170+
fn draw_function(&self) -> DrawFunctionId {
171+
self.draw_function
172+
}
173+
}
174+
175+
impl EntityPhaseItem for Opaque3d {
176+
#[inline]
177+
fn entity(&self) -> Entity {
178+
self.entity
179+
}
180+
}
181+
182+
impl CachedPipelinePhaseItem for Opaque3d {
183+
#[inline]
184+
fn cached_pipeline(&self) -> CachedPipelineId {
185+
self.pipeline
186+
}
187+
}
188+
189+
pub struct AlphaMask3d {
190+
pub distance: f32,
191+
pub pipeline: CachedPipelineId,
192+
pub entity: Entity,
193+
pub draw_function: DrawFunctionId,
194+
}
195+
196+
impl PhaseItem for AlphaMask3d {
197+
type SortKey = FloatOrd;
198+
199+
#[inline]
200+
fn sort_key(&self) -> Self::SortKey {
201+
FloatOrd(self.distance)
202+
}
203+
204+
#[inline]
205+
fn draw_function(&self) -> DrawFunctionId {
206+
self.draw_function
207+
}
208+
}
209+
210+
impl EntityPhaseItem for AlphaMask3d {
211+
#[inline]
212+
fn entity(&self) -> Entity {
213+
self.entity
214+
}
215+
}
216+
217+
impl CachedPipelinePhaseItem for AlphaMask3d {
218+
#[inline]
219+
fn cached_pipeline(&self) -> CachedPipelineId {
220+
self.pipeline
221+
}
222+
}
223+
150224
pub struct Transparent3d {
151225
pub distance: f32,
152226
pub pipeline: CachedPipelineId,
@@ -203,9 +277,11 @@ pub fn extract_core_pipeline_camera_phases(
203277
}
204278
if let Some(camera_3d) = active_cameras.get(CameraPlugin::CAMERA_3D) {
205279
if let Some(entity) = camera_3d.entity {
206-
commands
207-
.get_or_spawn(entity)
208-
.insert(RenderPhase::<Transparent3d>::default());
280+
commands.get_or_spawn(entity).insert_bundle((
281+
RenderPhase::<Opaque3d>::default(),
282+
RenderPhase::<AlphaMask3d>::default(),
283+
RenderPhase::<Transparent3d>::default(),
284+
));
209285
}
210286
}
211287
}
@@ -215,7 +291,14 @@ pub fn prepare_core_views_system(
215291
mut texture_cache: ResMut<TextureCache>,
216292
msaa: Res<Msaa>,
217293
render_device: Res<RenderDevice>,
218-
views_3d: Query<(Entity, &ExtractedView), With<RenderPhase<Transparent3d>>>,
294+
views_3d: Query<
295+
(Entity, &ExtractedView),
296+
(
297+
With<RenderPhase<Opaque3d>>,
298+
With<RenderPhase<AlphaMask3d>>,
299+
With<RenderPhase<Transparent3d>>,
300+
),
301+
>,
219302
) {
220303
for (entity, view) in views_3d.iter() {
221304
let cached_texture = texture_cache.get(
Lines changed: 108 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
use crate::{ClearColor, Transparent3d};
1+
use crate::{AlphaMask3d, ClearColor, Opaque3d, Transparent3d};
22
use bevy_ecs::prelude::*;
33
use bevy_render2::{
44
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
55
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
6-
render_resource::{
7-
LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
8-
RenderPassDescriptor,
9-
},
6+
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},
107
renderer::RenderContext,
118
view::{ExtractedView, ViewDepthTexture, ViewTarget},
129
};
1310

1411
pub struct MainPass3dNode {
1512
query: QueryState<
1613
(
14+
&'static RenderPhase<Opaque3d>,
15+
&'static RenderPhase<AlphaMask3d>,
1716
&'static RenderPhase<Transparent3d>,
1817
&'static ViewTarget,
1918
&'static ViewDepthTexture,
@@ -48,52 +47,119 @@ impl Node for MainPass3dNode {
4847
world: &World,
4948
) -> Result<(), NodeRunError> {
5049
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
51-
let (transparent_phase, target, depth) = self
50+
let (opaque_phase, alpha_mask_phase, transparent_phase, target, depth) = self
5251
.query
5352
.get_manual(world, view_entity)
5453
.expect("view entity should exist");
5554
let clear_color = world.get_resource::<ClearColor>().unwrap();
56-
let pass_descriptor = RenderPassDescriptor {
57-
label: Some("main_pass_3d"),
58-
color_attachments: &[RenderPassColorAttachment {
59-
view: if let Some(sampled_target) = &target.sampled_target {
60-
sampled_target
61-
} else {
62-
&target.view
63-
},
64-
resolve_target: if target.sampled_target.is_some() {
65-
Some(&target.view)
66-
} else {
67-
None
68-
},
69-
ops: Operations {
55+
56+
{
57+
// Run the opaque pass, sorted front-to-back
58+
// NOTE: Scoped to drop the mutable borrow of render_context
59+
let pass_descriptor = RenderPassDescriptor {
60+
label: Some("main_opaque_pass_3d"),
61+
// NOTE: The opaque pass clears and initializes the color
62+
// buffer as well as writing to it.
63+
color_attachments: &[target.get_color_attachment(Operations {
7064
load: LoadOp::Clear(clear_color.0.into()),
7165
store: true,
72-
},
73-
}],
74-
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
75-
view: &depth.view,
76-
depth_ops: Some(Operations {
77-
load: LoadOp::Clear(0.0),
66+
})],
67+
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
68+
view: &depth.view,
69+
// NOTE: The opaque main pass clears and writes to the depth buffer.
70+
depth_ops: Some(Operations {
71+
load: LoadOp::Clear(0.0),
72+
store: true,
73+
}),
74+
stencil_ops: None,
75+
}),
76+
};
77+
78+
let draw_functions = world.get_resource::<DrawFunctions<Opaque3d>>().unwrap();
79+
80+
let render_pass = render_context
81+
.command_encoder
82+
.begin_render_pass(&pass_descriptor);
83+
let mut draw_functions = draw_functions.write();
84+
let mut tracked_pass = TrackedRenderPass::new(render_pass);
85+
for item in opaque_phase.items.iter() {
86+
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
87+
draw_function.draw(world, &mut tracked_pass, view_entity, item);
88+
}
89+
}
90+
91+
{
92+
// Run the alpha mask pass, sorted front-to-back
93+
// NOTE: Scoped to drop the mutable borrow of render_context
94+
let pass_descriptor = RenderPassDescriptor {
95+
label: Some("main_alpha_mask_pass_3d"),
96+
// NOTE: The alpha_mask pass loads the color buffer as well as overwriting it where appropriate.
97+
color_attachments: &[target.get_color_attachment(Operations {
98+
load: LoadOp::Load,
7899
store: true,
100+
})],
101+
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
102+
view: &depth.view,
103+
// NOTE: The alpha mask pass loads the depth buffer and possibly overwrites it
104+
depth_ops: Some(Operations {
105+
load: LoadOp::Load,
106+
store: true,
107+
}),
108+
stencil_ops: None,
79109
}),
80-
stencil_ops: None,
81-
}),
82-
};
83-
84-
let draw_functions = world
85-
.get_resource::<DrawFunctions<Transparent3d>>()
86-
.unwrap();
87-
88-
let render_pass = render_context
89-
.command_encoder
90-
.begin_render_pass(&pass_descriptor);
91-
let mut draw_functions = draw_functions.write();
92-
let mut tracked_pass = TrackedRenderPass::new(render_pass);
93-
for item in transparent_phase.items.iter() {
94-
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
95-
draw_function.draw(world, &mut tracked_pass, view_entity, item);
110+
};
111+
112+
let draw_functions = world.get_resource::<DrawFunctions<AlphaMask3d>>().unwrap();
113+
114+
let render_pass = render_context
115+
.command_encoder
116+
.begin_render_pass(&pass_descriptor);
117+
let mut draw_functions = draw_functions.write();
118+
let mut tracked_pass = TrackedRenderPass::new(render_pass);
119+
for item in alpha_mask_phase.items.iter() {
120+
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
121+
draw_function.draw(world, &mut tracked_pass, view_entity, item);
122+
}
96123
}
124+
125+
{
126+
// Run the transparent pass, sorted back-to-front
127+
// NOTE: Scoped to drop the mutable borrow of render_context
128+
let pass_descriptor = RenderPassDescriptor {
129+
label: Some("main_transparent_pass_3d"),
130+
// NOTE: The transparent pass loads the color buffer as well as overwriting it where appropriate.
131+
color_attachments: &[target.get_color_attachment(Operations {
132+
load: LoadOp::Load,
133+
store: true,
134+
})],
135+
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
136+
view: &depth.view,
137+
// NOTE: For the transparent pass we load the depth buffer but do not write to it.
138+
// As the opaque and alpha mask passes run first, opaque meshes can occlude
139+
// transparent ones.
140+
depth_ops: Some(Operations {
141+
load: LoadOp::Load,
142+
store: false,
143+
}),
144+
stencil_ops: None,
145+
}),
146+
};
147+
148+
let draw_functions = world
149+
.get_resource::<DrawFunctions<Transparent3d>>()
150+
.unwrap();
151+
152+
let render_pass = render_context
153+
.command_encoder
154+
.begin_render_pass(&pass_descriptor);
155+
let mut draw_functions = draw_functions.write();
156+
let mut tracked_pass = TrackedRenderPass::new(render_pass);
157+
for item in transparent_phase.items.iter() {
158+
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
159+
draw_function.draw(world, &mut tracked_pass, view_entity, item);
160+
}
161+
}
162+
97163
Ok(())
98164
}
99165
}

pipelined/bevy_gltf2/src/loader.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use bevy_core::Name;
66
use bevy_ecs::world::World;
77
use bevy_log::warn;
88
use bevy_math::{Mat4, Vec3};
9-
use bevy_pbr2::{PbrBundle, StandardMaterial};
9+
use bevy_pbr2::{AlphaMode, PbrBundle, StandardMaterial};
1010
use bevy_render2::{
1111
camera::{
1212
Camera, CameraPlugin, CameraProjection, OrthographicProjection, PerspectiveProjection,
@@ -438,6 +438,7 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
438438
emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0),
439439
emissive_texture,
440440
unlit: material.unlit(),
441+
alpha_mode: alpha_mode(material),
441442
..Default::default()
442443
}),
443444
)
@@ -649,6 +650,14 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
649650
}
650651
}
651652

653+
fn alpha_mode(material: &Material) -> AlphaMode {
654+
match material.alpha_mode() {
655+
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
656+
gltf::material::AlphaMode::Mask => AlphaMode::Mask(material.alpha_cutoff().unwrap_or(0.5)),
657+
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
658+
}
659+
}
660+
652661
async fn load_buffers(
653662
gltf: &gltf::Gltf,
654663
load_context: &LoadContext<'_>,

pipelined/bevy_pbr2/src/alpha.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use bevy_ecs::reflect::ReflectComponent;
2+
use bevy_reflect::Reflect;
3+
4+
// FIXME: This should probably be part of bevy_render2!
5+
/// Alpha mode
6+
#[derive(Debug, Reflect, Clone, PartialEq)]
7+
#[reflect(Component)]
8+
pub enum AlphaMode {
9+
Opaque,
10+
/// An alpha cutoff must be supplied where alpha values >= the cutoff
11+
/// will be fully opaque and < will be fully transparent
12+
Mask(f32),
13+
Blend,
14+
}
15+
16+
impl Eq for AlphaMode {}
17+
18+
impl Default for AlphaMode {
19+
fn default() -> Self {
20+
AlphaMode::Opaque
21+
}
22+
}

pipelined/bevy_pbr2/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
mod alpha;
12
mod bundle;
23
mod light;
34
mod material;
45
mod render;
56

7+
pub use alpha::*;
68
pub use bundle::*;
79
pub use light::*;
810
pub use material::*;
911
pub use render::*;
1012

1113
use bevy_app::prelude::*;
1214
use bevy_asset::{Assets, Handle, HandleUntyped};
13-
use bevy_core_pipeline::Transparent3d;
15+
use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d};
1416
use bevy_ecs::prelude::*;
1517
use bevy_reflect::TypeUuid;
1618
use bevy_render2::{
@@ -109,6 +111,8 @@ impl Plugin for PbrPlugin {
109111
.init_resource::<SpecializedPipelines<ShadowPipeline>>();
110112

111113
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
114+
render_app.add_render_command::<Opaque3d, DrawPbr>();
115+
render_app.add_render_command::<AlphaMask3d, DrawPbr>();
112116
render_app.add_render_command::<Transparent3d, DrawPbr>();
113117
render_app.add_render_command::<Shadow, DrawShadowMesh>();
114118
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();

0 commit comments

Comments
 (0)