Add support for a configurable number of transmissive steps

This commit is contained in:
Marco Buono 2023-04-16 15:58:52 -03:00
parent 17474123d6
commit 727a89dbe7
5 changed files with 143 additions and 42 deletions

View File

@ -15,7 +15,7 @@ use bevy_transform::prelude::{GlobalTransform, Transform};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Configuration for the "main 3d render graph". /// Configuration for the "main 3d render graph".
#[derive(Component, Reflect, Clone, Default, ExtractComponent)] #[derive(Component, Reflect, Clone, ExtractComponent)]
#[extract_component_filter(With<Camera>)] #[extract_component_filter(With<Camera>)]
#[reflect(Component)] #[reflect(Component)]
pub struct Camera3d { pub struct Camera3d {
@ -23,6 +23,22 @@ pub struct Camera3d {
pub clear_color: ClearColorConfig, pub clear_color: ClearColorConfig,
/// The depth clear operation to perform for the main 3d pass. /// The depth clear operation to perform for the main 3d pass.
pub depth_load_op: Camera3dDepthLoadOp, pub depth_load_op: Camera3dDepthLoadOp,
/// How many individual steps should be performed in the transmissive 3d pass
///
/// Roughly corresponds to how many “layers of transparency” are rendered for
/// transmissive objects. Each step requires making one additional texture copy,
/// so it's recommended to keep this number to a resonably low value.
pub transmissive_steps: usize,
}
impl Default for Camera3d {
fn default() -> Self {
Camera3d {
clear_color: ClearColorConfig::default(),
depth_load_op: Camera3dDepthLoadOp::default(),
transmissive_steps: 1,
}
}
} }
/// The depth clear operation to perform for the main 3d pass. /// The depth clear operation to perform for the main 3d pass.

View File

@ -1,4 +1,4 @@
use super::ViewTransmissionTexture; use super::{Camera3d, ViewTransmissionTexture};
use crate::core_3d::Transmissive3d; use crate::core_3d::Transmissive3d;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_render::{ use bevy_render::{
@ -13,15 +13,17 @@ use bevy_render::{
}; };
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
use bevy_utils::tracing::info_span; use bevy_utils::tracing::info_span;
use std::ops::Range;
/// A [`Node`] that runs the [`Transmissive3d`] [`RenderPhase`]. /// A [`Node`] that runs the [`Transmissive3d`] [`RenderPhase`].
pub struct MainTransmissivePass3dNode { pub struct MainTransmissivePass3dNode {
query: QueryState< query: QueryState<
( (
&'static ExtractedCamera, &'static ExtractedCamera,
&'static Camera3d,
&'static RenderPhase<Transmissive3d>, &'static RenderPhase<Transmissive3d>,
&'static ViewTarget, &'static ViewTarget,
&'static ViewTransmissionTexture, Option<&'static ViewTransmissionTexture>,
&'static ViewDepthTexture, &'static ViewDepthTexture,
), ),
With<ExtractedView>, With<ExtractedView>,
@ -50,6 +52,7 @@ impl Node for MainTransmissivePass3dNode {
let view_entity = graph.view_entity(); let view_entity = graph.view_entity();
let Ok(( let Ok((
camera, camera,
camera_3d,
transmissive_phase, transmissive_phase,
target, target,
transmission, transmission,
@ -60,47 +63,94 @@ impl Node for MainTransmissivePass3dNode {
}; };
let physical_target_size = camera.physical_target_size.unwrap(); 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() { let render_pass_descriptor = RenderPassDescriptor {
// Run the transmissive pass, sorted back-to-front label: Some("main_transmissive_pass_3d"),
// NOTE: Scoped to drop the mutable borrow of render_context // NOTE: The transmissive pass loads the color buffer as well as overwriting it where appropriate.
#[cfg(feature = "trace")] color_attachments: &[Some(target.get_color_attachment(Operations {
let _main_transmissive_pass_3d_span = info_span!("main_transmissive_pass_3d").entered(); load: LoadOp::Load,
store: true,
let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { }))],
label: Some("main_transmissive_pass_3d"), depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
// NOTE: The transmissive pass loads the color buffer as well as overwriting it where appropriate. view: &depth.view,
color_attachments: &[Some(target.get_color_attachment(Operations { // NOTE: The transmissive main pass loads the depth buffer and possibly overwrites it
depth_ops: Some(Operations {
load: LoadOp::Load, load: LoadOp::Load,
store: true, 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,
}), }),
}); stencil_ops: None,
}),
};
if let Some(viewport) = camera.viewport.as_ref() { // Run the transmissive pass, sorted back-to-front
render_pass.set_camera_viewport(viewport); // 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();
if !transmissive_phase.items.is_empty() {
let transmissive_steps = camera_3d.transmissive_steps;
if transmissive_steps > 0 {
let transmission =
transmission.expect("`ViewTransmissionTexture` should exist at this point");
for range in split_range(0..transmissive_phase.items.len(), transmissive_steps) {
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,
},
);
let mut render_pass =
render_context.begin_tracked_render_pass(render_pass_descriptor.clone());
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
}
transmissive_phase.render_range(&mut render_pass, world, view_entity, range);
}
} else {
let mut render_pass =
render_context.begin_tracked_render_pass(render_pass_descriptor);
if let Some(viewport) = camera.viewport.as_ref() {
render_pass.set_camera_viewport(viewport);
}
transmissive_phase.render(&mut render_pass, world, view_entity);
} }
transmissive_phase.render(&mut render_pass, world, view_entity);
} }
Ok(()) Ok(())
} }
} }
/// Splits a [`Range`] into at most `max_num_splits` sub-ranges without overlaps
///
/// Properly takes into account remainders of inexact divisions (by adding extra
/// elements to the initial sub-ranges as needed)
fn split_range(range: Range<usize>, max_num_splits: usize) -> impl Iterator<Item = Range<usize>> {
let len = range.end - range.start;
assert!(len > 0, "to be split, a range must not be empty");
assert!(max_num_splits > 0, "max_num_splits must be at least 1");
let num_splits = max_num_splits.min(len);
let step = len / num_splits;
let mut rem = len % num_splits;
let mut start = range.start;
(0..num_splits).map(move |_| {
let extra = if rem > 0 {
rem -= 1;
1
} else {
0
};
let end = (start + step + extra).min(range.end);
let result = start..end;
start = end;
result
})
}

