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};
/// Configuration for the "main 3d render graph".
#[derive(Component, Reflect, Clone, Default, ExtractComponent)]
#[derive(Component, Reflect, Clone, ExtractComponent)]
#[extract_component_filter(With<Camera>)]
#[reflect(Component)]
pub struct Camera3d {
@ -23,6 +23,22 @@ pub struct Camera3d {
pub clear_color: ClearColorConfig,
/// The depth clear operation to perform for the main 3d pass.
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.

View file

@ -1,4 +1,4 @@
use super::ViewTransmissionTexture;
use super::{Camera3d, ViewTransmissionTexture};
use crate::core_3d::Transmissive3d;
use bevy_ecs::prelude::*;
use bevy_render::{
@ -13,15 +13,17 @@ use bevy_render::{
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
use std::ops::Range;
/// A [`Node`] that runs the [`Transmissive3d`] [`RenderPhase`].
pub struct MainTransmissivePass3dNode {
query: QueryState<
(
&'static ExtractedCamera,
&'static Camera3d,
&'static RenderPhase<Transmissive3d>,
&'static ViewTarget,
&'static ViewTransmissionTexture,
Option<&'static ViewTransmissionTexture>,
&'static ViewDepthTexture,
),
With<ExtractedView>,
@ -50,6 +52,7 @@ impl Node for MainTransmissivePass3dNode {
let view_entity = graph.view_entity();
let Ok((
camera,
camera_3d,
transmissive_phase,
target,
transmission,
@ -60,47 +63,94 @@ impl Node for MainTransmissivePass3dNode {
};
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 {
let render_pass_descriptor = 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,
}))],
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() {
render_pass.set_camera_viewport(viewport);
// 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();
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(())
}
}
/// 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>,
render_device: Res<RenderDevice>,
views_3d: Query<
(Entity, &ExtractedCamera, &ExtractedView),
(Entity, &ExtractedCamera, &Camera3d, &ExtractedView),
(
With<RenderPhase<Opaque3d>>,
With<RenderPhase<AlphaMask3d>>,
@ -384,11 +384,16 @@ pub fn prepare_core_3d_transmission_textures(
>,
) {
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 {
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
.entry(camera.target.clone())
.or_insert_with(|| {

View file

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