mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 12:13:25 +00:00
Do not re-check visibility or re-render shadow maps for point and spot lights for each view (#15156)
# Objective _If I understand it correctly_, we were checking mesh visibility, as well as re-rendering point and spot light shadow maps for each view. This makes it so that M views and N lights produce M x N complexity. This PR aims to fix that, as well as introduce a stress test for this specific scenario. ## Solution - Keep track of what lights have already had mesh visibility calculated and do not calculate it again; - Reuse shadow depth textures and attachments across all views, and only render shadow maps for the _first_ time a light is encountered on a view; - Directional lights remain unaltered, since their shadow map cascades are view-dependent; - Add a new `many_cameras_lights` stress test example to verify the solution ## Showcase 110% speed up on the stress test 83% reduction of memory usage in stress test ### Before (5.35 FPS on stress test) <img width="1392" alt="Screenshot 2024-09-11 at 12 25 57" src="https://github.com/user-attachments/assets/136b0785-e9a4-44df-9a22-f99cc465e126"> ### After (11.34 FPS on stress test) <img width="1392" alt="Screenshot 2024-09-11 at 12 24 35" src="https://github.com/user-attachments/assets/b8dd858f-5e19-467f-8344-2b46ca039630"> ## Testing - Did you test these changes? If so, how? - On my game project where I have two cameras, and many shadow casting lights I managed to get pretty much double the FPS. - Also included a stress test, see the comparison above - Are there any parts that need more testing? - Yes, I would like help verifying that this fix is indeed correct, and that we were really re-rendering the shadow maps by mistake and it's indeed okay to not do that - How can other people (reviewers) test your changes? Is there anything specific they need to know? - Run the `many_cameras_lights` example - On the `main` branch, cherry pick the commit with the example (`git cherry-pick --no-commit 1ed4ace01`) and run it - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? - macOS --------- Co-authored-by: François Mockers <francois.mockers@vleue.com>
This commit is contained in:
parent
aad1fc6eba
commit
ef23f465ce
6 changed files with 297 additions and 131 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -2706,6 +2706,17 @@ description = "Test rendering of many UI elements"
|
|||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_cameras_lights"
|
||||
path = "examples/stress_tests/many_cameras_lights.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.many_cameras_lights]
|
||||
name = "Many Cameras & Lights"
|
||||
description = "Test rendering of many cameras and lights"
|
||||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_cubes"
|
||||
path = "examples/stress_tests/many_cubes.rs"
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use core::ops::DerefMut;
|
||||
|
||||
use bevy_ecs::{entity::EntityHashMap, prelude::*};
|
||||
use bevy_ecs::{
|
||||
entity::{EntityHashMap, EntityHashSet},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy_math::{ops, Mat4, Vec3A, Vec4};
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::{
|
||||
|
@ -836,6 +839,7 @@ pub fn check_dir_light_mesh_visibility(
|
|||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn check_point_light_mesh_visibility(
|
||||
visible_point_lights: Query<&VisibleClusterableObjects>,
|
||||
mut point_lights: Query<(
|
||||
|
@ -872,10 +876,17 @@ pub fn check_point_light_mesh_visibility(
|
|||
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
|
||||
mut cubemap_visible_entities_queue: Local<Parallel<[Vec<Entity>; 6]>>,
|
||||
mut spot_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
|
||||
mut checked_lights: Local<EntityHashSet>,
|
||||
) {
|
||||
checked_lights.clear();
|
||||
|
||||
let visible_entity_ranges = visible_entity_ranges.as_deref();
|
||||
for visible_lights in &visible_point_lights {
|
||||
for light_entity in visible_lights.entities.iter().copied() {
|
||||
if !checked_lights.insert(light_entity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Point lights
|
||||
if let Ok((
|
||||
point_light,
|
||||
|
|
|
@ -9,6 +9,7 @@ use bevy_ecs::{
|
|||
system::lifetimeless::Read,
|
||||
};
|
||||
use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||
use bevy_render::camera::SortedCameras;
|
||||
use bevy_render::sync_world::{MainEntity, RenderEntity, TemporaryRenderEntity};
|
||||
use bevy_render::{
|
||||
diagnostic::RecordDiagnostics,
|
||||
|
@ -29,6 +30,7 @@ use bevy_utils::tracing::info_span;
|
|||
use bevy_utils::{
|
||||
default,
|
||||
tracing::{error, warn},
|
||||
HashMap,
|
||||
};
|
||||
use core::{hash::Hash, ops::Range};
|
||||
|
||||
|
@ -677,10 +679,11 @@ pub fn prepare_lights(
|
|||
point_light_shadow_map: Res<PointLightShadowMap>,
|
||||
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
|
||||
mut shadow_render_phases: ResMut<ViewBinnedRenderPhases<Shadow>>,
|
||||
(mut max_directional_lights_warning_emitted, mut max_cascades_per_light_warning_emitted): (
|
||||
Local<bool>,
|
||||
Local<bool>,
|
||||
),
|
||||
(
|
||||
mut max_directional_lights_warning_emitted,
|
||||
mut max_cascades_per_light_warning_emitted,
|
||||
mut live_shadow_mapping_lights,
|
||||
): (Local<bool>, Local<bool>, Local<EntityHashSet>),
|
||||
point_lights: Query<(
|
||||
Entity,
|
||||
&ExtractedPointLight,
|
||||
|
@ -688,7 +691,7 @@ pub fn prepare_lights(
|
|||
)>,
|
||||
directional_lights: Query<(Entity, &ExtractedDirectionalLight)>,
|
||||
mut light_view_entities: Query<&mut LightViewEntities>,
|
||||
mut live_shadow_mapping_lights: Local<EntityHashSet>,
|
||||
sorted_cameras: Res<SortedCameras>,
|
||||
) {
|
||||
let views_iter = views.iter();
|
||||
let views_count = views_iter.len();
|
||||
|
@ -984,11 +987,8 @@ pub fn prepare_lights(
|
|||
|
||||
live_shadow_mapping_lights.clear();
|
||||
|
||||
let mut live_views = EntityHashSet::with_capacity_and_hasher(views_count, EntityHash);
|
||||
|
||||
// set up light data for each view
|
||||
for (entity, extracted_view, clusters, maybe_layers) in views.iter() {
|
||||
live_views.insert(entity);
|
||||
let mut point_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
|
||||
let mut directional_light_depth_attachments = HashMap::<u32, DepthAttachment>::default();
|
||||
|
||||
let point_light_depth_texture = texture_cache.get(
|
||||
&render_device,
|
||||
|
@ -1007,6 +1007,36 @@ pub fn prepare_lights(
|
|||
view_formats: &[],
|
||||
},
|
||||
);
|
||||
|
||||
let point_light_depth_texture_view =
|
||||
point_light_depth_texture
|
||||
.texture
|
||||
.create_view(&TextureViewDescriptor {
|
||||
label: Some("point_light_shadow_map_array_texture_view"),
|
||||
format: None,
|
||||
// NOTE: iOS Simulator is missing CubeArray support so we use Cube instead.
|
||||
// See https://github.com/bevyengine/bevy/pull/12052 - remove if support is added.
|
||||
#[cfg(all(
|
||||
not(feature = "ios_simulator"),
|
||||
any(
|
||||
not(feature = "webgl"),
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "webgpu"
|
||||
)
|
||||
))]
|
||||
dimension: Some(TextureViewDimension::CubeArray),
|
||||
#[cfg(any(
|
||||
feature = "ios_simulator",
|
||||
all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu"))
|
||||
))]
|
||||
dimension: Some(TextureViewDimension::Cube),
|
||||
aspect: TextureAspect::DepthOnly,
|
||||
base_mip_level: 0,
|
||||
mip_level_count: None,
|
||||
base_array_layer: 0,
|
||||
array_layer_count: None,
|
||||
});
|
||||
|
||||
let directional_light_depth_texture = texture_cache.get(
|
||||
&render_device,
|
||||
TextureDescriptor {
|
||||
|
@ -1028,6 +1058,37 @@ pub fn prepare_lights(
|
|||
view_formats: &[],
|
||||
},
|
||||
);
|
||||
|
||||
let directional_light_depth_texture_view =
|
||||
directional_light_depth_texture
|
||||
.texture
|
||||
.create_view(&TextureViewDescriptor {
|
||||
label: Some("directional_light_shadow_map_array_texture_view"),
|
||||
format: None,
|
||||
#[cfg(any(
|
||||
not(feature = "webgl"),
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "webgpu"
|
||||
))]
|
||||
dimension: Some(TextureViewDimension::D2Array),
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
dimension: Some(TextureViewDimension::D2),
|
||||
aspect: TextureAspect::DepthOnly,
|
||||
base_mip_level: 0,
|
||||
mip_level_count: None,
|
||||
base_array_layer: 0,
|
||||
array_layer_count: None,
|
||||
});
|
||||
|
||||
let mut live_views = EntityHashSet::with_capacity_and_hasher(views_count, EntityHash);
|
||||
|
||||
// set up light data for each view
|
||||
for (entity, extracted_view, clusters, maybe_layers) in sorted_cameras
|
||||
.0
|
||||
.iter()
|
||||
.filter_map(|sorted_camera| views.get(sorted_camera.entity).ok())
|
||||
{
|
||||
live_views.insert(entity);
|
||||
let mut view_lights = Vec::new();
|
||||
|
||||
let is_orthographic = extracted_view.clip_from_view.w_axis.w == 1.0;
|
||||
|
@ -1102,6 +1163,14 @@ pub fn prepare_lights(
|
|||
.zip(light_view_entities.iter().copied())
|
||||
.enumerate()
|
||||
{
|
||||
let mut first = false;
|
||||
let base_array_layer = (light_index * 6 + face_index) as u32;
|
||||
|
||||
let depth_attachment = point_light_depth_attachments
|
||||
.entry(base_array_layer)
|
||||
.or_insert_with(|| {
|
||||
first = true;
|
||||
|
||||
let depth_texture_view =
|
||||
point_light_depth_texture
|
||||
.texture
|
||||
|
@ -1112,13 +1181,17 @@ pub fn prepare_lights(
|
|||
aspect: TextureAspect::All,
|
||||
base_mip_level: 0,
|
||||
mip_level_count: None,
|
||||
base_array_layer: (light_index * 6 + face_index) as u32,
|
||||
base_array_layer,
|
||||
array_layer_count: Some(1u32),
|
||||
});
|
||||
|
||||
DepthAttachment::new(depth_texture_view, Some(0.0))
|
||||
})
|
||||
.clone();
|
||||
|
||||
commands.entity(view_light_entity).insert((
|
||||
ShadowView {
|
||||
depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)),
|
||||
depth_attachment,
|
||||
pass_name: format!(
|
||||
"shadow pass point light {} {}",
|
||||
light_index,
|
||||
|
@ -1147,10 +1220,13 @@ pub fn prepare_lights(
|
|||
|
||||
view_lights.push(view_light_entity);
|
||||
|
||||
if first {
|
||||
// Subsequent views with the same light entity will reuse the same shadow map
|
||||
shadow_render_phases.insert_or_clear(view_light_entity);
|
||||
live_shadow_mapping_lights.insert(view_light_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spot lights
|
||||
for (light_index, &(light_entity, light, (_, spot_light_frustum))) in point_lights
|
||||
|
@ -1177,19 +1253,30 @@ pub fn prepare_lights(
|
|||
[point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
|
||||
let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
|
||||
|
||||
let depth_texture_view =
|
||||
directional_light_depth_texture
|
||||
.texture
|
||||
.create_view(&TextureViewDescriptor {
|
||||
let mut first = false;
|
||||
let base_array_layer = (num_directional_cascades_enabled + light_index) as u32;
|
||||
|
||||
let depth_attachment = directional_light_depth_attachments
|
||||
.entry(base_array_layer)
|
||||
.or_insert_with(|| {
|
||||
first = true;
|
||||
|
||||
let depth_texture_view = directional_light_depth_texture.texture.create_view(
|
||||
&TextureViewDescriptor {
|
||||
label: Some("spot_light_shadow_map_texture_view"),
|
||||
format: None,
|
||||
dimension: Some(TextureViewDimension::D2),
|
||||
aspect: TextureAspect::All,
|
||||
base_mip_level: 0,
|
||||
mip_level_count: None,
|
||||
base_array_layer: (num_directional_cascades_enabled + light_index) as u32,
|
||||
base_array_layer,
|
||||
array_layer_count: Some(1u32),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
DepthAttachment::new(depth_texture_view, Some(0.0))
|
||||
})
|
||||
.clone();
|
||||
|
||||
let light_view_entities = light_view_entities
|
||||
.entry(entity)
|
||||
|
@ -1199,7 +1286,7 @@ pub fn prepare_lights(
|
|||
|
||||
commands.entity(view_light_entity).insert((
|
||||
ShadowView {
|
||||
depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)),
|
||||
depth_attachment,
|
||||
pass_name: format!("shadow pass spot light {light_index}"),
|
||||
},
|
||||
ExtractedView {
|
||||
|
@ -1221,9 +1308,12 @@ pub fn prepare_lights(
|
|||
|
||||
view_lights.push(view_light_entity);
|
||||
|
||||
if first {
|
||||
// Subsequent views with the same light entity will reuse the same shadow map
|
||||
shadow_render_phases.insert_or_clear(view_light_entity);
|
||||
live_shadow_mapping_lights.insert(view_light_entity);
|
||||
}
|
||||
}
|
||||
|
||||
// directional lights
|
||||
let mut directional_depth_texture_array_index = 0u32;
|
||||
|
@ -1307,6 +1397,12 @@ pub fn prepare_lights(
|
|||
base_array_layer: directional_depth_texture_array_index,
|
||||
array_layer_count: Some(1u32),
|
||||
});
|
||||
|
||||
// NOTE: For point and spotlights, we reuse the same depth attachment for all views.
|
||||
// However, for directional lights, we want a new depth attachment for each view,
|
||||
// so that the view is cleared for each view.
|
||||
let depth_attachment = DepthAttachment::new(depth_texture_view, Some(0.0));
|
||||
|
||||
directional_depth_texture_array_index += 1;
|
||||
|
||||
let mut frustum = *frustum;
|
||||
|
@ -1316,7 +1412,7 @@ pub fn prepare_lights(
|
|||
|
||||
commands.entity(view_light_entity).insert((
|
||||
ShadowView {
|
||||
depth_attachment: DepthAttachment::new(depth_texture_view, Some(0.0)),
|
||||
depth_attachment,
|
||||
pass_name: format!(
|
||||
"shadow pass directional light {light_index} cascade {cascade_index}"
|
||||
),
|
||||
|
@ -1342,65 +1438,19 @@ pub fn prepare_lights(
|
|||
));
|
||||
view_lights.push(view_light_entity);
|
||||
|
||||
// Subsequent views with the same light entity will **NOT** reuse the same shadow map
|
||||
// (Because the cascades are unique to each view)
|
||||
shadow_render_phases.insert_or_clear(view_light_entity);
|
||||
live_shadow_mapping_lights.insert(view_light_entity);
|
||||
}
|
||||
}
|
||||
|
||||
let point_light_depth_texture_view =
|
||||
point_light_depth_texture
|
||||
.texture
|
||||
.create_view(&TextureViewDescriptor {
|
||||
label: Some("point_light_shadow_map_array_texture_view"),
|
||||
format: None,
|
||||
// NOTE: iOS Simulator is missing CubeArray support so we use Cube instead.
|
||||
// See https://github.com/bevyengine/bevy/pull/12052 - remove if support is added.
|
||||
#[cfg(all(
|
||||
not(feature = "ios_simulator"),
|
||||
any(
|
||||
not(feature = "webgl"),
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "webgpu"
|
||||
)
|
||||
))]
|
||||
dimension: Some(TextureViewDimension::CubeArray),
|
||||
#[cfg(any(
|
||||
feature = "ios_simulator",
|
||||
all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu"))
|
||||
))]
|
||||
dimension: Some(TextureViewDimension::Cube),
|
||||
aspect: TextureAspect::DepthOnly,
|
||||
base_mip_level: 0,
|
||||
mip_level_count: None,
|
||||
base_array_layer: 0,
|
||||
array_layer_count: None,
|
||||
});
|
||||
let directional_light_depth_texture_view = directional_light_depth_texture
|
||||
.texture
|
||||
.create_view(&TextureViewDescriptor {
|
||||
label: Some("directional_light_shadow_map_array_texture_view"),
|
||||
format: None,
|
||||
#[cfg(any(
|
||||
not(feature = "webgl"),
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "webgpu"
|
||||
))]
|
||||
dimension: Some(TextureViewDimension::D2Array),
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
dimension: Some(TextureViewDimension::D2),
|
||||
aspect: TextureAspect::DepthOnly,
|
||||
base_mip_level: 0,
|
||||
mip_level_count: None,
|
||||
base_array_layer: 0,
|
||||
array_layer_count: None,
|
||||
});
|
||||
|
||||
commands.entity(entity).insert((
|
||||
ViewShadowBindings {
|
||||
point_light_depth_texture: point_light_depth_texture.texture,
|
||||
point_light_depth_texture_view,
|
||||
directional_light_depth_texture: directional_light_depth_texture.texture,
|
||||
directional_light_depth_texture_view,
|
||||
point_light_depth_texture: point_light_depth_texture.texture.clone(),
|
||||
point_light_depth_texture_view: point_light_depth_texture_view.clone(),
|
||||
directional_light_depth_texture: directional_light_depth_texture.texture.clone(),
|
||||
directional_light_depth_texture_view: directional_light_depth_texture_view.clone(),
|
||||
},
|
||||
ViewLightEntities {
|
||||
lights: view_lights,
|
||||
|
|
|
@ -80,6 +80,7 @@ impl ColorAttachment {
|
|||
}
|
||||
|
||||
/// A wrapper for a [`TextureView`] that is used as a depth-only [`RenderPassDepthStencilAttachment`].
|
||||
#[derive(Clone)]
|
||||
pub struct DepthAttachment {
|
||||
pub view: TextureView,
|
||||
clear_value: Option<f32>,
|
||||
|
|
|
@ -459,6 +459,7 @@ Example | Description
|
|||
[Bevymark](../examples/stress_tests/bevymark.rs) | A heavy sprite rendering workload to benchmark your system with Bevy
|
||||
[Many Animated Sprites](../examples/stress_tests/many_animated_sprites.rs) | Displays many animated sprites in a grid arrangement with slight offsets to their animation timers. Used for performance testing.
|
||||
[Many Buttons](../examples/stress_tests/many_buttons.rs) | Test rendering of many UI elements
|
||||
[Many Cameras & Lights](../examples/stress_tests/many_cameras_lights.rs) | Test rendering of many cameras and lights
|
||||
[Many Cubes](../examples/stress_tests/many_cubes.rs) | Simple benchmark to test per-entity draw overhead. Run with the `sphere` argument to test frustum culling
|
||||
[Many Foxes](../examples/stress_tests/many_foxes.rs) | Loads an animated fox model and spawns lots of them. Good for testing skinned mesh performance. Takes an unsigned integer argument for the number of foxes to spawn. Defaults to 1000
|
||||
[Many Gizmos](../examples/stress_tests/many_gizmos.rs) | Test rendering of many gizmos
|
||||
|
|
92
examples/stress_tests/many_cameras_lights.rs
Normal file
92
examples/stress_tests/many_cameras_lights.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
//! Test rendering of many cameras and lights
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::{
|
||||
math::ops::{cos, sin},
|
||||
prelude::*,
|
||||
render::camera::Viewport,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, rotate_cameras)
|
||||
.run();
|
||||
}
|
||||
|
||||
const CAMERA_ROWS: usize = 4;
|
||||
const CAMERA_COLS: usize = 4;
|
||||
const NUM_LIGHTS: usize = 5;
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
window: Query<&Window>,
|
||||
) {
|
||||
// circular base
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Circle::new(4.0))),
|
||||
MeshMaterial3d(materials.add(Color::WHITE)),
|
||||
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||
));
|
||||
|
||||
// cube
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
|
||||
MeshMaterial3d(materials.add(Color::WHITE)),
|
||||
Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
));
|
||||
|
||||
// lights
|
||||
for i in 0..NUM_LIGHTS {
|
||||
let angle = (i as f32) / (NUM_LIGHTS as f32) * PI * 2.0;
|
||||
commands.spawn((
|
||||
PointLight {
|
||||
color: Color::hsv(angle.to_degrees(), 1.0, 1.0),
|
||||
intensity: 2_000_000.0 / NUM_LIGHTS as f32,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
Transform::from_xyz(sin(angle) * 4.0, 2.0, cos(angle) * 4.0),
|
||||
));
|
||||
}
|
||||
|
||||
// cameras
|
||||
let window = window.single();
|
||||
let width = window.resolution.width() / CAMERA_COLS as f32 * window.resolution.scale_factor();
|
||||
let height = window.resolution.height() / CAMERA_ROWS as f32 * window.resolution.scale_factor();
|
||||
let mut i = 0;
|
||||
for y in 0..CAMERA_COLS {
|
||||
for x in 0..CAMERA_ROWS {
|
||||
let angle = i as f32 / (CAMERA_ROWS * CAMERA_COLS) as f32 * PI * 2.0;
|
||||
commands.spawn((
|
||||
Camera3d::default(),
|
||||
Camera {
|
||||
viewport: Some(Viewport {
|
||||
physical_position: UVec2::new(
|
||||
(x as f32 * width) as u32,
|
||||
(y as f32 * height) as u32,
|
||||
),
|
||||
physical_size: UVec2::new(width as u32, height as u32),
|
||||
..default()
|
||||
}),
|
||||
order: i,
|
||||
..default()
|
||||
},
|
||||
Transform::from_xyz(sin(angle) * 4.0, 2.5, cos(angle) * 4.0)
|
||||
.looking_at(Vec3::ZERO, Vec3::Y),
|
||||
));
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_cameras(time: Res<Time>, mut query: Query<&mut Transform, With<Camera>>) {
|
||||
for mut transform in query.iter_mut() {
|
||||
transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_secs()));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue