Implement procedural atmospheric scattering from [Sebastien Hillaire's 2020 paper](https://sebh.github.io/publications/egsr2020.pdf). This approach should scale well even down to mobile hardware, and is physically accurate. ## Co-author: @mate-h He helped massively with getting this over the finish line, ensuring everything was physically correct, correcting several places where I had misunderstood or misapplied the paper, and improving the performance in several places as well. Thanks! ## Credits @aevyrie: helped find numerous bugs and improve the example to best show off this feature :) Built off of @mtsr's original branch, which handled the transmittance lut (arguably the most important part) ## Showcase:   ## For followup - Integrate with pcwalton's volumetrics code - refactor/reorganize for better integration with other effects - have atmosphere transmittance affect directional lights - add support for generating skybox/environment map --------- Co-authored-by: Emerson Coskey <56370779+EmersonCoskey@users.noreply.github.com> Co-authored-by: atlv <email@atlasdostal.com> Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com> Co-authored-by: Emerson Coskey <coskey@emerlabs.net> Co-authored-by: Máté Homolya <mate.homolya@gmail.com>
222 lines
6.9 KiB
Rust
222 lines
6.9 KiB
Rust
use bevy_ecs::{query::QueryItem, system::lifetimeless::Read, world::World};
|
|
use bevy_math::{UVec2, Vec3Swizzles};
|
|
use bevy_render::{
|
|
extract_component::DynamicUniformIndex,
|
|
render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode},
|
|
render_resource::{ComputePass, ComputePassDescriptor, PipelineCache, RenderPassDescriptor},
|
|
renderer::RenderContext,
|
|
view::{ViewTarget, ViewUniformOffset},
|
|
};
|
|
|
|
use crate::ViewLightsUniformOffset;
|
|
|
|
use super::{
|
|
resources::{
|
|
AtmosphereBindGroups, AtmosphereLutPipelines, AtmosphereTransformsOffset,
|
|
RenderSkyPipelineId,
|
|
},
|
|
Atmosphere, AtmosphereSettings,
|
|
};
|
|
|
|
#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash, RenderLabel)]
|
|
pub enum AtmosphereNode {
|
|
RenderLuts,
|
|
RenderSky,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub(super) struct AtmosphereLutsNode {}
|
|
|
|
impl ViewNode for AtmosphereLutsNode {
|
|
type ViewQuery = (
|
|
Read<AtmosphereSettings>,
|
|
Read<AtmosphereBindGroups>,
|
|
Read<DynamicUniformIndex<Atmosphere>>,
|
|
Read<DynamicUniformIndex<AtmosphereSettings>>,
|
|
Read<AtmosphereTransformsOffset>,
|
|
Read<ViewUniformOffset>,
|
|
Read<ViewLightsUniformOffset>,
|
|
);
|
|
|
|
fn run(
|
|
&self,
|
|
_graph: &mut RenderGraphContext,
|
|
render_context: &mut RenderContext,
|
|
(
|
|
settings,
|
|
bind_groups,
|
|
atmosphere_uniforms_offset,
|
|
settings_uniforms_offset,
|
|
atmosphere_transforms_offset,
|
|
view_uniforms_offset,
|
|
lights_uniforms_offset,
|
|
): QueryItem<Self::ViewQuery>,
|
|
world: &World,
|
|
) -> Result<(), NodeRunError> {
|
|
let pipelines = world.resource::<AtmosphereLutPipelines>();
|
|
let pipeline_cache = world.resource::<PipelineCache>();
|
|
let (
|
|
Some(transmittance_lut_pipeline),
|
|
Some(multiscattering_lut_pipeline),
|
|
Some(sky_view_lut_pipeline),
|
|
Some(aerial_view_lut_pipeline),
|
|
) = (
|
|
pipeline_cache.get_compute_pipeline(pipelines.transmittance_lut),
|
|
pipeline_cache.get_compute_pipeline(pipelines.multiscattering_lut),
|
|
pipeline_cache.get_compute_pipeline(pipelines.sky_view_lut),
|
|
pipeline_cache.get_compute_pipeline(pipelines.aerial_view_lut),
|
|
)
|
|
else {
|
|
return Ok(());
|
|
};
|
|
|
|
let command_encoder = render_context.command_encoder();
|
|
|
|
let mut luts_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
|
|
label: Some("atmosphere_luts_pass"),
|
|
timestamp_writes: None,
|
|
});
|
|
|
|
fn dispatch_2d(compute_pass: &mut ComputePass, size: UVec2) {
|
|
const WORKGROUP_SIZE: u32 = 16;
|
|
let workgroups_x = size.x.div_ceil(WORKGROUP_SIZE);
|
|
let workgroups_y = size.y.div_ceil(WORKGROUP_SIZE);
|
|
compute_pass.dispatch_workgroups(workgroups_x, workgroups_y, 1);
|
|
}
|
|
|
|
// Transmittance LUT
|
|
|
|
luts_pass.set_pipeline(transmittance_lut_pipeline);
|
|
luts_pass.set_bind_group(
|
|
0,
|
|
&bind_groups.transmittance_lut,
|
|
&[
|
|
atmosphere_uniforms_offset.index(),
|
|
settings_uniforms_offset.index(),
|
|
],
|
|
);
|
|
|
|
dispatch_2d(&mut luts_pass, settings.transmittance_lut_size);
|
|
|
|
// Multiscattering LUT
|
|
|
|
luts_pass.set_pipeline(multiscattering_lut_pipeline);
|
|
luts_pass.set_bind_group(
|
|
0,
|
|
&bind_groups.multiscattering_lut,
|
|
&[
|
|
atmosphere_uniforms_offset.index(),
|
|
settings_uniforms_offset.index(),
|
|
],
|
|
);
|
|
|
|
luts_pass.dispatch_workgroups(
|
|
settings.multiscattering_lut_size.x,
|
|
settings.multiscattering_lut_size.y,
|
|
1,
|
|
);
|
|
|
|
// Sky View LUT
|
|
|
|
luts_pass.set_pipeline(sky_view_lut_pipeline);
|
|
luts_pass.set_bind_group(
|
|
0,
|
|
&bind_groups.sky_view_lut,
|
|
&[
|
|
atmosphere_uniforms_offset.index(),
|
|
settings_uniforms_offset.index(),
|
|
atmosphere_transforms_offset.index(),
|
|
view_uniforms_offset.offset,
|
|
lights_uniforms_offset.offset,
|
|
],
|
|
);
|
|
|
|
dispatch_2d(&mut luts_pass, settings.sky_view_lut_size);
|
|
|
|
// Aerial View LUT
|
|
|
|
luts_pass.set_pipeline(aerial_view_lut_pipeline);
|
|
luts_pass.set_bind_group(
|
|
0,
|
|
&bind_groups.aerial_view_lut,
|
|
&[
|
|
atmosphere_uniforms_offset.index(),
|
|
settings_uniforms_offset.index(),
|
|
view_uniforms_offset.offset,
|
|
lights_uniforms_offset.offset,
|
|
],
|
|
);
|
|
|
|
dispatch_2d(&mut luts_pass, settings.aerial_view_lut_size.xy());
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub(super) struct RenderSkyNode;
|
|
|
|
impl ViewNode for RenderSkyNode {
|
|
type ViewQuery = (
|
|
Read<AtmosphereBindGroups>,
|
|
Read<ViewTarget>,
|
|
Read<DynamicUniformIndex<Atmosphere>>,
|
|
Read<DynamicUniformIndex<AtmosphereSettings>>,
|
|
Read<AtmosphereTransformsOffset>,
|
|
Read<ViewUniformOffset>,
|
|
Read<ViewLightsUniformOffset>,
|
|
Read<RenderSkyPipelineId>,
|
|
);
|
|
|
|
fn run<'w>(
|
|
&self,
|
|
_graph: &mut RenderGraphContext,
|
|
render_context: &mut RenderContext<'w>,
|
|
(
|
|
atmosphere_bind_groups,
|
|
view_target,
|
|
atmosphere_uniforms_offset,
|
|
settings_uniforms_offset,
|
|
atmosphere_transforms_offset,
|
|
view_uniforms_offset,
|
|
lights_uniforms_offset,
|
|
render_sky_pipeline_id,
|
|
): QueryItem<'w, Self::ViewQuery>,
|
|
world: &'w World,
|
|
) -> Result<(), NodeRunError> {
|
|
let pipeline_cache = world.resource::<PipelineCache>();
|
|
let Some(render_sky_pipeline) =
|
|
pipeline_cache.get_render_pipeline(render_sky_pipeline_id.0)
|
|
else {
|
|
return Ok(());
|
|
}; //TODO: warning
|
|
|
|
let mut render_sky_pass =
|
|
render_context
|
|
.command_encoder()
|
|
.begin_render_pass(&RenderPassDescriptor {
|
|
label: Some("render_sky_pass"),
|
|
color_attachments: &[Some(view_target.get_color_attachment())],
|
|
depth_stencil_attachment: None,
|
|
timestamp_writes: None,
|
|
occlusion_query_set: None,
|
|
});
|
|
|
|
render_sky_pass.set_pipeline(render_sky_pipeline);
|
|
render_sky_pass.set_bind_group(
|
|
0,
|
|
&atmosphere_bind_groups.render_sky,
|
|
&[
|
|
atmosphere_uniforms_offset.index(),
|
|
settings_uniforms_offset.index(),
|
|
atmosphere_transforms_offset.index(),
|
|
view_uniforms_offset.offset,
|
|
lights_uniforms_offset.offset,
|
|
],
|
|
);
|
|
render_sky_pass.draw(0..3, 0..1);
|
|
|
|
Ok(())
|
|
}
|
|
}
|