mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 06:30:19 +00:00
19bfa41768
This commit implements a more physically-accurate, but slower, form of fog than the `bevy_pbr::fog` module does. Notably, this *volumetric fog* allows for light beams from directional lights to shine through, creating what is known as *light shafts* or *god rays*. To add volumetric fog to a scene, add `VolumetricFogSettings` to the camera, and add `VolumetricLight` to directional lights that you wish to be volumetric. `VolumetricFogSettings` has numerous settings that allow you to define the accuracy of the simulation, as well as the look of the fog. Currently, only interaction with directional lights that have shadow maps is supported. Note that the overhead of the effect scales directly with the number of directional lights in use, so apply `VolumetricLight` sparingly for the best results. The overall algorithm, which is implemented as a postprocessing effect, is a combination of the techniques described in [Scratchapixel] and [this blog post]. It uses raymarching in screen space, transformed into shadow map space for sampling and combined with physically-based modeling of absorption and scattering. Bevy employs the widely-used [Henyey-Greenstein phase function] to model asymmetry; this essentially allows light shafts to fade into and out of existence as the user views them. Volumetric rendering is a huge subject, and I deliberately kept the scope of this commit small. Possible follow-ups include: 1. Raymarching at a lower resolution. 2. A post-processing blur (especially useful when combined with (1)). 3. Supporting point lights and spot lights. 4. Supporting lights with no shadow maps. 5. Supporting irradiance volumes and reflection probes. 6. Voxel components that reuse the volumetric fog code to create voxel shapes. 7. *Horizon: Zero Dawn*-style clouds. These are all useful, but out of scope of this patch for now, to keep things tidy and easy to review. A new example, `volumetric_fog`, has been added to demonstrate the effect. ## Changelog ### Added * A new component, `VolumetricFog`, is available, to allow for a more physically-accurate, but more resource-intensive, form of fog. * A new component, `VolumetricLight`, can be placed on directional lights to make them interact with `VolumetricFog`. Notably, this allows such lights to emit light shafts/god rays. ![Screenshot 2024-04-21 162808](https://github.com/bevyengine/bevy/assets/157897/7a1fc81d-eed5-4735-9419-286c496391a9) ![Screenshot 2024-04-21 132005](https://github.com/bevyengine/bevy/assets/157897/e6d3b5ca-8f59-488d-a3de-15e95aaf4995) [Scratchapixel]: https://www.scratchapixel.com/lessons/3d-basic-rendering/volume-rendering-for-developers/intro-volume-rendering.html [this blog post]: https://www.alexandre-pestana.com/volumetric-lights/ [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction
117 lines
3.7 KiB
Rust
117 lines
3.7 KiB
Rust
//! Demonstrates volumetric fog and lighting (light shafts or god rays).
|
|
|
|
use bevy::{
|
|
core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping, Skybox},
|
|
math::vec3,
|
|
pbr::{VolumetricFogSettings, VolumetricLight},
|
|
prelude::*,
|
|
};
|
|
|
|
const DIRECTIONAL_LIGHT_MOVEMENT_SPEED: f32 = 0.02;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.insert_resource(ClearColor(Color::Srgba(Srgba {
|
|
red: 0.02,
|
|
green: 0.02,
|
|
blue: 0.02,
|
|
alpha: 1.0,
|
|
})))
|
|
.insert_resource(AmbientLight::NONE)
|
|
.add_systems(Startup, setup)
|
|
.add_systems(Update, tweak_scene)
|
|
.add_systems(Update, move_directional_light)
|
|
.run();
|
|
}
|
|
|
|
/// Initializes the scene.
|
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|
// Spawn the glTF scene.
|
|
commands.spawn(SceneBundle {
|
|
scene: asset_server.load("models/VolumetricFogExample/VolumetricFogExample.glb#Scene0"),
|
|
..default()
|
|
});
|
|
|
|
// Spawn the camera. Add the volumetric fog.
|
|
commands
|
|
.spawn(Camera3dBundle {
|
|
transform: Transform::from_xyz(-1.7, 1.5, 4.5)
|
|
.looking_at(vec3(-1.5, 1.7, 3.5), Vec3::Y),
|
|
camera: Camera {
|
|
hdr: true,
|
|
..default()
|
|
},
|
|
..default()
|
|
})
|
|
.insert(Tonemapping::TonyMcMapface)
|
|
.insert(BloomSettings::default())
|
|
.insert(Skybox {
|
|
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
|
|
brightness: 1000.0,
|
|
})
|
|
.insert(VolumetricFogSettings::default());
|
|
|
|
// Add the help text.
|
|
commands.spawn(
|
|
TextBundle {
|
|
text: Text::from_section(
|
|
"Press WASD or the arrow keys to change the light direction",
|
|
TextStyle {
|
|
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
|
font_size: 24.0,
|
|
..default()
|
|
},
|
|
),
|
|
..default()
|
|
}
|
|
.with_style(Style {
|
|
position_type: PositionType::Absolute,
|
|
bottom: Val::Px(10.0),
|
|
left: Val::Px(10.0),
|
|
..default()
|
|
}),
|
|
);
|
|
}
|
|
|
|
/// A system that makes directional lights in the glTF scene into volumetric
|
|
/// lights with shadows.
|
|
fn tweak_scene(
|
|
mut commands: Commands,
|
|
mut lights: Query<(Entity, &mut DirectionalLight), Changed<DirectionalLight>>,
|
|
) {
|
|
for (light, mut directional_light) in lights.iter_mut() {
|
|
// Shadows are needed for volumetric lights to work.
|
|
directional_light.shadows_enabled = true;
|
|
commands.entity(light).insert(VolumetricLight);
|
|
}
|
|
}
|
|
|
|
/// Processes user requests to move the directional light.
|
|
fn move_directional_light(
|
|
input: Res<ButtonInput<KeyCode>>,
|
|
mut directional_lights: Query<&mut Transform, With<DirectionalLight>>,
|
|
) {
|
|
let mut delta_theta = Vec2::ZERO;
|
|
if input.pressed(KeyCode::KeyW) || input.pressed(KeyCode::ArrowUp) {
|
|
delta_theta.y += DIRECTIONAL_LIGHT_MOVEMENT_SPEED;
|
|
}
|
|
if input.pressed(KeyCode::KeyS) || input.pressed(KeyCode::ArrowDown) {
|
|
delta_theta.y -= DIRECTIONAL_LIGHT_MOVEMENT_SPEED;
|
|
}
|
|
if input.pressed(KeyCode::KeyA) || input.pressed(KeyCode::ArrowLeft) {
|
|
delta_theta.x += DIRECTIONAL_LIGHT_MOVEMENT_SPEED;
|
|
}
|
|
if input.pressed(KeyCode::KeyD) || input.pressed(KeyCode::ArrowRight) {
|
|
delta_theta.x -= DIRECTIONAL_LIGHT_MOVEMENT_SPEED;
|
|
}
|
|
|
|
if delta_theta == Vec2::ZERO {
|
|
return;
|
|
}
|
|
|
|
let delta_quat = Quat::from_euler(EulerRot::XZY, delta_theta.y, 0.0, delta_theta.x);
|
|
for mut transform in directional_lights.iter_mut() {
|
|
transform.rotate(delta_quat);
|
|
}
|
|
}
|