bevy_solari: RIS for Direct Lighting (#19620)
# Objective - Start the realtime direct lighting work for bevy solari ## Solution - Setup all the CPU-side code for the realtime lighting path (minus some parts for the temporal reuse I haven't written yet) - Implement RIS with 32 samples to choose a good random light - Don't sample a disk for the directional light, just treat it as a single point. This is faster and not much worse quality. ## Future - Spatiotemporal reuse (ReSTIR DI) - Denoiser (DLSS-RR) - Light tile optimization for faster light selection - Indirect lighting (ReSTIR GI) ## Testing - Run the solari example to see realtime - Run the solari example with `-- --pathtracer` to see the existing pathtracer --- ## Showcase 1 frame direct lighting:  Accumulated pathtracer output:  --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
61a5a37584
commit
0518eda2ad
@ -1284,9 +1284,9 @@ required-features = ["bevy_solari"]
|
|||||||
|
|
||||||
[package.metadata.example.solari]
|
[package.metadata.example.solari]
|
||||||
name = "Solari"
|
name = "Solari"
|
||||||
description = "Demonstrates realtime dynamic global illumination rendering using Bevy Solari."
|
description = "Demonstrates realtime dynamic raytraced lighting using Bevy Solari."
|
||||||
category = "3D Rendering"
|
category = "3D Rendering"
|
||||||
wasm = false
|
wasm = false # Raytracing is not supported on the web
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "spherical_area_lights"
|
name = "spherical_area_lights"
|
||||||
|
@ -148,7 +148,7 @@ pub struct PassSpanGuard<'a, R: ?Sized, P> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {
|
impl<R: RecordDiagnostics + ?Sized, P: Pass> PassSpanGuard<'_, R, P> {
|
||||||
/// End the span. You have to provide the same encoder which was used to begin the span.
|
/// End the span. You have to provide the same pass which was used to begin the span.
|
||||||
pub fn end(self, pass: &mut P) {
|
pub fn end(self, pass: &mut P) {
|
||||||
self.recorder.end_pass_span(pass);
|
self.recorder.end_pass_span(pass);
|
||||||
core::mem::forget(self);
|
core::mem::forget(self);
|
||||||
|
@ -15,6 +15,7 @@ bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
|||||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
|
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
||||||
@ -27,8 +28,9 @@ bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
|||||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
bytemuck = { version = "1" }
|
||||||
derive_more = { version = "1", default-features = false, features = ["from"] }
|
derive_more = { version = "1", default-features = false, features = ["from"] }
|
||||||
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
//!
|
//!
|
||||||
//! 
|
//! 
|
||||||
pub mod pathtracer;
|
pub mod pathtracer;
|
||||||
|
pub mod realtime;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
|
|
||||||
/// The solari prelude.
|
/// The solari prelude.
|
||||||
@ -13,28 +14,28 @@ pub mod scene;
|
|||||||
/// This includes the most common types in this crate, re-exported for your convenience.
|
/// This includes the most common types in this crate, re-exported for your convenience.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::SolariPlugin;
|
pub use super::SolariPlugin;
|
||||||
pub use crate::pathtracer::Pathtracer;
|
pub use crate::realtime::SolariLighting;
|
||||||
pub use crate::scene::RaytracingMesh3d;
|
pub use crate::scene::RaytracingMesh3d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::realtime::SolariLightingPlugin;
|
||||||
|
use crate::scene::RaytracingScenePlugin;
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_render::settings::WgpuFeatures;
|
use bevy_render::settings::WgpuFeatures;
|
||||||
use pathtracer::PathtracingPlugin;
|
|
||||||
use scene::RaytracingScenePlugin;
|
|
||||||
|
|
||||||
/// An experimental plugin for raytraced lighting.
|
/// An experimental plugin for raytraced lighting.
|
||||||
///
|
///
|
||||||
/// This plugin provides:
|
/// This plugin provides:
|
||||||
/// * (Coming soon) - Raytraced direct and indirect lighting.
|
/// * [`SolariLightingPlugin`] - Raytraced direct and indirect lighting (indirect lighting not yet implemented).
|
||||||
/// * [`RaytracingScenePlugin`] - BLAS building, resource and lighting binding.
|
/// * [`RaytracingScenePlugin`] - BLAS building, resource and lighting binding.
|
||||||
/// * [`PathtracingPlugin`] - A non-realtime pathtracer for validation purposes.
|
/// * [`pathtracer::PathtracingPlugin`] - A non-realtime pathtracer for validation purposes.
|
||||||
///
|
///
|
||||||
/// To get started, add `RaytracingMesh3d` and `MeshMaterial3d::<StandardMaterial>` to your entities.
|
/// To get started, add `RaytracingMesh3d` and `MeshMaterial3d::<StandardMaterial>` to your entities.
|
||||||
pub struct SolariPlugin;
|
pub struct SolariPlugin;
|
||||||
|
|
||||||
impl Plugin for SolariPlugin {
|
impl Plugin for SolariPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_plugins((RaytracingScenePlugin, PathtracingPlugin));
|
app.add_plugins((RaytracingScenePlugin, SolariLightingPlugin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
27
crates/bevy_solari/src/realtime/extract.rs
Normal file
27
crates/bevy_solari/src/realtime/extract.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use super::{prepare::SolariLightingResources, SolariLighting};
|
||||||
|
use bevy_ecs::system::{Commands, ResMut};
|
||||||
|
use bevy_pbr::deferred::SkipDeferredLighting;
|
||||||
|
use bevy_render::{camera::Camera, sync_world::RenderEntity, MainWorld};
|
||||||
|
|
||||||
|
pub fn extract_solari_lighting(mut main_world: ResMut<MainWorld>, mut commands: Commands) {
|
||||||
|
let mut cameras_3d = main_world.query::<(RenderEntity, &Camera, Option<&mut SolariLighting>)>();
|
||||||
|
|
||||||
|
for (entity, camera, mut solari_lighting) in cameras_3d.iter_mut(&mut main_world) {
|
||||||
|
let mut entity_commands = commands
|
||||||
|
.get_entity(entity)
|
||||||
|
.expect("Camera entity wasn't synced.");
|
||||||
|
if solari_lighting.is_some() && camera.is_active {
|
||||||
|
entity_commands.insert((
|
||||||
|
solari_lighting.as_deref().unwrap().clone(),
|
||||||
|
SkipDeferredLighting,
|
||||||
|
));
|
||||||
|
solari_lighting.as_mut().unwrap().reset = false;
|
||||||
|
} else {
|
||||||
|
entity_commands.remove::<(
|
||||||
|
SolariLighting,
|
||||||
|
SolariLightingResources,
|
||||||
|
SkipDeferredLighting,
|
||||||
|
)>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
crates/bevy_solari/src/realtime/mod.rs
Normal file
91
crates/bevy_solari/src/realtime/mod.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
mod extract;
|
||||||
|
mod node;
|
||||||
|
mod prepare;
|
||||||
|
|
||||||
|
use crate::SolariPlugin;
|
||||||
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_asset::embedded_asset;
|
||||||
|
use bevy_core_pipeline::{
|
||||||
|
core_3d::graph::{Core3d, Node3d},
|
||||||
|
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass},
|
||||||
|
};
|
||||||
|
use bevy_ecs::{component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs};
|
||||||
|
use bevy_pbr::DefaultOpaqueRendererMethod;
|
||||||
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
|
use bevy_render::{
|
||||||
|
load_shader_library,
|
||||||
|
render_graph::{RenderGraphApp, ViewNodeRunner},
|
||||||
|
renderer::RenderDevice,
|
||||||
|
view::Hdr,
|
||||||
|
ExtractSchedule, Render, RenderApp, RenderSystems,
|
||||||
|
};
|
||||||
|
use extract::extract_solari_lighting;
|
||||||
|
use node::SolariLightingNode;
|
||||||
|
use prepare::prepare_solari_lighting_resources;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
pub struct SolariLightingPlugin;
|
||||||
|
|
||||||
|
impl Plugin for SolariLightingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
embedded_asset!(app, "restir_di.wgsl");
|
||||||
|
load_shader_library!(app, "reservoir.wgsl");
|
||||||
|
|
||||||
|
app.register_type::<SolariLighting>()
|
||||||
|
.insert_resource(DefaultOpaqueRendererMethod::deferred());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(&self, app: &mut App) {
|
||||||
|
let render_app = app.sub_app_mut(RenderApp);
|
||||||
|
|
||||||
|
let render_device = render_app.world().resource::<RenderDevice>();
|
||||||
|
let features = render_device.features();
|
||||||
|
if !features.contains(SolariPlugin::required_wgpu_features()) {
|
||||||
|
warn!(
|
||||||
|
"SolariLightingPlugin not loaded. GPU lacks support for required features: {:?}.",
|
||||||
|
SolariPlugin::required_wgpu_features().difference(features)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
render_app
|
||||||
|
.add_systems(ExtractSchedule, extract_solari_lighting)
|
||||||
|
.add_systems(
|
||||||
|
Render,
|
||||||
|
prepare_solari_lighting_resources.in_set(RenderSystems::PrepareResources),
|
||||||
|
)
|
||||||
|
.add_render_graph_node::<ViewNodeRunner<SolariLightingNode>>(
|
||||||
|
Core3d,
|
||||||
|
node::graph::SolariLightingNode,
|
||||||
|
)
|
||||||
|
.add_render_graph_edges(
|
||||||
|
Core3d,
|
||||||
|
(Node3d::EndMainPass, node::graph::SolariLightingNode),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A component for a 3d camera entity to enable the Solari raytraced lighting system.
|
||||||
|
///
|
||||||
|
/// Must be used with `CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING)`, and
|
||||||
|
/// `Msaa::Off`.
|
||||||
|
#[derive(Component, Reflect, Clone)]
|
||||||
|
#[reflect(Component, Default, Clone)]
|
||||||
|
#[require(Hdr, DeferredPrepass, DepthPrepass, MotionVectorPrepass)]
|
||||||
|
pub struct SolariLighting {
|
||||||
|
/// Set to true to delete the saved temporal history (past frames).
|
||||||
|
///
|
||||||
|
/// Useful for preventing ghosting when the history is no longer
|
||||||
|
/// representative of the current frame, such as in sudden camera cuts.
|
||||||
|
///
|
||||||
|
/// After setting this to true, it will automatically be toggled
|
||||||
|
/// back to false at the end of the frame.
|
||||||
|
pub reset: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SolariLighting {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
reset: true, // No temporal history on the first frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
200
crates/bevy_solari/src/realtime/node.rs
Normal file
200
crates/bevy_solari/src/realtime/node.rs
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
use super::{prepare::SolariLightingResources, SolariLighting};
|
||||||
|
use crate::scene::RaytracingSceneBindings;
|
||||||
|
use bevy_asset::load_embedded_asset;
|
||||||
|
use bevy_core_pipeline::prepass::ViewPrepassTextures;
|
||||||
|
use bevy_diagnostic::FrameCount;
|
||||||
|
use bevy_ecs::{
|
||||||
|
query::QueryItem,
|
||||||
|
world::{FromWorld, World},
|
||||||
|
};
|
||||||
|
use bevy_render::{
|
||||||
|
camera::ExtractedCamera,
|
||||||
|
diagnostic::RecordDiagnostics,
|
||||||
|
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
|
||||||
|
render_resource::{
|
||||||
|
binding_types::{
|
||||||
|
storage_buffer_sized, texture_2d, texture_depth_2d, texture_storage_2d, uniform_buffer,
|
||||||
|
},
|
||||||
|
BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedComputePipelineId,
|
||||||
|
ComputePassDescriptor, ComputePipelineDescriptor, PipelineCache, PushConstantRange,
|
||||||
|
ShaderStages, StorageTextureAccess, TextureSampleType,
|
||||||
|
},
|
||||||
|
renderer::{RenderContext, RenderDevice},
|
||||||
|
view::{ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod graph {
|
||||||
|
use bevy_render::render_graph::RenderLabel;
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
|
||||||
|
pub struct SolariLightingNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SolariLightingNode {
|
||||||
|
bind_group_layout: BindGroupLayout,
|
||||||
|
initial_and_temporal_pipeline: CachedComputePipelineId,
|
||||||
|
spatial_and_shade_pipeline: CachedComputePipelineId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewNode for SolariLightingNode {
|
||||||
|
type ViewQuery = (
|
||||||
|
&'static SolariLighting,
|
||||||
|
&'static SolariLightingResources,
|
||||||
|
&'static ExtractedCamera,
|
||||||
|
&'static ViewTarget,
|
||||||
|
&'static ViewPrepassTextures,
|
||||||
|
&'static ViewUniformOffset,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_graph: &mut RenderGraphContext,
|
||||||
|
render_context: &mut RenderContext,
|
||||||
|
(
|
||||||
|
solari_lighting,
|
||||||
|
solari_lighting_resources,
|
||||||
|
camera,
|
||||||
|
view_target,
|
||||||
|
view_prepass_textures,
|
||||||
|
view_uniform_offset,
|
||||||
|
): QueryItem<Self::ViewQuery>,
|
||||||
|
world: &World,
|
||||||
|
) -> Result<(), NodeRunError> {
|
||||||
|
let pipeline_cache = world.resource::<PipelineCache>();
|
||||||
|
let scene_bindings = world.resource::<RaytracingSceneBindings>();
|
||||||
|
let view_uniforms = world.resource::<ViewUniforms>();
|
||||||
|
let frame_count = world.resource::<FrameCount>();
|
||||||
|
let (
|
||||||
|
Some(initial_and_temporal_pipeline),
|
||||||
|
Some(spatial_and_shade_pipeline),
|
||||||
|
Some(scene_bindings),
|
||||||
|
Some(viewport),
|
||||||
|
Some(gbuffer),
|
||||||
|
Some(depth_buffer),
|
||||||
|
Some(motion_vectors),
|
||||||
|
Some(view_uniforms),
|
||||||
|
) = (
|
||||||
|
pipeline_cache.get_compute_pipeline(self.initial_and_temporal_pipeline),
|
||||||
|
pipeline_cache.get_compute_pipeline(self.spatial_and_shade_pipeline),
|
||||||
|
&scene_bindings.bind_group,
|
||||||
|
camera.physical_viewport_size,
|
||||||
|
view_prepass_textures.deferred_view(),
|
||||||
|
view_prepass_textures.depth_view(),
|
||||||
|
view_prepass_textures.motion_vectors_view(),
|
||||||
|
view_uniforms.uniforms.binding(),
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let bind_group = render_context.render_device().create_bind_group(
|
||||||
|
"solari_lighting_bind_group",
|
||||||
|
&self.bind_group_layout,
|
||||||
|
&BindGroupEntries::sequential((
|
||||||
|
view_target.get_unsampled_color_attachment().view,
|
||||||
|
solari_lighting_resources.reservoirs_a.as_entire_binding(),
|
||||||
|
solari_lighting_resources.reservoirs_b.as_entire_binding(),
|
||||||
|
gbuffer,
|
||||||
|
depth_buffer,
|
||||||
|
motion_vectors,
|
||||||
|
view_uniforms,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Choice of number here is arbitrary
|
||||||
|
let frame_index = frame_count.0.wrapping_mul(5782582);
|
||||||
|
|
||||||
|
let diagnostics = render_context.diagnostic_recorder();
|
||||||
|
let command_encoder = render_context.command_encoder();
|
||||||
|
|
||||||
|
let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
|
||||||
|
label: Some("solari_lighting"),
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
let pass_span = diagnostics.pass_span(&mut pass, "solari_lighting");
|
||||||
|
|
||||||
|
pass.set_bind_group(0, scene_bindings, &[]);
|
||||||
|
pass.set_bind_group(1, &bind_group, &[view_uniform_offset.offset]);
|
||||||
|
|
||||||
|
pass.set_pipeline(initial_and_temporal_pipeline);
|
||||||
|
pass.set_push_constants(
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]),
|
||||||
|
);
|
||||||
|
pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1);
|
||||||
|
|
||||||
|
pass.set_pipeline(spatial_and_shade_pipeline);
|
||||||
|
pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1);
|
||||||
|
|
||||||
|
pass_span.end(&mut pass);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWorld for SolariLightingNode {
|
||||||
|
fn from_world(world: &mut World) -> Self {
|
||||||
|
let render_device = world.resource::<RenderDevice>();
|
||||||
|
let pipeline_cache = world.resource::<PipelineCache>();
|
||||||
|
let scene_bindings = world.resource::<RaytracingSceneBindings>();
|
||||||
|
|
||||||
|
let bind_group_layout = render_device.create_bind_group_layout(
|
||||||
|
"solari_lighting_bind_group_layout",
|
||||||
|
&BindGroupLayoutEntries::sequential(
|
||||||
|
ShaderStages::COMPUTE,
|
||||||
|
(
|
||||||
|
texture_storage_2d(
|
||||||
|
ViewTarget::TEXTURE_FORMAT_HDR,
|
||||||
|
StorageTextureAccess::WriteOnly,
|
||||||
|
),
|
||||||
|
storage_buffer_sized(false, None),
|
||||||
|
storage_buffer_sized(false, None),
|
||||||
|
texture_2d(TextureSampleType::Uint),
|
||||||
|
texture_depth_2d(),
|
||||||
|
texture_2d(TextureSampleType::Float { filterable: true }),
|
||||||
|
uniform_buffer::<ViewUniform>(true),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let initial_and_temporal_pipeline =
|
||||||
|
pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
|
||||||
|
label: Some("solari_lighting_initial_and_temporal_pipeline".into()),
|
||||||
|
layout: vec![
|
||||||
|
scene_bindings.bind_group_layout.clone(),
|
||||||
|
bind_group_layout.clone(),
|
||||||
|
],
|
||||||
|
push_constant_ranges: vec![PushConstantRange {
|
||||||
|
stages: ShaderStages::COMPUTE,
|
||||||
|
range: 0..8,
|
||||||
|
}],
|
||||||
|
shader: load_embedded_asset!(world, "restir_di.wgsl"),
|
||||||
|
shader_defs: vec![],
|
||||||
|
entry_point: "initial_and_temporal".into(),
|
||||||
|
zero_initialize_workgroup_memory: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let spatial_and_shade_pipeline =
|
||||||
|
pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
|
||||||
|
label: Some("solari_lighting_spatial_and_shade_pipeline".into()),
|
||||||
|
layout: vec![
|
||||||
|
scene_bindings.bind_group_layout.clone(),
|
||||||
|
bind_group_layout.clone(),
|
||||||
|
],
|
||||||
|
push_constant_ranges: vec![PushConstantRange {
|
||||||
|
stages: ShaderStages::COMPUTE,
|
||||||
|
range: 0..8,
|
||||||
|
}],
|
||||||
|
shader: load_embedded_asset!(world, "restir_di.wgsl"),
|
||||||
|
shader_defs: vec![],
|
||||||
|
entry_point: "spatial_and_shade".into(),
|
||||||
|
zero_initialize_workgroup_memory: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
bind_group_layout,
|
||||||
|
initial_and_temporal_pipeline,
|
||||||
|
spatial_and_shade_pipeline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
crates/bevy_solari/src/realtime/prepare.rs
Normal file
65
crates/bevy_solari/src/realtime/prepare.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
use super::SolariLighting;
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
|
query::With,
|
||||||
|
system::{Commands, Query, Res},
|
||||||
|
};
|
||||||
|
use bevy_math::UVec2;
|
||||||
|
use bevy_render::{
|
||||||
|
camera::ExtractedCamera,
|
||||||
|
render_resource::{Buffer, BufferDescriptor, BufferUsages},
|
||||||
|
renderer::RenderDevice,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Size of a Reservoir shader struct in bytes.
|
||||||
|
const RESERVOIR_STRUCT_SIZE: u64 = 32;
|
||||||
|
|
||||||
|
/// Internal rendering resources used for Solari lighting.
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SolariLightingResources {
|
||||||
|
pub reservoirs_a: Buffer,
|
||||||
|
pub reservoirs_b: Buffer,
|
||||||
|
pub view_size: UVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_solari_lighting_resources(
|
||||||
|
query: Query<
|
||||||
|
(Entity, &ExtractedCamera, Option<&SolariLightingResources>),
|
||||||
|
With<SolariLighting>,
|
||||||
|
>,
|
||||||
|
render_device: Res<RenderDevice>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for (entity, camera, solari_lighting_resources) in &query {
|
||||||
|
let Some(view_size) = camera.physical_viewport_size else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if solari_lighting_resources.map(|r| r.view_size) == Some(view_size) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = (view_size.x * view_size.y) as u64 * RESERVOIR_STRUCT_SIZE;
|
||||||
|
|
||||||
|
let reservoirs_a = render_device.create_buffer(&BufferDescriptor {
|
||||||
|
label: Some("solari_lighting_reservoirs_a"),
|
||||||
|
size,
|
||||||
|
usage: BufferUsages::STORAGE,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let reservoirs_b = render_device.create_buffer(&BufferDescriptor {
|
||||||
|
label: Some("solari_lighting_reservoirs_b"),
|
||||||
|
size,
|
||||||
|
usage: BufferUsages::STORAGE,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.entity(entity).insert(SolariLightingResources {
|
||||||
|
reservoirs_a,
|
||||||
|
reservoirs_b,
|
||||||
|
view_size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
30
crates/bevy_solari/src/realtime/reservoir.wgsl
Normal file
30
crates/bevy_solari/src/realtime/reservoir.wgsl
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// https://intro-to-restir.cwyman.org/presentations/2023ReSTIR_Course_Notes.pdf
|
||||||
|
|
||||||
|
#define_import_path bevy_solari::reservoir
|
||||||
|
|
||||||
|
#import bevy_solari::sampling::LightSample
|
||||||
|
|
||||||
|
const NULL_RESERVOIR_SAMPLE = 0xFFFFFFFFu;
|
||||||
|
|
||||||
|
// Don't adjust the size of this struct without also adjusting RESERVOIR_STRUCT_SIZE.
|
||||||
|
struct Reservoir {
|
||||||
|
sample: LightSample,
|
||||||
|
weight_sum: f32,
|
||||||
|
confidence_weight: f32,
|
||||||
|
unbiased_contribution_weight: f32,
|
||||||
|
_padding: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_reservoir() -> Reservoir {
|
||||||
|
return Reservoir(
|
||||||
|
LightSample(vec2(NULL_RESERVOIR_SAMPLE, 0u), vec2(0.0)),
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reservoir_valid(reservoir: Reservoir) -> bool {
|
||||||
|
return reservoir.sample.light_id.x != NULL_RESERVOIR_SAMPLE;
|
||||||
|
}
|
117
crates/bevy_solari/src/realtime/restir_di.wgsl
Normal file
117
crates/bevy_solari/src/realtime/restir_di.wgsl
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#import bevy_core_pipeline::tonemapping::tonemapping_luminance as luminance
|
||||||
|
#import bevy_pbr::pbr_deferred_types::unpack_24bit_normal
|
||||||
|
#import bevy_pbr::rgb9e5::rgb9e5_to_vec3_
|
||||||
|
#import bevy_pbr::utils::{rand_f, octahedral_decode}
|
||||||
|
#import bevy_render::maths::PI
|
||||||
|
#import bevy_render::view::View
|
||||||
|
#import bevy_solari::reservoir::{Reservoir, empty_reservoir, reservoir_valid}
|
||||||
|
#import bevy_solari::sampling::{generate_random_light_sample, calculate_light_contribution, trace_light_visibility}
|
||||||
|
|
||||||
|
@group(1) @binding(0) var view_output: texture_storage_2d<rgba16float, write>;
|
||||||
|
@group(1) @binding(1) var<storage, read_write> reservoirs_a: array<Reservoir>;
|
||||||
|
@group(1) @binding(2) var<storage, read_write> reservoirs_b: array<Reservoir>;
|
||||||
|
@group(1) @binding(3) var gbuffer: texture_2d<u32>;
|
||||||
|
@group(1) @binding(4) var depth_buffer: texture_depth_2d;
|
||||||
|
@group(1) @binding(5) var motion_vectors: texture_2d<f32>;
|
||||||
|
@group(1) @binding(6) var<uniform> view: View;
|
||||||
|
struct PushConstants { frame_index: u32, reset: u32 }
|
||||||
|
var<push_constant> constants: PushConstants;
|
||||||
|
|
||||||
|
const INITIAL_SAMPLES = 32u;
|
||||||
|
const SPATIAL_REUSE_RADIUS_PIXELS = 30.0;
|
||||||
|
const CONFIDENCE_WEIGHT_CAP = 20.0 * f32(INITIAL_SAMPLES);
|
||||||
|
|
||||||
|
@compute @workgroup_size(8, 8, 1)
|
||||||
|
fn initial_and_temporal(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
||||||
|
if any(global_id.xy >= vec2u(view.viewport.zw)) { return; }
|
||||||
|
|
||||||
|
let pixel_index = global_id.x + global_id.y * u32(view.viewport.z);
|
||||||
|
var rng = pixel_index + constants.frame_index;
|
||||||
|
|
||||||
|
let depth = textureLoad(depth_buffer, global_id.xy, 0);
|
||||||
|
if depth == 0.0 {
|
||||||
|
reservoirs_b[pixel_index] = empty_reservoir();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let gpixel = textureLoad(gbuffer, global_id.xy, 0);
|
||||||
|
let world_position = reconstruct_world_position(global_id.xy, depth);
|
||||||
|
let world_normal = octahedral_decode(unpack_24bit_normal(gpixel.a));
|
||||||
|
let base_color = pow(unpack4x8unorm(gpixel.r).rgb, vec3(2.2));
|
||||||
|
let diffuse_brdf = base_color / PI;
|
||||||
|
|
||||||
|
let initial_reservoir = generate_initial_reservoir(world_position, world_normal, diffuse_brdf, &rng);
|
||||||
|
|
||||||
|
reservoirs_b[pixel_index] = initial_reservoir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@compute @workgroup_size(8, 8, 1)
|
||||||
|
fn spatial_and_shade(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
||||||
|
if any(global_id.xy >= vec2u(view.viewport.zw)) { return; }
|
||||||
|
|
||||||
|
let pixel_index = global_id.x + global_id.y * u32(view.viewport.z);
|
||||||
|
var rng = pixel_index + constants.frame_index;
|
||||||
|
|
||||||
|
let depth = textureLoad(depth_buffer, global_id.xy, 0);
|
||||||
|
if depth == 0.0 {
|
||||||
|
reservoirs_a[pixel_index] = empty_reservoir();
|
||||||
|
textureStore(view_output, global_id.xy, vec4(vec3(0.0), 1.0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let gpixel = textureLoad(gbuffer, global_id.xy, 0);
|
||||||
|
let world_position = reconstruct_world_position(global_id.xy, depth);
|
||||||
|
let world_normal = octahedral_decode(unpack_24bit_normal(gpixel.a));
|
||||||
|
let base_color = pow(unpack4x8unorm(gpixel.r).rgb, vec3(2.2));
|
||||||
|
let diffuse_brdf = base_color / PI;
|
||||||
|
let emissive = rgb9e5_to_vec3_(gpixel.g);
|
||||||
|
|
||||||
|
let input_reservoir = reservoirs_b[pixel_index];
|
||||||
|
|
||||||
|
var radiance = vec3(0.0);
|
||||||
|
if reservoir_valid(input_reservoir) {
|
||||||
|
radiance = calculate_light_contribution(input_reservoir.sample, world_position, world_normal).radiance;
|
||||||
|
}
|
||||||
|
|
||||||
|
reservoirs_a[pixel_index] = input_reservoir;
|
||||||
|
|
||||||
|
var pixel_color = radiance * input_reservoir.unbiased_contribution_weight;
|
||||||
|
pixel_color *= view.exposure;
|
||||||
|
pixel_color *= diffuse_brdf;
|
||||||
|
pixel_color += emissive;
|
||||||
|
textureStore(view_output, global_id.xy, vec4(pixel_color, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_initial_reservoir(world_position: vec3<f32>, world_normal: vec3<f32>, diffuse_brdf: vec3<f32>, rng: ptr<function, u32>) -> Reservoir{
|
||||||
|
var reservoir = empty_reservoir();
|
||||||
|
var reservoir_target_function = 0.0;
|
||||||
|
for (var i = 0u; i < INITIAL_SAMPLES; i++) {
|
||||||
|
let light_sample = generate_random_light_sample(rng);
|
||||||
|
|
||||||
|
let mis_weight = 1.0 / f32(INITIAL_SAMPLES);
|
||||||
|
let light_contribution = calculate_light_contribution(light_sample, world_position, world_normal);
|
||||||
|
let target_function = luminance(light_contribution.radiance * diffuse_brdf);
|
||||||
|
let resampling_weight = mis_weight * (target_function * light_contribution.inverse_pdf);
|
||||||
|
|
||||||
|
reservoir.weight_sum += resampling_weight;
|
||||||
|
|
||||||
|
if rand_f(rng) < resampling_weight / reservoir.weight_sum {
|
||||||
|
reservoir.sample = light_sample;
|
||||||
|
reservoir_target_function = target_function;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if reservoir_valid(reservoir) {
|
||||||
|
let inverse_target_function = select(0.0, 1.0 / reservoir_target_function, reservoir_target_function > 0.0);
|
||||||
|
reservoir.unbiased_contribution_weight = reservoir.weight_sum * inverse_target_function;
|
||||||
|
reservoir.unbiased_contribution_weight *= trace_light_visibility(reservoir.sample, world_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
reservoir.confidence_weight = f32(INITIAL_SAMPLES);
|
||||||
|
return reservoir;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reconstruct_world_position(pixel_id: vec2<u32>, depth: f32) -> vec3<f32> {
|
||||||
|
let uv = (vec2<f32>(pixel_id) + 0.5) / view.viewport.zw;
|
||||||
|
let xy_ndc = (uv - vec2(0.5)) * vec2(2.0, -2.0);
|
||||||
|
let world_pos = view.world_from_clip * vec4(xy_ndc, depth, 1.0);
|
||||||
|
return world_pos.xyz / world_pos.w;
|
||||||
|
}
|
@ -71,7 +71,7 @@ struct DirectionalLight {
|
|||||||
@group(0) @binding(9) var<storage> light_sources: array<LightSource>;
|
@group(0) @binding(9) var<storage> light_sources: array<LightSource>;
|
||||||
@group(0) @binding(10) var<storage> directional_lights: array<DirectionalLight>;
|
@group(0) @binding(10) var<storage> directional_lights: array<DirectionalLight>;
|
||||||
|
|
||||||
const RAY_T_MIN = 0.0001;
|
const RAY_T_MIN = 0.01;
|
||||||
const RAY_T_MAX = 100000.0;
|
const RAY_T_MAX = 100000.0;
|
||||||
|
|
||||||
const RAY_NO_CULL = 0xFFu;
|
const RAY_NO_CULL = 0xFFu;
|
||||||
|
@ -69,6 +69,7 @@ fn calculate_light_contribution(light_sample: LightSample, ray_origin: vec3<f32>
|
|||||||
fn calculate_directional_light_contribution(light_sample: LightSample, directional_light_id: u32, origin_world_normal: vec3<f32>) -> LightContribution {
|
fn calculate_directional_light_contribution(light_sample: LightSample, directional_light_id: u32, origin_world_normal: vec3<f32>) -> LightContribution {
|
||||||
let directional_light = directional_lights[directional_light_id];
|
let directional_light = directional_lights[directional_light_id];
|
||||||
|
|
||||||
|
#ifdef DIRECTIONAL_LIGHT_SOFT_SHADOWS
|
||||||
// Sample a random direction within a cone whose base is the sun approximated as a disk
|
// Sample a random direction within a cone whose base is the sun approximated as a disk
|
||||||
// https://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.9.pdf#0004286901.INDD%3ASec30%3A305
|
// https://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.9.pdf#0004286901.INDD%3ASec30%3A305
|
||||||
let cos_theta = (1.0 - light_sample.random.x) + light_sample.random.x * directional_light.cos_theta_max;
|
let cos_theta = (1.0 - light_sample.random.x) + light_sample.random.x * directional_light.cos_theta_max;
|
||||||
@ -80,6 +81,9 @@ fn calculate_directional_light_contribution(light_sample: LightSample, direction
|
|||||||
|
|
||||||
// Rotate the ray so that the cone it was sampled from is aligned with the light direction
|
// Rotate the ray so that the cone it was sampled from is aligned with the light direction
|
||||||
ray_direction = build_orthonormal_basis(directional_light.direction_to_light) * ray_direction;
|
ray_direction = build_orthonormal_basis(directional_light.direction_to_light) * ray_direction;
|
||||||
|
#else
|
||||||
|
let ray_direction = directional_light.direction_to_light;
|
||||||
|
#endif
|
||||||
|
|
||||||
let cos_theta_origin = saturate(dot(ray_direction, origin_world_normal));
|
let cos_theta_origin = saturate(dot(ray_direction, origin_world_normal));
|
||||||
let radiance = directional_light.luminance * cos_theta_origin;
|
let radiance = directional_light.luminance * cos_theta_origin;
|
||||||
@ -119,6 +123,7 @@ fn trace_light_visibility(light_sample: LightSample, ray_origin: vec3<f32>) -> f
|
|||||||
fn trace_directional_light_visibility(light_sample: LightSample, directional_light_id: u32, ray_origin: vec3<f32>) -> f32 {
|
fn trace_directional_light_visibility(light_sample: LightSample, directional_light_id: u32, ray_origin: vec3<f32>) -> f32 {
|
||||||
let directional_light = directional_lights[directional_light_id];
|
let directional_light = directional_lights[directional_light_id];
|
||||||
|
|
||||||
|
#ifdef DIRECTIONAL_LIGHT_SOFT_SHADOWS
|
||||||
// Sample a random direction within a cone whose base is the sun approximated as a disk
|
// Sample a random direction within a cone whose base is the sun approximated as a disk
|
||||||
// https://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.9.pdf#0004286901.INDD%3ASec30%3A305
|
// https://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.9.pdf#0004286901.INDD%3ASec30%3A305
|
||||||
let cos_theta = (1.0 - light_sample.random.x) + light_sample.random.x * directional_light.cos_theta_max;
|
let cos_theta = (1.0 - light_sample.random.x) + light_sample.random.x * directional_light.cos_theta_max;
|
||||||
@ -130,6 +135,9 @@ fn trace_directional_light_visibility(light_sample: LightSample, directional_lig
|
|||||||
|
|
||||||
// Rotate the ray so that the cone it was sampled from is aligned with the light direction
|
// Rotate the ray so that the cone it was sampled from is aligned with the light direction
|
||||||
ray_direction = build_orthonormal_basis(directional_light.direction_to_light) * ray_direction;
|
ray_direction = build_orthonormal_basis(directional_light.direction_to_light) * ray_direction;
|
||||||
|
#else
|
||||||
|
let ray_direction = directional_light.direction_to_light;
|
||||||
|
#endif
|
||||||
|
|
||||||
let ray_hit = trace_ray(ray_origin, ray_direction, RAY_T_MIN, RAY_T_MAX, RAY_FLAG_TERMINATE_ON_FIRST_HIT);
|
let ray_hit = trace_ray(ray_origin, ray_direction, RAY_T_MIN, RAY_T_MAX, RAY_FLAG_TERMINATE_ON_FIRST_HIT);
|
||||||
return f32(ray_hit.kind == RAY_QUERY_INTERSECTION_NONE);
|
return f32(ray_hit.kind == RAY_QUERY_INTERSECTION_NONE);
|
||||||
|
@ -1,28 +1,45 @@
|
|||||||
//! Demonstrates realtime dynamic global illumination rendering using Bevy Solari.
|
//! Demonstrates realtime dynamic raytraced lighting using Bevy Solari.
|
||||||
|
|
||||||
#[path = "../helpers/camera_controller.rs"]
|
#[path = "../helpers/camera_controller.rs"]
|
||||||
mod camera_controller;
|
mod camera_controller;
|
||||||
|
|
||||||
|
use argh::FromArgs;
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
render::{camera::CameraMainTextureUsages, mesh::Indices, render_resource::TextureUsages},
|
render::{camera::CameraMainTextureUsages, mesh::Indices, render_resource::TextureUsages},
|
||||||
scene::SceneInstanceReady,
|
scene::SceneInstanceReady,
|
||||||
solari::{
|
solari::{
|
||||||
pathtracer::Pathtracer,
|
pathtracer::{Pathtracer, PathtracingPlugin},
|
||||||
prelude::{RaytracingMesh3d, SolariPlugin},
|
prelude::{RaytracingMesh3d, SolariLighting, SolariPlugin},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use camera_controller::{CameraController, CameraControllerPlugin};
|
use camera_controller::{CameraController, CameraControllerPlugin};
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
fn main() {
|
/// `bevy_solari` demo.
|
||||||
App::new()
|
#[derive(FromArgs, Resource, Clone, Copy)]
|
||||||
.add_plugins((DefaultPlugins, SolariPlugin, CameraControllerPlugin))
|
struct Args {
|
||||||
.add_systems(Startup, setup)
|
/// use the reference pathtracer instead of the realtime lighting system.
|
||||||
.run();
|
#[argh(switch)]
|
||||||
|
pathtracer: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
fn main() {
|
||||||
|
let args: Args = argh::from_env();
|
||||||
|
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins((DefaultPlugins, SolariPlugin, CameraControllerPlugin))
|
||||||
|
.insert_resource(args)
|
||||||
|
.add_systems(Startup, setup);
|
||||||
|
|
||||||
|
if args.pathtracer == Some(true) {
|
||||||
|
app.add_plugins(PathtracingPlugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
|
||||||
commands
|
commands
|
||||||
.spawn(SceneRoot(asset_server.load(
|
.spawn(SceneRoot(asset_server.load(
|
||||||
GltfAssetLabel::Scene(0).from_asset("models/CornellBox/CornellBox.glb"),
|
GltfAssetLabel::Scene(0).from_asset("models/CornellBox/CornellBox.glb"),
|
||||||
@ -32,13 +49,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
commands.spawn((
|
commands.spawn((
|
||||||
DirectionalLight {
|
DirectionalLight {
|
||||||
illuminance: light_consts::lux::FULL_DAYLIGHT,
|
illuminance: light_consts::lux::FULL_DAYLIGHT,
|
||||||
shadows_enabled: true,
|
shadows_enabled: false, // Solari replaces shadow mapping
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, PI * -0.43, PI * -0.08, 0.0)),
|
Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, PI * -0.43, PI * -0.08, 0.0)),
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.spawn((
|
let mut camera = commands.spawn((
|
||||||
Camera3d::default(),
|
Camera3d::default(),
|
||||||
Camera {
|
Camera {
|
||||||
clear_color: ClearColorConfig::Custom(Color::BLACK),
|
clear_color: ClearColorConfig::Custom(Color::BLACK),
|
||||||
@ -49,10 +66,16 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||||||
run_speed: 1500.0,
|
run_speed: 1500.0,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Pathtracer::default(),
|
|
||||||
CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING),
|
|
||||||
Transform::from_xyz(-278.0, 273.0, 800.0),
|
Transform::from_xyz(-278.0, 273.0, 800.0),
|
||||||
|
// Msaa::Off and CameraMainTextureUsages with STORAGE_BINDING are required for Solari
|
||||||
|
CameraMainTextureUsages::default().with(TextureUsages::STORAGE_BINDING),
|
||||||
|
Msaa::Off,
|
||||||
));
|
));
|
||||||
|
if args.pathtracer == Some(true) {
|
||||||
|
camera.insert(Pathtracer::default());
|
||||||
|
} else {
|
||||||
|
camera.insert(SolariLighting::default());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_raytracing_meshes_on_scene_load(
|
fn add_raytracing_meshes_on_scene_load(
|
||||||
@ -60,11 +83,14 @@ fn add_raytracing_meshes_on_scene_load(
|
|||||||
children: Query<&Children>,
|
children: Query<&Children>,
|
||||||
mesh: Query<&Mesh3d>,
|
mesh: Query<&Mesh3d>,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
args: Res<Args>,
|
||||||
) {
|
) {
|
||||||
// Ensure meshes are bery_solari compatible
|
// Ensure meshes are bevy_solari compatible
|
||||||
for (_, mesh) in meshes.iter_mut() {
|
for (_, mesh) in meshes.iter_mut() {
|
||||||
mesh.remove_attribute(Mesh::ATTRIBUTE_UV_1.id);
|
mesh.remove_attribute(Mesh::ATTRIBUTE_UV_1.id);
|
||||||
|
mesh.remove_attribute(Mesh::ATTRIBUTE_COLOR.id);
|
||||||
mesh.generate_tangents().unwrap();
|
mesh.generate_tangents().unwrap();
|
||||||
|
|
||||||
if let Some(indices) = mesh.indices_mut() {
|
if let Some(indices) = mesh.indices_mut() {
|
||||||
@ -74,12 +100,21 @@ fn add_raytracing_meshes_on_scene_load(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add raytracing mesh handles
|
||||||
for descendant in children.iter_descendants(trigger.target()) {
|
for descendant in children.iter_descendants(trigger.target()) {
|
||||||
if let Ok(mesh) = mesh.get(descendant) {
|
if let Ok(mesh) = mesh.get(descendant) {
|
||||||
commands
|
commands
|
||||||
.entity(descendant)
|
.entity(descendant)
|
||||||
.insert(RaytracingMesh3d(mesh.0.clone()))
|
.insert(RaytracingMesh3d(mesh.0.clone()));
|
||||||
.remove::<Mesh3d>();
|
|
||||||
|
if args.pathtracer == Some(true) {
|
||||||
|
commands.entity(descendant).remove::<Mesh3d>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Increase material emissive intensity to make it prettier for the example
|
||||||
|
for (_, material) in materials.iter_mut() {
|
||||||
|
material.emissive *= 200.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ Example | Description
|
|||||||
[Shadow Biases](../examples/3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
|
[Shadow Biases](../examples/3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
|
||||||
[Shadow Caster and Receiver](../examples/3d/shadow_caster_receiver.rs) | Demonstrates how to prevent meshes from casting/receiving shadows in a 3d scene
|
[Shadow Caster and Receiver](../examples/3d/shadow_caster_receiver.rs) | Demonstrates how to prevent meshes from casting/receiving shadows in a 3d scene
|
||||||
[Skybox](../examples/3d/skybox.rs) | Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats.
|
[Skybox](../examples/3d/skybox.rs) | Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats.
|
||||||
[Solari](../examples/3d/solari.rs) | Demonstrates realtime dynamic global illumination rendering using Bevy Solari.
|
[Solari](../examples/3d/solari.rs) | Demonstrates realtime dynamic raytraced lighting using Bevy Solari.
|
||||||
[Specular Tint](../examples/3d/specular_tint.rs) | Demonstrates specular tints and maps
|
[Specular Tint](../examples/3d/specular_tint.rs) | Demonstrates specular tints and maps
|
||||||
[Spherical Area Lights](../examples/3d/spherical_area_lights.rs) | Demonstrates how point light radius values affect light behavior
|
[Spherical Area Lights](../examples/3d/spherical_area_lights.rs) | Demonstrates how point light radius values affect light behavior
|
||||||
[Split Screen](../examples/3d/split_screen.rs) | Demonstrates how to render two cameras to the same window to accomplish "split screen"
|
[Split Screen](../examples/3d/split_screen.rs) | Demonstrates how to render two cameras to the same window to accomplish "split screen"
|
||||||
|
@ -13,6 +13,7 @@ LOD = "LOD" # Level of detail
|
|||||||
reparametrization = "reparametrization" # Mathematical term in curve context (reparameterize)
|
reparametrization = "reparametrization" # Mathematical term in curve context (reparameterize)
|
||||||
reparametrize = "reparametrize"
|
reparametrize = "reparametrize"
|
||||||
reparametrized = "reparametrized"
|
reparametrized = "reparametrized"
|
||||||
|
mis = "mis" # mis - multiple importance sampling
|
||||||
|
|
||||||
# Match a Whole Word - Case Sensitive
|
# Match a Whole Word - Case Sensitive
|
||||||
[default.extend-identifiers]
|
[default.extend-identifiers]
|
||||||
|
Loading…
Reference in New Issue
Block a user