use marker components for cameras instead of name strings (#3635)

**Problem**
- whenever you want more than one of the builtin cameras (for example multiple windows, split screen, portals), you need to add a render graph node that executes the correct sub graph, extract the camera into the render world and add the correct `RenderPhase<T>` components
- querying for the 3d camera is annoying because you need to compare the camera's name to e.g. `CameraPlugin::CAMERA_3d`

**Solution**
- Introduce the marker types `Camera3d`, `Camera2d` and `CameraUi`
-> `Query<&mut Transform, With<Camera3d>>` works
- `PerspectiveCameraBundle::new_3d()` and `PerspectiveCameraBundle::<Camera3d>::default()` contain the `Camera3d` marker
- `OrthographicCameraBundle::new_3d()` has `Camera3d`, `OrthographicCameraBundle::new_2d()` has `Camera2d`
- remove `ActiveCameras`, `ExtractedCameraNames`
- run 2d, 3d and ui passes for every camera of their respective marker
-> no custom setup for multiple windows example needed

**Open questions**
- do we need a replacement for `ActiveCameras`? What about a component `ActiveCamera { is_active: bool }` similar to `Visibility`?

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Jakob Hellermann 2022-03-12 00:41:06 +00:00
parent 0e821da704
commit bf6de89622
16 changed files with 289 additions and 392 deletions

View file

@ -23,7 +23,7 @@ use bevy_app::{App, Plugin};
use bevy_core::FloatOrd;
use bevy_ecs::prelude::*;
use bevy_render::{
camera::{ActiveCameras, CameraPlugin, RenderTarget},
camera::{ActiveCamera, Camera2d, Camera3d, RenderTarget},
color::Color,
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
render_phase::{
@ -367,23 +367,20 @@ pub fn extract_clear_color(
pub fn extract_core_pipeline_camera_phases(
mut commands: Commands,
active_cameras: Res<ActiveCameras>,
active_2d: Res<ActiveCamera<Camera2d>>,
active_3d: Res<ActiveCamera<Camera3d>>,
) {
if let Some(camera_2d) = active_cameras.get(CameraPlugin::CAMERA_2D) {
if let Some(entity) = camera_2d.entity {
commands
.get_or_spawn(entity)
.insert(RenderPhase::<Transparent2d>::default());
}
if let Some(entity) = active_2d.get() {
commands
.get_or_spawn(entity)
.insert(RenderPhase::<Transparent2d>::default());
}
if let Some(camera_3d) = active_cameras.get(CameraPlugin::CAMERA_3D) {
if let Some(entity) = camera_3d.entity {
commands.get_or_spawn(entity).insert_bundle((
RenderPhase::<Opaque3d>::default(),
RenderPhase::<AlphaMask3d>::default(),
RenderPhase::<Transparent3d>::default(),
));
}
if let Some(entity) = active_3d.get() {
commands.get_or_spawn(entity).insert_bundle((
RenderPhase::<Opaque3d>::default(),
RenderPhase::<AlphaMask3d>::default(),
RenderPhase::<Transparent3d>::default(),
));
}
}

View file

@ -1,6 +1,6 @@
use bevy_ecs::world::World;
use bevy_render::{
camera::{CameraPlugin, ExtractedCameraNames},
camera::{ActiveCamera, Camera2d, Camera3d},
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
renderer::RenderContext,
};
@ -14,18 +14,17 @@ impl Node for MainPassDriverNode {
_render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let extracted_cameras = world.resource::<ExtractedCameraNames>();
if let Some(camera_2d) = extracted_cameras.entities.get(CameraPlugin::CAMERA_2D) {
if let Some(camera_2d) = world.resource::<ActiveCamera<Camera2d>>().get() {
graph.run_sub_graph(
crate::draw_2d_graph::NAME,
vec![SlotValue::Entity(*camera_2d)],
vec![SlotValue::Entity(camera_2d)],
)?;
}
if let Some(camera_3d) = extracted_cameras.entities.get(CameraPlugin::CAMERA_3D) {
if let Some(camera_3d) = world.resource::<ActiveCamera<Camera3d>>().get() {
graph.run_sub_graph(
crate::draw_3d_graph::NAME,
vec![SlotValue::Entity(*camera_3d)],
vec![SlotValue::Entity(camera_3d)],
)?;
}

View file

@ -12,7 +12,7 @@ use bevy_pbr::{
};
use bevy_render::{
camera::{
Camera, CameraPlugin, CameraProjection, OrthographicProjection, PerspectiveProjection,
Camera, Camera2d, Camera3d, CameraProjection, OrthographicProjection, PerspectiveProjection,
},
color::Color,
mesh::{Indices, Mesh, VertexAttributeValues},
@ -494,11 +494,10 @@ fn load_node(
};
node.insert(Camera {
name: Some(CameraPlugin::CAMERA_2D.to_owned()),
projection_matrix: orthographic_projection.get_projection_matrix(),
..Default::default()
});
node.insert(orthographic_projection);
node.insert(orthographic_projection).insert(Camera2d);
}
gltf::camera::Projection::Perspective(perspective) => {
let mut perspective_projection: PerspectiveProjection = PerspectiveProjection {
@ -513,13 +512,13 @@ fn load_node(
perspective_projection.aspect_ratio = aspect_ratio;
}
node.insert(Camera {
name: Some(CameraPlugin::CAMERA_3D.to_owned()),
projection_matrix: perspective_projection.get_projection_matrix(),
near: perspective_projection.near,
far: perspective_projection.far,
..Default::default()
});
node.insert(perspective_projection);
node.insert(Camera3d);
}
}
}

View file

@ -1,73 +0,0 @@
use super::Camera;
use bevy_ecs::{
entity::Entity,
system::{Query, ResMut},
};
use bevy_utils::HashMap;
#[derive(Debug, Default)]
pub struct ActiveCamera {
pub name: String,
pub entity: Option<Entity>,
}
#[derive(Debug, Default)]
pub struct ActiveCameras {
cameras: HashMap<String, ActiveCamera>,
}
impl ActiveCameras {
pub fn add(&mut self, name: &str) {
self.cameras.insert(
name.to_string(),
ActiveCamera {
name: name.to_string(),
..Default::default()
},
);
}
pub fn get(&self, name: &str) -> Option<&ActiveCamera> {
self.cameras.get(name)
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut ActiveCamera> {
self.cameras.get_mut(name)
}
pub fn remove(&mut self, name: &str) -> Option<ActiveCamera> {
self.cameras.remove(name)
}
pub fn iter(&self) -> impl Iterator<Item = &ActiveCamera> {
self.cameras.values()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ActiveCamera> {
self.cameras.values_mut()
}
}
pub fn active_cameras_system(
mut active_cameras: ResMut<ActiveCameras>,
query: Query<(Entity, &Camera)>,
) {
for (name, active_camera) in &mut active_cameras.cameras {
if active_camera
.entity
.map_or(false, |entity| query.get(entity).is_err())
{
active_camera.entity = None;
}
if active_camera.entity.is_none() {
for (camera_entity, camera) in query.iter() {
if let Some(ref current_name) = camera.name {
if current_name == name {
active_camera.entity = Some(camera_entity);
}
}
}
}
}
}

View file

@ -1,36 +1,48 @@
use crate::{
camera::{
Camera, CameraPlugin, DepthCalculation, OrthographicProjection, PerspectiveProjection,
ScalingMode,
},
camera::{Camera, DepthCalculation, OrthographicProjection, PerspectiveProjection},
primitives::Frustum,
view::VisibleEntities,
};
use bevy_ecs::bundle::Bundle;
use bevy_ecs::{bundle::Bundle, prelude::Component};
use bevy_math::Vec3;
use bevy_transform::components::{GlobalTransform, Transform};
use super::CameraProjection;
use super::{CameraProjection, ScalingMode};
#[derive(Component, Default)]
pub struct Camera3d;
#[derive(Component, Default)]
pub struct Camera2d;
/// Component bundle for camera entities with perspective projection
///
/// Use this for 3D rendering.
#[derive(Bundle)]
pub struct PerspectiveCameraBundle {
pub struct PerspectiveCameraBundle<M: Component> {
pub camera: Camera,
pub perspective_projection: PerspectiveProjection,
pub visible_entities: VisibleEntities,
pub frustum: Frustum,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub marker: M,
}
impl PerspectiveCameraBundle {
pub fn new_3d() -> Self {
Default::default()
impl Default for PerspectiveCameraBundle<Camera3d> {
fn default() -> Self {
PerspectiveCameraBundle::new_3d()
}
}
pub fn with_name(name: &str) -> Self {
impl PerspectiveCameraBundle<Camera3d> {
pub fn new_3d() -> Self {
PerspectiveCameraBundle::new()
}
}
impl<M: Component + Default> PerspectiveCameraBundle<M> {
pub fn new() -> Self {
let perspective_projection = PerspectiveProjection::default();
let view_projection = perspective_projection.get_projection_matrix();
let frustum = Frustum::from_view_projection(
@ -41,7 +53,6 @@ impl PerspectiveCameraBundle {
);
PerspectiveCameraBundle {
camera: Camera {
name: Some(name.to_string()),
near: perspective_projection.near,
far: perspective_projection.far,
..Default::default()
@ -51,30 +62,56 @@ impl PerspectiveCameraBundle {
frustum,
transform: Default::default(),
global_transform: Default::default(),
marker: M::default(),
}
}
}
impl Default for PerspectiveCameraBundle {
fn default() -> Self {
PerspectiveCameraBundle::with_name(CameraPlugin::CAMERA_3D)
}
}
/// Component bundle for camera entities with orthographic projection
///
/// Use this for 2D games, isometric games, CAD-like 3D views.
#[derive(Bundle)]
pub struct OrthographicCameraBundle {
pub struct OrthographicCameraBundle<M: Component> {
pub camera: Camera,
pub orthographic_projection: OrthographicProjection,
pub visible_entities: VisibleEntities,
pub frustum: Frustum,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub marker: M,
}
impl OrthographicCameraBundle {
impl OrthographicCameraBundle<Camera3d> {
pub fn new_3d() -> Self {
let orthographic_projection = OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical,
depth_calculation: DepthCalculation::Distance,
..Default::default()
};
let view_projection = orthographic_projection.get_projection_matrix();
let frustum = Frustum::from_view_projection(
&view_projection,
&Vec3::ZERO,
&Vec3::Z,
orthographic_projection.far(),
);
OrthographicCameraBundle {
camera: Camera {
near: orthographic_projection.near,
far: orthographic_projection.far,
..Default::default()
},
orthographic_projection,
visible_entities: VisibleEntities::default(),
frustum,
transform: Default::default(),
global_transform: Default::default(),
marker: Camera3d,
}
}
}
impl OrthographicCameraBundle<Camera2d> {
/// Create an orthographic projection camera to render 2D content.
///
/// The projection creates a camera space where X points to the right of the screen,
@ -112,7 +149,6 @@ impl OrthographicCameraBundle {
);
OrthographicCameraBundle {
camera: Camera {
name: Some(CameraPlugin::CAMERA_2D.to_string()),
near: orthographic_projection.near,
far: orthographic_projection.far,
..Default::default()
@ -122,58 +158,7 @@ impl OrthographicCameraBundle {
frustum,
transform,
global_transform: Default::default(),
}
}
pub fn new_3d() -> Self {
let orthographic_projection = OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical,
depth_calculation: DepthCalculation::Distance,
..Default::default()
};
let view_projection = orthographic_projection.get_projection_matrix();
let frustum = Frustum::from_view_projection(
&view_projection,
&Vec3::ZERO,
&Vec3::Z,
orthographic_projection.far(),
);
OrthographicCameraBundle {
camera: Camera {
name: Some(CameraPlugin::CAMERA_3D.to_string()),
near: orthographic_projection.near,
far: orthographic_projection.far,
..Default::default()
},
orthographic_projection,
visible_entities: VisibleEntities::default(),
frustum,
transform: Default::default(),
global_transform: Default::default(),
}
}
pub fn with_name(name: &str) -> Self {
let orthographic_projection = OrthographicProjection::default();
let view_projection = orthographic_projection.get_projection_matrix();
let frustum = Frustum::from_view_projection(
&view_projection,
&Vec3::ZERO,
&Vec3::Z,
orthographic_projection.far(),
);
OrthographicCameraBundle {
camera: Camera {
name: Some(name.to_string()),
near: orthographic_projection.near,
far: orthographic_projection.far,
..Default::default()
},
orthographic_projection,
visible_entities: VisibleEntities::default(),
frustum,
transform: Default::default(),
global_transform: Default::default(),
marker: Camera2d,
}
}
}

View file

@ -1,16 +1,23 @@
use std::marker::PhantomData;
use crate::{
camera::CameraProjection, prelude::Image, render_asset::RenderAssets,
render_resource::TextureView, view::ExtractedWindows,
camera::CameraProjection,
prelude::Image,
render_asset::RenderAssets,
render_resource::TextureView,
view::{ExtractedView, ExtractedWindows, VisibleEntities},
RenderApp, RenderStage,
};
use bevy_app::{App, CoreStage, Plugin, StartupStage};
use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_ecs::{
component::Component,
entity::Entity,
event::EventReader,
prelude::{DetectChanges, QueryState},
prelude::{DetectChanges, QueryState, With},
query::Added,
reflect::ReflectComponent,
system::{QuerySet, Res},
system::{Commands, Query, QuerySet, Res, ResMut},
};
use bevy_math::{Mat4, UVec2, Vec2, Vec3};
use bevy_reflect::{Reflect, ReflectDeserialize};
@ -24,7 +31,6 @@ use wgpu::Extent3d;
#[reflect(Component)]
pub struct Camera {
pub projection_matrix: Mat4,
pub name: Option<String>,
#[reflect(ignore)]
pub target: RenderTarget,
#[reflect(ignore)]
@ -203,3 +209,111 @@ pub fn camera_system<T: CameraProjection + Component>(
}
}
}
pub struct CameraTypePlugin<T: Component + Default>(PhantomData<T>);
impl<T: Component + Default> Default for CameraTypePlugin<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T: Component + Default> Plugin for CameraTypePlugin<T> {
fn build(&self, app: &mut App) {
app.init_resource::<ActiveCamera<T>>()
.add_startup_system_to_stage(StartupStage::PostStartup, set_active_camera::<T>)
.add_system_to_stage(CoreStage::PostUpdate, set_active_camera::<T>);
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_system_to_stage(RenderStage::Extract, extract_cameras::<T>);
}
}
}
/// The canonical source of the "active camera" of the given camera type `T`.
#[derive(Debug)]
pub struct ActiveCamera<T: Component> {
camera: Option<Entity>,
marker: PhantomData<T>,
}
impl<T: Component> Default for ActiveCamera<T> {
fn default() -> Self {
Self {
camera: Default::default(),
marker: Default::default(),
}
}
}
impl<T: Component> Clone for ActiveCamera<T> {
fn clone(&self) -> Self {
Self {
camera: self.camera,
marker: self.marker,
}
}
}
impl<T: Component> ActiveCamera<T> {
/// Sets the active camera to the given `camera` entity.
pub fn set(&mut self, camera: Entity) {
self.camera = Some(camera);
}
/// Returns the active camera, if it exists.
pub fn get(&self) -> Option<Entity> {
self.camera
}
}
pub fn set_active_camera<T: Component>(
mut active_camera: ResMut<ActiveCamera<T>>,
cameras: Query<Entity, With<T>>,
) {
if active_camera.get().is_some() {
return;
}
if let Some(camera) = cameras.iter().next() {
active_camera.camera = Some(camera);
}
}
#[derive(Component, Debug)]
pub struct ExtractedCamera {
pub target: RenderTarget,
pub physical_size: Option<UVec2>,
}
pub fn extract_cameras<M: Component + Default>(
mut commands: Commands,
windows: Res<Windows>,
images: Res<Assets<Image>>,
active_camera: Res<ActiveCamera<M>>,
query: Query<(&Camera, &GlobalTransform, &VisibleEntities), With<M>>,
) {
if let Some(entity) = active_camera.get() {
if let Ok((camera, transform, visible_entities)) = query.get(entity) {
if let Some(size) = camera.target.get_physical_size(&windows, &images) {
commands.get_or_spawn(entity).insert_bundle((
ExtractedCamera {
target: camera.target.clone(),
physical_size: camera.target.get_physical_size(&windows, &images),
},
ExtractedView {
projection: camera.projection_matrix,
transform: *transform,
width: size.x.max(1),
height: size.y.max(1),
near: camera.near,
far: camera.far,
},
visible_entities.clone(),
M::default(),
));
}
}
}
commands.insert_resource(active_camera.clone())
}

View file

@ -1,41 +1,23 @@
mod active_cameras;
mod bundle;
#[allow(clippy::module_inception)]
mod camera;
mod projection;
pub use active_cameras::*;
use bevy_asset::Assets;
use bevy_math::UVec2;
use bevy_transform::components::GlobalTransform;
use bevy_utils::HashMap;
use bevy_window::Windows;
pub use bundle::*;
pub use camera::*;
pub use projection::*;
use crate::{
prelude::Image,
primitives::Aabb,
view::{ComputedVisibility, ExtractedView, Visibility, VisibleEntities},
RenderApp, RenderStage,
view::{ComputedVisibility, Visibility, VisibleEntities},
};
use bevy_app::{App, CoreStage, Plugin};
use bevy_ecs::prelude::*;
#[derive(Default)]
pub struct CameraPlugin;
impl CameraPlugin {
pub const CAMERA_2D: &'static str = "camera_2d";
pub const CAMERA_3D: &'static str = "camera_3d";
}
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
let mut active_cameras = ActiveCameras::default();
active_cameras.add(Self::CAMERA_2D);
active_cameras.add(Self::CAMERA_3D);
app.register_type::<Camera>()
.register_type::<Visibility>()
.register_type::<ComputedVisibility>()
@ -46,8 +28,6 @@ impl Plugin for CameraPlugin {
.register_type::<ScalingMode>()
.register_type::<DepthCalculation>()
.register_type::<Aabb>()
.insert_resource(active_cameras)
.add_system_to_stage(CoreStage::PostUpdate, crate::camera::active_cameras_system)
.add_system_to_stage(
CoreStage::PostUpdate,
crate::camera::camera_system::<OrthographicProjection>,
@ -55,61 +35,8 @@ impl Plugin for CameraPlugin {
.add_system_to_stage(
CoreStage::PostUpdate,
crate::camera::camera_system::<PerspectiveProjection>,
);
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ExtractedCameraNames>()
.add_system_to_stage(RenderStage::Extract, extract_cameras);
}
)
.add_plugin(CameraTypePlugin::<Camera3d>::default())
.add_plugin(CameraTypePlugin::<Camera2d>::default());
}
}
#[derive(Default)]
pub struct ExtractedCameraNames {
pub entities: HashMap<String, Entity>,
}
#[derive(Component, Debug)]
pub struct ExtractedCamera {
pub target: RenderTarget,
pub name: Option<String>,
pub physical_size: Option<UVec2>,
}
fn extract_cameras(
mut commands: Commands,
active_cameras: Res<ActiveCameras>,
windows: Res<Windows>,
images: Res<Assets<Image>>,
query: Query<(Entity, &Camera, &GlobalTransform, &VisibleEntities)>,
) {
let mut entities = HashMap::default();
for camera in active_cameras.iter() {
let name = &camera.name;
if let Some((entity, camera, transform, visible_entities)) =
camera.entity.and_then(|e| query.get(e).ok())
{
if let Some(size) = camera.target.get_physical_size(&windows, &images) {
entities.insert(name.clone(), entity);
commands.get_or_spawn(entity).insert_bundle((
ExtractedCamera {
target: camera.target.clone(),
name: camera.name.clone(),
physical_size: camera.target.get_physical_size(&windows, &images),
},
ExtractedView {
projection: camera.projection_matrix,
transform: *transform,
width: size.x.max(1),
height: size.y.max(1),
near: camera.near,
far: camera.far,
},
visible_entities.clone(),
));
}
}
}
commands.insert_resource(ExtractedCameraNames { entities });
}

View file

@ -9,7 +9,7 @@ use wgpu::{
pub use window::*;
use crate::{
camera::{ExtractedCamera, ExtractedCameraNames},
camera::ExtractedCamera,
prelude::Image,
render_asset::RenderAssets,
render_resource::{std140::AsStd140, DynamicUniformVec, Texture, TextureView},
@ -174,20 +174,14 @@ fn prepare_view_uniforms(
#[allow(clippy::too_many_arguments)]
fn prepare_view_targets(
mut commands: Commands,
camera_names: Res<ExtractedCameraNames>,
windows: Res<ExtractedWindows>,
images: Res<RenderAssets<Image>>,
msaa: Res<Msaa>,
render_device: Res<RenderDevice>,
mut texture_cache: ResMut<TextureCache>,
cameras: Query<&ExtractedCamera>,
cameras: Query<(Entity, &ExtractedCamera)>,
) {
for entity in camera_names.entities.values().copied() {
let camera = if let Ok(camera) = cameras.get(entity) {
camera
} else {
continue;
};
for (entity, camera) in cameras.iter() {
if let Some(size) = camera.physical_size {
if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) {
let sampled_target = if msaa.samples > 1 {

View file

@ -2,9 +2,9 @@
use crate::{
widget::{Button, ImageMode},
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI,
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage,
};
use bevy_ecs::bundle::Bundle;
use bevy_ecs::{bundle::Bundle, prelude::Component};
use bevy_render::{
camera::{Camera, DepthCalculation, OrthographicProjection, WindowOrigin},
view::{Visibility, VisibleEntities},
@ -135,10 +135,12 @@ impl Default for ButtonBundle {
}
}
}
#[derive(Component, Default)]
pub struct CameraUi;
/// The camera that is needed to see UI elements
#[derive(Bundle, Debug)]
pub struct UiCameraBundle {
pub struct UiCameraBundle<M: Component> {
/// The camera component
pub camera: Camera,
/// The orthographic projection settings
@ -150,16 +152,16 @@ pub struct UiCameraBundle {
/// Contains visible entities
// FIXME there is no frustrum culling for UI
pub visible_entities: VisibleEntities,
pub marker: M,
}
impl Default for UiCameraBundle {
impl Default for UiCameraBundle<CameraUi> {
fn default() -> Self {
// we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset
// the camera's translation by far and use a right handed coordinate system
let far = 1000.0;
UiCameraBundle {
camera: Camera {
name: Some(CAMERA_UI.to_string()),
..Default::default()
},
orthographic_projection: OrthographicProjection {
@ -171,6 +173,7 @@ impl Default for UiCameraBundle {
transform: Transform::from_xyz(0.0, 0.0, far - 0.1),
global_transform: Default::default(),
visible_entities: Default::default(),
marker: CameraUi,
}
}
}

View file

@ -12,6 +12,7 @@ pub mod entity;
pub mod update;
pub mod widget;
use bevy_render::camera::CameraTypePlugin;
pub use flex::*;
pub use focus::*;
pub use margins::*;
@ -31,6 +32,8 @@ use bevy_math::{Rect, Size};
use bevy_transform::TransformSystem;
use update::{ui_z_system, update_clipping_system};
use crate::prelude::CameraUi;
/// The basic plugin for Bevy UI
#[derive(Default)]
pub struct UiPlugin;
@ -46,7 +49,8 @@ pub enum UiSystem {
impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<FlexSurface>()
app.add_plugin(CameraTypePlugin::<CameraUi>::default())
.init_resource::<FlexSurface>()
.register_type::<AlignContent>()
.register_type::<AlignItems>()
.register_type::<AlignSelf>()

View file

@ -1,16 +1,18 @@
use bevy_ecs::prelude::*;
use bevy_render::{camera::ActiveCameras, render_phase::RenderPhase};
use bevy_render::{camera::ActiveCamera, render_phase::RenderPhase};
/// The name of the UI camera
pub const CAMERA_UI: &str = "camera_ui";
use crate::prelude::CameraUi;
use super::TransparentUi;
/// Inserts the [`RenderPhase`] into the UI camera
pub fn extract_ui_camera_phases(mut commands: Commands, active_cameras: Res<ActiveCameras>) {
if let Some(camera_ui) = active_cameras.get(CAMERA_UI) {
if let Some(entity) = camera_ui.entity {
commands
.get_or_spawn(entity)
.insert(RenderPhase::<super::TransparentUi>::default());
}
pub fn extract_ui_camera_phases(
mut commands: Commands,
active_camera: Res<ActiveCamera<CameraUi>>,
) {
if let Some(entity) = active_camera.get() {
commands
.get_or_spawn(entity)
.insert(RenderPhase::<TransparentUi>::default());
}
}

View file

@ -6,8 +6,7 @@ pub use camera::*;
pub use pipeline::*;
pub use render_pass::*;
use std::ops::Range;
use crate::{CalculatedClip, Node, UiColor, UiImage};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped};
use bevy_core::FloatOrd;
@ -15,7 +14,6 @@ use bevy_ecs::prelude::*;
use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec4Swizzles};
use bevy_reflect::TypeUuid;
use bevy_render::{
camera::ActiveCameras,
color::Color,
render_asset::RenderAssets,
render_graph::{RenderGraph, SlotInfo, SlotType},
@ -31,10 +29,8 @@ use bevy_text::{DefaultTextPipeline, Text};
use bevy_transform::components::GlobalTransform;
use bevy_utils::HashMap;
use bevy_window::{WindowId, Windows};
use bytemuck::{Pod, Zeroable};
use crate::{CalculatedClip, Node, UiColor, UiImage};
use std::ops::Range;
pub mod node {
pub const UI_PASS_DRIVER: &str = "ui_pass_driver";
@ -61,9 +57,6 @@ pub enum RenderUiSystem {
pub fn build_ui_render(app: &mut App) {
load_internal_asset!(app, UI_SHADER_HANDLE, "ui.wgsl", Shader::from_wgsl);
let mut active_cameras = app.world.resource_mut::<ActiveCameras>();
active_cameras.add(CAMERA_UI);
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,

View file

@ -4,7 +4,7 @@ use bevy_ecs::{
system::{lifetimeless::*, SystemParamItem},
};
use bevy_render::{
camera::ExtractedCameraNames,
camera::ActiveCamera,
render_graph::*,
render_phase::*,
render_resource::{
@ -14,20 +14,21 @@ use bevy_render::{
view::*,
};
use super::{draw_ui_graph, UiBatch, UiImageBindGroups, UiMeta, CAMERA_UI};
use crate::prelude::CameraUi;
use super::{draw_ui_graph, UiBatch, UiImageBindGroups, UiMeta};
pub struct UiPassDriverNode;
impl bevy_render::render_graph::Node for UiPassDriverNode {
impl Node for UiPassDriverNode {
fn run(
&self,
graph: &mut RenderGraphContext,
_render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let extracted_cameras = world.resource::<ExtractedCameraNames>();
if let Some(camera_ui) = extracted_cameras.entities.get(CAMERA_UI) {
graph.run_sub_graph(draw_ui_graph::NAME, vec![SlotValue::Entity(*camera_ui)])?;
if let Some(camera_ui) = world.resource::<ActiveCamera<CameraUi>>().get() {
graph.run_sub_graph(draw_ui_graph::NAME, vec![SlotValue::Entity(camera_ui)])?;
}
Ok(())
@ -49,7 +50,7 @@ impl UiPassNode {
}
}
impl bevy_render::render_graph::Node for UiPassNode {
impl Node for UiPassNode {
fn input(&self) -> Vec<SlotInfo> {
vec![SlotInfo::new(UiPassNode::IN_VIEW, SlotType::Entity)]
}

View file

@ -5,8 +5,8 @@ use bevy::{
prelude::*,
reflect::TypeUuid,
render::{
camera::{ActiveCameras, Camera, ExtractedCameraNames, RenderTarget},
render_graph::{NodeRunError, RenderGraph, RenderGraphContext, SlotValue},
camera::{ActiveCamera, Camera, CameraTypePlugin, RenderTarget},
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotValue},
render_phase::RenderPhase,
render_resource::{
Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
@ -17,6 +17,9 @@ use bevy::{
},
};
#[derive(Component, Default)]
pub struct FirstPassCamera;
// This handle will point at the texture to which we will render in the first pass.
pub const RENDER_IMAGE_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Image::TYPE_UUID, 13378939762009864029);
@ -24,26 +27,24 @@ pub const RENDER_IMAGE_HANDLE: HandleUntyped =
// The name of the final node of the first pass.
pub const FIRST_PASS_DRIVER: &str = "first_pass_driver";
// The name of the camera that determines the view rendered in the first pass.
pub const FIRST_PASS_CAMERA: &str = "first_pass_camera";
fn main() {
let mut app = App::new();
app.insert_resource(Msaa { samples: 4 }) // Use 4x MSAA
.add_plugins(DefaultPlugins)
.add_plugin(CameraTypePlugin::<FirstPassCamera>::default())
.add_startup_system(setup)
.add_system(cube_rotator_system)
.add_system(rotator_system);
let render_app = app.sub_app_mut(RenderApp);
let driver = FirstPassCameraDriver::new(&mut render_app.world);
// This will add 3D render phases for the new camera.
render_app.add_system_to_stage(RenderStage::Extract, extract_first_pass_camera_phases);
let mut graph = render_app.world.resource_mut::<RenderGraph>();
// Add a node for the first pass.
graph.add_node(FIRST_PASS_DRIVER, FirstPassCameraDriver);
graph.add_node(FIRST_PASS_DRIVER, driver);
// The first pass's dependencies include those of the main pass.
graph
@ -61,30 +62,44 @@ fn main() {
}
// Add 3D render phases for FIRST_PASS_CAMERA.
fn extract_first_pass_camera_phases(mut commands: Commands, active_cameras: Res<ActiveCameras>) {
if let Some(camera) = active_cameras.get(FIRST_PASS_CAMERA) {
if let Some(entity) = camera.entity {
commands.get_or_spawn(entity).insert_bundle((
RenderPhase::<Opaque3d>::default(),
RenderPhase::<AlphaMask3d>::default(),
RenderPhase::<Transparent3d>::default(),
));
}
fn extract_first_pass_camera_phases(
mut commands: Commands,
active: Res<ActiveCamera<FirstPassCamera>>,
) {
if let Some(entity) = active.get() {
commands.get_or_spawn(entity).insert_bundle((
RenderPhase::<Opaque3d>::default(),
RenderPhase::<AlphaMask3d>::default(),
RenderPhase::<Transparent3d>::default(),
));
}
}
// A node for the first pass camera that runs draw_3d_graph with this camera.
struct FirstPassCameraDriver;
impl bevy::render::render_graph::Node for FirstPassCameraDriver {
struct FirstPassCameraDriver {
query: QueryState<Entity, With<FirstPassCamera>>,
}
impl FirstPassCameraDriver {
pub fn new(render_world: &mut World) -> Self {
Self {
query: QueryState::new(render_world),
}
}
}
impl Node for FirstPassCameraDriver {
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run(
&self,
graph: &mut RenderGraphContext,
_render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let extracted_cameras = world.resource::<ExtractedCameraNames>();
if let Some(camera_3d) = extracted_cameras.entities.get(FIRST_PASS_CAMERA) {
graph.run_sub_graph(draw_3d_graph::NAME, vec![SlotValue::Entity(*camera_3d)])?;
for camera in self.query.iter_manual(world) {
graph.run_sub_graph(draw_3d_graph::NAME, vec![SlotValue::Entity(camera)])?;
}
Ok(())
}
@ -102,7 +117,6 @@ fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut active_cameras: ResMut<ActiveCameras>,
mut images: ResMut<Assets<Image>>,
mut clear_colors: ResMut<RenderTargetClearColors>,
) {
@ -165,17 +179,15 @@ fn setup(
// First pass camera
let render_target = RenderTarget::Image(image_handle);
clear_colors.insert(render_target.clone(), Color::WHITE);
active_cameras.add(FIRST_PASS_CAMERA);
commands
.spawn_bundle(PerspectiveCameraBundle {
.spawn_bundle(PerspectiveCameraBundle::<FirstPassCamera> {
camera: Camera {
name: Some(FIRST_PASS_CAMERA.to_string()),
target: render_target,
..default()
},
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0))
.looking_at(Vec3::default(), Vec3::Y),
..default()
..PerspectiveCameraBundle::new()
})
.insert(first_pass_layer);
// NOTE: omitting the RenderLayers component for this camera may cause a validation error:

View file

@ -1,6 +1,4 @@
use bevy::{
core::FixedTimestep, ecs::schedule::SystemSet, prelude::*, render::camera::CameraPlugin,
};
use bevy::{core::FixedTimestep, ecs::schedule::SystemSet, prelude::*, render::camera::Camera3d};
use rand::Rng;
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
@ -257,7 +255,7 @@ fn focus_camera(
time: Res<Time>,
mut game: ResMut<Game>,
mut transforms: QuerySet<(
QueryState<(&mut Transform, &Camera)>,
QueryState<&mut Transform, With<Camera3d>>,
QueryState<&Transform>,
)>,
) {
@ -292,10 +290,8 @@ fn focus_camera(
game.camera_is_focus += camera_motion;
}
// look at that new camera's actual focus
for (mut transform, camera) in transforms.q0().iter_mut() {
if camera.name == Some(CameraPlugin::CAMERA_3D.to_string()) {
*transform = transform.looking_at(game.camera_is_focus, Vec3::Y);
}
for mut transform in transforms.q0().iter_mut() {
*transform = transform.looking_at(game.camera_is_focus, Vec3::Y);
}
}

View file

@ -1,54 +1,19 @@
use bevy::{
core_pipeline::{draw_3d_graph, node, AlphaMask3d, Opaque3d, Transparent3d},
prelude::*,
render::{
camera::{ActiveCameras, ExtractedCameraNames, RenderTarget},
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotValue},
render_phase::RenderPhase,
renderer::RenderContext,
RenderApp, RenderStage,
},
render::camera::RenderTarget,
window::{CreateWindow, PresentMode, WindowId},
};
/// This example creates a second window and draws a mesh from two different cameras, one in each window
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins)
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_startup_system(create_new_window);
let render_app = app.sub_app_mut(RenderApp);
render_app.add_system_to_stage(RenderStage::Extract, extract_secondary_camera_phases);
let mut graph = render_app.world.resource_mut::<RenderGraph>();
graph.add_node(SECONDARY_PASS_DRIVER, SecondaryCameraDriver);
graph
.add_node_edge(node::MAIN_PASS_DEPENDENCIES, SECONDARY_PASS_DRIVER)
.unwrap();
app.run();
.add_startup_system(create_new_window)
.run();
}
fn extract_secondary_camera_phases(mut commands: Commands, active_cameras: Res<ActiveCameras>) {
if let Some(secondary) = active_cameras.get(SECONDARY_CAMERA_NAME) {
if let Some(entity) = secondary.entity {
commands.get_or_spawn(entity).insert_bundle((
RenderPhase::<Opaque3d>::default(),
RenderPhase::<AlphaMask3d>::default(),
RenderPhase::<Transparent3d>::default(),
));
}
}
}
const SECONDARY_CAMERA_NAME: &str = "Secondary";
const SECONDARY_PASS_DRIVER: &str = "secondary_pass_driver";
fn create_new_window(
mut create_window_events: EventWriter<CreateWindow>,
mut commands: Commands,
mut active_cameras: ResMut<ActiveCameras>,
) {
fn create_new_window(mut create_window_events: EventWriter<CreateWindow>, mut commands: Commands) {
let window_id = WindowId::new();
// sends out a "CreateWindow" event, which will be received by the windowing backend
@ -62,37 +27,16 @@ fn create_new_window(
..default()
},
});
// second window camera
commands.spawn_bundle(PerspectiveCameraBundle {
camera: Camera {
target: RenderTarget::Window(window_id),
name: Some(SECONDARY_CAMERA_NAME.into()),
..default()
},
transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
active_cameras.add(SECONDARY_CAMERA_NAME);
}
struct SecondaryCameraDriver;
impl Node for SecondaryCameraDriver {
fn run(
&self,
graph: &mut RenderGraphContext,
_render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let extracted_cameras = world.resource::<ExtractedCameraNames>();
if let Some(camera_3d) = extracted_cameras.entities.get(SECONDARY_CAMERA_NAME) {
graph.run_sub_graph(
crate::draw_3d_graph::NAME,
vec![SlotValue::Entity(*camera_3d)],
)?;
}
Ok(())
}
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {