Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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));
}
102 changes: 52 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,15 @@ 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 Some(camera) = camera else {
warn_once!("No camera with CloudCamera component found. Clouds will not render.");
return;
};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should just panic here with an error message. Logs are easy to miss and this is clearly a case where the user should either remove the CloudsPlugin or add CloudsCamera. Although we might still want to allow not having CloudsCamera when there is only one render camera, but not sure.

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 +209,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 +236,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 +253,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 +275,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 +285,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 +308,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
Loading