Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ use bevy_volumetric_clouds::CloudsPlugin;
app.add_plugins(CloudsPlugin);
```

Then add `CloudCamera` component to the camera that should have clouds.
```rust
commands.spawn((Camera3d::default(), Hdr, CloudCamera));
```

Look at [the minimal example](examples/minimal.rs) for a working example.

The [the demo example](examples/demo.rs) features a usable demo where you can move the camera around
Expand Down
3 changes: 2 additions & 1 deletion examples/demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use bevy::prelude::*;
use bevy::render::view::Hdr;
#[cfg(feature = "debug")]
use bevy_egui::EguiPlugin;
use bevy_volumetric_clouds::CloudsPlugin;
#[cfg(feature = "fly_camera")]
use bevy_volumetric_clouds::fly_camera::{FlyCam, FlyCameraPlugin};
use bevy_volumetric_clouds::{CloudCamera, CloudsPlugin};

fn close_on_esc(
mut commands: Commands,
Expand Down Expand Up @@ -45,6 +45,7 @@ fn setup(
commands.spawn((
Camera3d::default(),
Hdr,
CloudCamera,
#[cfg(feature = "fly_camera")]
FlyCam,
Transform::from_translation(Vec3::new(0.0, 3.0, 0.0)).looking_to(Vec3::X, Vec3::Y),
Expand Down
4 changes: 2 additions & 2 deletions examples/minimal.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! A minimal example featuring clouds.
use bevy::{prelude::*, render::view::Hdr};
use bevy_volumetric_clouds::CloudsPlugin;
use bevy_volumetric_clouds::{CloudCamera, CloudsPlugin};

fn main() {
App::new()
Expand All @@ -10,5 +10,5 @@ fn main() {
}

fn setup(mut commands: Commands) {
commands.spawn((Camera3d::default(), Hdr));
commands.spawn((Camera3d::default(), Hdr, CloudCamera));
}
99 changes: 49 additions & 50 deletions src/compute.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use bevy::{
asset::load_embedded_asset,
camera::CameraProjection,
core_pipeline::core_3d::graph::{Core3d, Node3d},
ecs::system::ResMut,
prelude::*,
render::{
Extract, Render, RenderApp, RenderSystems,
extract_component::ExtractComponentPlugin,
extract_resource::ExtractResourcePlugin,
render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, RenderLabel},
render_graph::{
NodeRunError, RenderGraphContext, RenderGraphExt, RenderLabel, ViewNode, ViewNodeRunner,
},
render_resource::{
AsBindGroup, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries,
CachedComputePipelineId, CachedPipelineState, ComputePassDescriptor,
Expand All @@ -19,7 +24,7 @@ use bevy::{
/// Controls the compute shader which renders the volumetric clouds.
use std::borrow::Cow;

use crate::config::CloudsConfig;
use crate::{CloudCamera, config::CloudsConfig};

use super::{
images::IMAGE_SIZE,
Expand Down Expand Up @@ -47,11 +52,12 @@ fn prepare_uniforms_bind_group(
pipeline: Res<CloudsPipeline>,
render_queue: Res<RenderQueue>,
mut clouds_uniform_buffer: ResMut<CloudsUniformBuffer>,
camera: ResMut<CameraMatrices>,
camera: Option<Res<CameraMatrices>>,
clouds_config: Res<CloudsConfig>,
render_device: Res<RenderDevice>,
time: Res<Time>,
) {
let camera = camera.expect("No camera with CloudCamera component found.");
let buffer = clouds_uniform_buffer.buffer.get_mut();

buffer.clouds_raymarch_steps_count = clouds_config.clouds_raymarch_steps_count;
Expand Down Expand Up @@ -200,27 +206,25 @@ impl Default for CloudsNode {
}
}

impl Node for CloudsNode {
impl ViewNode for CloudsNode {
type ViewQuery = Has<CloudCamera>;

fn update(&mut self, world: &mut World) {
let pipeline = world.resource::<CloudsPipeline>();
let pipeline_cache = world.resource::<PipelineCache>();
let cache = world.resource::<PipelineCache>();

// if the corresponding pipeline has loaded, transition to the next stage
match self.state {
CloudsState::Loading => {
if let CachedPipelineState::Ok(_) =
pipeline_cache.get_compute_pipeline_state(pipeline.init_pipeline)
{
self.state = CloudsState::Init;
}
}
CloudsState::Init => {
if let CachedPipelineState::Ok(_) =
pipeline_cache.get_compute_pipeline_state(pipeline.update_pipeline)
{
self.state = CloudsState::Update;
match cache.get_compute_pipeline_state(pipeline.init_pipeline) {
CachedPipelineState::Ok(_) => self.state = CloudsState::Init,
_ => {}
}
}
CloudsState::Init => match cache.get_compute_pipeline_state(pipeline.update_pipeline) {
CachedPipelineState::Ok(_) => self.state = CloudsState::Update,
_ => {}
},
CloudsState::Update => {}
}
}
Expand All @@ -229,8 +233,12 @@ impl Node for CloudsNode {
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
is_cloud_camera: bool,
world: &World,
) -> Result<(), NodeRunError> {
if !is_cloud_camera {
return Ok(());
}
let texture_bind_group = &world.resource::<CloudsImageBindGroup>().0;
let uniform_bind_group = &world.resource::<CloudsUniformBindGroup>().0;
let pipeline_cache = world.resource::<PipelineCache>();
Expand All @@ -242,32 +250,14 @@ impl Node for CloudsNode {

pass.set_bind_group(0, uniform_bind_group, &[]);
pass.set_bind_group(1, texture_bind_group, &[]);

match self.state {
CloudsState::Loading => {}
CloudsState::Init => {
let init_pipeline = pipeline_cache
.get_compute_pipeline(pipeline.init_pipeline)
.unwrap();
pass.set_pipeline(init_pipeline);
pass.dispatch_workgroups(
IMAGE_SIZE / WORKGROUP_SIZE,
IMAGE_SIZE / WORKGROUP_SIZE,
1,
);
}
CloudsState::Update => {
let update_pipeline = pipeline_cache
.get_compute_pipeline(pipeline.update_pipeline)
.unwrap();
pass.set_pipeline(update_pipeline);
pass.dispatch_workgroups(
IMAGE_SIZE / WORKGROUP_SIZE,
IMAGE_SIZE / WORKGROUP_SIZE,
1,
);
}
}
let compute = match self.state {
CloudsState::Init => pipeline.init_pipeline,
CloudsState::Update => pipeline.update_pipeline,
_ => return Ok(()),
};
let pipeline = pipeline_cache.get_compute_pipeline(compute).unwrap();
pass.set_pipeline(pipeline);
pass.dispatch_workgroups(IMAGE_SIZE / WORKGROUP_SIZE, IMAGE_SIZE / WORKGROUP_SIZE, 1);
Ok(())
}
}
Expand All @@ -282,7 +272,7 @@ impl Plugin for CloudsComputePlugin {
fn build(&self, app: &mut App) {
app.add_plugins(ExtractResourcePlugin::<CloudsImage>::default());
app.add_plugins(ExtractResourcePlugin::<CloudsUniform>::default());

app.add_plugins(ExtractComponentPlugin::<CloudCamera>::default());
let render_app = app.sub_app_mut(RenderApp);
render_app.add_systems(
Render,
Expand All @@ -292,11 +282,8 @@ impl Plugin for CloudsComputePlugin {
Render,
prepare_uniforms_bind_group.in_set(RenderSystems::PrepareResources),
);

let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();
render_graph.add_node(CloudsLabel, CloudsNode::default());
render_graph.add_node_edge(CloudsLabel, bevy::render::graph::CameraDriverLabel);

render_app.add_render_graph_node::<ViewNodeRunner<CloudsNode>>(Core3d, CloudsLabel);
render_app.add_render_graph_edge(Core3d, Node3d::EndMainPass, CloudsLabel);
render_app.add_systems(
ExtractSchedule,
(extract_clouds_config, extract_time, extract_camera_matrices),
Expand All @@ -318,6 +305,18 @@ fn extract_time(mut commands: Commands, time: Extract<Res<Time>>) {
commands.insert_resource(**time);
}

fn extract_camera_matrices(mut commands: Commands, camera: Extract<Res<CameraMatrices>>) {
commands.insert_resource(**camera);
fn extract_camera_matrices(
mut commands: Commands,
camera: Extract<Single<(&GlobalTransform, &Projection), With<CloudCamera>>>,
) {
let proj: Mat4 = match camera.1 {
Projection::Perspective(p) => p.get_clip_from_view(),
Projection::Orthographic(o) => o.get_clip_from_view(),
Projection::Custom(c) => c.get_clip_from_view(),
};
commands.insert_resource(CameraMatrices {
translation: camera.0.translation(),
inverse_camera_view: camera.0.to_matrix(),
inverse_camera_projection: proj.inverse(),
});
}
18 changes: 16 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod skybox;
#[cfg(feature = "debug")]
mod ui;
mod uniforms;
use bevy::prelude::*;
use bevy::{prelude::*, render::extract_component::ExtractComponent};

#[cfg(feature = "debug")]
use self::ui::ui_system;
Expand All @@ -30,6 +30,20 @@ use crate::{

use self::compute::CloudsComputePlugin;

/// Marker component for cameras that should render volumetric clouds.
/// **Note:** Currently only supports one CloudCamera at a time.
#[derive(Component, Clone, Copy)]
pub struct CloudCamera;

impl ExtractComponent for CloudCamera {
type QueryData = ();
type QueryFilter = With<CloudCamera>;
type Out = CloudCamera;

fn extract_component(_: ()) -> Option<Self::Out> {
Some(CloudCamera)
}
}
/// A plugin for rendering clouds.
///
/// The configuration of the clouds can be changed using the [`CloudsConfig`] resource.
Expand Down Expand Up @@ -84,7 +98,7 @@ fn clouds_setup(
}

fn update_camera_matrices(
cam_query: Single<(&GlobalTransform, &Camera)>,
cam_query: Single<(&GlobalTransform, &Camera), With<CloudCamera>>,
mut config: ResMut<CameraMatrices>,
) {
let (camera_transform, camera) = *cam_query;
Expand Down
7 changes: 6 additions & 1 deletion src/skybox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use core::f32::consts::PI;

use bevy::{light::light_consts::lux::FULL_DAYLIGHT, prelude::*};

use crate::CloudCamera;

#[derive(Component)]
pub(crate) struct SkyboxPlane {
pub orig_translation: Vec3,
Expand Down Expand Up @@ -118,7 +120,10 @@ pub(crate) fn setup_daylight(mut commands: Commands) {
}

pub(crate) fn update_skybox_transform(
camera: Single<(&Transform, &Camera, &Projection), Without<SkyboxPlane>>,
camera: Single<
(&Transform, &Camera, &Projection),
(Without<SkyboxPlane>, With<CloudCamera>),
>,
mut skybox: Query<(&mut Transform, &SkyboxPlane)>,
) {
let far = match camera.2 {
Expand Down