Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/volumetric_fog/render.rs
9427 views
1
//! Rendering of fog volumes.
2
3
use core::array;
4
5
use bevy_asset::{load_embedded_asset, AssetId, AssetServer, Handle};
6
use bevy_camera::Camera3d;
7
use bevy_color::ColorToComponents as _;
8
use bevy_core_pipeline::prepass::{
9
DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass,
10
};
11
use bevy_derive::{Deref, DerefMut};
12
use bevy_ecs::{
13
component::Component,
14
entity::Entity,
15
query::{Has, With},
16
resource::Resource,
17
system::{Commands, Local, Query, Res, ResMut},
18
};
19
use bevy_image::{BevyDefault, Image};
20
use bevy_light::{FogVolume, VolumetricFog, VolumetricLight};
21
use bevy_math::{vec4, Affine3A, Mat4, Vec3, Vec3A, Vec4};
22
use bevy_mesh::{Mesh, MeshVertexBufferLayoutRef};
23
use bevy_render::{
24
mesh::{allocator::MeshAllocator, RenderMesh, RenderMeshBufferInfo},
25
render_asset::RenderAssets,
26
render_resource::{
27
binding_types::{
28
sampler, texture_3d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer,
29
},
30
BindGroupLayoutDescriptor, BindGroupLayoutEntries, BindingResource, BlendComponent,
31
BlendFactor, BlendOperation, BlendState, CachedRenderPipelineId, ColorTargetState,
32
ColorWrites, DynamicBindGroupEntries, DynamicUniformBuffer, Face, FragmentState, LoadOp,
33
Operations, PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor,
34
RenderPipelineDescriptor, SamplerBindingType, ShaderStages, ShaderType,
35
SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp, TextureFormat,
36
TextureSampleType, TextureUsages, VertexState,
37
},
38
renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
39
sync_world::RenderEntity,
40
texture::GpuImage,
41
view::{ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniformOffset},
42
Extract,
43
};
44
use bevy_shader::Shader;
45
use bevy_transform::components::GlobalTransform;
46
use bevy_utils::prelude::default;
47
use bitflags::bitflags;
48
49
use crate::{
50
ExtractedAtmosphere, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup,
51
ViewContactShadowsUniformOffset, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset,
52
ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset,
53
};
54
55
use super::FogAssets;
56
57
bitflags! {
58
/// Flags that describe the bind group layout used to render volumetric fog.
59
#[derive(Clone, Copy, PartialEq)]
60
struct VolumetricFogBindGroupLayoutKey: u8 {
61
/// The framebuffer is multisampled.
62
const MULTISAMPLED = 0x1;
63
/// The volumetric fog has a 3D voxel density texture.
64
const DENSITY_TEXTURE = 0x2;
65
}
66
}
67
68
bitflags! {
69
/// Flags that describe the rasterization pipeline used to render volumetric
70
/// fog.
71
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
72
struct VolumetricFogPipelineKeyFlags: u8 {
73
/// The view's color format has high dynamic range.
74
const HDR = 0x1;
75
/// The volumetric fog has a 3D voxel density texture.
76
const DENSITY_TEXTURE = 0x2;
77
}
78
}
79
80
/// The total number of bind group layouts.
81
///
82
/// This is the total number of combinations of all
83
/// [`VolumetricFogBindGroupLayoutKey`] flags.
84
const VOLUMETRIC_FOG_BIND_GROUP_LAYOUT_COUNT: usize =
85
VolumetricFogBindGroupLayoutKey::all().bits() as usize + 1;
86
87
/// A matrix that converts from local 1×1×1 space to UVW 3D density texture
88
/// space.
89
static UVW_FROM_LOCAL: Mat4 = Mat4::from_cols(
90
vec4(1.0, 0.0, 0.0, 0.0),
91
vec4(0.0, 1.0, 0.0, 0.0),
92
vec4(0.0, 0.0, 1.0, 0.0),
93
vec4(0.5, 0.5, 0.5, 1.0),
94
);
95
96
/// The GPU pipeline for the volumetric fog postprocessing effect.
97
#[derive(Resource)]
98
pub struct VolumetricFogPipeline {
99
/// A reference to the shared set of mesh pipeline view layouts.
100
mesh_view_layouts: MeshPipelineViewLayouts,
101
102
/// All bind group layouts.
103
///
104
/// Since there aren't too many of these, we precompile them all.
105
volumetric_view_bind_group_layouts:
106
[BindGroupLayoutDescriptor; VOLUMETRIC_FOG_BIND_GROUP_LAYOUT_COUNT],
107
108
// The shader asset handle.
109
shader: Handle<Shader>,
110
}
111
112
/// The two render pipelines that we use for fog volumes: one for when a 3D
113
/// density texture is present and one for when it isn't.
114
#[derive(Component)]
115
pub struct ViewVolumetricFogPipelines {
116
/// The render pipeline that we use when no density texture is present, and
117
/// the density distribution is uniform.
118
pub textureless: CachedRenderPipelineId,
119
/// The render pipeline that we use when a density texture is present.
120
pub textured: CachedRenderPipelineId,
121
}
122
123
/// Identifies a single specialization of the volumetric fog shader.
124
#[derive(PartialEq, Eq, Hash, Clone)]
125
pub struct VolumetricFogPipelineKey {
126
/// The layout of the view, which is needed for the raymarching.
127
mesh_pipeline_view_key: MeshPipelineViewLayoutKey,
128
129
/// The vertex buffer layout of the primitive.
130
///
131
/// Both planes (used when the camera is inside the fog volume) and cubes
132
/// (used when the camera is outside the fog volume) use identical vertex
133
/// buffer layouts, so we only need one of them.
134
vertex_buffer_layout: MeshVertexBufferLayoutRef,
135
136
/// Flags that specify features on the pipeline key.
137
flags: VolumetricFogPipelineKeyFlags,
138
}
139
140
/// The same as [`VolumetricFog`] and [`FogVolume`], but formatted for
141
/// the GPU.
142
///
143
/// See the documentation of those structures for more information on these
144
/// fields.
145
#[derive(ShaderType)]
146
pub struct VolumetricFogUniform {
147
clip_from_local: Mat4,
148
149
/// The transform from world space to 3D density texture UVW space.
150
uvw_from_world: Mat4,
151
152
/// View-space plane equations of the far faces of the fog volume cuboid.
153
///
154
/// The vector takes the form V = (N, -N⋅Q), where N is the normal of the
155
/// plane and Q is any point in it, in view space. The equation of the plane
156
/// for homogeneous point P = (Px, Py, Pz, Pw) is V⋅P = 0.
157
far_planes: [Vec4; 3],
158
159
fog_color: Vec3,
160
light_tint: Vec3,
161
ambient_color: Vec3,
162
ambient_intensity: f32,
163
step_count: u32,
164
165
/// The radius of a sphere that bounds the fog volume in view space.
166
bounding_radius: f32,
167
168
absorption: f32,
169
scattering: f32,
170
density: f32,
171
density_texture_offset: Vec3,
172
scattering_asymmetry: f32,
173
light_intensity: f32,
174
jitter_strength: f32,
175
}
176
177
/// Specifies the offset within the [`VolumetricFogUniformBuffer`] of the
178
/// [`VolumetricFogUniform`] for a specific view.
179
#[derive(Component, Deref, DerefMut)]
180
pub struct ViewVolumetricFog(Vec<ViewFogVolume>);
181
182
/// Information that the render world needs to maintain about each fog volume.
183
pub struct ViewFogVolume {
184
/// The 3D voxel density texture for this volume, if present.
185
density_texture: Option<AssetId<Image>>,
186
/// The offset of this view's [`VolumetricFogUniform`] structure within the
187
/// [`VolumetricFogUniformBuffer`].
188
uniform_buffer_offset: u32,
189
/// True if the camera is outside the fog volume; false if it's inside the
190
/// fog volume.
191
exterior: bool,
192
}
193
194
/// The GPU buffer that stores the [`VolumetricFogUniform`] data.
195
#[derive(Resource, Default, Deref, DerefMut)]
196
pub struct VolumetricFogUniformBuffer(pub DynamicUniformBuffer<VolumetricFogUniform>);
197
198
pub fn init_volumetric_fog_pipeline(
199
mut commands: Commands,
200
mesh_view_layouts: Res<MeshPipelineViewLayouts>,
201
asset_server: Res<AssetServer>,
202
) {
203
// Create the bind group layout entries common to all bind group
204
// layouts.
205
let base_bind_group_layout_entries = &BindGroupLayoutEntries::single(
206
ShaderStages::VERTEX_FRAGMENT,
207
// `volumetric_fog`
208
uniform_buffer::<VolumetricFogUniform>(true),
209
);
210
211
// For every combination of `VolumetricFogBindGroupLayoutKey` bits,
212
// create a bind group layout.
213
let bind_group_layouts = array::from_fn(|bits| {
214
let flags = VolumetricFogBindGroupLayoutKey::from_bits_retain(bits as u8);
215
216
let mut bind_group_layout_entries = base_bind_group_layout_entries.to_vec();
217
218
// `depth_texture`
219
bind_group_layout_entries.extend_from_slice(&BindGroupLayoutEntries::with_indices(
220
ShaderStages::FRAGMENT,
221
((
222
1,
223
if flags.contains(VolumetricFogBindGroupLayoutKey::MULTISAMPLED) {
224
texture_depth_2d_multisampled()
225
} else {
226
texture_depth_2d()
227
},
228
),),
229
));
230
231
// `density_texture` and `density_sampler`
232
if flags.contains(VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE) {
233
bind_group_layout_entries.extend_from_slice(&BindGroupLayoutEntries::with_indices(
234
ShaderStages::FRAGMENT,
235
(
236
(2, texture_3d(TextureSampleType::Float { filterable: true })),
237
(3, sampler(SamplerBindingType::Filtering)),
238
),
239
));
240
}
241
242
// Create the bind group layout.
243
let description = flags.bind_group_layout_description();
244
BindGroupLayoutDescriptor::new(description, &bind_group_layout_entries)
245
});
246
247
commands.insert_resource(VolumetricFogPipeline {
248
mesh_view_layouts: mesh_view_layouts.clone(),
249
volumetric_view_bind_group_layouts: bind_group_layouts,
250
shader: load_embedded_asset!(asset_server.as_ref(), "volumetric_fog.wgsl"),
251
});
252
}
253
254
/// Extracts [`VolumetricFog`], [`FogVolume`], and [`VolumetricLight`]s
255
/// from the main world to the render world.
256
pub fn extract_volumetric_fog(
257
mut commands: Commands,
258
view_targets: Extract<Query<(RenderEntity, &VolumetricFog)>>,
259
fog_volumes: Extract<Query<(RenderEntity, &FogVolume, &GlobalTransform)>>,
260
volumetric_lights: Extract<Query<(RenderEntity, &VolumetricLight)>>,
261
) {
262
if volumetric_lights.is_empty() {
263
// TODO: needs better way to handle clean up in render world
264
for (entity, ..) in view_targets.iter() {
265
commands
266
.entity(entity)
267
.remove::<(VolumetricFog, ViewVolumetricFogPipelines, ViewVolumetricFog)>();
268
}
269
for (entity, ..) in fog_volumes.iter() {
270
commands.entity(entity).remove::<FogVolume>();
271
}
272
return;
273
}
274
275
for (entity, volumetric_fog) in view_targets.iter() {
276
commands
277
.get_entity(entity)
278
.expect("Volumetric fog entity wasn't synced.")
279
.insert(*volumetric_fog);
280
}
281
282
for (entity, fog_volume, fog_transform) in fog_volumes.iter() {
283
commands
284
.get_entity(entity)
285
.expect("Fog volume entity wasn't synced.")
286
.insert((*fog_volume).clone())
287
.insert(*fog_transform);
288
}
289
290
for (entity, volumetric_light) in volumetric_lights.iter() {
291
commands
292
.get_entity(entity)
293
.expect("Volumetric light entity wasn't synced.")
294
.insert(*volumetric_light);
295
}
296
}
297
298
pub fn volumetric_fog(
299
view: ViewQuery<(
300
&ViewTarget,
301
&ViewDepthTexture,
302
&ViewVolumetricFogPipelines,
303
&ViewUniformOffset,
304
&ViewLightsUniformOffset,
305
&ViewFogUniformOffset,
306
&ViewLightProbesUniformOffset,
307
&ViewVolumetricFog,
308
&MeshViewBindGroup,
309
&ViewScreenSpaceReflectionsUniformOffset,
310
&ViewContactShadowsUniformOffset,
311
&Msaa,
312
&ViewEnvironmentMapUniformOffset,
313
)>,
314
pipeline_cache: Res<PipelineCache>,
315
volumetric_lighting_pipeline: Res<VolumetricFogPipeline>,
316
volumetric_lighting_uniform_buffers: Res<VolumetricFogUniformBuffer>,
317
image_assets: Res<RenderAssets<GpuImage>>,
318
mesh_allocator: Res<MeshAllocator>,
319
fog_assets: Res<FogAssets>,
320
render_meshes: Res<RenderAssets<RenderMesh>>,
321
mut ctx: RenderContext,
322
) {
323
let (
324
view_target,
325
view_depth_texture,
326
view_volumetric_lighting_pipelines,
327
view_uniform_offset,
328
view_lights_offset,
329
view_fog_offset,
330
view_light_probes_offset,
331
view_fog_volumes,
332
view_bind_group,
333
view_ssr_offset,
334
view_contact_shadows_offset,
335
msaa,
336
view_environment_map_offset,
337
) = view.into_inner();
338
339
// Fetch the uniform buffer and binding.
340
let (
341
Some(textureless_pipeline),
342
Some(textured_pipeline),
343
Some(volumetric_lighting_uniform_buffer_binding),
344
) = (
345
pipeline_cache.get_render_pipeline(view_volumetric_lighting_pipelines.textureless),
346
pipeline_cache.get_render_pipeline(view_volumetric_lighting_pipelines.textured),
347
volumetric_lighting_uniform_buffers.binding(),
348
)
349
else {
350
return;
351
};
352
353
let command_encoder = ctx.command_encoder();
354
command_encoder.push_debug_group("volumetric_lighting");
355
356
for view_fog_volume in view_fog_volumes.iter() {
357
// If the camera is outside the fog volume, pick the cube mesh;
358
// otherwise, pick the plane mesh. In the latter case we'll be
359
// effectively rendering a full-screen quad.
360
let mesh_handle = if view_fog_volume.exterior {
361
fog_assets.cube_mesh.clone()
362
} else {
363
fog_assets.plane_mesh.clone()
364
};
365
366
let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_handle.id()) else {
367
continue;
368
};
369
370
let density_image = view_fog_volume
371
.density_texture
372
.and_then(|density_texture| image_assets.get(density_texture));
373
374
// Pick the right pipeline, depending on whether a density texture
375
// is present or not.
376
let pipeline = if density_image.is_some() {
377
textured_pipeline
378
} else {
379
textureless_pipeline
380
};
381
382
// This should always succeed, but if the asset was unloaded don't
383
// panic.
384
let Some(render_mesh) = render_meshes.get(&mesh_handle) else {
385
return;
386
};
387
388
// Create the bind group for the view.
389
//
390
// TODO: Cache this.
391
392
let mut bind_group_layout_key = VolumetricFogBindGroupLayoutKey::empty();
393
bind_group_layout_key.set(
394
VolumetricFogBindGroupLayoutKey::MULTISAMPLED,
395
!matches!(*msaa, Msaa::Off),
396
);
397
398
// Create the bind group entries. The ones relating to the density
399
// texture will only be filled in if that texture is present.
400
let mut bind_group_entries = DynamicBindGroupEntries::sequential((
401
volumetric_lighting_uniform_buffer_binding.clone(),
402
BindingResource::TextureView(view_depth_texture.view()),
403
));
404
if let Some(density_image) = density_image {
405
bind_group_layout_key.insert(VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE);
406
bind_group_entries = bind_group_entries.extend_sequential((
407
BindingResource::TextureView(&density_image.texture_view),
408
BindingResource::Sampler(&density_image.sampler),
409
));
410
}
411
412
let volumetric_view_bind_group_layout = &volumetric_lighting_pipeline
413
.volumetric_view_bind_group_layouts[bind_group_layout_key.bits() as usize];
414
415
let volumetric_view_bind_group = ctx.render_device().create_bind_group(
416
None,
417
&pipeline_cache.get_bind_group_layout(volumetric_view_bind_group_layout),
418
&bind_group_entries,
419
);
420
421
let render_pass_descriptor = RenderPassDescriptor {
422
label: Some("volumetric lighting pass"),
423
color_attachments: &[Some(RenderPassColorAttachment {
424
view: view_target.main_texture_view(),
425
depth_slice: None,
426
resolve_target: None,
427
ops: Operations {
428
load: LoadOp::Load,
429
store: StoreOp::Store,
430
},
431
})],
432
depth_stencil_attachment: None,
433
timestamp_writes: None,
434
occlusion_query_set: None,
435
multiview_mask: None,
436
};
437
438
let command_encoder = ctx.command_encoder();
439
let mut render_pass = command_encoder.begin_render_pass(&render_pass_descriptor);
440
441
render_pass.set_vertex_buffer(0, *vertex_buffer_slice.buffer.slice(..));
442
render_pass.set_pipeline(pipeline);
443
render_pass.set_bind_group(
444
0,
445
&view_bind_group.main,
446
&[
447
view_uniform_offset.offset,
448
view_lights_offset.offset,
449
view_fog_offset.offset,
450
**view_light_probes_offset,
451
**view_ssr_offset,
452
**view_contact_shadows_offset,
453
**view_environment_map_offset,
454
],
455
);
456
render_pass.set_bind_group(
457
1,
458
&volumetric_view_bind_group,
459
&[view_fog_volume.uniform_buffer_offset],
460
);
461
462
// Draw elements or arrays, as appropriate.
463
match &render_mesh.buffer_info {
464
RenderMeshBufferInfo::Indexed {
465
index_format,
466
count,
467
} => {
468
let Some(index_buffer_slice) = mesh_allocator.mesh_index_slice(&mesh_handle.id())
469
else {
470
continue;
471
};
472
473
render_pass.set_index_buffer(*index_buffer_slice.buffer.slice(..), *index_format);
474
render_pass.draw_indexed(
475
index_buffer_slice.range.start..(index_buffer_slice.range.start + count),
476
vertex_buffer_slice.range.start as i32,
477
0..1,
478
);
479
}
480
RenderMeshBufferInfo::NonIndexed => {
481
render_pass.draw(vertex_buffer_slice.range, 0..1);
482
}
483
}
484
}
485
486
ctx.command_encoder().pop_debug_group();
487
}
488
489
impl SpecializedRenderPipeline for VolumetricFogPipeline {
490
type Key = VolumetricFogPipelineKey;
491
492
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
493
// We always use hardware 2x2 filtering for sampling the shadow map; the
494
// more accurate versions with percentage-closer filtering aren't worth
495
// the overhead.
496
let mut shader_defs = vec!["SHADOW_FILTER_METHOD_HARDWARE_2X2".into()];
497
498
// We need a separate layout for MSAA and non-MSAA, as well as one for
499
// the presence or absence of the density texture.
500
let mut bind_group_layout_key = VolumetricFogBindGroupLayoutKey::empty();
501
bind_group_layout_key.set(
502
VolumetricFogBindGroupLayoutKey::MULTISAMPLED,
503
key.mesh_pipeline_view_key
504
.contains(MeshPipelineViewLayoutKey::MULTISAMPLED),
505
);
506
bind_group_layout_key.set(
507
VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE,
508
key.flags
509
.contains(VolumetricFogPipelineKeyFlags::DENSITY_TEXTURE),
510
);
511
512
let volumetric_view_bind_group_layout =
513
self.volumetric_view_bind_group_layouts[bind_group_layout_key.bits() as usize].clone();
514
515
// Both the cube and plane have the same vertex layout, so we don't need
516
// to distinguish between the two.
517
let vertex_format = key
518
.vertex_buffer_layout
519
.0
520
.get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)])
521
.expect("Failed to get vertex layout for volumetric fog hull");
522
523
if key
524
.mesh_pipeline_view_key
525
.contains(MeshPipelineViewLayoutKey::MULTISAMPLED)
526
{
527
shader_defs.push("MULTISAMPLED".into());
528
}
529
530
if key
531
.mesh_pipeline_view_key
532
.contains(MeshPipelineViewLayoutKey::ATMOSPHERE)
533
{
534
shader_defs.push("ATMOSPHERE".into());
535
}
536
537
if key
538
.flags
539
.contains(VolumetricFogPipelineKeyFlags::DENSITY_TEXTURE)
540
{
541
shader_defs.push("DENSITY_TEXTURE".into());
542
}
543
544
let layout = self
545
.mesh_view_layouts
546
.get_view_layout(key.mesh_pipeline_view_key);
547
let layout = vec![
548
layout.main_layout.clone(),
549
volumetric_view_bind_group_layout.clone(),
550
];
551
552
RenderPipelineDescriptor {
553
label: Some("volumetric lighting pipeline".into()),
554
layout,
555
vertex: VertexState {
556
shader: self.shader.clone(),
557
shader_defs: shader_defs.clone(),
558
buffers: vec![vertex_format],
559
..default()
560
},
561
primitive: PrimitiveState {
562
cull_mode: Some(Face::Back),
563
..default()
564
},
565
fragment: Some(FragmentState {
566
shader: self.shader.clone(),
567
shader_defs,
568
targets: vec![Some(ColorTargetState {
569
format: if key.flags.contains(VolumetricFogPipelineKeyFlags::HDR) {
570
ViewTarget::TEXTURE_FORMAT_HDR
571
} else {
572
TextureFormat::bevy_default()
573
},
574
// Blend on top of what's already in the framebuffer. Doing
575
// the alpha blending with the hardware blender allows us to
576
// avoid having to use intermediate render targets.
577
blend: Some(BlendState {
578
color: BlendComponent {
579
src_factor: BlendFactor::One,
580
dst_factor: BlendFactor::OneMinusSrcAlpha,
581
operation: BlendOperation::Add,
582
},
583
alpha: BlendComponent {
584
src_factor: BlendFactor::Zero,
585
dst_factor: BlendFactor::One,
586
operation: BlendOperation::Add,
587
},
588
}),
589
write_mask: ColorWrites::ALL,
590
})],
591
..default()
592
}),
593
..default()
594
}
595
}
596
}
597
598
/// Specializes volumetric fog pipelines for all views with that effect enabled.
599
pub fn prepare_volumetric_fog_pipelines(
600
mut commands: Commands,
601
pipeline_cache: Res<PipelineCache>,
602
mut pipelines: ResMut<SpecializedRenderPipelines<VolumetricFogPipeline>>,
603
volumetric_lighting_pipeline: Res<VolumetricFogPipeline>,
604
fog_assets: Res<FogAssets>,
605
view_targets: Query<
606
(
607
Entity,
608
&ExtractedView,
609
&Msaa,
610
Has<NormalPrepass>,
611
Has<DepthPrepass>,
612
Has<MotionVectorPrepass>,
613
Has<DeferredPrepass>,
614
Has<ExtractedAtmosphere>,
615
),
616
With<VolumetricFog>,
617
>,
618
meshes: Res<RenderAssets<RenderMesh>>,
619
) {
620
let Some(plane_mesh) = meshes.get(&fog_assets.plane_mesh) else {
621
// There's an off chance that the mesh won't be prepared yet if `RenderAssetBytesPerFrame` limiting is in use.
622
return;
623
};
624
625
for (
626
entity,
627
view,
628
msaa,
629
normal_prepass,
630
depth_prepass,
631
motion_vector_prepass,
632
deferred_prepass,
633
atmosphere,
634
) in view_targets.iter()
635
{
636
// Create a mesh pipeline view layout key corresponding to the view.
637
let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from(*msaa);
638
mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::NORMAL_PREPASS, normal_prepass);
639
mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::DEPTH_PREPASS, depth_prepass);
640
mesh_pipeline_view_key.set(
641
MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS,
642
motion_vector_prepass,
643
);
644
mesh_pipeline_view_key.set(
645
MeshPipelineViewLayoutKey::DEFERRED_PREPASS,
646
deferred_prepass,
647
);
648
mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::ATMOSPHERE, atmosphere);
649
if cfg!(feature = "bluenoise_texture") {
650
mesh_pipeline_view_key |= MeshPipelineViewLayoutKey::STBN;
651
}
652
653
let mut textureless_flags = VolumetricFogPipelineKeyFlags::empty();
654
textureless_flags.set(VolumetricFogPipelineKeyFlags::HDR, view.hdr);
655
656
// Specialize the pipeline.
657
let textureless_pipeline_key = VolumetricFogPipelineKey {
658
mesh_pipeline_view_key,
659
vertex_buffer_layout: plane_mesh.layout.clone(),
660
flags: textureless_flags,
661
};
662
let textureless_pipeline_id = pipelines.specialize(
663
&pipeline_cache,
664
&volumetric_lighting_pipeline,
665
textureless_pipeline_key.clone(),
666
);
667
let textured_pipeline_id = pipelines.specialize(
668
&pipeline_cache,
669
&volumetric_lighting_pipeline,
670
VolumetricFogPipelineKey {
671
flags: textureless_pipeline_key.flags
672
| VolumetricFogPipelineKeyFlags::DENSITY_TEXTURE,
673
..textureless_pipeline_key
674
},
675
);
676
677
commands.entity(entity).insert(ViewVolumetricFogPipelines {
678
textureless: textureless_pipeline_id,
679
textured: textured_pipeline_id,
680
});
681
}
682
}
683
684
/// A system that converts [`VolumetricFog`] into [`VolumetricFogUniform`]s.
685
pub fn prepare_volumetric_fog_uniforms(
686
mut commands: Commands,
687
mut volumetric_lighting_uniform_buffer: ResMut<VolumetricFogUniformBuffer>,
688
view_targets: Query<(Entity, &ExtractedView, &VolumetricFog)>,
689
fog_volumes: Query<(Entity, &FogVolume, &GlobalTransform)>,
690
render_device: Res<RenderDevice>,
691
render_queue: Res<RenderQueue>,
692
mut local_from_world_matrices: Local<Vec<Affine3A>>,
693
) {
694
// Do this up front to avoid O(n^2) matrix inversion.
695
local_from_world_matrices.clear();
696
for (_, _, fog_transform) in fog_volumes.iter() {
697
local_from_world_matrices.push(fog_transform.affine().inverse());
698
}
699
700
let uniform_count = view_targets.iter().len() * local_from_world_matrices.len();
701
702
let Some(mut writer) =
703
volumetric_lighting_uniform_buffer.get_writer(uniform_count, &render_device, &render_queue)
704
else {
705
return;
706
};
707
708
for (view_entity, extracted_view, volumetric_fog) in view_targets.iter() {
709
let world_from_view = extracted_view.world_from_view.affine();
710
711
let mut view_fog_volumes = vec![];
712
713
for ((_, fog_volume, _), local_from_world) in
714
fog_volumes.iter().zip(local_from_world_matrices.iter())
715
{
716
// Calculate the transforms to and from 1×1×1 local space.
717
let local_from_view = *local_from_world * world_from_view;
718
let view_from_local = local_from_view.inverse();
719
720
// Determine whether the camera is inside or outside the volume, and
721
// calculate the clip space transform.
722
let interior = camera_is_inside_fog_volume(&local_from_view);
723
let hull_clip_from_local = calculate_fog_volume_clip_from_local_transforms(
724
interior,
725
&extracted_view.clip_from_view,
726
&view_from_local,
727
);
728
729
// Calculate the radius of the sphere that bounds the fog volume.
730
let bounding_radius = view_from_local
731
.transform_vector3a(Vec3A::splat(0.5))
732
.length();
733
734
// Write out our uniform.
735
let uniform_buffer_offset = writer.write(&VolumetricFogUniform {
736
clip_from_local: hull_clip_from_local,
737
uvw_from_world: UVW_FROM_LOCAL * *local_from_world,
738
far_planes: get_far_planes(&view_from_local),
739
fog_color: fog_volume.fog_color.to_linear().to_vec3(),
740
light_tint: fog_volume.light_tint.to_linear().to_vec3(),
741
ambient_color: volumetric_fog.ambient_color.to_linear().to_vec3(),
742
ambient_intensity: volumetric_fog.ambient_intensity,
743
step_count: volumetric_fog.step_count,
744
bounding_radius,
745
absorption: fog_volume.absorption,
746
scattering: fog_volume.scattering,
747
density: fog_volume.density_factor,
748
density_texture_offset: fog_volume.density_texture_offset,
749
scattering_asymmetry: fog_volume.scattering_asymmetry,
750
light_intensity: fog_volume.light_intensity,
751
jitter_strength: volumetric_fog.jitter,
752
});
753
754
view_fog_volumes.push(ViewFogVolume {
755
uniform_buffer_offset,
756
exterior: !interior,
757
density_texture: fog_volume.density_texture.as_ref().map(Handle::id),
758
});
759
}
760
761
commands
762
.entity(view_entity)
763
.insert(ViewVolumetricFog(view_fog_volumes));
764
}
765
}
766
767
/// A system that marks all view depth textures as readable in shaders.
768
///
769
/// The volumetric lighting pass needs to do this, and it doesn't happen by
770
/// default.
771
pub fn prepare_view_depth_textures_for_volumetric_fog(
772
mut view_targets: Query<&mut Camera3d>,
773
fog_volumes: Query<&VolumetricFog>,
774
) {
775
if fog_volumes.is_empty() {
776
return;
777
}
778
779
for mut camera in view_targets.iter_mut() {
780
camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
781
}
782
}
783
784
fn get_far_planes(view_from_local: &Affine3A) -> [Vec4; 3] {
785
let (mut far_planes, mut next_index) = ([Vec4::ZERO; 3], 0);
786
787
for &local_normal in &[
788
Vec3A::X,
789
Vec3A::NEG_X,
790
Vec3A::Y,
791
Vec3A::NEG_Y,
792
Vec3A::Z,
793
Vec3A::NEG_Z,
794
] {
795
let view_normal = view_from_local
796
.transform_vector3a(local_normal)
797
.normalize_or_zero();
798
if view_normal.z <= 0.0 {
799
continue;
800
}
801
802
let view_position = view_from_local.transform_point3a(-local_normal * 0.5);
803
let plane_coords = view_normal.extend(-view_normal.dot(view_position));
804
805
far_planes[next_index] = plane_coords;
806
next_index += 1;
807
if next_index == far_planes.len() {
808
continue;
809
}
810
}
811
812
far_planes
813
}
814
815
impl VolumetricFogBindGroupLayoutKey {
816
/// Creates an appropriate debug description for the bind group layout with
817
/// these flags.
818
fn bind_group_layout_description(&self) -> String {
819
if self.is_empty() {
820
return "volumetric lighting view bind group layout".to_owned();
821
}
822
823
format!(
824
"volumetric lighting view bind group layout ({})",
825
self.iter()
826
.filter_map(|flag| {
827
if flag == VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE {
828
Some("density texture")
829
} else if flag == VolumetricFogBindGroupLayoutKey::MULTISAMPLED {
830
Some("multisampled")
831
} else {
832
None
833
}
834
})
835
.collect::<Vec<_>>()
836
.join(", ")
837
)
838
}
839
}
840
841
/// Given the transform from the view to the 1×1×1 cube in local fog volume
842
/// space, returns true if the camera is inside the volume.
843
fn camera_is_inside_fog_volume(local_from_view: &Affine3A) -> bool {
844
local_from_view
845
.translation
846
.abs()
847
.cmple(Vec3A::splat(0.5))
848
.all()
849
}
850
851
/// Given the local transforms, returns the matrix that transforms model space
852
/// to clip space.
853
fn calculate_fog_volume_clip_from_local_transforms(
854
interior: bool,
855
clip_from_view: &Mat4,
856
view_from_local: &Affine3A,
857
) -> Mat4 {
858
if !interior {
859
return *clip_from_view * Mat4::from(*view_from_local);
860
}
861
862
// If the camera is inside the fog volume, then we'll be rendering a full
863
// screen quad. The shader will start its raymarch at the fragment depth
864
// value, however, so we need to make sure that the depth of the full screen
865
// quad is at the near clip plane `z_near`.
866
let z_near = clip_from_view.w_axis[2];
867
Mat4::from_cols(
868
vec4(z_near, 0.0, 0.0, 0.0),
869
vec4(0.0, z_near, 0.0, 0.0),
870
vec4(0.0, 0.0, 0.0, 0.0),
871
vec4(0.0, 0.0, z_near, z_near),
872
)
873
}
874
875