Split Transmissive3d into a separate pass

This commit is contained in:
Marco Buono 2023-04-10 01:01:06 -03:00
parent fb490297e9
commit 3bc0a018cd
6 changed files with 193 additions and 25 deletions

View file

@ -0,0 +1,106 @@
use super::ViewTransmissionTexture;
use crate::core_3d::Transmissive3d;
use bevy_ecs::prelude::*;
use bevy_render::{
camera::ExtractedCamera,
render_graph::{Node, NodeRunError, RenderGraphContext},
render_phase::RenderPhase,
render_resource::{
Extent3d, LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor,
},
renderer::RenderContext,
view::{ExtractedView, ViewDepthTexture, ViewTarget},
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
/// A [`Node`] that runs the [`Transmissive3d`] [`RenderPhase`].
pub struct MainTransmissivePass3dNode {
query: QueryState<
(
&'static ExtractedCamera,
&'static RenderPhase<Transmissive3d>,
&'static ViewTarget,
&'static ViewTransmissionTexture,
&'static ViewDepthTexture,
),
With<ExtractedView>,
>,
}
impl FromWorld for MainTransmissivePass3dNode {
fn from_world(world: &mut World) -> Self {
Self {
query: world.query_filtered(),
}
}
}
impl Node for MainTransmissivePass3dNode {
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let Ok((
camera,
transmissive_phase,
target,
transmission,
depth,
)) = self.query.get_manual(world, view_entity) else {
// No window
return Ok(());
};
let physical_target_size = camera.physical_target_size.unwrap();
render_context.command_encoder().copy_texture_to_texture(
target.main_texture().as_image_copy(),
transmission.texture.as_image_copy(),
Extent3d {
width: physical_target_size.x,
height: physical_target_size.y,
depth_or_array_layers: 1,
},
);
if !transmissive_phase.items.is_empty() {
// Run the transmissive pass, sorted back-to-front
// NOTE: Scoped to drop the mutable borrow of render_context
#[cfg(feature = "trace")]
let _main_transmissive_pass_3d_span = info_span!("main_transmissive_pass_3d").entered();
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
label: Some("main_transmissive_pass_3d"),
// NOTE: The transmissive pass loads the color buffer as well as overwriting it where appropriate.
color_attachments: &[Some(target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
}))],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
// NOTE: The transmissive main pass loads the depth buffer and possibly overwrites it
depth_ops: Some(Operations {
load: LoadOp::Load,
store: true,
}),
stencil_ops: None,
}),
});
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
}
transmissive_phase.render(&mut render_pass, world, view_entity);
}
Ok(())
}
}

View file

@ -1,13 +1,10 @@
use super::ViewTransmissionTexture;
use crate::core_3d::Transparent3d;
use bevy_ecs::prelude::*;
use bevy_render::{
camera::ExtractedCamera,
render_graph::{Node, NodeRunError, RenderGraphContext},
render_phase::RenderPhase,
render_resource::{
Extent3d, LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor,
},
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},
renderer::RenderContext,
view::{ExtractedView, ViewDepthTexture, ViewTarget},
};
@ -21,7 +18,6 @@ pub struct MainTransparentPass3dNode {
&'static ExtractedCamera,
&'static RenderPhase<Transparent3d>,
&'static ViewTarget,
&'static ViewTransmissionTexture,
&'static ViewDepthTexture,
),
With<ExtractedView>,
@ -52,24 +48,12 @@ impl Node for MainTransparentPass3dNode {
camera,
transparent_phase,
target,
transmission,
depth,
)) = self.query.get_manual(world, view_entity) else {
// No window
return Ok(());
};
let physical_target_size = camera.physical_target_size.unwrap();
render_context.command_encoder().copy_texture_to_texture(
target.main_texture().as_image_copy(),
transmission.texture.as_image_copy(),
Extent3d {
width: physical_target_size.x,
height: physical_target_size.y,
depth_or_array_layers: 1,
},
);
if !transparent_phase.items.is_empty() {
// Run the transparent pass, sorted back-to-front
// NOTE: Scoped to drop the mutable borrow of render_context

View file

@ -1,5 +1,6 @@
mod camera_3d;
mod main_opaque_pass_3d_node;
mod main_transmissive_pass_3d_node;
mod main_transparent_pass_3d_node;
pub mod graph {
@ -12,6 +13,7 @@ pub mod graph {
pub const PREPASS: &str = "prepass";
pub const START_MAIN_PASS: &str = "start_main_pass";
pub const MAIN_OPAQUE_PASS: &str = "main_opaque_pass";
pub const MAIN_TRANSMISSIVE_PASS: &str = "main_transmissive_pass";
pub const MAIN_TRANSPARENT_PASS: &str = "main_transparent_pass";
pub const END_MAIN_PASS: &str = "end_main_pass";
pub const BLOOM: &str = "bloom";
@ -53,6 +55,7 @@ use bevy_render::{
use bevy_utils::{FloatOrd, HashMap};
use crate::{
core_3d::main_transmissive_pass_3d_node::MainTransmissivePass3dNode,
prepass::{node::PrepassNode, DepthPrepass},
skybox::SkyboxPlugin,
tonemapping::TonemappingNode,
@ -76,6 +79,7 @@ impl Plugin for Core3dPlugin {
render_app
.init_resource::<DrawFunctions<Opaque3d>>()
.init_resource::<DrawFunctions<AlphaMask3d>>()
.init_resource::<DrawFunctions<Transmissive3d>>()
.init_resource::<DrawFunctions<Transparent3d>>()
.add_systems(ExtractSchedule, extract_core_3d_camera_phases)
.add_systems(
@ -89,6 +93,7 @@ impl Plugin for Core3dPlugin {
.after(bevy_render::view::prepare_windows),
sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Transmissive3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
),
);
@ -99,6 +104,7 @@ impl Plugin for Core3dPlugin {
.add_render_graph_node::<PrepassNode>(CORE_3D, PREPASS)
.add_render_graph_node::<EmptyNode>(CORE_3D, START_MAIN_PASS)
.add_render_graph_node::<MainOpaquePass3dNode>(CORE_3D, MAIN_OPAQUE_PASS)
.add_render_graph_node::<MainTransmissivePass3dNode>(CORE_3D, MAIN_TRANSMISSIVE_PASS)
.add_render_graph_node::<MainTransparentPass3dNode>(CORE_3D, MAIN_TRANSPARENT_PASS)
.add_render_graph_node::<EmptyNode>(CORE_3D, END_MAIN_PASS)
.add_render_graph_node::<TonemappingNode>(CORE_3D, TONEMAPPING)
@ -110,6 +116,7 @@ impl Plugin for Core3dPlugin {
PREPASS,
START_MAIN_PASS,
MAIN_OPAQUE_PASS,
MAIN_TRANSMISSIVE_PASS,
MAIN_TRANSPARENT_PASS,
END_MAIN_PASS,
TONEMAPPING,
@ -200,6 +207,45 @@ impl CachedRenderPipelinePhaseItem for AlphaMask3d {
}
}
pub struct Transmissive3d {
pub distance: f32,
pub pipeline: CachedRenderPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for Transmissive3d {
// NOTE: Values increase towards the camera. Back-to-front ordering for transmissive means we need an ascending sort.
type SortKey = FloatOrd;
#[inline]
fn entity(&self) -> Entity {
self.entity
}
#[inline]
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
#[inline]
fn draw_function(&self) -> DrawFunctionId {
self.draw_function
}
#[inline]
fn sort(items: &mut [Self]) {
radsort::sort_by_key(items, |item| item.distance);
}
}
impl CachedRenderPipelinePhaseItem for Transmissive3d {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline
}
}
pub struct Transparent3d {
pub distance: f32,
pub pipeline: CachedRenderPipelineId,
@ -248,6 +294,7 @@ pub fn extract_core_3d_camera_phases(
commands.get_or_spawn(entity).insert((
RenderPhase::<Opaque3d>::default(),
RenderPhase::<AlphaMask3d>::default(),
RenderPhase::<Transmissive3d>::default(),
RenderPhase::<Transparent3d>::default(),
));
}
@ -264,6 +311,7 @@ pub fn prepare_core_3d_depth_textures(
(
With<RenderPhase<Opaque3d>>,
With<RenderPhase<AlphaMask3d>>,
With<RenderPhase<Transmissive3d>>,
With<RenderPhase<Transparent3d>>,
),
>,
@ -330,6 +378,7 @@ pub fn prepare_core_3d_transmission_textures(
(
With<RenderPhase<Opaque3d>>,
With<RenderPhase<AlphaMask3d>>,
With<RenderPhase<Transmissive3d>>,
With<RenderPhase<Transparent3d>>,
),
>,

View file

@ -6,7 +6,7 @@ use crate::{
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle};
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Transparent3d},
core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d},
prepass::NormalPrepass,
tonemapping::{DebandDither, Tonemapping},
};
@ -133,6 +133,13 @@ pub trait Material: AsBindGroup + Send + Sync + Clone + TypeUuid + Sized + 'stat
0.0
}
#[inline]
/// Returns whether or not the material has light transmission properties. Transmissive materials use the color
/// output from the [`Opaque3d`] pass as an input and therefore must run in a separate [`Transmissive3d`] pass.
fn transmissive(&self) -> bool {
false
}
/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the default prepass vertex shader
/// will be used.
fn prepass_vertex_shader() -> ShaderRef {
@ -193,6 +200,7 @@ where
render_app
.init_resource::<DrawFunctions<Shadow>>()
.add_render_command::<Shadow, DrawPrepass<M>>()
.add_render_command::<Transmissive3d, DrawMaterial<M>>()
.add_render_command::<Transparent3d, DrawMaterial<M>>()
.add_render_command::<Opaque3d, DrawMaterial<M>>()
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
@ -366,6 +374,7 @@ impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterial
pub fn queue_material_meshes<M: Material>(
opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
alpha_mask_draw_functions: Res<DrawFunctions<AlphaMask3d>>,
transmissive_draw_functions: Res<DrawFunctions<Transmissive3d>>,
transparent_draw_functions: Res<DrawFunctions<Transparent3d>>,
material_pipeline: Res<MaterialPipeline<M>>,
mut pipelines: ResMut<SpecializedMeshPipelines<MaterialPipeline<M>>>,
@ -384,6 +393,7 @@ pub fn queue_material_meshes<M: Material>(
Option<&NormalPrepass>,
&mut RenderPhase<Opaque3d>,
&mut RenderPhase<AlphaMask3d>,
&mut RenderPhase<Transmissive3d>,
&mut RenderPhase<Transparent3d>,
)>,
) where
@ -398,11 +408,13 @@ pub fn queue_material_meshes<M: Material>(
normal_prepass,
mut opaque_phase,
mut alpha_mask_phase,
mut transmissive_phase,
mut transparent_phase,
) in &mut views
{
let draw_opaque_pbr = opaque_draw_functions.read().id::<DrawMaterial<M>>();
let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::<DrawMaterial<M>>();
let draw_transmissive_pbr = transmissive_draw_functions.read().id::<DrawMaterial<M>>();
let draw_transparent_pbr = transparent_draw_functions.read().id::<DrawMaterial<M>>();
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
@ -491,12 +503,21 @@ pub fn queue_material_meshes<M: Material>(
+ material.properties.depth_bias;
match material.properties.alpha_mode {
AlphaMode::Opaque => {
opaque_phase.add(Opaque3d {
entity: *visible_entity,
draw_function: draw_opaque_pbr,
pipeline: pipeline_id,
distance,
});
if material.properties.transmissive {
transmissive_phase.add(Transmissive3d {
entity: *visible_entity,
draw_function: draw_transmissive_pbr,
pipeline: pipeline_id,
distance,
});
} else {
opaque_phase.add(Opaque3d {
entity: *visible_entity,
draw_function: draw_opaque_pbr,
pipeline: pipeline_id,
distance,
});
}
}
AlphaMode::Mask(_) => {
alpha_mask_phase.add(AlphaMask3d {
@ -532,6 +553,9 @@ pub struct MaterialProperties {
/// for meshes with equal depth, to avoid z-fighting.
/// The bias is in depth-texture units so large values may be needed to overcome small depth differences.
pub depth_bias: f32,
/// Whether or not the material has light transmission properties. Transmissive materials use the color
/// output from the [`Opaque3d`] pass as an input and therefore must run in a separate [`Transmissive3d`] pass.
pub transmissive: bool,
}
/// Data prepared for a [`Material`] instance.
@ -685,6 +709,7 @@ fn prepare_material<M: Material>(
properties: MaterialProperties {
alpha_mode: material.alpha_mode(),
depth_bias: material.depth_bias(),
transmissive: material.transmissive(),
},
})
}

View file

@ -530,4 +530,9 @@ impl Material for StandardMaterial {
fn depth_bias(&self) -> f32 {
self.depth_bias
}
#[inline]
fn transmissive(&self) -> bool {
self.transmission > 0.0
}
}

View file

@ -74,7 +74,6 @@ fn setup(
thickness: 1.0,
ior: 1.5,
perceptual_roughness: 0.12,
alpha_mode: AlphaMode::Blend,
..default()
}),
transform: Transform::from_xyz(1.0, 0.0, 0.0),