View File

@ -374,7 +374,7 @@ pub fn prepare_core_3d_transmission_textures(
mut texture_cache: ResMut<TextureCache>, mut texture_cache: ResMut<TextureCache>,
render_device: Res<RenderDevice>, render_device: Res<RenderDevice>,
views_3d: Query< views_3d: Query<
(Entity, &ExtractedCamera, &ExtractedView), (Entity, &ExtractedCamera, &Camera3d, &ExtractedView),
( (
With<RenderPhase<Opaque3d>>, With<RenderPhase<Opaque3d>>,
With<RenderPhase<AlphaMask3d>>, With<RenderPhase<AlphaMask3d>>,
@ -384,11 +384,16 @@ pub fn prepare_core_3d_transmission_textures(
>, >,
) { ) {
let mut textures = HashMap::default(); let mut textures = HashMap::default();
for (entity, camera, view) in &views_3d { for (entity, camera, camera_3d, view) in &views_3d {
let Some(physical_target_size) = camera.physical_target_size else { let Some(physical_target_size) = camera.physical_target_size else {
continue; continue;
}; };
// Don't prepare a transmission texture if the number of steps is set to 0
if camera_3d.transmissive_steps == 0 {
continue;
}
let cached_texture = textures let cached_texture = textures
.entry(camera.target.clone()) .entry(camera.target.clone())
.or_insert_with(|| { .or_insert_with(|| {

View File

@ -34,7 +34,7 @@ use bevy_render::{
render_resource::*, render_resource::*,
renderer::{RenderDevice, RenderQueue}, renderer::{RenderDevice, RenderQueue},
texture::{ texture::{
BevyDefault, DefaultImageSampler, FallbackImageCubemap, FallbackImagesDepth, BevyDefault, DefaultImageSampler, FallbackImage, FallbackImageCubemap, FallbackImagesDepth,
FallbackImagesMsaa, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, FallbackImagesMsaa, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
}, },
view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},
@ -997,11 +997,12 @@ pub fn queue_mesh_view_bind_groups(
Option<&ViewPrepassTextures>, Option<&ViewPrepassTextures>,
Option<&EnvironmentMapLight>, Option<&EnvironmentMapLight>,
&Tonemapping, &Tonemapping,
&ViewTransmissionTexture, Option<&ViewTransmissionTexture>,
)>, )>,
images: Res<RenderAssets<Image>>, images: Res<RenderAssets<Image>>,
mut fallback_images: FallbackImagesMsaa, mut fallback_images: FallbackImagesMsaa,
mut fallback_depths: FallbackImagesDepth, mut fallback_depths: FallbackImagesDepth,
fallback_image: Res<FallbackImage>,
fallback_cubemap: Res<FallbackImageCubemap>, fallback_cubemap: Res<FallbackImageCubemap>,
msaa: Res<Msaa>, msaa: Res<Msaa>,
globals_buffer: Res<GlobalsBuffer>, globals_buffer: Res<GlobalsBuffer>,
@ -1113,11 +1114,17 @@ pub fn queue_mesh_view_bind_groups(
entries.extend_from_slice(&[ entries.extend_from_slice(&[
BindGroupEntry { BindGroupEntry {
binding: 19, binding: 19,
resource: BindingResource::TextureView(&transmission.view), resource: BindingResource::TextureView(transmission.map_or_else(
|| &fallback_image.texture_view,
|transmission| &transmission.view,
)),
}, },
BindGroupEntry { BindGroupEntry {
binding: 20, binding: 20,
resource: BindingResource::Sampler(&transmission.sampler), resource: BindingResource::Sampler(&transmission.map_or_else(
|| &fallback_image.sampler,
|transmission| &transmission.sampler,
)),
}, },
]); ]);

View File

@ -89,6 +89,29 @@ impl<I: PhaseItem> RenderPhase<I> {
draw_function.draw(world, render_pass, view, item); draw_function.draw(world, render_pass, view, item);
} }
} }
/// Renders a range of the [`PhaseItem`]s using their corresponding draw functions.
pub fn render_range<'w>(
&self,
render_pass: &mut TrackedRenderPass<'w>,
world: &'w World,
view: Entity,
range: Range<usize>,
) {
let draw_functions = world.resource::<DrawFunctions<I>>();
let mut draw_functions = draw_functions.write();
draw_functions.prepare(world);
for item in self
.items
.iter()
.skip(range.start)
.take(range.end - range.start)
{
let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();
draw_function.draw(world, render_pass, view, item);
}
}
} }
impl<I: BatchedPhaseItem> RenderPhase<I> { impl<I: BatchedPhaseItem> RenderPhase<I> {