Split Camera.hdr out into a new component (#18873)

# Objective

- Simplify `Camera` initialization
- allow effects to require HDR

## Solution

- Split out `Camera.hdr` into a marker `Hdr` component

## Testing

- ran `bloom_3d` example

---

## Showcase

```rs
// before
commands.spawn((
  Camera3d
  Camera {
    hdr: true
    ..Default::default()
  }
))

// after
commands.spawn((Camera3d, Hdr));

// other rendering components can require that the camera enables hdr!
// currently implemented for Bloom, AutoExposure, and Atmosphere.
#[require(Hdr)]
pub struct Bloom;
```
This commit is contained in:
Emerson Coskey 2025-05-26 12:24:45 -07:00 committed by GitHub
parent cedf8d357a
commit 7ab00ca185
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 109 additions and 153 deletions

View File

@ -5,7 +5,7 @@ use bevy_asset::Handle;
use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
use bevy_image::Image; use bevy_image::Image;
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::extract_component::ExtractComponent; use bevy_render::{extract_component::ExtractComponent, view::Hdr};
use bevy_utils::default; use bevy_utils::default;
/// Component that enables auto exposure for an HDR-enabled 2d or 3d camera. /// Component that enables auto exposure for an HDR-enabled 2d or 3d camera.
@ -25,6 +25,7 @@ use bevy_utils::default;
/// **Auto Exposure requires compute shaders and is not compatible with WebGL2.** /// **Auto Exposure requires compute shaders and is not compatible with WebGL2.**
#[derive(Component, Clone, Reflect, ExtractComponent)] #[derive(Component, Clone, Reflect, ExtractComponent)]
#[reflect(Component, Default, Clone)] #[reflect(Component, Default, Clone)]
#[require(Hdr)]
pub struct AutoExposure { pub struct AutoExposure {
/// The range of exposure values for the histogram. /// The range of exposure values for the histogram.
/// ///

View File

@ -1,8 +1,12 @@
use super::downsampling_pipeline::BloomUniforms; use super::downsampling_pipeline::BloomUniforms;
use bevy_ecs::{prelude::Component, query::QueryItem, reflect::ReflectComponent}; use bevy_ecs::{
prelude::Component,
query::{QueryItem, With},
reflect::ReflectComponent,
};
use bevy_math::{AspectRatio, URect, UVec4, Vec2, Vec4}; use bevy_math::{AspectRatio, URect, UVec4, Vec2, Vec4};
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{extract_component::ExtractComponent, prelude::Camera}; use bevy_render::{extract_component::ExtractComponent, prelude::Camera, view::Hdr};
/// Applies a bloom effect to an HDR-enabled 2d or 3d camera. /// Applies a bloom effect to an HDR-enabled 2d or 3d camera.
/// ///
@ -26,6 +30,7 @@ use bevy_render::{extract_component::ExtractComponent, prelude::Camera};
/// used in Bevy as well as a visualization of the curve's respective scattering profile. /// used in Bevy as well as a visualization of the curve's respective scattering profile.
#[derive(Component, Reflect, Clone)] #[derive(Component, Reflect, Clone)]
#[reflect(Component, Default, Clone)] #[reflect(Component, Default, Clone)]
#[require(Hdr)]
pub struct Bloom { pub struct Bloom {
/// Controls the baseline of how much the image is scattered (default: 0.15). /// Controls the baseline of how much the image is scattered (default: 0.15).
/// ///
@ -219,7 +224,7 @@ pub enum BloomCompositeMode {
impl ExtractComponent for Bloom { impl ExtractComponent for Bloom {
type QueryData = (&'static Self, &'static Camera); type QueryData = (&'static Self, &'static Camera);
type QueryFilter = (); type QueryFilter = With<Hdr>;
type Out = (Self, BloomUniforms); type Out = (Self, BloomUniforms);
fn extract_component((bloom, camera): QueryItem<'_, Self::QueryData>) -> Option<Self::Out> { fn extract_component((bloom, camera): QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
@ -228,9 +233,8 @@ impl ExtractComponent for Bloom {
camera.physical_viewport_size(), camera.physical_viewport_size(),
camera.physical_target_size(), camera.physical_target_size(),
camera.is_active, camera.is_active,
camera.hdr,
) { ) {
(Some(URect { min: origin, .. }), Some(size), Some(target_size), true, true) (Some(URect { min: origin, .. }), Some(size), Some(target_size), true)
if size.x != 0 && size.y != 0 => if size.x != 0 && size.y != 0 =>
{ {
let threshold = bloom.prefilter.threshold; let threshold = bloom.prefilter.threshold;

View File

@ -50,6 +50,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{ use bevy_render::{
extract_component::UniformComponentPlugin, extract_component::UniformComponentPlugin,
render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines}, render_resource::{DownlevelFlags, ShaderType, SpecializedRenderPipelines},
view::Hdr,
}; };
use bevy_render::{ use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin}, extract_component::{ExtractComponent, ExtractComponentPlugin},
@ -246,7 +247,7 @@ impl Plugin for AtmospherePlugin {
/// from the planet's surface, ozone only exists in a band centered at a fairly /// from the planet's surface, ozone only exists in a band centered at a fairly
/// high altitude. /// high altitude.
#[derive(Clone, Component, Reflect, ShaderType)] #[derive(Clone, Component, Reflect, ShaderType)]
#[require(AtmosphereSettings)] #[require(AtmosphereSettings, Hdr)]
#[reflect(Clone, Default)] #[reflect(Clone, Default)]
pub struct Atmosphere { pub struct Atmosphere {
/// Radius of the planet /// Radius of the planet

View File

@ -325,7 +325,6 @@ pub(crate) struct RenderSkyPipelineId(pub CachedRenderPipelineId);
#[derive(Copy, Clone, Hash, PartialEq, Eq)] #[derive(Copy, Clone, Hash, PartialEq, Eq)]
pub(crate) struct RenderSkyPipelineKey { pub(crate) struct RenderSkyPipelineKey {
pub msaa_samples: u32, pub msaa_samples: u32,
pub hdr: bool,
pub dual_source_blending: bool, pub dual_source_blending: bool,
} }
@ -338,9 +337,6 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts {
if key.msaa_samples > 1 { if key.msaa_samples > 1 {
shader_defs.push("MULTISAMPLED".into()); shader_defs.push("MULTISAMPLED".into());
} }
if key.hdr {
shader_defs.push("TONEMAP_IN_SHADER".into());
}
if key.dual_source_blending { if key.dual_source_blending {
shader_defs.push("DUAL_SOURCE_BLENDING".into()); shader_defs.push("DUAL_SOURCE_BLENDING".into());
} }
@ -394,20 +390,19 @@ impl SpecializedRenderPipeline for RenderSkyBindGroupLayouts {
} }
pub(super) fn queue_render_sky_pipelines( pub(super) fn queue_render_sky_pipelines(
views: Query<(Entity, &Camera, &Msaa), With<Atmosphere>>, views: Query<(Entity, &Msaa), (With<Camera>, With<Atmosphere>)>,
pipeline_cache: Res<PipelineCache>, pipeline_cache: Res<PipelineCache>,
layouts: Res<RenderSkyBindGroupLayouts>, layouts: Res<RenderSkyBindGroupLayouts>,
mut specializer: ResMut<SpecializedRenderPipelines<RenderSkyBindGroupLayouts>>, mut specializer: ResMut<SpecializedRenderPipelines<RenderSkyBindGroupLayouts>>,
render_device: Res<RenderDevice>, render_device: Res<RenderDevice>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, camera, msaa) in &views { for (entity, msaa) in &views {
let id = specializer.specialize( let id = specializer.specialize(
&pipeline_cache, &pipeline_cache,
&layouts, &layouts,
RenderSkyPipelineKey { RenderSkyPipelineKey {
msaa_samples: msaa.samples(), msaa_samples: msaa.samples(),
hdr: camera.hdr,
dual_source_blending: render_device dual_source_blending: render_device
.features() .features()
.contains(WgpuFeatures::DUAL_SOURCE_BLENDING), .contains(WgpuFeatures::DUAL_SOURCE_BLENDING),

View File

@ -13,7 +13,7 @@ use crate::{
sync_world::{RenderEntity, SyncToRenderWorld}, sync_world::{RenderEntity, SyncToRenderWorld},
texture::GpuImage, texture::GpuImage,
view::{ view::{
ColorGrading, ExtractedView, ExtractedWindows, Msaa, NoIndirectDrawing, RenderLayers, ColorGrading, ExtractedView, ExtractedWindows, Hdr, Msaa, NoIndirectDrawing, RenderLayers,
RenderVisibleEntities, RetainedViewEntity, ViewUniformOffset, Visibility, VisibleEntities, RenderVisibleEntities, RetainedViewEntity, ViewUniformOffset, Visibility, VisibleEntities,
}, },
Extract, Extract,
@ -356,9 +356,6 @@ pub struct Camera {
pub computed: ComputedCameraValues, pub computed: ComputedCameraValues,
/// The "target" that this camera will render to. /// The "target" that this camera will render to.
pub target: RenderTarget, pub target: RenderTarget,
/// If this is set to `true`, the camera will use an intermediate "high dynamic range" render texture.
/// This allows rendering with a wider range of lighting values.
pub hdr: bool,
// todo: reflect this when #6042 lands // todo: reflect this when #6042 lands
/// The [`CameraOutputMode`] for this camera. /// The [`CameraOutputMode`] for this camera.
#[reflect(ignore, clone)] #[reflect(ignore, clone)]
@ -389,7 +386,6 @@ impl Default for Camera {
computed: Default::default(), computed: Default::default(),
target: Default::default(), target: Default::default(),
output_mode: Default::default(), output_mode: Default::default(),
hdr: false,
msaa_writeback: true, msaa_writeback: true,
clear_color: Default::default(), clear_color: Default::default(),
sub_camera_view: None, sub_camera_view: None,
@ -1101,6 +1097,7 @@ pub fn extract_cameras(
&GlobalTransform, &GlobalTransform,
&VisibleEntities, &VisibleEntities,
&Frustum, &Frustum,
Has<Hdr>,
Option<&ColorGrading>, Option<&ColorGrading>,
Option<&Exposure>, Option<&Exposure>,
Option<&TemporalJitter>, Option<&TemporalJitter>,
@ -1122,6 +1119,7 @@ pub fn extract_cameras(
transform, transform,
visible_entities, visible_entities,
frustum, frustum,
hdr,
color_grading, color_grading,
exposure, exposure,
temporal_jitter, temporal_jitter,
@ -1200,14 +1198,14 @@ pub fn extract_cameras(
exposure: exposure exposure: exposure
.map(Exposure::exposure) .map(Exposure::exposure)
.unwrap_or_else(|| Exposure::default().exposure()), .unwrap_or_else(|| Exposure::default().exposure()),
hdr: camera.hdr, hdr,
}, },
ExtractedView { ExtractedView {
retained_view_entity: RetainedViewEntity::new(main_entity.into(), None, 0), retained_view_entity: RetainedViewEntity::new(main_entity.into(), None, 0),
clip_from_view: camera.clip_from_view(), clip_from_view: camera.clip_from_view(),
world_from_view: *transform, world_from_view: *transform,
clip_from_world: None, clip_from_world: None,
hdr: camera.hdr, hdr,
viewport: UVec4::new( viewport: UVec4::new(
viewport_origin.x, viewport_origin.x,
viewport_origin.y, viewport_origin.y,

View File

@ -114,6 +114,7 @@ impl Plugin for ViewPlugin {
.register_type::<OcclusionCulling>() .register_type::<OcclusionCulling>()
// NOTE: windows.is_changed() handles cases where a window was resized // NOTE: windows.is_changed() handles cases where a window was resized
.add_plugins(( .add_plugins((
ExtractComponentPlugin::<Hdr>::default(),
ExtractComponentPlugin::<Msaa>::default(), ExtractComponentPlugin::<Msaa>::default(),
ExtractComponentPlugin::<OcclusionCulling>::default(), ExtractComponentPlugin::<OcclusionCulling>::default(),
VisibilityPlugin, VisibilityPlugin,
@ -199,6 +200,16 @@ impl Msaa {
} }
} }
/// If this component is added to a camera, the camera will use an intermediate "high dynamic range" render texture.
/// This allows rendering with a wider range of lighting values. However, this does *not* affect
/// whether the camera will render with hdr display output (which bevy does not support currently)
/// and only affects the intermediate render texture.
#[derive(
Component, Default, Copy, Clone, ExtractComponent, Reflect, PartialEq, Eq, Hash, Debug,
)]
#[reflect(Component, Default, PartialEq, Hash, Debug)]
pub struct Hdr;
/// An identifier for a view that is stable across frames. /// An identifier for a view that is stable across frames.
/// ///
/// We can't use [`Entity`] for this because render world entities aren't /// We can't use [`Entity`] for this because render world entities aren't

View File

@ -28,7 +28,7 @@ use bevy_render::render_phase::ViewSortedRenderPhases;
use bevy_render::renderer::RenderContext; use bevy_render::renderer::RenderContext;
use bevy_render::sync_world::MainEntity; use bevy_render::sync_world::MainEntity;
use bevy_render::texture::TRANSPARENT_IMAGE_HANDLE; use bevy_render::texture::TRANSPARENT_IMAGE_HANDLE;
use bevy_render::view::RetainedViewEntity; use bevy_render::view::{Hdr, RetainedViewEntity};
use bevy_render::{ use bevy_render::{
camera::Camera, camera::Camera,
render_asset::RenderAssets, render_asset::RenderAssets,
@ -662,6 +662,7 @@ pub fn extract_ui_camera_view(
Entity, Entity,
RenderEntity, RenderEntity,
&Camera, &Camera,
Has<Hdr>,
Option<&UiAntiAlias>, Option<&UiAntiAlias>,
Option<&BoxShadowSamples>, Option<&BoxShadowSamples>,
), ),
@ -672,7 +673,7 @@ pub fn extract_ui_camera_view(
) { ) {
live_entities.clear(); live_entities.clear();
for (main_entity, render_entity, camera, ui_anti_alias, shadow_samples) in &query { for (main_entity, render_entity, camera, hdr, ui_anti_alias, shadow_samples) in &query {
// ignore inactive cameras // ignore inactive cameras
if !camera.is_active { if !camera.is_active {
commands commands
@ -708,7 +709,7 @@ pub fn extract_ui_camera_view(
UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET, UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET,
), ),
clip_from_world: None, clip_from_world: None,
hdr: camera.hdr, hdr,
viewport: UVec4::from(( viewport: UVec4::from((
physical_viewport_rect.min, physical_viewport_rect.min,
physical_viewport_rect.size(), physical_viewport_rect.size(),

View File

@ -25,7 +25,6 @@ fn setup(
commands.spawn(( commands.spawn((
Camera2d, Camera2d,
Camera { Camera {
hdr: true, // 1. HDR is required for bloom
clear_color: ClearColorConfig::Custom(Color::BLACK), clear_color: ClearColorConfig::Custom(Color::BLACK),
..default() ..default()
}, },

View File

@ -17,6 +17,7 @@ use bevy::{
camera::TemporalJitter, camera::TemporalJitter,
render_asset::RenderAssetUsages, render_asset::RenderAssetUsages,
render_resource::{Extent3d, TextureDimension, TextureFormat}, render_resource::{Extent3d, TextureDimension, TextureFormat},
view::Hdr,
}, },
}; };
@ -300,10 +301,7 @@ fn setup(
// Camera // Camera
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera { Hdr,
hdr: true,
..default()
},
Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
ContrastAdaptiveSharpening { ContrastAdaptiveSharpening {
enabled: false, enabled: false,

View File

@ -20,11 +20,6 @@ fn main() {
fn setup_camera_fog(mut commands: Commands) { fn setup_camera_fog(mut commands: Commands) {
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
// HDR is required for atmospheric scattering to be properly applied to the scene
Camera {
hdr: true,
..default()
},
Transform::from_xyz(-1.2, 0.15, 0.0).looking_at(Vec3::Y * 0.1, Vec3::Y), Transform::from_xyz(-1.2, 0.15, 0.0).looking_at(Vec3::Y * 0.1, Vec3::Y),
// This is the component that enables atmospheric scattering for a camera // This is the component that enables atmospheric scattering for a camera
Atmosphere::EARTH, Atmosphere::EARTH,
@ -36,7 +31,7 @@ fn setup_camera_fog(mut commands: Commands) {
scene_units_to_m: 1e+4, scene_units_to_m: 1e+4,
..Default::default() ..Default::default()
}, },
// The directional light illuminance used in this scene // The directional light illuminance used in this scene
// (the one recommended for use with this feature) is // (the one recommended for use with this feature) is
// quite bright, so raising the exposure compensation helps // quite bright, so raising the exposure compensation helps
// bring the scene to a nicer brightness range. // bring the scene to a nicer brightness range.

View File

@ -40,10 +40,6 @@ fn setup(
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera {
hdr: true,
..default()
},
Transform::from_xyz(1.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(1.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
AutoExposure { AutoExposure {
metering_mask: metering_mask.clone(), metering_mask: metering_mask.clone(),

View File

@ -10,7 +10,7 @@
//! | `Spacebar` | Toggle Unlit | //! | `Spacebar` | Toggle Unlit |
//! | `C` | Randomize Colors | //! | `C` | Randomize Colors |
use bevy::{color::palettes::css::ORANGE, prelude::*}; use bevy::{color::palettes::css::ORANGE, prelude::*, render::view::Hdr};
use rand::random; use rand::random;
fn main() { fn main() {
@ -149,6 +149,7 @@ fn setup(
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Transform::from_xyz(0.0, 2.5, 10.0).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(0.0, 2.5, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
Hdr,
// Unfortunately, MSAA and HDR are not supported simultaneously under WebGL. // Unfortunately, MSAA and HDR are not supported simultaneously under WebGL.
// Since this example uses HDR, we must disable MSAA for Wasm builds, at least // Since this example uses HDR, we must disable MSAA for Wasm builds, at least
// until WebGPU is ready and no longer behind a feature flag in Web browsers. // until WebGPU is ready and no longer behind a feature flag in Web browsers.
@ -249,13 +250,23 @@ impl Default for ExampleState {
fn example_control_system( fn example_control_system(
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
controllable: Query<(&MeshMaterial3d<StandardMaterial>, &ExampleControls)>, controllable: Query<(&MeshMaterial3d<StandardMaterial>, &ExampleControls)>,
camera: Single<(&mut Camera, &mut Transform, &GlobalTransform), With<Camera3d>>, camera: Single<
(
Entity,
&mut Camera,
&mut Transform,
&GlobalTransform,
Has<Hdr>,
),
With<Camera3d>,
>,
mut labels: Query<(&mut Node, &ExampleLabel)>, mut labels: Query<(&mut Node, &ExampleLabel)>,
mut display: Single<&mut Text, With<ExampleDisplay>>, mut display: Single<&mut Text, With<ExampleDisplay>>,
labeled: Query<&GlobalTransform>, labeled: Query<&GlobalTransform>,
mut state: Local<ExampleState>, mut state: Local<ExampleState>,
time: Res<Time>, time: Res<Time>,
input: Res<ButtonInput<KeyCode>>, input: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
) { ) {
if input.pressed(KeyCode::ArrowUp) { if input.pressed(KeyCode::ArrowUp) {
state.alpha = (state.alpha + time.delta_secs()).min(1.0); state.alpha = (state.alpha + time.delta_secs()).min(1.0);
@ -289,10 +300,14 @@ fn example_control_system(
} }
} }
let (mut camera, mut camera_transform, camera_global_transform) = camera.into_inner(); let (entity, camera, mut camera_transform, camera_global_transform, hdr) = camera.into_inner();
if input.just_pressed(KeyCode::KeyH) { if input.just_pressed(KeyCode::KeyH) {
camera.hdr = !camera.hdr; if hdr {
commands.entity(entity).remove::<Hdr>();
} else {
commands.entity(entity).insert(Hdr);
}
} }
let rotation = if input.pressed(KeyCode::ArrowLeft) { let rotation = if input.pressed(KeyCode::ArrowLeft) {
@ -318,7 +333,7 @@ fn example_control_system(
display.0 = format!( display.0 = format!(
" HDR: {}\nAlpha: {:.2}", " HDR: {}\nAlpha: {:.2}",
if camera.hdr { "ON " } else { "OFF" }, if hdr { "ON " } else { "OFF" },
state.alpha state.alpha
); );
} }

View File

@ -29,17 +29,16 @@ fn setup_scene(
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera { Camera {
hdr: true, // 1. HDR is required for bloom
clear_color: ClearColorConfig::Custom(Color::BLACK), clear_color: ClearColorConfig::Custom(Color::BLACK),
..default() ..default()
}, },
Tonemapping::TonyMcMapface, // 2. Using a tonemapper that desaturates to white is recommended Tonemapping::TonyMcMapface, // 1. Using a tonemapper that desaturates to white is recommended
Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
Bloom::NATURAL, // 3. Enable bloom for the camera Bloom::NATURAL, // 2. Enable bloom for the camera
)); ));
let material_emissive1 = materials.add(StandardMaterial { let material_emissive1 = materials.add(StandardMaterial {
emissive: LinearRgba::rgb(0.0, 0.0, 150.0), // 4. Put something bright in a dark environment to see the effect emissive: LinearRgba::rgb(0.0, 0.0, 150.0), // 3. Put something bright in a dark environment to see the effect
..default() ..default()
}); });
let material_emissive2 = materials.add(StandardMaterial { let material_emissive2 = materials.add(StandardMaterial {

View File

@ -25,6 +25,7 @@ use bevy::{
image::ImageLoaderSettings, image::ImageLoaderSettings,
math::vec3, math::vec3,
prelude::*, prelude::*,
render::view::Hdr,
}; };
/// The size of each sphere. /// The size of each sphere.
@ -191,10 +192,7 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
commands commands
.spawn(( .spawn((
Camera3d::default(), Camera3d::default(),
Camera { Hdr,
hdr: true,
..default()
},
Projection::Perspective(PerspectiveProjection { Projection::Perspective(PerspectiveProjection {
fov: 27.0 / 180.0 * PI, fov: 27.0 / 180.0 * PI,
..default() ..default()

View File

@ -9,7 +9,7 @@ use bevy::{
ecs::system::EntityCommands, ecs::system::EntityCommands,
pbr::CascadeShadowConfigBuilder, pbr::CascadeShadowConfigBuilder,
prelude::*, prelude::*,
render::view::{ColorGrading, ColorGradingGlobal, ColorGradingSection}, render::view::{ColorGrading, ColorGradingGlobal, ColorGradingSection, Hdr},
}; };
use std::fmt::Display; use std::fmt::Display;
@ -334,10 +334,7 @@ fn add_text<'a>(
fn add_camera(commands: &mut Commands, asset_server: &AssetServer, color_grading: ColorGrading) { fn add_camera(commands: &mut Commands, asset_server: &AssetServer, color_grading: ColorGrading) {
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera { Hdr,
hdr: true,
..default()
},
Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
color_grading, color_grading,
DistanceFog { DistanceFog {

View File

@ -33,11 +33,6 @@ fn setup(
) { ) {
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera {
// Deferred both supports both hdr: true and hdr: false
hdr: false,
..default()
},
Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
// MSAA needs to be off for Deferred rendering // MSAA needs to be off for Deferred rendering
Msaa::Off, Msaa::Off,

View File

@ -74,10 +74,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: R
let mut camera = commands.spawn(( let mut camera = commands.spawn((
Camera3d::default(), Camera3d::default(),
Transform::from_xyz(0.0, 4.5, 8.25).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(0.0, 4.5, 8.25).looking_at(Vec3::ZERO, Vec3::Y),
Camera {
hdr: true,
..default()
},
Tonemapping::TonyMcMapface, Tonemapping::TonyMcMapface,
Bloom::NATURAL, Bloom::NATURAL,
)); ));

View File

@ -9,6 +9,7 @@ use bevy::{
math::vec3, math::vec3,
pbr::{FogVolume, VolumetricFog, VolumetricLight}, pbr::{FogVolume, VolumetricFog, VolumetricLight},
prelude::*, prelude::*,
render::view::Hdr,
}; };
/// Entry point. /// Entry point.
@ -58,10 +59,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Transform::from_xyz(-0.75, 1.0, 2.0).looking_at(vec3(0.0, 0.0, 0.0), Vec3::Y), Transform::from_xyz(-0.75, 1.0, 2.0).looking_at(vec3(0.0, 0.0, 0.0), Vec3::Y),
Camera { Hdr,
hdr: true,
..default()
},
VolumetricFog { VolumetricFog {
// Make this relatively high in order to increase the fog quality. // Make this relatively high in order to increase the fog quality.
step_count: 64, step_count: 64,

View File

@ -104,10 +104,6 @@ fn setup(
// Camera // Camera
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera {
hdr: true,
..default()
},
Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::ZERO, Vec3::Y),
Tonemapping::TonyMcMapface, Tonemapping::TonyMcMapface,
Bloom::default(), Bloom::default(),

View File

@ -6,6 +6,7 @@ use std::f32::consts::PI;
use bevy::{ use bevy::{
core_pipeline::post_process::ChromaticAberration, pbr::CascadeShadowConfigBuilder, prelude::*, core_pipeline::post_process::ChromaticAberration, pbr::CascadeShadowConfigBuilder, prelude::*,
render::view::Hdr,
}; };
/// The number of units per frame to add to or subtract from intensity when the /// The number of units per frame to add to or subtract from intensity when the
@ -60,10 +61,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: R
fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) { fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera { Hdr,
hdr: true,
..default()
},
Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
DistanceFog { DistanceFog {
color: Color::srgb_u8(43, 44, 47), color: Color::srgb_u8(43, 44, 47),

View File

@ -6,7 +6,7 @@
//! //!
//! Reflection probes don't work on WebGL 2 or WebGPU. //! Reflection probes don't work on WebGL 2 or WebGPU.
use bevy::{core_pipeline::Skybox, prelude::*}; use bevy::{core_pipeline::Skybox, prelude::*, render::view::Hdr};
use std::{ use std::{
f32::consts::PI, f32::consts::PI,
@ -105,11 +105,8 @@ fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
fn spawn_camera(commands: &mut Commands) { fn spawn_camera(commands: &mut Commands) {
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera {
hdr: true,
..default()
},
Transform::from_xyz(-6.483, 0.325, 4.381).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(-6.483, 0.325, 4.381).looking_at(Vec3::ZERO, Vec3::Y),
Hdr,
)); ));
} }

View File

@ -7,6 +7,7 @@ use bevy::{
core_pipeline::{tonemapping::Tonemapping::AcesFitted, Skybox}, core_pipeline::{tonemapping::Tonemapping::AcesFitted, Skybox},
image::ImageLoaderSettings, image::ImageLoaderSettings,
prelude::*, prelude::*,
render::view::Hdr,
}; };
/// Entry point. /// Entry point.
@ -95,10 +96,7 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
commands commands
.spawn(( .spawn((
Camera3d::default(), Camera3d::default(),
Camera { Hdr,
hdr: true,
..default()
},
Projection::Perspective(PerspectiveProjection { Projection::Perspective(PerspectiveProjection {
fov: 27.0 / 180.0 * PI, fov: 27.0 / 180.0 * PI,
..default() ..default()

View File

@ -49,10 +49,6 @@ fn setup(
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Transform::from_xyz(0.0, 2.0, 0.0).looking_at(Vec3::new(-5.0, 3.5, -6.0), Vec3::Y), Transform::from_xyz(0.0, 2.0, 0.0).looking_at(Vec3::new(-5.0, 3.5, -6.0), Vec3::Y),
Camera {
hdr: true,
..default()
},
Msaa::Off, Msaa::Off,
TemporalAntiAliasing::default(), TemporalAntiAliasing::default(),
Bloom::default(), Bloom::default(),

View File

@ -2,7 +2,7 @@
use std::f32::consts::PI; use std::f32::consts::PI;
use bevy::{color::palettes::css::WHITE, core_pipeline::Skybox, prelude::*}; use bevy::{color::palettes::css::WHITE, core_pipeline::Skybox, prelude::*, render::view::Hdr};
/// The camera rotation speed in radians per frame. /// The camera rotation speed in radians per frame.
const ROTATION_SPEED: f32 = 0.005; const ROTATION_SPEED: f32 = 0.005;
@ -82,10 +82,7 @@ fn setup(
// Spawns a camera. // Spawns a camera.
commands.spawn(( commands.spawn((
Transform::from_xyz(-2.0, 0.0, 3.5).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(-2.0, 0.0, 3.5).looking_at(Vec3::ZERO, Vec3::Y),
Camera { Hdr,
hdr: true,
..default()
},
Camera3d::default(), Camera3d::default(),
Skybox { Skybox {
image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),

View File

@ -7,6 +7,7 @@ use bevy::{
math::ops, math::ops,
pbr::NotShadowCaster, pbr::NotShadowCaster,
prelude::*, prelude::*,
render::view::Hdr,
}; };
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
@ -119,10 +120,7 @@ fn setup(
// camera // camera
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera { Hdr,
hdr: true,
..default()
},
Transform::from_xyz(-4.0, 5.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(-4.0, 5.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
)); ));

View File

@ -5,7 +5,7 @@ use bevy::{
math::ops, math::ops,
pbr::{ScreenSpaceAmbientOcclusion, ScreenSpaceAmbientOcclusionQualityLevel}, pbr::{ScreenSpaceAmbientOcclusion, ScreenSpaceAmbientOcclusionQualityLevel},
prelude::*, prelude::*,
render::camera::TemporalJitter, render::{camera::TemporalJitter, view::Hdr},
}; };
use std::f32::consts::PI; use std::f32::consts::PI;
@ -28,11 +28,8 @@ fn setup(
) { ) {
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera {
hdr: true,
..default()
},
Transform::from_xyz(-2.0, 2.0, -2.0).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(-2.0, 2.0, -2.0).looking_at(Vec3::ZERO, Vec3::Y),
Hdr,
Msaa::Off, Msaa::Off,
ScreenSpaceAmbientOcclusion::default(), ScreenSpaceAmbientOcclusion::default(),
TemporalAntiAliasing::default(), TemporalAntiAliasing::default(),

View File

@ -16,7 +16,10 @@ use bevy::{
DefaultOpaqueRendererMethod, ExtendedMaterial, MaterialExtension, ScreenSpaceReflections, DefaultOpaqueRendererMethod, ExtendedMaterial, MaterialExtension, ScreenSpaceReflections,
}, },
prelude::*, prelude::*,
render::render_resource::{AsBindGroup, ShaderRef, ShaderType}, render::{
render_resource::{AsBindGroup, ShaderRef, ShaderType},
view::Hdr,
},
}; };
/// This example uses a shader source file from the assets subdirectory /// This example uses a shader source file from the assets subdirectory
@ -227,10 +230,7 @@ fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
.spawn(( .spawn((
Camera3d::default(), Camera3d::default(),
Transform::from_translation(vec3(-1.25, 2.25, 4.5)).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_translation(vec3(-1.25, 2.25, 4.5)).looking_at(Vec3::ZERO, Vec3::Y),
Camera { Hdr,
hdr: true,
..default()
},
Msaa::Off, Msaa::Off,
)) ))
.insert(EnvironmentMapLight { .insert(EnvironmentMapLight {

View File

@ -9,7 +9,7 @@ use bevy::{
reflect::TypePath, reflect::TypePath,
render::{ render::{
render_resource::{AsBindGroup, ShaderRef}, render_resource::{AsBindGroup, ShaderRef},
view::{ColorGrading, ColorGradingGlobal, ColorGradingSection}, view::{ColorGrading, ColorGradingGlobal, ColorGradingSection, Hdr},
}, },
}; };
use std::f32::consts::PI; use std::f32::consts::PI;
@ -65,10 +65,7 @@ fn setup(
// camera // camera
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera { Hdr,
hdr: true,
..default()
},
camera_transform.0, camera_transform.0,
DistanceFog { DistanceFog {
color: Color::srgb_u8(43, 44, 47), color: Color::srgb_u8(43, 44, 47),

View File

@ -31,7 +31,7 @@ use bevy::{
prelude::*, prelude::*,
render::{ render::{
camera::{Exposure, TemporalJitter}, camera::{Exposure, TemporalJitter},
view::{ColorGrading, ColorGradingGlobal}, view::{ColorGrading, ColorGradingGlobal, Hdr},
}, },
}; };
@ -303,10 +303,6 @@ fn setup(
// Camera // Camera
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera {
hdr: true,
..default()
},
Transform::from_xyz(1.0, 1.8, 7.0).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(1.0, 1.8, 7.0).looking_at(Vec3::ZERO, Vec3::Y),
ColorGrading { ColorGrading {
global: ColorGradingGlobal { global: ColorGradingGlobal {
@ -387,11 +383,11 @@ fn example_control_system(
camera: Single< camera: Single<
( (
Entity, Entity,
&mut Camera,
&mut Camera3d, &mut Camera3d,
&mut Transform, &mut Transform,
Option<&DepthPrepass>, Option<&DepthPrepass>,
Option<&TemporalJitter>, Option<&TemporalJitter>,
Has<Hdr>,
), ),
With<Camera3d>, With<Camera3d>,
>, >,
@ -458,17 +454,15 @@ fn example_control_system(
} }
} }
let ( let (camera_entity, mut camera_3d, mut camera_transform, depth_prepass, temporal_jitter, hdr) =
camera_entity, camera.into_inner();
mut camera,
mut camera_3d,
mut camera_transform,
depth_prepass,
temporal_jitter,
) = camera.into_inner();
if input.just_pressed(KeyCode::KeyH) { if input.just_pressed(KeyCode::KeyH) {
camera.hdr = !camera.hdr; if hdr {
commands.entity(camera_entity).remove::<Hdr>();
} else {
commands.entity(camera_entity).insert(Hdr);
}
} }
#[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))] #[cfg(any(feature = "webgpu", not(target_arch = "wasm32")))]
@ -571,7 +565,7 @@ fn example_control_system(
state.ior, state.ior,
state.perceptual_roughness, state.perceptual_roughness,
state.reflectance, state.reflectance,
if camera.hdr { "ON " } else { "OFF" }, if hdr { "ON " } else { "OFF" },
if cfg!(any(feature = "webgpu", not(target_arch = "wasm32"))) { if cfg!(any(feature = "webgpu", not(target_arch = "wasm32"))) {
if depth_prepass.is_some() { if depth_prepass.is_some() {
"ON " "ON "

View File

@ -65,10 +65,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_settings: R
commands commands
.spawn(( .spawn((
Camera3d::default(), Camera3d::default(),
Camera {
hdr: true,
..default()
},
Transform::from_xyz(-1.7, 1.5, 4.5).looking_at(vec3(-1.5, 1.7, 3.5), Vec3::Y), Transform::from_xyz(-1.7, 1.5, 4.5).looking_at(vec3(-1.5, 1.7, 3.5), Vec3::Y),
Tonemapping::TonyMcMapface, Tonemapping::TonyMcMapface,
Bloom::default(), Bloom::default(),

View File

@ -44,7 +44,6 @@ fn setup(
Camera2d, Camera2d,
Camera { Camera {
clear_color: ClearColorConfig::Custom(BLACK.into()), clear_color: ClearColorConfig::Custom(BLACK.into()),
hdr: true,
..Default::default() ..Default::default()
}, },
Bloom { Bloom {

View File

@ -61,14 +61,7 @@ fn setup_instructions(mut commands: Commands) {
} }
fn setup_camera(mut commands: Commands) { fn setup_camera(mut commands: Commands) {
commands.spawn(( commands.spawn((Camera2d, Bloom::NATURAL));
Camera2d,
Camera {
hdr: true, // HDR is required for the bloom effect
..default()
},
Bloom::NATURAL,
));
} }
/// Update the camera position by tracking the player. /// Update the camera position by tracking the player.

View File

@ -340,7 +340,6 @@ fn setup(
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera { Camera {
hdr: true, // HDR is required for bloom
clear_color: ClearColorConfig::Custom(SKY_COLOR), clear_color: ClearColorConfig::Custom(SKY_COLOR),
..default() ..default()
}, },

View File

@ -121,10 +121,6 @@ mod bloom {
) { ) {
commands.spawn(( commands.spawn((
Camera2d, Camera2d,
Camera {
hdr: true,
..default()
},
Tonemapping::TonyMcMapface, Tonemapping::TonyMcMapface,
Bloom::default(), Bloom::default(),
DespawnOnExitState(super::Scene::Bloom), DespawnOnExitState(super::Scene::Bloom),

View File

@ -155,10 +155,6 @@ mod bloom {
) { ) {
commands.spawn(( commands.spawn((
Camera3d::default(), Camera3d::default(),
Camera {
hdr: true,
..default()
},
Tonemapping::TonyMcMapface, Tonemapping::TonyMcMapface,
Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
Bloom::NATURAL, Bloom::NATURAL,

View File

@ -0,0 +1,12 @@
---
title: Camera Restructure
pull_requests: [18873]
---
As part of the rendering crate reorganization, we've been working to simplify Bevy `Camera`s:
- `Camera.hdr` has been split out into a new marker component, `Hdr`
- before: `commands.spawn((Camera3d, Camera { hdr: true, ..default() });`
- after: `commands.spawn((Camera3d, Hdr));`
- rendering effects can now `#[require(Hdr)]` if they only function with an HDR camera.
This is currently implemented for `Bloom`, `AutoExposure`, and `Atmosphere`