mirror of
https://github.com/bevyengine/bevy
synced 2025-03-02 06:17:18 +00:00
Add support for a configurable number of transmissive steps
This commit is contained in:
parent
17474123d6
commit
727a89dbe7
5 changed files with 143 additions and 42 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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(|| {
|
||||
|
|
|
@ -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,
|
||||
)),
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Add table
Reference in a new issue