Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/view/mod.rs
6596 views
1
pub mod visibility;
2
pub mod window;
3
4
use bevy_camera::{
5
primitives::Frustum, CameraMainTextureUsages, ClearColor, ClearColorConfig, Exposure,
6
MainPassResolutionOverride, NormalizedRenderTarget,
7
};
8
use bevy_diagnostic::FrameCount;
9
pub use visibility::*;
10
pub use window::*;
11
12
use crate::{
13
camera::{ExtractedCamera, MipBias, NormalizedRenderTargetExt as _, TemporalJitter},
14
experimental::occlusion_culling::OcclusionCulling,
15
extract_component::ExtractComponentPlugin,
16
render_asset::RenderAssets,
17
render_phase::ViewRangefinder3d,
18
render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView},
19
renderer::{RenderDevice, RenderQueue},
20
sync_world::MainEntity,
21
texture::{
22
CachedTexture, ColorAttachment, DepthAttachment, GpuImage, ManualTextureViews,
23
OutputColorAttachment, TextureCache,
24
},
25
Render, RenderApp, RenderSystems,
26
};
27
use alloc::sync::Arc;
28
use bevy_app::{App, Plugin};
29
use bevy_color::LinearRgba;
30
use bevy_derive::{Deref, DerefMut};
31
use bevy_ecs::prelude::*;
32
use bevy_image::{BevyDefault as _, ToExtents};
33
use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles};
34
use bevy_platform::collections::{hash_map::Entry, HashMap};
35
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
36
use bevy_render_macros::ExtractComponent;
37
use bevy_shader::load_shader_library;
38
use bevy_transform::components::GlobalTransform;
39
use core::{
40
ops::Range,
41
sync::atomic::{AtomicUsize, Ordering},
42
};
43
use wgpu::{
44
BufferUsages, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp,
45
TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
46
};
47
48
/// The matrix that converts from the RGB to the LMS color space.
49
///
50
/// To derive this, first we convert from RGB to [CIE 1931 XYZ]:
51
///
52
/// ```text
53
/// ⎡ X ⎤ ⎡ 0.490 0.310 0.200 ⎤ ⎡ R ⎤
54
/// ⎢ Y ⎥ = ⎢ 0.177 0.812 0.011 ⎥ ⎢ G ⎥
55
/// ⎣ Z ⎦ ⎣ 0.000 0.010 0.990 ⎦ ⎣ B ⎦
56
/// ```
57
///
58
/// Then we convert to LMS according to the [CAM16 standard matrix]:
59
///
60
/// ```text
61
/// ⎡ L ⎤ ⎡ 0.401 0.650 -0.051 ⎤ ⎡ X ⎤
62
/// ⎢ M ⎥ = ⎢ -0.250 1.204 0.046 ⎥ ⎢ Y ⎥
63
/// ⎣ S ⎦ ⎣ -0.002 0.049 0.953 ⎦ ⎣ Z ⎦
64
/// ```
65
///
66
/// The resulting matrix is just the concatenation of these two matrices, to do
67
/// the conversion in one step.
68
///
69
/// [CIE 1931 XYZ]: https://en.wikipedia.org/wiki/CIE_1931_color_space
70
/// [CAM16 standard matrix]: https://en.wikipedia.org/wiki/LMS_color_space
71
static RGB_TO_LMS: Mat3 = mat3(
72
vec3(0.311692, 0.0905138, 0.00764433),
73
vec3(0.652085, 0.901341, 0.0486554),
74
vec3(0.0362225, 0.00814478, 0.943700),
75
);
76
77
/// The inverse of the [`RGB_TO_LMS`] matrix, converting from the LMS color
78
/// space back to RGB.
79
static LMS_TO_RGB: Mat3 = mat3(
80
vec3(4.06305, -0.40791, -0.0118812),
81
vec3(-2.93241, 1.40437, -0.0486532),
82
vec3(-0.130646, 0.00353630, 1.0605344),
83
);
84
85
/// The [CIE 1931] *xy* chromaticity coordinates of the [D65 white point].
86
///
87
/// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space
88
/// [D65 white point]: https://en.wikipedia.org/wiki/Standard_illuminant#D65_values
89
static D65_XY: Vec2 = vec2(0.31272, 0.32903);
90
91
/// The [D65 white point] in [LMS color space].
92
///
93
/// [LMS color space]: https://en.wikipedia.org/wiki/LMS_color_space
94
/// [D65 white point]: https://en.wikipedia.org/wiki/Standard_illuminant#D65_values
95
static D65_LMS: Vec3 = vec3(0.975538, 1.01648, 1.08475);
96
97
pub struct ViewPlugin;
98
99
impl Plugin for ViewPlugin {
100
fn build(&self, app: &mut App) {
101
load_shader_library!(app, "view.wgsl");
102
103
app
104
// NOTE: windows.is_changed() handles cases where a window was resized
105
.add_plugins((
106
ExtractComponentPlugin::<Hdr>::default(),
107
ExtractComponentPlugin::<Msaa>::default(),
108
ExtractComponentPlugin::<OcclusionCulling>::default(),
109
RenderVisibilityRangePlugin,
110
));
111
112
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
113
render_app.add_systems(
114
Render,
115
(
116
// `TextureView`s need to be dropped before reconfiguring window surfaces.
117
clear_view_attachments
118
.in_set(RenderSystems::ManageViews)
119
.before(create_surfaces),
120
prepare_view_attachments
121
.in_set(RenderSystems::ManageViews)
122
.before(prepare_view_targets)
123
.after(prepare_windows),
124
prepare_view_targets
125
.in_set(RenderSystems::ManageViews)
126
.after(prepare_windows)
127
.after(crate::render_asset::prepare_assets::<GpuImage>)
128
.ambiguous_with(crate::camera::sort_cameras), // doesn't use `sorted_camera_index_for_target`
129
prepare_view_uniforms.in_set(RenderSystems::PrepareResources),
130
),
131
);
132
}
133
}
134
135
fn finish(&self, app: &mut App) {
136
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
137
render_app
138
.init_resource::<ViewUniforms>()
139
.init_resource::<ViewTargetAttachments>();
140
}
141
}
142
}
143
144
/// Component for configuring the number of samples for [Multi-Sample Anti-Aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing)
145
/// for a [`Camera`](bevy_camera::Camera).
146
///
147
/// Defaults to 4 samples. A higher number of samples results in smoother edges.
148
///
149
/// Some advanced rendering features may require that MSAA is disabled.
150
///
151
/// Note that the web currently only supports 1 or 4 samples.
152
#[derive(
153
Component,
154
Default,
155
Clone,
156
Copy,
157
ExtractComponent,
158
Reflect,
159
PartialEq,
160
PartialOrd,
161
Eq,
162
Hash,
163
Debug,
164
)]
165
#[reflect(Component, Default, PartialEq, Hash, Debug)]
166
pub enum Msaa {
167
Off = 1,
168
Sample2 = 2,
169
#[default]
170
Sample4 = 4,
171
Sample8 = 8,
172
}
173
174
impl Msaa {
175
#[inline]
176
pub fn samples(&self) -> u32 {
177
*self as u32
178
}
179
180
pub fn from_samples(samples: u32) -> Self {
181
match samples {
182
1 => Msaa::Off,
183
2 => Msaa::Sample2,
184
4 => Msaa::Sample4,
185
8 => Msaa::Sample8,
186
_ => panic!("Unsupported MSAA sample count: {samples}"),
187
}
188
}
189
}
190
191
/// If this component is added to a camera, the camera will use an intermediate "high dynamic range" render texture.
192
/// This allows rendering with a wider range of lighting values. However, this does *not* affect
193
/// whether the camera will render with hdr display output (which bevy does not support currently)
194
/// and only affects the intermediate render texture.
195
#[derive(
196
Component, Default, Copy, Clone, ExtractComponent, Reflect, PartialEq, Eq, Hash, Debug,
197
)]
198
#[reflect(Component, Default, PartialEq, Hash, Debug)]
199
pub struct Hdr;
200
201
/// An identifier for a view that is stable across frames.
202
///
203
/// We can't use [`Entity`] for this because render world entities aren't
204
/// stable, and we can't use just [`MainEntity`] because some main world views
205
/// extract to multiple render world views. For example, a directional light
206
/// extracts to one render world view per cascade, and a point light extracts to
207
/// one render world view per cubemap face. So we pair the main entity with an
208
/// *auxiliary entity* and a *subview index*, which *together* uniquely identify
209
/// a view in the render world in a way that's stable from frame to frame.
210
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
211
pub struct RetainedViewEntity {
212
/// The main entity that this view corresponds to.
213
pub main_entity: MainEntity,
214
215
/// Another entity associated with the view entity.
216
///
217
/// This is currently used for shadow cascades. If there are multiple
218
/// cameras, each camera needs to have its own set of shadow cascades. Thus
219
/// the light and subview index aren't themselves enough to uniquely
220
/// identify a shadow cascade: we need the camera that the cascade is
221
/// associated with as well. This entity stores that camera.
222
///
223
/// If not present, this will be `MainEntity(Entity::PLACEHOLDER)`.
224
pub auxiliary_entity: MainEntity,
225
226
/// The index of the view corresponding to the entity.
227
///
228
/// For example, for point lights that cast shadows, this is the index of
229
/// the cubemap face (0 through 5 inclusive). For directional lights, this
230
/// is the index of the cascade.
231
pub subview_index: u32,
232
}
233
234
impl RetainedViewEntity {
235
/// Creates a new [`RetainedViewEntity`] from the given main world entity,
236
/// auxiliary main world entity, and subview index.
237
///
238
/// See [`RetainedViewEntity::subview_index`] for an explanation of what
239
/// `auxiliary_entity` and `subview_index` are.
240
pub fn new(
241
main_entity: MainEntity,
242
auxiliary_entity: Option<MainEntity>,
243
subview_index: u32,
244
) -> Self {
245
Self {
246
main_entity,
247
auxiliary_entity: auxiliary_entity.unwrap_or(Entity::PLACEHOLDER.into()),
248
subview_index,
249
}
250
}
251
}
252
253
/// Describes a camera in the render world.
254
///
255
/// Each entity in the main world can potentially extract to multiple subviews,
256
/// each of which has a [`RetainedViewEntity::subview_index`]. For instance, 3D
257
/// cameras extract to both a 3D camera subview with index 0 and a special UI
258
/// subview with index 1. Likewise, point lights with shadows extract to 6
259
/// subviews, one for each side of the shadow cubemap.
260
#[derive(Component)]
261
pub struct ExtractedView {
262
/// The entity in the main world corresponding to this render world view.
263
pub retained_view_entity: RetainedViewEntity,
264
/// Typically a column-major right-handed projection matrix, one of either:
265
///
266
/// Perspective (infinite reverse z)
267
/// ```text
268
/// f = 1 / tan(fov_y_radians / 2)
269
///
270
/// ⎡ f / aspect 0 0 0 ⎤
271
/// ⎢ 0 f 0 0 ⎥
272
/// ⎢ 0 0 0 near ⎥
273
/// ⎣ 0 0 -1 0 ⎦
274
/// ```
275
///
276
/// Orthographic
277
/// ```text
278
/// w = right - left
279
/// h = top - bottom
280
/// d = far - near
281
/// cw = -right - left
282
/// ch = -top - bottom
283
///
284
/// ⎡ 2 / w 0 0 cw / w ⎤
285
/// ⎢ 0 2 / h 0 ch / h ⎥
286
/// ⎢ 0 0 1 / d far / d ⎥
287
/// ⎣ 0 0 0 1 ⎦
288
/// ```
289
///
290
/// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic
291
///
292
/// Glam matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]`
293
///
294
/// Custom projections are also possible however.
295
pub clip_from_view: Mat4,
296
pub world_from_view: GlobalTransform,
297
// The view-projection matrix. When provided it is used instead of deriving it from
298
// `projection` and `transform` fields, which can be helpful in cases where numerical
299
// stability matters and there is a more direct way to derive the view-projection matrix.
300
pub clip_from_world: Option<Mat4>,
301
pub hdr: bool,
302
// uvec4(origin.x, origin.y, width, height)
303
pub viewport: UVec4,
304
pub color_grading: ColorGrading,
305
}
306
307
impl ExtractedView {
308
/// Creates a 3D rangefinder for a view
309
pub fn rangefinder3d(&self) -> ViewRangefinder3d {
310
ViewRangefinder3d::from_world_from_view(&self.world_from_view.affine())
311
}
312
}
313
314
/// Configures filmic color grading parameters to adjust the image appearance.
315
///
316
/// Color grading is applied just before tonemapping for a given
317
/// [`Camera`](bevy_camera::Camera) entity, with the sole exception of the
318
/// `post_saturation` value in [`ColorGradingGlobal`], which is applied after
319
/// tonemapping.
320
#[derive(Component, Reflect, Debug, Default, Clone)]
321
#[reflect(Component, Default, Debug, Clone)]
322
pub struct ColorGrading {
323
/// Filmic color grading values applied to the image as a whole (as opposed
324
/// to individual sections, like shadows and highlights).
325
pub global: ColorGradingGlobal,
326
327
/// Color grading values that are applied to the darker parts of the image.
328
///
329
/// The cutoff points can be customized with the
330
/// [`ColorGradingGlobal::midtones_range`] field.
331
pub shadows: ColorGradingSection,
332
333
/// Color grading values that are applied to the parts of the image with
334
/// intermediate brightness.
335
///
336
/// The cutoff points can be customized with the
337
/// [`ColorGradingGlobal::midtones_range`] field.
338
pub midtones: ColorGradingSection,
339
340
/// Color grading values that are applied to the lighter parts of the image.
341
///
342
/// The cutoff points can be customized with the
343
/// [`ColorGradingGlobal::midtones_range`] field.
344
pub highlights: ColorGradingSection,
345
}
346
347
/// Filmic color grading values applied to the image as a whole (as opposed to
348
/// individual sections, like shadows and highlights).
349
#[derive(Clone, Debug, Reflect)]
350
#[reflect(Default, Clone)]
351
pub struct ColorGradingGlobal {
352
/// Exposure value (EV) offset, measured in stops.
353
pub exposure: f32,
354
355
/// An adjustment made to the [CIE 1931] chromaticity *x* value.
356
///
357
/// Positive values make the colors redder. Negative values make the colors
358
/// bluer. This has no effect on luminance (brightness).
359
///
360
/// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space
361
pub temperature: f32,
362
363
/// An adjustment made to the [CIE 1931] chromaticity *y* value.
364
///
365
/// Positive values make the colors more magenta. Negative values make the
366
/// colors greener. This has no effect on luminance (brightness).
367
///
368
/// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space
369
pub tint: f32,
370
371
/// An adjustment to the [hue], in radians.
372
///
373
/// Adjusting this value changes the perceived colors in the image: red to
374
/// yellow to green to blue, etc. It has no effect on the saturation or
375
/// brightness of the colors.
376
///
377
/// [hue]: https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
378
pub hue: f32,
379
380
/// Saturation adjustment applied after tonemapping.
381
/// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image
382
/// with luminance defined by ITU-R BT.709
383
/// Values above 1.0 increase saturation.
384
pub post_saturation: f32,
385
386
/// The luminance (brightness) ranges that are considered part of the
387
/// "midtones" of the image.
388
///
389
/// This affects which [`ColorGradingSection`]s apply to which colors. Note
390
/// that the sections smoothly blend into one another, to avoid abrupt
391
/// transitions.
392
///
393
/// The default value is 0.2 to 0.7.
394
pub midtones_range: Range<f32>,
395
}
396
397
/// The [`ColorGrading`] structure, packed into the most efficient form for the
398
/// GPU.
399
#[derive(Clone, Copy, Debug, ShaderType)]
400
pub struct ColorGradingUniform {
401
pub balance: Mat3,
402
pub saturation: Vec3,
403
pub contrast: Vec3,
404
pub gamma: Vec3,
405
pub gain: Vec3,
406
pub lift: Vec3,
407
pub midtone_range: Vec2,
408
pub exposure: f32,
409
pub hue: f32,
410
pub post_saturation: f32,
411
}
412
413
/// A section of color grading values that can be selectively applied to
414
/// shadows, midtones, and highlights.
415
#[derive(Reflect, Debug, Copy, Clone, PartialEq)]
416
#[reflect(Clone, PartialEq)]
417
pub struct ColorGradingSection {
418
/// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image
419
/// with luminance defined by ITU-R BT.709.
420
/// Values above 1.0 increase saturation.
421
pub saturation: f32,
422
423
/// Adjusts the range of colors.
424
///
425
/// A value of 1.0 applies no changes. Values below 1.0 move the colors more
426
/// toward a neutral gray. Values above 1.0 spread the colors out away from
427
/// the neutral gray.
428
pub contrast: f32,
429
430
/// A nonlinear luminance adjustment, mainly affecting the high end of the
431
/// range.
432
///
433
/// This is the *n* exponent in the standard [ASC CDL] formula for color
434
/// correction:
435
///
436
/// ```text
437
/// out = (i × s + o)ⁿ
438
/// ```
439
///
440
/// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
441
pub gamma: f32,
442
443
/// A linear luminance adjustment, mainly affecting the middle part of the
444
/// range.
445
///
446
/// This is the *s* factor in the standard [ASC CDL] formula for color
447
/// correction:
448
///
449
/// ```text
450
/// out = (i × s + o)ⁿ
451
/// ```
452
///
453
/// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
454
pub gain: f32,
455
456
/// A fixed luminance adjustment, mainly affecting the lower part of the
457
/// range.
458
///
459
/// This is the *o* term in the standard [ASC CDL] formula for color
460
/// correction:
461
///
462
/// ```text
463
/// out = (i × s + o)ⁿ
464
/// ```
465
///
466
/// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
467
pub lift: f32,
468
}
469
470
impl Default for ColorGradingGlobal {
471
fn default() -> Self {
472
Self {
473
exposure: 0.0,
474
temperature: 0.0,
475
tint: 0.0,
476
hue: 0.0,
477
post_saturation: 1.0,
478
midtones_range: 0.2..0.7,
479
}
480
}
481
}
482
483
impl Default for ColorGradingSection {
484
fn default() -> Self {
485
Self {
486
saturation: 1.0,
487
contrast: 1.0,
488
gamma: 1.0,
489
gain: 1.0,
490
lift: 0.0,
491
}
492
}
493
}
494
495
impl ColorGrading {
496
/// Creates a new [`ColorGrading`] instance in which shadows, midtones, and
497
/// highlights all have the same set of color grading values.
498
pub fn with_identical_sections(
499
global: ColorGradingGlobal,
500
section: ColorGradingSection,
501
) -> ColorGrading {
502
ColorGrading {
503
global,
504
highlights: section,
505
midtones: section,
506
shadows: section,
507
}
508
}
509
510
/// Returns an iterator that visits the shadows, midtones, and highlights
511
/// sections, in that order.
512
pub fn all_sections(&self) -> impl Iterator<Item = &ColorGradingSection> {
513
[&self.shadows, &self.midtones, &self.highlights].into_iter()
514
}
515
516
/// Applies the given mutating function to the shadows, midtones, and
517
/// highlights sections, in that order.
518
///
519
/// Returns an array composed of the results of such evaluation, in that
520
/// order.
521
pub fn all_sections_mut(&mut self) -> impl Iterator<Item = &mut ColorGradingSection> {
522
[&mut self.shadows, &mut self.midtones, &mut self.highlights].into_iter()
523
}
524
}
525
526
#[derive(Clone, ShaderType)]
527
pub struct ViewUniform {
528
pub clip_from_world: Mat4,
529
pub unjittered_clip_from_world: Mat4,
530
pub world_from_clip: Mat4,
531
pub world_from_view: Mat4,
532
pub view_from_world: Mat4,
533
/// Typically a column-major right-handed projection matrix, one of either:
534
///
535
/// Perspective (infinite reverse z)
536
/// ```text
537
/// f = 1 / tan(fov_y_radians / 2)
538
///
539
/// ⎡ f / aspect 0 0 0 ⎤
540
/// ⎢ 0 f 0 0 ⎥
541
/// ⎢ 0 0 0 near ⎥
542
/// ⎣ 0 0 -1 0 ⎦
543
/// ```
544
///
545
/// Orthographic
546
/// ```text
547
/// w = right - left
548
/// h = top - bottom
549
/// d = far - near
550
/// cw = -right - left
551
/// ch = -top - bottom
552
///
553
/// ⎡ 2 / w 0 0 cw / w ⎤
554
/// ⎢ 0 2 / h 0 ch / h ⎥
555
/// ⎢ 0 0 1 / d far / d ⎥
556
/// ⎣ 0 0 0 1 ⎦
557
/// ```
558
///
559
/// `clip_from_view[3][3] == 1.0` is the standard way to check if a projection is orthographic
560
///
561
/// Glam matrices are column major, so for example getting the near plane of a perspective projection is `clip_from_view[3][2]`
562
///
563
/// Custom projections are also possible however.
564
pub clip_from_view: Mat4,
565
pub view_from_clip: Mat4,
566
pub world_position: Vec3,
567
pub exposure: f32,
568
// viewport(x_origin, y_origin, width, height)
569
pub viewport: Vec4,
570
pub main_pass_viewport: Vec4,
571
/// 6 world-space half spaces (normal: vec3, distance: f32) ordered left, right, top, bottom, near, far.
572
/// The normal vectors point towards the interior of the frustum.
573
/// A half space contains `p` if `normal.dot(p) + distance > 0.`
574
pub frustum: [Vec4; 6],
575
pub color_grading: ColorGradingUniform,
576
pub mip_bias: f32,
577
pub frame_count: u32,
578
}
579
580
#[derive(Resource)]
581
pub struct ViewUniforms {
582
pub uniforms: DynamicUniformBuffer<ViewUniform>,
583
}
584
585
impl FromWorld for ViewUniforms {
586
fn from_world(world: &mut World) -> Self {
587
let mut uniforms = DynamicUniformBuffer::default();
588
uniforms.set_label(Some("view_uniforms_buffer"));
589
590
let render_device = world.resource::<RenderDevice>();
591
if render_device.limits().max_storage_buffers_per_shader_stage > 0 {
592
uniforms.add_usages(BufferUsages::STORAGE);
593
}
594
595
Self { uniforms }
596
}
597
}
598
599
#[derive(Component)]
600
pub struct ViewUniformOffset {
601
pub offset: u32,
602
}
603
604
#[derive(Component)]
605
pub struct ViewTarget {
606
main_textures: MainTargetTextures,
607
main_texture_format: TextureFormat,
608
/// 0 represents `main_textures.a`, 1 represents `main_textures.b`
609
/// This is shared across view targets with the same render target
610
main_texture: Arc<AtomicUsize>,
611
out_texture: OutputColorAttachment,
612
}
613
614
/// Contains [`OutputColorAttachment`] used for each target present on any view in the current
615
/// frame, after being prepared by [`prepare_view_attachments`]. Users that want to override
616
/// the default output color attachment for a specific target can do so by adding a
617
/// [`OutputColorAttachment`] to this resource before [`prepare_view_targets`] is called.
618
#[derive(Resource, Default, Deref, DerefMut)]
619
pub struct ViewTargetAttachments(HashMap<NormalizedRenderTarget, OutputColorAttachment>);
620
621
pub struct PostProcessWrite<'a> {
622
pub source: &'a TextureView,
623
pub source_texture: &'a Texture,
624
pub destination: &'a TextureView,
625
pub destination_texture: &'a Texture,
626
}
627
628
impl From<ColorGrading> for ColorGradingUniform {
629
fn from(component: ColorGrading) -> Self {
630
// Compute the balance matrix that will be used to apply the white
631
// balance adjustment to an RGB color. Our general approach will be to
632
// convert both the color and the developer-supplied white point to the
633
// LMS color space, apply the conversion, and then convert back.
634
//
635
// First, we start with the CIE 1931 *xy* values of the standard D65
636
// illuminant:
637
// <https://en.wikipedia.org/wiki/Standard_illuminant#D65_values>
638
//
639
// We then adjust them based on the developer's requested white balance.
640
let white_point_xy = D65_XY + vec2(-component.global.temperature, component.global.tint);
641
642
// Convert the white point from CIE 1931 *xy* to LMS. First, we convert to XYZ:
643
//
644
// Y Y
645
// Y = 1 X = ─ x Z = ─ (1 - x - y)
646
// y y
647
//
648
// Then we convert from XYZ to LMS color space, using the CAM16 matrix
649
// from <https://en.wikipedia.org/wiki/LMS_color_space#Later_CIECAMs>:
650
//
651
// ⎡ L ⎤ ⎡ 0.401 0.650 -0.051 ⎤ ⎡ X ⎤
652
// ⎢ M ⎥ = ⎢ -0.250 1.204 0.046 ⎥ ⎢ Y ⎥
653
// ⎣ S ⎦ ⎣ -0.002 0.049 0.953 ⎦ ⎣ Z ⎦
654
//
655
// The following formula is just a simplification of the above.
656
657
let white_point_lms = vec3(0.701634, 1.15856, -0.904175)
658
+ (vec3(-0.051461, 0.045854, 0.953127)
659
+ vec3(0.452749, -0.296122, -0.955206) * white_point_xy.x)
660
/ white_point_xy.y;
661
662
// Now that we're in LMS space, perform the white point scaling.
663
let white_point_adjustment = Mat3::from_diagonal(D65_LMS / white_point_lms);
664
665
// Finally, combine the RGB → LMS → corrected LMS → corrected RGB
666
// pipeline into a single 3×3 matrix.
667
let balance = LMS_TO_RGB * white_point_adjustment * RGB_TO_LMS;
668
669
Self {
670
balance,
671
saturation: vec3(
672
component.shadows.saturation,
673
component.midtones.saturation,
674
component.highlights.saturation,
675
),
676
contrast: vec3(
677
component.shadows.contrast,
678
component.midtones.contrast,
679
component.highlights.contrast,
680
),
681
gamma: vec3(
682
component.shadows.gamma,
683
component.midtones.gamma,
684
component.highlights.gamma,
685
),
686
gain: vec3(
687
component.shadows.gain,
688
component.midtones.gain,
689
component.highlights.gain,
690
),
691
lift: vec3(
692
component.shadows.lift,
693
component.midtones.lift,
694
component.highlights.lift,
695
),
696
midtone_range: vec2(
697
component.global.midtones_range.start,
698
component.global.midtones_range.end,
699
),
700
exposure: component.global.exposure,
701
hue: component.global.hue,
702
post_saturation: component.global.post_saturation,
703
}
704
}
705
}
706
707
/// Add this component to a camera to disable *indirect mode*.
708
///
709
/// Indirect mode, automatically enabled on supported hardware, allows Bevy to
710
/// offload transform and cull operations to the GPU, reducing CPU overhead.
711
/// Doing this, however, reduces the amount of control that your app has over
712
/// instancing decisions. In certain circumstances, you may want to disable
713
/// indirect drawing so that your app can manually instance meshes as it sees
714
/// fit. See the `custom_shader_instancing` example.
715
///
716
/// The vast majority of applications will not need to use this component, as it
717
/// generally reduces rendering performance.
718
///
719
/// Note: This component should only be added when initially spawning a camera. Adding
720
/// or removing after spawn can result in unspecified behavior.
721
#[derive(Component, Default)]
722
pub struct NoIndirectDrawing;
723
724
impl ViewTarget {
725
pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float;
726
727
/// Retrieve this target's main texture's color attachment.
728
pub fn get_color_attachment(&self) -> RenderPassColorAttachment<'_> {
729
if self.main_texture.load(Ordering::SeqCst) == 0 {
730
self.main_textures.a.get_attachment()
731
} else {
732
self.main_textures.b.get_attachment()
733
}
734
}
735
736
/// Retrieve this target's "unsampled" main texture's color attachment.
737
pub fn get_unsampled_color_attachment(&self) -> RenderPassColorAttachment<'_> {
738
if self.main_texture.load(Ordering::SeqCst) == 0 {
739
self.main_textures.a.get_unsampled_attachment()
740
} else {
741
self.main_textures.b.get_unsampled_attachment()
742
}
743
}
744
745
/// The "main" unsampled texture.
746
pub fn main_texture(&self) -> &Texture {
747
if self.main_texture.load(Ordering::SeqCst) == 0 {
748
&self.main_textures.a.texture.texture
749
} else {
750
&self.main_textures.b.texture.texture
751
}
752
}
753
754
/// The _other_ "main" unsampled texture.
755
/// In most cases you should use [`Self::main_texture`] instead and never this.
756
/// The textures will naturally be swapped when [`Self::post_process_write`] is called.
757
///
758
/// A use case for this is to be able to prepare a bind group for all main textures
759
/// ahead of time.
760
pub fn main_texture_other(&self) -> &Texture {
761
if self.main_texture.load(Ordering::SeqCst) == 0 {
762
&self.main_textures.b.texture.texture
763
} else {
764
&self.main_textures.a.texture.texture
765
}
766
}
767
768
/// The "main" unsampled texture.
769
pub fn main_texture_view(&self) -> &TextureView {
770
if self.main_texture.load(Ordering::SeqCst) == 0 {
771
&self.main_textures.a.texture.default_view
772
} else {
773
&self.main_textures.b.texture.default_view
774
}
775
}
776
777
/// The _other_ "main" unsampled texture view.
778
/// In most cases you should use [`Self::main_texture_view`] instead and never this.
779
/// The textures will naturally be swapped when [`Self::post_process_write`] is called.
780
///
781
/// A use case for this is to be able to prepare a bind group for all main textures
782
/// ahead of time.
783
pub fn main_texture_other_view(&self) -> &TextureView {
784
if self.main_texture.load(Ordering::SeqCst) == 0 {
785
&self.main_textures.b.texture.default_view
786
} else {
787
&self.main_textures.a.texture.default_view
788
}
789
}
790
791
/// The "main" sampled texture.
792
pub fn sampled_main_texture(&self) -> Option<&Texture> {
793
self.main_textures
794
.a
795
.resolve_target
796
.as_ref()
797
.map(|sampled| &sampled.texture)
798
}
799
800
/// The "main" sampled texture view.
801
pub fn sampled_main_texture_view(&self) -> Option<&TextureView> {
802
self.main_textures
803
.a
804
.resolve_target
805
.as_ref()
806
.map(|sampled| &sampled.default_view)
807
}
808
809
#[inline]
810
pub fn main_texture_format(&self) -> TextureFormat {
811
self.main_texture_format
812
}
813
814
/// Returns `true` if and only if the main texture is [`Self::TEXTURE_FORMAT_HDR`]
815
#[inline]
816
pub fn is_hdr(&self) -> bool {
817
self.main_texture_format == ViewTarget::TEXTURE_FORMAT_HDR
818
}
819
820
/// The final texture this view will render to.
821
#[inline]
822
pub fn out_texture(&self) -> &TextureView {
823
&self.out_texture.view
824
}
825
826
pub fn out_texture_color_attachment(
827
&self,
828
clear_color: Option<LinearRgba>,
829
) -> RenderPassColorAttachment<'_> {
830
self.out_texture.get_attachment(clear_color)
831
}
832
833
/// The format of the final texture this view will render to
834
#[inline]
835
pub fn out_texture_format(&self) -> TextureFormat {
836
self.out_texture.format
837
}
838
839
/// This will start a new "post process write", which assumes that the caller
840
/// will write the [`PostProcessWrite`]'s `source` to the `destination`.
841
///
842
/// `source` is the "current" main texture. This will internally flip this
843
/// [`ViewTarget`]'s main texture to the `destination` texture, so the caller
844
/// _must_ ensure `source` is copied to `destination`, with or without modifications.
845
/// Failing to do so will cause the current main texture information to be lost.
846
pub fn post_process_write(&self) -> PostProcessWrite<'_> {
847
let old_is_a_main_texture = self.main_texture.fetch_xor(1, Ordering::SeqCst);
848
// if the old main texture is a, then the post processing must write from a to b
849
if old_is_a_main_texture == 0 {
850
self.main_textures.b.mark_as_cleared();
851
PostProcessWrite {
852
source: &self.main_textures.a.texture.default_view,
853
source_texture: &self.main_textures.a.texture.texture,
854
destination: &self.main_textures.b.texture.default_view,
855
destination_texture: &self.main_textures.b.texture.texture,
856
}
857
} else {
858
self.main_textures.a.mark_as_cleared();
859
PostProcessWrite {
860
source: &self.main_textures.b.texture.default_view,
861
source_texture: &self.main_textures.b.texture.texture,
862
destination: &self.main_textures.a.texture.default_view,
863
destination_texture: &self.main_textures.a.texture.texture,
864
}
865
}
866
}
867
}
868
869
#[derive(Component)]
870
pub struct ViewDepthTexture {
871
pub texture: Texture,
872
attachment: DepthAttachment,
873
}
874
875
impl ViewDepthTexture {
876
pub fn new(texture: CachedTexture, clear_value: Option<f32>) -> Self {
877
Self {
878
texture: texture.texture,
879
attachment: DepthAttachment::new(texture.default_view, clear_value),
880
}
881
}
882
883
pub fn get_attachment(&self, store: StoreOp) -> RenderPassDepthStencilAttachment<'_> {
884
self.attachment.get_attachment(store)
885
}
886
887
pub fn view(&self) -> &TextureView {
888
&self.attachment.view
889
}
890
}
891
892
pub fn prepare_view_uniforms(
893
mut commands: Commands,
894
render_device: Res<RenderDevice>,
895
render_queue: Res<RenderQueue>,
896
mut view_uniforms: ResMut<ViewUniforms>,
897
views: Query<(
898
Entity,
899
Option<&ExtractedCamera>,
900
&ExtractedView,
901
Option<&Frustum>,
902
Option<&TemporalJitter>,
903
Option<&MipBias>,
904
Option<&MainPassResolutionOverride>,
905
)>,
906
frame_count: Res<FrameCount>,
907
) {
908
let view_iter = views.iter();
909
let view_count = view_iter.len();
910
let Some(mut writer) =
911
view_uniforms
912
.uniforms
913
.get_writer(view_count, &render_device, &render_queue)
914
else {
915
return;
916
};
917
for (
918
entity,
919
extracted_camera,
920
extracted_view,
921
frustum,
922
temporal_jitter,
923
mip_bias,
924
resolution_override,
925
) in &views
926
{
927
let viewport = extracted_view.viewport.as_vec4();
928
let mut main_pass_viewport = viewport;
929
if let Some(resolution_override) = resolution_override {
930
main_pass_viewport.z = resolution_override.0.x as f32;
931
main_pass_viewport.w = resolution_override.0.y as f32;
932
}
933
934
let unjittered_projection = extracted_view.clip_from_view;
935
let mut clip_from_view = unjittered_projection;
936
937
if let Some(temporal_jitter) = temporal_jitter {
938
temporal_jitter.jitter_projection(&mut clip_from_view, main_pass_viewport.zw());
939
}
940
941
let view_from_clip = clip_from_view.inverse();
942
let world_from_view = extracted_view.world_from_view.to_matrix();
943
let view_from_world = world_from_view.inverse();
944
945
let clip_from_world = if temporal_jitter.is_some() {
946
clip_from_view * view_from_world
947
} else {
948
extracted_view
949
.clip_from_world
950
.unwrap_or_else(|| clip_from_view * view_from_world)
951
};
952
953
// Map Frustum type to shader array<vec4<f32>, 6>
954
let frustum = frustum
955
.map(|frustum| frustum.half_spaces.map(|h| h.normal_d()))
956
.unwrap_or([Vec4::ZERO; 6]);
957
958
let view_uniforms = ViewUniformOffset {
959
offset: writer.write(&ViewUniform {
960
clip_from_world,
961
unjittered_clip_from_world: unjittered_projection * view_from_world,
962
world_from_clip: world_from_view * view_from_clip,
963
world_from_view,
964
view_from_world,
965
clip_from_view,
966
view_from_clip,
967
world_position: extracted_view.world_from_view.translation(),
968
exposure: extracted_camera
969
.map(|c| c.exposure)
970
.unwrap_or_else(|| Exposure::default().exposure()),
971
viewport,
972
main_pass_viewport,
973
frustum,
974
color_grading: extracted_view.color_grading.clone().into(),
975
mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0,
976
frame_count: frame_count.0,
977
}),
978
};
979
980
commands.entity(entity).insert(view_uniforms);
981
}
982
}
983
984
#[derive(Clone)]
985
struct MainTargetTextures {
986
a: ColorAttachment,
987
b: ColorAttachment,
988
/// 0 represents `main_textures.a`, 1 represents `main_textures.b`
989
/// This is shared across view targets with the same render target
990
main_texture: Arc<AtomicUsize>,
991
}
992
993
/// Prepares the view target [`OutputColorAttachment`] for each view in the current frame.
994
pub fn prepare_view_attachments(
995
windows: Res<ExtractedWindows>,
996
images: Res<RenderAssets<GpuImage>>,
997
manual_texture_views: Res<ManualTextureViews>,
998
cameras: Query<&ExtractedCamera>,
999
mut view_target_attachments: ResMut<ViewTargetAttachments>,
1000
) {
1001
for camera in cameras.iter() {
1002
let Some(target) = &camera.target else {
1003
continue;
1004
};
1005
1006
match view_target_attachments.entry(target.clone()) {
1007
Entry::Occupied(_) => {}
1008
Entry::Vacant(entry) => {
1009
let Some(attachment) = target
1010
.get_texture_view(&windows, &images, &manual_texture_views)
1011
.cloned()
1012
.zip(target.get_texture_format(&windows, &images, &manual_texture_views))
1013
.map(|(view, format)| {
1014
OutputColorAttachment::new(view.clone(), format.add_srgb_suffix())
1015
})
1016
else {
1017
continue;
1018
};
1019
entry.insert(attachment);
1020
}
1021
};
1022
}
1023
}
1024
1025
/// Clears the view target [`OutputColorAttachment`]s.
1026
pub fn clear_view_attachments(mut view_target_attachments: ResMut<ViewTargetAttachments>) {
1027
view_target_attachments.clear();
1028
}
1029
1030
pub fn prepare_view_targets(
1031
mut commands: Commands,
1032
clear_color_global: Res<ClearColor>,
1033
render_device: Res<RenderDevice>,
1034
mut texture_cache: ResMut<TextureCache>,
1035
cameras: Query<(
1036
Entity,
1037
&ExtractedCamera,
1038
&ExtractedView,
1039
&CameraMainTextureUsages,
1040
&Msaa,
1041
)>,
1042
view_target_attachments: Res<ViewTargetAttachments>,
1043
) {
1044
let mut textures = <HashMap<_, _>>::default();
1045
for (entity, camera, view, texture_usage, msaa) in cameras.iter() {
1046
let (Some(target_size), Some(target)) = (camera.physical_target_size, &camera.target)
1047
else {
1048
continue;
1049
};
1050
1051
let Some(out_attachment) = view_target_attachments.get(target) else {
1052
continue;
1053
};
1054
1055
let main_texture_format = if view.hdr {
1056
ViewTarget::TEXTURE_FORMAT_HDR
1057
} else {
1058
TextureFormat::bevy_default()
1059
};
1060
1061
let clear_color = match camera.clear_color {
1062
ClearColorConfig::Custom(color) => Some(color),
1063
ClearColorConfig::None => None,
1064
_ => Some(clear_color_global.0),
1065
};
1066
1067
let (a, b, sampled, main_texture) = textures
1068
.entry((camera.target.clone(), texture_usage.0, view.hdr, msaa))
1069
.or_insert_with(|| {
1070
let descriptor = TextureDescriptor {
1071
label: None,
1072
size: target_size.to_extents(),
1073
mip_level_count: 1,
1074
sample_count: 1,
1075
dimension: TextureDimension::D2,
1076
format: main_texture_format,
1077
usage: texture_usage.0,
1078
view_formats: match main_texture_format {
1079
TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
1080
TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
1081
_ => &[],
1082
},
1083
};
1084
let a = texture_cache.get(
1085
&render_device,
1086
TextureDescriptor {
1087
label: Some("main_texture_a"),
1088
..descriptor
1089
},
1090
);
1091
let b = texture_cache.get(
1092
&render_device,
1093
TextureDescriptor {
1094
label: Some("main_texture_b"),
1095
..descriptor
1096
},
1097
);
1098
let sampled = if msaa.samples() > 1 {
1099
let sampled = texture_cache.get(
1100
&render_device,
1101
TextureDescriptor {
1102
label: Some("main_texture_sampled"),
1103
size: target_size.to_extents(),
1104
mip_level_count: 1,
1105
sample_count: msaa.samples(),
1106
dimension: TextureDimension::D2,
1107
format: main_texture_format,
1108
usage: TextureUsages::RENDER_ATTACHMENT,
1109
view_formats: descriptor.view_formats,
1110
},
1111
);
1112
Some(sampled)
1113
} else {
1114
None
1115
};
1116
let main_texture = Arc::new(AtomicUsize::new(0));
1117
(a, b, sampled, main_texture)
1118
});
1119
1120
let converted_clear_color = clear_color.map(Into::into);
1121
1122
let main_textures = MainTargetTextures {
1123
a: ColorAttachment::new(a.clone(), sampled.clone(), converted_clear_color),
1124
b: ColorAttachment::new(b.clone(), sampled.clone(), converted_clear_color),
1125
main_texture: main_texture.clone(),
1126
};
1127
1128
commands.entity(entity).insert(ViewTarget {
1129
main_texture: main_textures.main_texture.clone(),
1130
main_textures,
1131
main_texture_format,
1132
out_texture: out_attachment.clone(),
1133
});
1134
}
1135
}
1136
1137