Path: blob/main/crates/bevy_pbr/src/volumetric_fog/render.rs
9427 views
//! Rendering of fog volumes.12use core::array;34use bevy_asset::{load_embedded_asset, AssetId, AssetServer, Handle};5use bevy_camera::Camera3d;6use bevy_color::ColorToComponents as _;7use bevy_core_pipeline::prepass::{8DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass,9};10use bevy_derive::{Deref, DerefMut};11use bevy_ecs::{12component::Component,13entity::Entity,14query::{Has, With},15resource::Resource,16system::{Commands, Local, Query, Res, ResMut},17};18use bevy_image::{BevyDefault, Image};19use bevy_light::{FogVolume, VolumetricFog, VolumetricLight};20use bevy_math::{vec4, Affine3A, Mat4, Vec3, Vec3A, Vec4};21use bevy_mesh::{Mesh, MeshVertexBufferLayoutRef};22use bevy_render::{23mesh::{allocator::MeshAllocator, RenderMesh, RenderMeshBufferInfo},24render_asset::RenderAssets,25render_resource::{26binding_types::{27sampler, texture_3d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer,28},29BindGroupLayoutDescriptor, BindGroupLayoutEntries, BindingResource, BlendComponent,30BlendFactor, BlendOperation, BlendState, CachedRenderPipelineId, ColorTargetState,31ColorWrites, DynamicBindGroupEntries, DynamicUniformBuffer, Face, FragmentState, LoadOp,32Operations, PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor,33RenderPipelineDescriptor, SamplerBindingType, ShaderStages, ShaderType,34SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp, TextureFormat,35TextureSampleType, TextureUsages, VertexState,36},37renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},38sync_world::RenderEntity,39texture::GpuImage,40view::{ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniformOffset},41Extract,42};43use bevy_shader::Shader;44use bevy_transform::components::GlobalTransform;45use bevy_utils::prelude::default;46use bitflags::bitflags;4748use crate::{49ExtractedAtmosphere, MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup,50ViewContactShadowsUniformOffset, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset,51ViewLightProbesUniformOffset, ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset,52};5354use super::FogAssets;5556bitflags! {57/// Flags that describe the bind group layout used to render volumetric fog.58#[derive(Clone, Copy, PartialEq)]59struct VolumetricFogBindGroupLayoutKey: u8 {60/// The framebuffer is multisampled.61const MULTISAMPLED = 0x1;62/// The volumetric fog has a 3D voxel density texture.63const DENSITY_TEXTURE = 0x2;64}65}6667bitflags! {68/// Flags that describe the rasterization pipeline used to render volumetric69/// fog.70#[derive(Clone, Copy, PartialEq, Eq, Hash)]71struct VolumetricFogPipelineKeyFlags: u8 {72/// The view's color format has high dynamic range.73const HDR = 0x1;74/// The volumetric fog has a 3D voxel density texture.75const DENSITY_TEXTURE = 0x2;76}77}7879/// The total number of bind group layouts.80///81/// This is the total number of combinations of all82/// [`VolumetricFogBindGroupLayoutKey`] flags.83const VOLUMETRIC_FOG_BIND_GROUP_LAYOUT_COUNT: usize =84VolumetricFogBindGroupLayoutKey::all().bits() as usize + 1;8586/// A matrix that converts from local 1×1×1 space to UVW 3D density texture87/// space.88static UVW_FROM_LOCAL: Mat4 = Mat4::from_cols(89vec4(1.0, 0.0, 0.0, 0.0),90vec4(0.0, 1.0, 0.0, 0.0),91vec4(0.0, 0.0, 1.0, 0.0),92vec4(0.5, 0.5, 0.5, 1.0),93);9495/// The GPU pipeline for the volumetric fog postprocessing effect.96#[derive(Resource)]97pub struct VolumetricFogPipeline {98/// A reference to the shared set of mesh pipeline view layouts.99mesh_view_layouts: MeshPipelineViewLayouts,100101/// All bind group layouts.102///103/// Since there aren't too many of these, we precompile them all.104volumetric_view_bind_group_layouts:105[BindGroupLayoutDescriptor; VOLUMETRIC_FOG_BIND_GROUP_LAYOUT_COUNT],106107// The shader asset handle.108shader: Handle<Shader>,109}110111/// The two render pipelines that we use for fog volumes: one for when a 3D112/// density texture is present and one for when it isn't.113#[derive(Component)]114pub struct ViewVolumetricFogPipelines {115/// The render pipeline that we use when no density texture is present, and116/// the density distribution is uniform.117pub textureless: CachedRenderPipelineId,118/// The render pipeline that we use when a density texture is present.119pub textured: CachedRenderPipelineId,120}121122/// Identifies a single specialization of the volumetric fog shader.123#[derive(PartialEq, Eq, Hash, Clone)]124pub struct VolumetricFogPipelineKey {125/// The layout of the view, which is needed for the raymarching.126mesh_pipeline_view_key: MeshPipelineViewLayoutKey,127128/// The vertex buffer layout of the primitive.129///130/// Both planes (used when the camera is inside the fog volume) and cubes131/// (used when the camera is outside the fog volume) use identical vertex132/// buffer layouts, so we only need one of them.133vertex_buffer_layout: MeshVertexBufferLayoutRef,134135/// Flags that specify features on the pipeline key.136flags: VolumetricFogPipelineKeyFlags,137}138139/// The same as [`VolumetricFog`] and [`FogVolume`], but formatted for140/// the GPU.141///142/// See the documentation of those structures for more information on these143/// fields.144#[derive(ShaderType)]145pub struct VolumetricFogUniform {146clip_from_local: Mat4,147148/// The transform from world space to 3D density texture UVW space.149uvw_from_world: Mat4,150151/// View-space plane equations of the far faces of the fog volume cuboid.152///153/// The vector takes the form V = (N, -N⋅Q), where N is the normal of the154/// plane and Q is any point in it, in view space. The equation of the plane155/// for homogeneous point P = (Px, Py, Pz, Pw) is V⋅P = 0.156far_planes: [Vec4; 3],157158fog_color: Vec3,159light_tint: Vec3,160ambient_color: Vec3,161ambient_intensity: f32,162step_count: u32,163164/// The radius of a sphere that bounds the fog volume in view space.165bounding_radius: f32,166167absorption: f32,168scattering: f32,169density: f32,170density_texture_offset: Vec3,171scattering_asymmetry: f32,172light_intensity: f32,173jitter_strength: f32,174}175176/// Specifies the offset within the [`VolumetricFogUniformBuffer`] of the177/// [`VolumetricFogUniform`] for a specific view.178#[derive(Component, Deref, DerefMut)]179pub struct ViewVolumetricFog(Vec<ViewFogVolume>);180181/// Information that the render world needs to maintain about each fog volume.182pub struct ViewFogVolume {183/// The 3D voxel density texture for this volume, if present.184density_texture: Option<AssetId<Image>>,185/// The offset of this view's [`VolumetricFogUniform`] structure within the186/// [`VolumetricFogUniformBuffer`].187uniform_buffer_offset: u32,188/// True if the camera is outside the fog volume; false if it's inside the189/// fog volume.190exterior: bool,191}192193/// The GPU buffer that stores the [`VolumetricFogUniform`] data.194#[derive(Resource, Default, Deref, DerefMut)]195pub struct VolumetricFogUniformBuffer(pub DynamicUniformBuffer<VolumetricFogUniform>);196197pub fn init_volumetric_fog_pipeline(198mut commands: Commands,199mesh_view_layouts: Res<MeshPipelineViewLayouts>,200asset_server: Res<AssetServer>,201) {202// Create the bind group layout entries common to all bind group203// layouts.204let base_bind_group_layout_entries = &BindGroupLayoutEntries::single(205ShaderStages::VERTEX_FRAGMENT,206// `volumetric_fog`207uniform_buffer::<VolumetricFogUniform>(true),208);209210// For every combination of `VolumetricFogBindGroupLayoutKey` bits,211// create a bind group layout.212let bind_group_layouts = array::from_fn(|bits| {213let flags = VolumetricFogBindGroupLayoutKey::from_bits_retain(bits as u8);214215let mut bind_group_layout_entries = base_bind_group_layout_entries.to_vec();216217// `depth_texture`218bind_group_layout_entries.extend_from_slice(&BindGroupLayoutEntries::with_indices(219ShaderStages::FRAGMENT,220((2211,222if flags.contains(VolumetricFogBindGroupLayoutKey::MULTISAMPLED) {223texture_depth_2d_multisampled()224} else {225texture_depth_2d()226},227),),228));229230// `density_texture` and `density_sampler`231if flags.contains(VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE) {232bind_group_layout_entries.extend_from_slice(&BindGroupLayoutEntries::with_indices(233ShaderStages::FRAGMENT,234(235(2, texture_3d(TextureSampleType::Float { filterable: true })),236(3, sampler(SamplerBindingType::Filtering)),237),238));239}240241// Create the bind group layout.242let description = flags.bind_group_layout_description();243BindGroupLayoutDescriptor::new(description, &bind_group_layout_entries)244});245246commands.insert_resource(VolumetricFogPipeline {247mesh_view_layouts: mesh_view_layouts.clone(),248volumetric_view_bind_group_layouts: bind_group_layouts,249shader: load_embedded_asset!(asset_server.as_ref(), "volumetric_fog.wgsl"),250});251}252253/// Extracts [`VolumetricFog`], [`FogVolume`], and [`VolumetricLight`]s254/// from the main world to the render world.255pub fn extract_volumetric_fog(256mut commands: Commands,257view_targets: Extract<Query<(RenderEntity, &VolumetricFog)>>,258fog_volumes: Extract<Query<(RenderEntity, &FogVolume, &GlobalTransform)>>,259volumetric_lights: Extract<Query<(RenderEntity, &VolumetricLight)>>,260) {261if volumetric_lights.is_empty() {262// TODO: needs better way to handle clean up in render world263for (entity, ..) in view_targets.iter() {264commands265.entity(entity)266.remove::<(VolumetricFog, ViewVolumetricFogPipelines, ViewVolumetricFog)>();267}268for (entity, ..) in fog_volumes.iter() {269commands.entity(entity).remove::<FogVolume>();270}271return;272}273274for (entity, volumetric_fog) in view_targets.iter() {275commands276.get_entity(entity)277.expect("Volumetric fog entity wasn't synced.")278.insert(*volumetric_fog);279}280281for (entity, fog_volume, fog_transform) in fog_volumes.iter() {282commands283.get_entity(entity)284.expect("Fog volume entity wasn't synced.")285.insert((*fog_volume).clone())286.insert(*fog_transform);287}288289for (entity, volumetric_light) in volumetric_lights.iter() {290commands291.get_entity(entity)292.expect("Volumetric light entity wasn't synced.")293.insert(*volumetric_light);294}295}296297pub fn volumetric_fog(298view: ViewQuery<(299&ViewTarget,300&ViewDepthTexture,301&ViewVolumetricFogPipelines,302&ViewUniformOffset,303&ViewLightsUniformOffset,304&ViewFogUniformOffset,305&ViewLightProbesUniformOffset,306&ViewVolumetricFog,307&MeshViewBindGroup,308&ViewScreenSpaceReflectionsUniformOffset,309&ViewContactShadowsUniformOffset,310&Msaa,311&ViewEnvironmentMapUniformOffset,312)>,313pipeline_cache: Res<PipelineCache>,314volumetric_lighting_pipeline: Res<VolumetricFogPipeline>,315volumetric_lighting_uniform_buffers: Res<VolumetricFogUniformBuffer>,316image_assets: Res<RenderAssets<GpuImage>>,317mesh_allocator: Res<MeshAllocator>,318fog_assets: Res<FogAssets>,319render_meshes: Res<RenderAssets<RenderMesh>>,320mut ctx: RenderContext,321) {322let (323view_target,324view_depth_texture,325view_volumetric_lighting_pipelines,326view_uniform_offset,327view_lights_offset,328view_fog_offset,329view_light_probes_offset,330view_fog_volumes,331view_bind_group,332view_ssr_offset,333view_contact_shadows_offset,334msaa,335view_environment_map_offset,336) = view.into_inner();337338// Fetch the uniform buffer and binding.339let (340Some(textureless_pipeline),341Some(textured_pipeline),342Some(volumetric_lighting_uniform_buffer_binding),343) = (344pipeline_cache.get_render_pipeline(view_volumetric_lighting_pipelines.textureless),345pipeline_cache.get_render_pipeline(view_volumetric_lighting_pipelines.textured),346volumetric_lighting_uniform_buffers.binding(),347)348else {349return;350};351352let command_encoder = ctx.command_encoder();353command_encoder.push_debug_group("volumetric_lighting");354355for view_fog_volume in view_fog_volumes.iter() {356// If the camera is outside the fog volume, pick the cube mesh;357// otherwise, pick the plane mesh. In the latter case we'll be358// effectively rendering a full-screen quad.359let mesh_handle = if view_fog_volume.exterior {360fog_assets.cube_mesh.clone()361} else {362fog_assets.plane_mesh.clone()363};364365let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_handle.id()) else {366continue;367};368369let density_image = view_fog_volume370.density_texture371.and_then(|density_texture| image_assets.get(density_texture));372373// Pick the right pipeline, depending on whether a density texture374// is present or not.375let pipeline = if density_image.is_some() {376textured_pipeline377} else {378textureless_pipeline379};380381// This should always succeed, but if the asset was unloaded don't382// panic.383let Some(render_mesh) = render_meshes.get(&mesh_handle) else {384return;385};386387// Create the bind group for the view.388//389// TODO: Cache this.390391let mut bind_group_layout_key = VolumetricFogBindGroupLayoutKey::empty();392bind_group_layout_key.set(393VolumetricFogBindGroupLayoutKey::MULTISAMPLED,394!matches!(*msaa, Msaa::Off),395);396397// Create the bind group entries. The ones relating to the density398// texture will only be filled in if that texture is present.399let mut bind_group_entries = DynamicBindGroupEntries::sequential((400volumetric_lighting_uniform_buffer_binding.clone(),401BindingResource::TextureView(view_depth_texture.view()),402));403if let Some(density_image) = density_image {404bind_group_layout_key.insert(VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE);405bind_group_entries = bind_group_entries.extend_sequential((406BindingResource::TextureView(&density_image.texture_view),407BindingResource::Sampler(&density_image.sampler),408));409}410411let volumetric_view_bind_group_layout = &volumetric_lighting_pipeline412.volumetric_view_bind_group_layouts[bind_group_layout_key.bits() as usize];413414let volumetric_view_bind_group = ctx.render_device().create_bind_group(415None,416&pipeline_cache.get_bind_group_layout(volumetric_view_bind_group_layout),417&bind_group_entries,418);419420let render_pass_descriptor = RenderPassDescriptor {421label: Some("volumetric lighting pass"),422color_attachments: &[Some(RenderPassColorAttachment {423view: view_target.main_texture_view(),424depth_slice: None,425resolve_target: None,426ops: Operations {427load: LoadOp::Load,428store: StoreOp::Store,429},430})],431depth_stencil_attachment: None,432timestamp_writes: None,433occlusion_query_set: None,434multiview_mask: None,435};436437let command_encoder = ctx.command_encoder();438let mut render_pass = command_encoder.begin_render_pass(&render_pass_descriptor);439440render_pass.set_vertex_buffer(0, *vertex_buffer_slice.buffer.slice(..));441render_pass.set_pipeline(pipeline);442render_pass.set_bind_group(4430,444&view_bind_group.main,445&[446view_uniform_offset.offset,447view_lights_offset.offset,448view_fog_offset.offset,449**view_light_probes_offset,450**view_ssr_offset,451**view_contact_shadows_offset,452**view_environment_map_offset,453],454);455render_pass.set_bind_group(4561,457&volumetric_view_bind_group,458&[view_fog_volume.uniform_buffer_offset],459);460461// Draw elements or arrays, as appropriate.462match &render_mesh.buffer_info {463RenderMeshBufferInfo::Indexed {464index_format,465count,466} => {467let Some(index_buffer_slice) = mesh_allocator.mesh_index_slice(&mesh_handle.id())468else {469continue;470};471472render_pass.set_index_buffer(*index_buffer_slice.buffer.slice(..), *index_format);473render_pass.draw_indexed(474index_buffer_slice.range.start..(index_buffer_slice.range.start + count),475vertex_buffer_slice.range.start as i32,4760..1,477);478}479RenderMeshBufferInfo::NonIndexed => {480render_pass.draw(vertex_buffer_slice.range, 0..1);481}482}483}484485ctx.command_encoder().pop_debug_group();486}487488impl SpecializedRenderPipeline for VolumetricFogPipeline {489type Key = VolumetricFogPipelineKey;490491fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {492// We always use hardware 2x2 filtering for sampling the shadow map; the493// more accurate versions with percentage-closer filtering aren't worth494// the overhead.495let mut shader_defs = vec!["SHADOW_FILTER_METHOD_HARDWARE_2X2".into()];496497// We need a separate layout for MSAA and non-MSAA, as well as one for498// the presence or absence of the density texture.499let mut bind_group_layout_key = VolumetricFogBindGroupLayoutKey::empty();500bind_group_layout_key.set(501VolumetricFogBindGroupLayoutKey::MULTISAMPLED,502key.mesh_pipeline_view_key503.contains(MeshPipelineViewLayoutKey::MULTISAMPLED),504);505bind_group_layout_key.set(506VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE,507key.flags508.contains(VolumetricFogPipelineKeyFlags::DENSITY_TEXTURE),509);510511let volumetric_view_bind_group_layout =512self.volumetric_view_bind_group_layouts[bind_group_layout_key.bits() as usize].clone();513514// Both the cube and plane have the same vertex layout, so we don't need515// to distinguish between the two.516let vertex_format = key517.vertex_buffer_layout518.0519.get_layout(&[Mesh::ATTRIBUTE_POSITION.at_shader_location(0)])520.expect("Failed to get vertex layout for volumetric fog hull");521522if key523.mesh_pipeline_view_key524.contains(MeshPipelineViewLayoutKey::MULTISAMPLED)525{526shader_defs.push("MULTISAMPLED".into());527}528529if key530.mesh_pipeline_view_key531.contains(MeshPipelineViewLayoutKey::ATMOSPHERE)532{533shader_defs.push("ATMOSPHERE".into());534}535536if key537.flags538.contains(VolumetricFogPipelineKeyFlags::DENSITY_TEXTURE)539{540shader_defs.push("DENSITY_TEXTURE".into());541}542543let layout = self544.mesh_view_layouts545.get_view_layout(key.mesh_pipeline_view_key);546let layout = vec![547layout.main_layout.clone(),548volumetric_view_bind_group_layout.clone(),549];550551RenderPipelineDescriptor {552label: Some("volumetric lighting pipeline".into()),553layout,554vertex: VertexState {555shader: self.shader.clone(),556shader_defs: shader_defs.clone(),557buffers: vec![vertex_format],558..default()559},560primitive: PrimitiveState {561cull_mode: Some(Face::Back),562..default()563},564fragment: Some(FragmentState {565shader: self.shader.clone(),566shader_defs,567targets: vec![Some(ColorTargetState {568format: if key.flags.contains(VolumetricFogPipelineKeyFlags::HDR) {569ViewTarget::TEXTURE_FORMAT_HDR570} else {571TextureFormat::bevy_default()572},573// Blend on top of what's already in the framebuffer. Doing574// the alpha blending with the hardware blender allows us to575// avoid having to use intermediate render targets.576blend: Some(BlendState {577color: BlendComponent {578src_factor: BlendFactor::One,579dst_factor: BlendFactor::OneMinusSrcAlpha,580operation: BlendOperation::Add,581},582alpha: BlendComponent {583src_factor: BlendFactor::Zero,584dst_factor: BlendFactor::One,585operation: BlendOperation::Add,586},587}),588write_mask: ColorWrites::ALL,589})],590..default()591}),592..default()593}594}595}596597/// Specializes volumetric fog pipelines for all views with that effect enabled.598pub fn prepare_volumetric_fog_pipelines(599mut commands: Commands,600pipeline_cache: Res<PipelineCache>,601mut pipelines: ResMut<SpecializedRenderPipelines<VolumetricFogPipeline>>,602volumetric_lighting_pipeline: Res<VolumetricFogPipeline>,603fog_assets: Res<FogAssets>,604view_targets: Query<605(606Entity,607&ExtractedView,608&Msaa,609Has<NormalPrepass>,610Has<DepthPrepass>,611Has<MotionVectorPrepass>,612Has<DeferredPrepass>,613Has<ExtractedAtmosphere>,614),615With<VolumetricFog>,616>,617meshes: Res<RenderAssets<RenderMesh>>,618) {619let Some(plane_mesh) = meshes.get(&fog_assets.plane_mesh) else {620// There's an off chance that the mesh won't be prepared yet if `RenderAssetBytesPerFrame` limiting is in use.621return;622};623624for (625entity,626view,627msaa,628normal_prepass,629depth_prepass,630motion_vector_prepass,631deferred_prepass,632atmosphere,633) in view_targets.iter()634{635// Create a mesh pipeline view layout key corresponding to the view.636let mut mesh_pipeline_view_key = MeshPipelineViewLayoutKey::from(*msaa);637mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::NORMAL_PREPASS, normal_prepass);638mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::DEPTH_PREPASS, depth_prepass);639mesh_pipeline_view_key.set(640MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS,641motion_vector_prepass,642);643mesh_pipeline_view_key.set(644MeshPipelineViewLayoutKey::DEFERRED_PREPASS,645deferred_prepass,646);647mesh_pipeline_view_key.set(MeshPipelineViewLayoutKey::ATMOSPHERE, atmosphere);648if cfg!(feature = "bluenoise_texture") {649mesh_pipeline_view_key |= MeshPipelineViewLayoutKey::STBN;650}651652let mut textureless_flags = VolumetricFogPipelineKeyFlags::empty();653textureless_flags.set(VolumetricFogPipelineKeyFlags::HDR, view.hdr);654655// Specialize the pipeline.656let textureless_pipeline_key = VolumetricFogPipelineKey {657mesh_pipeline_view_key,658vertex_buffer_layout: plane_mesh.layout.clone(),659flags: textureless_flags,660};661let textureless_pipeline_id = pipelines.specialize(662&pipeline_cache,663&volumetric_lighting_pipeline,664textureless_pipeline_key.clone(),665);666let textured_pipeline_id = pipelines.specialize(667&pipeline_cache,668&volumetric_lighting_pipeline,669VolumetricFogPipelineKey {670flags: textureless_pipeline_key.flags671| VolumetricFogPipelineKeyFlags::DENSITY_TEXTURE,672..textureless_pipeline_key673},674);675676commands.entity(entity).insert(ViewVolumetricFogPipelines {677textureless: textureless_pipeline_id,678textured: textured_pipeline_id,679});680}681}682683/// A system that converts [`VolumetricFog`] into [`VolumetricFogUniform`]s.684pub fn prepare_volumetric_fog_uniforms(685mut commands: Commands,686mut volumetric_lighting_uniform_buffer: ResMut<VolumetricFogUniformBuffer>,687view_targets: Query<(Entity, &ExtractedView, &VolumetricFog)>,688fog_volumes: Query<(Entity, &FogVolume, &GlobalTransform)>,689render_device: Res<RenderDevice>,690render_queue: Res<RenderQueue>,691mut local_from_world_matrices: Local<Vec<Affine3A>>,692) {693// Do this up front to avoid O(n^2) matrix inversion.694local_from_world_matrices.clear();695for (_, _, fog_transform) in fog_volumes.iter() {696local_from_world_matrices.push(fog_transform.affine().inverse());697}698699let uniform_count = view_targets.iter().len() * local_from_world_matrices.len();700701let Some(mut writer) =702volumetric_lighting_uniform_buffer.get_writer(uniform_count, &render_device, &render_queue)703else {704return;705};706707for (view_entity, extracted_view, volumetric_fog) in view_targets.iter() {708let world_from_view = extracted_view.world_from_view.affine();709710let mut view_fog_volumes = vec![];711712for ((_, fog_volume, _), local_from_world) in713fog_volumes.iter().zip(local_from_world_matrices.iter())714{715// Calculate the transforms to and from 1×1×1 local space.716let local_from_view = *local_from_world * world_from_view;717let view_from_local = local_from_view.inverse();718719// Determine whether the camera is inside or outside the volume, and720// calculate the clip space transform.721let interior = camera_is_inside_fog_volume(&local_from_view);722let hull_clip_from_local = calculate_fog_volume_clip_from_local_transforms(723interior,724&extracted_view.clip_from_view,725&view_from_local,726);727728// Calculate the radius of the sphere that bounds the fog volume.729let bounding_radius = view_from_local730.transform_vector3a(Vec3A::splat(0.5))731.length();732733// Write out our uniform.734let uniform_buffer_offset = writer.write(&VolumetricFogUniform {735clip_from_local: hull_clip_from_local,736uvw_from_world: UVW_FROM_LOCAL * *local_from_world,737far_planes: get_far_planes(&view_from_local),738fog_color: fog_volume.fog_color.to_linear().to_vec3(),739light_tint: fog_volume.light_tint.to_linear().to_vec3(),740ambient_color: volumetric_fog.ambient_color.to_linear().to_vec3(),741ambient_intensity: volumetric_fog.ambient_intensity,742step_count: volumetric_fog.step_count,743bounding_radius,744absorption: fog_volume.absorption,745scattering: fog_volume.scattering,746density: fog_volume.density_factor,747density_texture_offset: fog_volume.density_texture_offset,748scattering_asymmetry: fog_volume.scattering_asymmetry,749light_intensity: fog_volume.light_intensity,750jitter_strength: volumetric_fog.jitter,751});752753view_fog_volumes.push(ViewFogVolume {754uniform_buffer_offset,755exterior: !interior,756density_texture: fog_volume.density_texture.as_ref().map(Handle::id),757});758}759760commands761.entity(view_entity)762.insert(ViewVolumetricFog(view_fog_volumes));763}764}765766/// A system that marks all view depth textures as readable in shaders.767///768/// The volumetric lighting pass needs to do this, and it doesn't happen by769/// default.770pub fn prepare_view_depth_textures_for_volumetric_fog(771mut view_targets: Query<&mut Camera3d>,772fog_volumes: Query<&VolumetricFog>,773) {774if fog_volumes.is_empty() {775return;776}777778for mut camera in view_targets.iter_mut() {779camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();780}781}782783fn get_far_planes(view_from_local: &Affine3A) -> [Vec4; 3] {784let (mut far_planes, mut next_index) = ([Vec4::ZERO; 3], 0);785786for &local_normal in &[787Vec3A::X,788Vec3A::NEG_X,789Vec3A::Y,790Vec3A::NEG_Y,791Vec3A::Z,792Vec3A::NEG_Z,793] {794let view_normal = view_from_local795.transform_vector3a(local_normal)796.normalize_or_zero();797if view_normal.z <= 0.0 {798continue;799}800801let view_position = view_from_local.transform_point3a(-local_normal * 0.5);802let plane_coords = view_normal.extend(-view_normal.dot(view_position));803804far_planes[next_index] = plane_coords;805next_index += 1;806if next_index == far_planes.len() {807continue;808}809}810811far_planes812}813814impl VolumetricFogBindGroupLayoutKey {815/// Creates an appropriate debug description for the bind group layout with816/// these flags.817fn bind_group_layout_description(&self) -> String {818if self.is_empty() {819return "volumetric lighting view bind group layout".to_owned();820}821822format!(823"volumetric lighting view bind group layout ({})",824self.iter()825.filter_map(|flag| {826if flag == VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE {827Some("density texture")828} else if flag == VolumetricFogBindGroupLayoutKey::MULTISAMPLED {829Some("multisampled")830} else {831None832}833})834.collect::<Vec<_>>()835.join(", ")836)837}838}839840/// Given the transform from the view to the 1×1×1 cube in local fog volume841/// space, returns true if the camera is inside the volume.842fn camera_is_inside_fog_volume(local_from_view: &Affine3A) -> bool {843local_from_view844.translation845.abs()846.cmple(Vec3A::splat(0.5))847.all()848}849850/// Given the local transforms, returns the matrix that transforms model space851/// to clip space.852fn calculate_fog_volume_clip_from_local_transforms(853interior: bool,854clip_from_view: &Mat4,855view_from_local: &Affine3A,856) -> Mat4 {857if !interior {858return *clip_from_view * Mat4::from(*view_from_local);859}860861// If the camera is inside the fog volume, then we'll be rendering a full862// screen quad. The shader will start its raymarch at the fragment depth863// value, however, so we need to make sure that the depth of the full screen864// quad is at the near clip plane `z_near`.865let z_near = clip_from_view.w_axis[2];866Mat4::from_cols(867vec4(z_near, 0.0, 0.0, 0.0),868vec4(0.0, z_near, 0.0, 0.0),869vec4(0.0, 0.0, 0.0, 0.0),870vec4(0.0, 0.0, z_near, z_near),871)872}873874875