Add support for a configurable number of transmissive steps
This commit is contained in:
parent
17474123d6
commit
727a89dbe7
@ -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.
|
||||||
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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(|| {
|
||||||
|
@ -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,
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -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> {
|
||||||
|
Loading…
Reference in New Issue
Block a user