From d27aa050d27804dff91e8bb8d6cd7b857a3fd015 Mon Sep 17 00:00:00 2001 From: Emerson Coskey <56370779+EmersonCoskey@users.noreply.github.com> Date: Tue, 12 Nov 2024 08:07:44 -0800 Subject: [PATCH] move to cubemap --- .../src/atmosphere/aerial_view_lut.wgsl | 12 +-- crates/bevy_pbr/src/atmosphere/bindings.wgsl | 2 +- crates/bevy_pbr/src/atmosphere/functions.wgsl | 36 +++---- crates/bevy_pbr/src/atmosphere/mod.rs | 30 +++--- crates/bevy_pbr/src/atmosphere/node.rs | 18 ++-- .../bevy_pbr/src/atmosphere/render_sky.wgsl | 37 +++++-- crates/bevy_pbr/src/atmosphere/resources.rs | 96 ++++++++++--------- .../bevy_pbr/src/atmosphere/sky_view_lut.wgsl | 56 ++++++++--- crates/bevy_pbr/src/atmosphere/types.wgsl | 6 +- examples/3d/atmosphere.rs | 7 +- 10 files changed, 177 insertions(+), 123 deletions(-) diff --git a/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl b/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl index d618be819ed47..ef0fcf2100ffc 100644 --- a/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl +++ b/crates/bevy_pbr/src/atmosphere/aerial_view_lut.wgsl @@ -6,20 +6,19 @@ functions::{ sample_transmittance_lut, sample_atmosphere, rayleigh, henyey_greenstein, sample_multiscattering_lut, AtmosphereSample, sample_local_inscattering, - get_local_r, get_local_up, view_radius, M_TO_KM, uv_to_ndc, position_ndc_to_world, depth_ndc_to_view_z + get_local_r, get_local_up, view_radius, uv_to_ndc, position_ndc_to_world, depth_ndc_to_view_z }, bruneton_functions::{distance_to_top_atmosphere_boundary, distance_to_bottom_atmosphere_boundary,ray_intersects_ground} } } -@group(0) @binding(13) var aerial_view_lut_out: texture_storage_3d; +@group(0) @binding(12) var aerial_view_lut_out: texture_storage_3d; @compute @workgroup_size(16, 16, 1) //TODO: this approach makes it so closer slices get fewer samples. But we also expect those to have less scattering. So win/win? fn main(@builtin(global_invocation_id) idx: vec3) { if any(idx.xy > settings.aerial_view_lut_size.xy) { return; } - var optical_depth: vec3 = vec3(0.0); let uv = (vec2(idx.xy) + 0.5) / vec2(settings.aerial_view_lut_size.xy); let ray_dir = uv_to_ray_direction(uv); //TODO: negate for lighting calcs? @@ -28,6 +27,7 @@ fn main(@builtin(global_invocation_id) idx: vec3) { var prev_t = 0.0; var total_inscattering = vec3(0.0); + var optical_depth = vec3(0.0); for (var slice_i: i32 = i32(settings.aerial_view_lut_size.z - 1); slice_i >= 0; slice_i--) { //reversed loop to iterate raw depth values near->far var sum_transmittance = 0.0; for (var step_i: i32 = i32(settings.aerial_view_lut_samples - 1); step_i >= 0; step_i--) { //same here @@ -50,11 +50,9 @@ fn main(@builtin(global_invocation_id) idx: vec3) { var local_inscattering = sample_local_inscattering(local_atmosphere, transmittance_to_sample, ray_dir.xyz, local_r, local_up); total_inscattering += local_inscattering * step_length; sum_transmittance += transmittance_to_sample.r + transmittance_to_sample.g + transmittance_to_sample.b; - - let mean_transmittance = sum_transmittance / (f32(settings.aerial_view_lut_samples) * 3.0); - textureStore(aerial_view_lut_out, vec3(vec2(idx.xy), slice_i), vec4(total_inscattering, mean_transmittance)); - //textureStore(aerial_view_lut_out, vec3(vec2(idx.xy), slice_i), vec4(optical_depth, step_length)); } + let mean_transmittance = sum_transmittance / (f32(settings.aerial_view_lut_samples) * 3.0); + textureStore(aerial_view_lut_out, vec3(vec2(idx.xy), slice_i), vec4(total_inscattering, mean_transmittance)); } } diff --git a/crates/bevy_pbr/src/atmosphere/bindings.wgsl b/crates/bevy_pbr/src/atmosphere/bindings.wgsl index f45a67bcd2a54..0081c6fabead4 100644 --- a/crates/bevy_pbr/src/atmosphere/bindings.wgsl +++ b/crates/bevy_pbr/src/atmosphere/bindings.wgsl @@ -15,7 +15,7 @@ @group(0) @binding(5) var transmittance_lut_sampler: sampler; @group(0) @binding(6) var multiscattering_lut: texture_2d; @group(0) @binding(7) var multiscattering_lut_sampler: sampler; -@group(0) @binding(8) var sky_view_lut: texture_2d; +@group(0) @binding(8) var sky_view_lut: texture_cube; @group(0) @binding(9) var sky_view_lut_sampler: sampler; @group(0) @binding(10) var aerial_view_lut: texture_3d; @group(0) @binding(11) var aerial_view_lut_sampler: sampler; diff --git a/crates/bevy_pbr/src/atmosphere/functions.wgsl b/crates/bevy_pbr/src/atmosphere/functions.wgsl index 9c9288a38f10f..f111f2a6ad98a 100644 --- a/crates/bevy_pbr/src/atmosphere/functions.wgsl +++ b/crates/bevy_pbr/src/atmosphere/functions.wgsl @@ -32,17 +32,15 @@ fn multiscattering_lut_uv_to_r_mu(uv: vec2) -> vec2 { return vec2(r, mu); } -fn sky_view_lut_lat_long_to_uv(lat: f32, long: f32) -> vec2 { - let u = long * FRAC_PI + 0.5; - let v = sqrt(2 * abs(lat) * FRAC_PI) * sign(lat) * 0.5 + 0.5; - return vec2(u, v); +fn sky_view_lut_squash_ray_dir(ray_dir_vs: vec3) -> vec3 { + let new_y = sqrt(abs(ray_dir_vs.y)) * sign(ray_dir_vs.y); + return normalize(vec3(ray_dir_vs.x, new_y, ray_dir_vs.z)); } -fn sky_view_lut_uv_to_lat_long(uv: vec2) -> vec2 { - let long = (uv.x - 0.5) * TAU; - let v_minus_half = uv.y - 0.5; - let lat = TAU * (v_minus_half * v_minus_half) * sign(v_minus_half); - return vec2(lat, long); +fn sky_view_lut_unsquash_ray_dir(ray_dir_vs: vec3) -> vec3 { + let abs_y = abs(ray_dir_vs.y); + let new_y = abs_y * abs_y * sign(ray_dir_vs.y); + return normalize(vec3(ray_dir_vs.x, new_y, ray_dir_vs.z)); } // LUT SAMPLING @@ -57,12 +55,9 @@ fn sample_multiscattering_lut(r: f32, mu: f32) -> vec3 { return textureSampleLevel(multiscattering_lut, multiscattering_lut_sampler, uv, 0.0).rgb; } -fn sample_sky_view_lut(ray_dir: vec3) -> vec3 { - let lat_long = ray_dir_to_lat_long(ray_dir); - let uv = sky_view_lut_lat_long_to_uv(lat_long.x, lat_long.y); - let long = fract(abs(lat_long.y)); - //return vec3(long, long, long); - return textureSampleLevel(sky_view_lut, sky_view_lut_sampler, uv, 0.0).rgb; +fn sample_sky_view_lut(ray_dir_vs: vec3) -> vec3 { + let ray_dir_vs_squashed = sky_view_lut_squash_ray_dir(ray_dir_vs); + return textureSampleLevel(sky_view_lut, sky_view_lut_sampler, ray_dir_vs_squashed, 0.0).rgb; } //RGB channels: total inscattered light along the camera ray to the current sample. @@ -83,7 +78,6 @@ fn henyey_greenstein(neg_LdotV: f32) -> f32 { return FRAC_4_PI * (1.0 - g * g) / (denom * sqrt(denom)); } - // ATMOSPHERE SAMPLING struct AtmosphereSample { @@ -144,6 +138,7 @@ fn sample_local_inscattering(local_atmosphere: AtmosphereSample, transmittance_t //TODO: make pr to specify light angular size on struct itself const SUN_ANGULAR_SIZE: f32 = 0.00436332; //angular radius of sun in radians +//const SUN_ANGULAR_SIZE: f32 = 0.1; fn sample_sun_disk(ray_dir: vec3, transmittance: vec3) -> vec3 { var sun_contribution = vec3(0.0); @@ -221,11 +216,10 @@ fn uv_to_ray_direction(uv: vec2) -> vec4 { return vec4(normalize(ray_direction), -view_ray_direction.z); } -fn ray_dir_to_lat_long(ray_dir: vec3) -> vec2 { - let view_dir = -view.world_from_view[2].xyz; - let lat = asin(ray_dir.y); - let long = atan2(view_dir.z, view_dir.x) - atan2(ray_dir.z, ray_dir.x); //TODO: explain - return vec2(lat, long); +/// Convert a view space direction to world space +fn direction_view_to_world(view_dir: vec3) -> vec3 { + let world_dir = view.world_from_view * vec4(view_dir, 0.0); + return world_dir.xyz; } /// Convert ndc depth to linear view z. diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index bfdabf35f5a17..e330f3ca61ecc 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -178,25 +178,25 @@ pub struct Atmosphere { /// Radius of the planet /// /// units: km - bottom_radius: f32, + pub bottom_radius: f32, /// Radius at which we consider the atmosphere to 'end' for out calculations (from center of planet) /// units: km - top_radius: f32, + pub top_radius: f32, - ground_albedo: Vec3, //used for estimating multiscattering + pub ground_albedo: Vec3, //used for estimating multiscattering - rayleigh_density_exp_scale: f32, - rayleigh_scattering: Vec3, + pub rayleigh_density_exp_scale: f32, + pub rayleigh_scattering: Vec3, - mie_density_exp_scale: f32, - mie_scattering: f32, //units: km^-1 - mie_absorption: f32, //units: km^-1 - mie_asymmetry: f32, //the "asymmetry" value of the phase function, unitless. Domain: (-1, 1) + pub mie_density_exp_scale: f32, + pub mie_scattering: f32, //units: km^-1 + pub mie_absorption: f32, //units: km^-1 + pub mie_asymmetry: f32, //the "asymmetry" value of the phase function, unitless. Domain: (-1, 1) - ozone_layer_center_altitude: f32, //units: km - ozone_layer_half_width: f32, //units: km - ozone_absorption: Vec3, //ozone absorption. units: km^-1 + pub ozone_layer_center_altitude: f32, //units: km + pub ozone_layer_half_width: f32, //units: km + pub ozone_absorption: Vec3, //ozone absorption. units: km^-1 } impl Atmosphere { @@ -240,10 +240,10 @@ impl ExtractComponent for Atmosphere { pub struct AtmosphereSettings { pub transmittance_lut_size: UVec2, pub multiscattering_lut_size: UVec2, - pub sky_view_lut_size: UVec2, + pub aerial_view_lut_size: UVec3, + pub sky_view_lut_size: u32, pub multiscattering_lut_dirs: u32, pub transmittance_lut_samples: u32, - pub aerial_view_lut_size: UVec3, pub multiscattering_lut_samples: u32, pub sky_view_lut_samples: u32, pub aerial_view_lut_samples: u32, @@ -258,7 +258,7 @@ impl Default for AtmosphereSettings { multiscattering_lut_size: UVec2::new(32, 32), multiscattering_lut_dirs: 64, multiscattering_lut_samples: 20, - sky_view_lut_size: UVec2::new(200, 100), + sky_view_lut_size: 64, sky_view_lut_samples: 30, aerial_view_lut_size: UVec3::new(32, 32, 32), aerial_view_lut_samples: 10, diff --git a/crates/bevy_pbr/src/atmosphere/node.rs b/crates/bevy_pbr/src/atmosphere/node.rs index a11d30b937d6d..c03eea0f25a84 100644 --- a/crates/bevy_pbr/src/atmosphere/node.rs +++ b/crates/bevy_pbr/src/atmosphere/node.rs @@ -62,7 +62,7 @@ impl ViewNode for AtmosphereLutsNode { ) = ( pipeline_cache.get_render_pipeline(pipelines.transmittance_lut), pipeline_cache.get_compute_pipeline(pipelines.multiscattering_lut), - pipeline_cache.get_render_pipeline(pipelines.sky_view_lut), + pipeline_cache.get_compute_pipeline(pipelines.sky_view_lut), pipeline_cache.get_compute_pipeline(pipelines.aerial_view_lut), ) else { @@ -127,16 +127,14 @@ impl ViewNode for AtmosphereLutsNode { multiscattering_lut_pass.dispatch_workgroups(workgroups_x, workgroups_y, 1); } + const SKY_VIEW_WORKGROUP_SIZE: u32 = 16; + let workgroups_x = settings.sky_view_lut_size.div_ceil(SKY_VIEW_WORKGROUP_SIZE); + let workgroups_y = settings.sky_view_lut_size.div_ceil(SKY_VIEW_WORKGROUP_SIZE); + { - let mut sky_view_lut_pass = commands.begin_render_pass(&RenderPassDescriptor { + let mut sky_view_lut_pass = commands.begin_compute_pass(&ComputePassDescriptor { label: Some("sky_view_lut_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view: &textures.sky_view_lut.default_view, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - ..Default::default() + timestamp_writes: None, }); sky_view_lut_pass.set_pipeline(sky_view_lut_pipeline); sky_view_lut_pass.set_bind_group( @@ -149,7 +147,7 @@ impl ViewNode for AtmosphereLutsNode { lights_uniforms_offset.offset, ], ); - sky_view_lut_pass.draw(0..3, 0..1); + sky_view_lut_pass.dispatch_workgroups(workgroups_x, workgroups_y, 6); } { diff --git a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl index 2ed70cc5f855a..ce778a9f1ecbe 100644 --- a/crates/bevy_pbr/src/atmosphere/render_sky.wgsl +++ b/crates/bevy_pbr/src/atmosphere/render_sky.wgsl @@ -1,7 +1,7 @@ #import bevy_pbr::atmosphere::{ types::{Atmosphere, AtmosphereSettings}, bindings::{atmosphere, view}, - functions::{sample_transmittance_lut, sample_sky_view_lut, uv_to_ray_direction, uv_to_ndc, sample_aerial_view_lut, view_radius, sample_sun_disk}, + functions::{sample_transmittance_lut, sample_sky_view_lut, direction_view_to_world, uv_to_ndc, sample_aerial_view_lut, view_radius, sample_sun_disk}, }; #import bevy_render::view::View; @@ -13,13 +13,14 @@ fn main(in: FullscreenVertexOutput) -> @location(0) vec4 { let depth = textureLoad(depth_texture, vec2(in.position.xy), 0); if depth == 0.0 { - let ray_dir = uv_to_ray_direction(in.uv).xyz; + let view_ray_dir = uv_to_ray_direction(in.uv).xyz; + let world_ray_dir = direction_view_to_world(view_ray_dir); let r = view_radius(); - let mu = ray_dir.y; - let sky_view = sample_sky_view_lut(ray_dir); + let mu = world_ray_dir.y; + let sky_view = sample_sky_view_lut(view_ray_dir); let transmittance = sample_transmittance_lut(r, mu); - let sun_disk = sample_sun_disk(ray_dir, transmittance); - return vec4(sky_view + sun_disk, 1.0); + let sun_disk = sample_sun_disk(world_ray_dir, transmittance); + return vec4(sky_view + sun_disk, (transmittance.r + transmittance.g + transmittance.b) / 3.0); } else { let ndc_xy = uv_to_ndc(in.uv); let ndc = vec3(ndc_xy, depth); @@ -27,3 +28,27 @@ fn main(in: FullscreenVertexOutput) -> @location(0) vec4 { return inscattering; } } + +//Modified from skybox.wgsl. For this pass we don't need to apply a separate sky transform or consider camera viewport. +//w component is the cosine of the view direction with the view forward vector, to correct step distance at the edges of the viewport +fn uv_to_ray_direction(uv: vec2) -> vec4 { + // Using world positions of the fragment and camera to calculate a ray direction + // breaks down at large translations. This code only needs to know the ray direction. + // The ray direction is along the direction from the camera to the fragment position. + // In view space, the camera is at the origin, so the view space ray direction is + // along the direction of the fragment position - (0,0,0) which is just the + // fragment position. + // Use the position on the near clipping plane to avoid -inf world position + // because the far plane of an infinite reverse projection is at infinity. + let view_position_homogeneous = view.view_from_clip * vec4( + uv_to_ndc(uv), + 1.0, + 1.0, + ); + + // Transforming the view space ray direction by the skybox transform matrix, it is + // equivalent to rotating the skybox itself. + let view_ray_direction = view_position_homogeneous.xyz / view_position_homogeneous.w; //TODO: remove this step and just use position_ndc_to_world? we didn't need to transform in view space + + return vec4(normalize(view_ray_direction), -view_ray_direction.z); +} diff --git a/crates/bevy_pbr/src/atmosphere/resources.rs b/crates/bevy_pbr/src/atmosphere/resources.rs index 1f3fe5c19507a..ba3129c9e1781 100644 --- a/crates/bevy_pbr/src/atmosphere/resources.rs +++ b/crates/bevy_pbr/src/atmosphere/resources.rs @@ -12,7 +12,8 @@ use bevy_render::{ extract_component::ComponentUniforms, render_resource::{ binding_types::{ - sampler, texture_2d, texture_3d, texture_storage_2d, texture_storage_3d, uniform_buffer, + sampler, texture_2d, texture_3d, texture_cube, texture_storage_2d, + texture_storage_2d_array, texture_storage_3d, uniform_buffer, }, AddressMode, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, BlendComponent, BlendFactor, BlendOperation, BlendState, CachedComputePipelineId, @@ -20,7 +21,7 @@ use bevy_render::{ FilterMode, FragmentState, MultisampleState, PipelineCache, PrimitiveState, RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages, StorageTextureAccess, TextureDescriptor, TextureDimension, TextureFormat, - TextureSampleType, TextureUsages, + TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor, TextureViewDimension, }, renderer::RenderDevice, texture::{CachedTexture, TextureCache}, @@ -78,7 +79,7 @@ impl FromWorld for AtmosphereBindGroupLayouts { let sky_view_lut = render_device.create_bind_group_layout( "sky_view_lut_bind_group_layout", &BindGroupLayoutEntries::with_indices( - ShaderStages::FRAGMENT, + ShaderStages::COMPUTE, ( (0, uniform_buffer::(true)), (1, uniform_buffer::(true)), @@ -88,6 +89,13 @@ impl FromWorld for AtmosphereBindGroupLayouts { (5, sampler(SamplerBindingType::Filtering)), (6, texture_2d(TextureSampleType::Float { filterable: true })), //multiscattering lut and sampler (7, sampler(SamplerBindingType::Filtering)), + ( + 12, + texture_storage_2d_array( + TextureFormat::Rgba16Float, + StorageTextureAccess::WriteOnly, + ), + ), ), ), ); @@ -107,7 +115,7 @@ impl FromWorld for AtmosphereBindGroupLayouts { (7, sampler(SamplerBindingType::Filtering)), ( //Aerial view lut storage texture - 13, + 12, texture_storage_3d( TextureFormat::Rgba16Float, StorageTextureAccess::WriteOnly, @@ -128,7 +136,11 @@ impl FromWorld for AtmosphereBindGroupLayouts { (3, uniform_buffer::(true)), (4, texture_2d(TextureSampleType::Float { filterable: true })), //transmittance lut and sampler (5, sampler(SamplerBindingType::Filtering)), - (8, texture_2d(TextureSampleType::Float { filterable: true })), //sky view lut and sampler + ( + //sky view lut and sampler + 8, + texture_cube(TextureSampleType::Float { filterable: true }), + ), (9, sampler(SamplerBindingType::Filtering)), ( // aerial view lut and sampler @@ -167,37 +179,31 @@ impl FromWorld for AtmosphereSamplers { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); - let transmittance_lut = render_device.create_sampler(&SamplerDescriptor { - label: Some("transmittance_lut_sampler"), + let base_sampler = SamplerDescriptor { mag_filter: FilterMode::Linear, min_filter: FilterMode::Linear, - mipmap_filter: FilterMode::Linear, + mipmap_filter: FilterMode::Nearest, ..Default::default() + }; + + let transmittance_lut = render_device.create_sampler(&SamplerDescriptor { + label: Some("transmittance_lut_sampler"), + ..base_sampler }); let multiscattering_lut = render_device.create_sampler(&SamplerDescriptor { label: Some("multiscattering_lut_sampler"), - mag_filter: FilterMode::Linear, - min_filter: FilterMode::Linear, - mipmap_filter: FilterMode::Linear, - ..Default::default() + ..base_sampler }); let sky_view_lut = render_device.create_sampler(&SamplerDescriptor { label: Some("sky_view_lut_sampler"), - mag_filter: FilterMode::Linear, - min_filter: FilterMode::Linear, - mipmap_filter: FilterMode::Linear, - address_mode_u: AddressMode::Repeat, //want to wrap along the equator - ..Default::default() + ..base_sampler }); let aerial_view_lut = render_device.create_sampler(&SamplerDescriptor { label: Some("aerial_view_lut_sampler"), - mag_filter: FilterMode::Linear, - min_filter: FilterMode::Linear, - mipmap_filter: FilterMode::Linear, - ..Default::default() + ..base_sampler }); Self { @@ -213,7 +219,7 @@ impl FromWorld for AtmosphereSamplers { pub(crate) struct AtmospherePipelines { pub transmittance_lut: CachedRenderPipelineId, pub multiscattering_lut: CachedComputePipelineId, - pub sky_view_lut: CachedRenderPipelineId, + pub sky_view_lut: CachedComputePipelineId, pub aerial_view_lut: CachedComputePipelineId, pub render_sky: CachedRenderPipelineId, } @@ -255,25 +261,14 @@ impl FromWorld for AtmospherePipelines { zero_initialize_workgroup_memory: false, }); - let sky_view_lut = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { + let sky_view_lut = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { label: Some("sky_view_lut_pipeline".into()), layout: vec![layouts.sky_view_lut.clone()], push_constant_ranges: vec![], - vertex: fullscreen_shader_vertex_state(), - primitive: PrimitiveState::default(), - depth_stencil: None, - multisample: MultisampleState::default(), + shader: shaders::SKY_VIEW_LUT, + shader_defs: vec![], + entry_point: "main".into(), zero_initialize_workgroup_memory: false, - fragment: Some(FragmentState { - shader: shaders::SKY_VIEW_LUT.clone(), - shader_defs: vec![], - entry_point: "main".into(), - targets: vec![Some(ColorTargetState { - format: TextureFormat::Rgba16Float, - blend: None, - write_mask: ColorWrites::ALL, - })], - }), }); let aerial_view_lut = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { @@ -303,8 +298,8 @@ impl FromWorld for AtmospherePipelines { format: TextureFormat::Rgba16Float, blend: Some(BlendState { color: BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::One, + src_factor: BlendFactor::One, + dst_factor: BlendFactor::SrcAlpha, operation: BlendOperation::Add, }, alpha: BlendComponent { @@ -333,6 +328,7 @@ pub struct AtmosphereTextures { pub transmittance_lut: CachedTexture, pub multiscattering_lut: CachedTexture, pub sky_view_lut: CachedTexture, + pub sky_view_lut_cube_view: TextureView, pub aerial_view_lut: CachedTexture, } @@ -384,19 +380,25 @@ pub(super) fn prepare_atmosphere_textures( TextureDescriptor { label: Some("sky_view_lut"), size: Extent3d { - width: lut_settings.sky_view_lut_size.x, - height: lut_settings.sky_view_lut_size.y, - depth_or_array_layers: 1, + width: lut_settings.sky_view_lut_size, + height: lut_settings.sky_view_lut_size, + depth_or_array_layers: 6, }, mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, - format: TextureFormat::Rgba16Float, //TODO: check if needs hdr - usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, + format: TextureFormat::Rgba16Float, + usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING, view_formats: &[], }, ); + let sky_view_lut_cube_view = sky_view_lut.texture.create_view(&TextureViewDescriptor { + label: Some("sky_view_lut_cube"), + dimension: Some(TextureViewDimension::Cube), + ..Default::default() + }); + let aerial_view_lut = texture_cache.get( &render_device, TextureDescriptor { @@ -420,6 +422,7 @@ pub(super) fn prepare_atmosphere_textures( transmittance_lut, multiscattering_lut, sky_view_lut, + sky_view_lut_cube_view, aerial_view_lut, } }); @@ -503,6 +506,7 @@ pub(super) fn prepare_atmosphere_bind_groups( (5, &samplers.transmittance_lut), (6, &textures.multiscattering_lut.default_view), (7, &samplers.multiscattering_lut), + (12, &textures.sky_view_lut.default_view), )), ); @@ -518,7 +522,7 @@ pub(super) fn prepare_atmosphere_bind_groups( (5, &samplers.transmittance_lut), (6, &textures.multiscattering_lut.default_view), (7, &samplers.multiscattering_lut), - (13, &textures.aerial_view_lut.default_view), + (12, &textures.aerial_view_lut.default_view), )), ); @@ -532,7 +536,7 @@ pub(super) fn prepare_atmosphere_bind_groups( (3, lights_binding.clone()), (4, &textures.transmittance_lut.default_view), (5, &samplers.transmittance_lut), - (8, &textures.sky_view_lut.default_view), + (8, &textures.sky_view_lut_cube_view), (9, &samplers.sky_view_lut), (10, &textures.aerial_view_lut.default_view), (11, &samplers.aerial_view_lut), diff --git a/crates/bevy_pbr/src/atmosphere/sky_view_lut.wgsl b/crates/bevy_pbr/src/atmosphere/sky_view_lut.wgsl index 4bfdeda281ab1..dbc54f9c5eaa9 100644 --- a/crates/bevy_pbr/src/atmosphere/sky_view_lut.wgsl +++ b/crates/bevy_pbr/src/atmosphere/sky_view_lut.wgsl @@ -4,27 +4,32 @@ types::{Atmosphere, AtmosphereSettings}, bindings::{atmosphere, view, settings}, functions::{ - sample_atmosphere, get_local_up, - sample_transmittance_lut, sample_multiscattering_lut, rayleigh, henyey_greenstein, - distance_to_bottom_atmosphere_boundary, ray_intersects_ground, AtmosphereSample, - sky_view_lut_uv_to_lat_long, sample_local_inscattering, get_local_r, view_radius + sample_atmosphere, get_local_up, AtmosphereSample, + sample_local_inscattering, get_local_r, view_radius, + sky_view_lut_unsquash_ray_dir, direction_view_to_world }, - bruneton_functions::{distance_to_top_atmosphere_boundary, distance_to_bottom_atmosphere_boundary, ray_intersects_ground} + bruneton_functions::distance_to_top_atmosphere_boundary, } } #import bevy_render::view::View; #import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput -@fragment -fn main(in: FullscreenVertexOutput) -> @location(0) vec4 { - let lat_long = sky_view_lut_uv_to_lat_long(in.uv); - let ray_dir = get_ray_direction(lat_long); +@group(0) @binding(12) var sky_view_lut_out: texture_storage_2d_array; + +@compute +@workgroup_size(16, 16, 1) +fn main(@builtin(global_invocation_id) idx: vec3) { + let uv = (vec2(idx.xy) + vec2(0.5)) / f32(settings.sky_view_lut_size); + let ray_dir_vs_squashed = cubemap_coords_to_ray_dir(uv, idx.z); + let ray_dir_vs = sky_view_lut_unsquash_ray_dir(ray_dir_vs_squashed); + let ray_dir = direction_view_to_world(ray_dir_vs); + let r = view_radius(); //TODO: paper says to center the sky view on the planet ground let mu = ray_dir.y; - let atmosphere_dist = distance_to_top_atmosphere_boundary(r, mu); - let step_length = atmosphere_dist / f32(settings.sky_view_lut_samples); + let t_top = distance_to_top_atmosphere_boundary(r, mu); + let step_length = t_top / f32(settings.sky_view_lut_samples); var total_inscattering = vec3(0.0); var optical_depth = vec3(0.0); @@ -34,16 +39,41 @@ fn main(in: FullscreenVertexOutput) -> @location(0) vec4 { let local_up = get_local_up(r, t_i, ray_dir); let local_atmosphere = sample_atmosphere(local_r); - optical_depth += local_atmosphere.extinction * step_length; //TODO: Units between atmosphere and step_length + optical_depth += local_atmosphere.extinction * step_length; let transmittance_to_sample = exp(-optical_depth); var local_inscattering = sample_local_inscattering(local_atmosphere, transmittance_to_sample, ray_dir, local_r, local_up); total_inscattering += local_inscattering * step_length; } - return vec4(total_inscattering, 1.0); + textureStore(sky_view_lut_out, idx.xy, idx.z, vec4(total_inscattering, 1.0)); } +fn cubemap_coords_to_ray_dir(uv: vec2, face_index: u32) -> vec3 { + let quotient: u32 = face_index / 2u; + let remainder: u32 = face_index % 2u; + let sign: f32 = 1.0 - 2.0 * f32(remainder); + var ray_dir = vec3(0.0); + let uv1_1 = uv * 2 - 1; + switch quotient { + case 0u: { // x axis + ray_dir = vec3(sign, -uv1_1.y, -sign * uv1_1.x); + } + case 1u: { // y axis + ray_dir = vec3(uv1_1.x, sign, sign * uv1_1.y); + } + case 2u: { // z axis + ray_dir = vec3(sign * uv1_1.x, -uv1_1.y, sign); + } + default: { + ray_dir = vec3(0.0, 1.0, 0.0); + } + } + return normalize(ray_dir); +} + + + //lat-long projection [-pi, pi] x [-pi/2, pi/2] -> S^2 fn get_ray_direction(lat_long: vec2) -> vec3 { let cos_long = cos(lat_long.y); diff --git a/crates/bevy_pbr/src/atmosphere/types.wgsl b/crates/bevy_pbr/src/atmosphere/types.wgsl index 281501ffd35ee..b28635fa2f34a 100644 --- a/crates/bevy_pbr/src/atmosphere/types.wgsl +++ b/crates/bevy_pbr/src/atmosphere/types.wgsl @@ -25,10 +25,10 @@ struct Atmosphere { struct AtmosphereSettings { transmittance_lut_size: vec2, multiscattering_lut_size: vec2, - sky_view_lut_size: vec2, - transmittance_lut_samples: u32, - multiscattering_lut_dirs: u32, aerial_view_lut_size: vec3, //Gross ordering for padding reasons + sky_view_lut_size: u32, + multiscattering_lut_dirs: u32, + transmittance_lut_samples: u32, multiscattering_lut_samples: u32, sky_view_lut_samples: u32, aerial_view_lut_samples: u32, diff --git a/examples/3d/atmosphere.rs b/examples/3d/atmosphere.rs index 7aacb18475722..c11495dbf959d 100644 --- a/examples/3d/atmosphere.rs +++ b/examples/3d/atmosphere.rs @@ -26,6 +26,7 @@ fn main() { (setup_camera_fog, setup_terrain_scene, setup_instructions), ) .add_systems(Update, rotate_sun) + //.add_systems(Update, rotate_camera) .run(); } @@ -43,7 +44,7 @@ fn setup_camera_fog(mut commands: Commands) { }, Msaa::Off, Tonemapping::AcesFitted, - Transform::from_xyz(-1.2, 0.15, 0.0).looking_at(Vec3::Y * 0.1, Vec3::Y), + Transform::from_xyz(-1.2, 0.15, 0.0).looking_at(Vec3::Y * 0.15, Vec3::Y), Atmosphere::EARTH, AtmosphereSettings { scene_units_to_km: 1.0, @@ -112,3 +113,7 @@ fn setup_instructions(mut commands: Commands) { fn rotate_sun(mut sun: Single<&mut Transform, With>, time: Res