diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs
index 4bcaaa9458633..9874a1edd9438 100644
--- a/crates/bevy_pbr/src/light/mod.rs
+++ b/crates/bevy_pbr/src/light/mod.rs
@@ -7,7 +7,7 @@ use bevy_ecs::{
 use bevy_math::{ops, Mat4, Vec3A, Vec4};
 use bevy_reflect::prelude::*;
 use bevy_render::{
-    camera::{Camera, CameraProjection, Projection},
+    camera::{Camera, Projection},
     extract_component::ExtractComponent,
     extract_resource::ExtractResource,
     mesh::Mesh3d,
diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs
index d38cb3fbcac67..f6d90970628c2 100644
--- a/crates/bevy_render/src/camera/camera.rs
+++ b/crates/bevy_render/src/camera/camera.rs
@@ -5,7 +5,7 @@
 use super::{ClearColorConfig, Projection};
 use crate::{
     batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
-    camera::{CameraProjection, ManualTextureViewHandle, ManualTextureViews},
+    camera::{ManualTextureViewHandle, ManualTextureViews},
     primitives::Frustum,
     render_asset::RenderAssets,
     render_graph::{InternedRenderSubGraph, RenderSubGraph},
@@ -659,7 +659,7 @@ impl Camera {
     /// To get the coordinates in the render target's viewport dimensions, you should use
     /// [`world_to_viewport`](Self::world_to_viewport).
     ///
-    /// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by [`CameraProjection`] contain `NAN`.
+    /// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by [`Projection`] contain `NAN`.
     ///
     /// # Panics
     ///
diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs
index e3f95cb0361e5..b7de26c6fb2e6 100644
--- a/crates/bevy_render/src/camera/projection.rs
+++ b/crates/bevy_render/src/camera/projection.rs
@@ -128,7 +128,7 @@ mod sealed {
 /// custom projection.
 ///
 /// The contained dynamic object can be downcast into a static type using [`CustomProjection::get`].
-#[derive(Component, Debug, Reflect, Deref, DerefMut)]
+#[derive(Debug, Reflect, Deref, DerefMut)]
 #[reflect(Default, Clone)]
 pub struct CustomProjection {
     #[reflect(ignore)]
@@ -201,6 +201,36 @@ impl CustomProjection {
     }
 }
 
+// TODO: remove when trait upcasting is stabilized.
+// The deref impl can return `dyn DynCameraProjection`, which can be coerced into
+// `dyn CameraProjection` using trait upcasting.
+impl CameraProjection for CustomProjection {
+    #[inline(always)]
+    fn get_clip_from_view(&self) -> Mat4 {
+        self.dyn_projection.get_clip_from_view()
+    }
+
+    #[inline(always)]
+    fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
+        self.dyn_projection.get_clip_from_view_for_sub(sub_view)
+    }
+
+    #[inline(always)]
+    fn update(&mut self, width: f32, height: f32) {
+        self.dyn_projection.update(width, height);
+    }
+
+    #[inline(always)]
+    fn far(&self) -> f32 {
+        self.dyn_projection.far()
+    }
+
+    #[inline(always)]
+    fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
+        self.dyn_projection.get_frustum_corners(z_near, z_far)
+    }
+}
+
 /// Component that defines how to compute a [`Camera`]'s projection matrix.
 ///
 /// Common projections, like perspective and orthographic, are provided out of the box to handle the
@@ -237,7 +267,7 @@ impl Projection {
         // that, say, the `Debug` implementation is missing. Wrapping these traits behind a super
         // trait or some other indirection will make the errors harder to understand.
         //
-        // For example, we don't use the `DynCameraProjection`` trait bound, because it is not the
+        // For example, we don't use the `DynCameraProjection` trait bound, because it is not the
         // trait the user should be implementing - they only need to worry about implementing
         // `CameraProjection`.
         P: CameraProjection + Debug + Send + Sync + Clone + 'static,
@@ -248,44 +278,24 @@ impl Projection {
     }
 }
 
-impl CameraProjection for Projection {
-    fn get_clip_from_view(&self) -> Mat4 {
-        match self {
-            Projection::Perspective(projection) => projection.get_clip_from_view(),
-            Projection::Orthographic(projection) => projection.get_clip_from_view(),
-            Projection::Custom(projection) => projection.get_clip_from_view(),
-        }
-    }
-
-    fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
-        match self {
-            Projection::Perspective(projection) => projection.get_clip_from_view_for_sub(sub_view),
-            Projection::Orthographic(projection) => projection.get_clip_from_view_for_sub(sub_view),
-            Projection::Custom(projection) => projection.get_clip_from_view_for_sub(sub_view),
-        }
-    }
+impl core::ops::Deref for Projection {
+    type Target = dyn CameraProjection;
 
-    fn update(&mut self, width: f32, height: f32) {
-        match self {
-            Projection::Perspective(projection) => projection.update(width, height),
-            Projection::Orthographic(projection) => projection.update(width, height),
-            Projection::Custom(projection) => projection.update(width, height),
-        }
-    }
-
-    fn far(&self) -> f32 {
+    fn deref(&self) -> &Self::Target {
         match self {
-            Projection::Perspective(projection) => projection.far(),
-            Projection::Orthographic(projection) => projection.far(),
-            Projection::Custom(projection) => projection.far(),
+            Projection::Perspective(projection) => projection,
+            Projection::Orthographic(projection) => projection,
+            Projection::Custom(projection) => projection,
         }
     }
+}
 
-    fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
+impl core::ops::DerefMut for Projection {
+    fn deref_mut(&mut self) -> &mut Self::Target {
         match self {
-            Projection::Perspective(projection) => projection.get_frustum_corners(z_near, z_far),
-            Projection::Orthographic(projection) => projection.get_frustum_corners(z_near, z_far),
-            Projection::Custom(projection) => projection.get_frustum_corners(z_near, z_far),
+            Projection::Perspective(projection) => projection,
+            Projection::Orthographic(projection) => projection,
+            Projection::Custom(projection) => projection,
         }
     }
 }
diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs
index d72c52abf7b97..1e880dba207bc 100644
--- a/crates/bevy_render/src/view/visibility/mod.rs
+++ b/crates/bevy_render/src/view/visibility/mod.rs
@@ -20,7 +20,7 @@ use smallvec::SmallVec;
 
 use super::NoCpuCulling;
 use crate::{
-    camera::{Camera, CameraProjection, Projection},
+    camera::{Camera, Projection},
     mesh::{Mesh, Mesh3d, MeshAabb},
     primitives::{Aabb, Frustum, Sphere},
     sync_world::MainEntity,