├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── benches └── precompute.rs ├── examples └── dump.rs ├── shaders ├── direct_irradiance.comp ├── fullscreen.vert ├── indirect_irradiance.comp ├── irradiance.h ├── multiple_scattering.comp ├── params.h ├── render_lighting.h ├── render_sky.frag ├── render_sky.h ├── scattering.h ├── scattering_density.comp ├── single_scattering.comp ├── transmittance.comp ├── transmittance.h └── util.h ├── src ├── lib.rs ├── precompute.rs └── render.rs └── tests └── smoke.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | pull_request: 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Install shaderc 13 | run: | 14 | wget -nv -r -nd -A install.tgz 'https://storage.googleapis.com/shaderc/badges/build_link_linux_clang_release.html' 15 | tar xf install.tgz 16 | export SHADERC_LIB_DIR="$PWD/install/lib" 17 | - uses: actions/checkout@v1 18 | - uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: stable 22 | override: true 23 | - uses: actions-rs/cargo@v1 24 | with: 25 | command: build 26 | args: --workspace --all-targets 27 | - uses: actions-rs/cargo@v1 28 | with: 29 | command: test 30 | args: --workspace 31 | 32 | lint: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Install shaderc 36 | run: | 37 | wget -nv -r -nd -A install.tgz 'https://storage.googleapis.com/shaderc/badges/build_link_linux_clang_release.html' 38 | tar xf install.tgz 39 | export SHADERC_LIB_DIR="$PWD/install/lib" 40 | - uses: actions/checkout@v1 41 | - uses: actions-rs/toolchain@v1 42 | with: 43 | profile: minimal 44 | toolchain: stable 45 | override: true 46 | components: rustfmt, clippy 47 | - uses: actions-rs/cargo@v1 48 | with: 49 | command: fmt 50 | args: --all -- --check 51 | - uses: actions-rs/cargo@v1 52 | if: always() 53 | with: 54 | command: clippy 55 | args: --workspace --all-targets -- -D warnings 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzzyblue" 3 | version = "0.2.0" 4 | authors = ["Benjamin Saunders "] 5 | license = "MIT/Apache-2.0" 6 | edition = "2018" 7 | 8 | [dependencies] 9 | ash = "0.31" 10 | vk-shader-macros = "0.2" 11 | 12 | [dev-dependencies] 13 | openexr = "0.7" 14 | half = "1" 15 | renderdoc = { version = "0.9", default-features = false } 16 | bencher = "0.1.5" 17 | 18 | [[bench]] 19 | name = "precompute" 20 | harness = false 21 | -------------------------------------------------------------------------------- /benches/precompute.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::ffi::CStr; 3 | use std::os::raw::{c_char, c_void}; 4 | use std::ptr; 5 | use std::sync::Arc; 6 | 7 | use ash::version::{DeviceV1_0, EntryV1_0, InstanceV1_0}; 8 | use ash::{extensions::ext::DebugReport, vk, Entry}; 9 | use bencher::{benchmark_group, benchmark_main, Bencher}; 10 | use renderdoc::{RenderDoc, V100}; 11 | 12 | fn precompute(bench: &mut Bencher) { 13 | let mut rd = RenderDoc::::new().ok(); 14 | unsafe { 15 | let entry = Entry::new().unwrap(); 16 | let app_name = CStr::from_bytes_with_nul(b"fuzzyblue smoke test\0").unwrap(); 17 | let instance = entry 18 | .create_instance( 19 | &vk::InstanceCreateInfo::builder() 20 | .application_info( 21 | &vk::ApplicationInfo::builder() 22 | .application_name(&app_name) 23 | .application_version(0) 24 | .engine_name(&app_name) 25 | .engine_version(0) 26 | .api_version(vk::make_version(1, 0, 36)), 27 | ) 28 | .enabled_extension_names(&[DebugReport::name().as_ptr()]), 29 | None, 30 | ) 31 | .unwrap(); 32 | 33 | let debug_report_loader = DebugReport::new(&entry, &instance); 34 | let debug_call_back = debug_report_loader 35 | .create_debug_report_callback( 36 | &vk::DebugReportCallbackCreateInfoEXT::builder() 37 | .flags( 38 | vk::DebugReportFlagsEXT::ERROR 39 | | vk::DebugReportFlagsEXT::WARNING 40 | | vk::DebugReportFlagsEXT::PERFORMANCE_WARNING, 41 | ) 42 | .pfn_callback(Some(vulkan_debug_callback)), 43 | None, 44 | ) 45 | .unwrap(); 46 | 47 | let (pdevice, queue_family_index) = instance 48 | .enumerate_physical_devices() 49 | .unwrap() 50 | .iter() 51 | .map(|pdevice| { 52 | instance 53 | .get_physical_device_queue_family_properties(*pdevice) 54 | .iter() 55 | .enumerate() 56 | .filter_map(|(index, ref info)| { 57 | if info.queue_flags.contains(vk::QueueFlags::GRAPHICS) { 58 | Some((*pdevice, index as u32)) 59 | } else { 60 | None 61 | } 62 | }) 63 | .next() 64 | }) 65 | .filter_map(|v| v) 66 | .next() 67 | .expect("no graphics device available"); 68 | 69 | let device = Arc::new( 70 | instance 71 | .create_device( 72 | pdevice, 73 | &vk::DeviceCreateInfo::builder() 74 | .queue_create_infos(&[vk::DeviceQueueCreateInfo::builder() 75 | .queue_family_index(queue_family_index) 76 | .queue_priorities(&[1.0]) 77 | .build()]) 78 | .enabled_features(&vk::PhysicalDeviceFeatures { 79 | robust_buffer_access: vk::TRUE, 80 | ..Default::default() 81 | }), 82 | None, 83 | ) 84 | .unwrap(), 85 | ); 86 | let queue = device.get_device_queue(queue_family_index as u32, 0); 87 | 88 | let pool = device 89 | .create_command_pool( 90 | &vk::CommandPoolCreateInfo::builder() 91 | .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER) 92 | .queue_family_index(queue_family_index), 93 | None, 94 | ) 95 | .unwrap(); 96 | 97 | if let Some(ref mut rd) = rd { 98 | rd.start_frame_capture(ptr::null(), ptr::null()); 99 | } 100 | 101 | let cmd = device 102 | .allocate_command_buffers( 103 | &vk::CommandBufferAllocateInfo::builder() 104 | .command_buffer_count(1) 105 | .command_pool(pool) 106 | .level(vk::CommandBufferLevel::PRIMARY), 107 | ) 108 | .unwrap()[0]; 109 | 110 | let builder = Arc::new(fuzzyblue::Builder::new( 111 | &instance, 112 | device.clone(), 113 | vk::PipelineCache::null(), 114 | pdevice, 115 | queue_family_index, 116 | None, 117 | )); 118 | 119 | device 120 | .begin_command_buffer(cmd, &vk::CommandBufferBeginInfo::builder()) 121 | .unwrap(); 122 | 123 | let pending = fuzzyblue::Atmosphere::build( 124 | builder, 125 | cmd, 126 | // Simplified for speed 127 | &fuzzyblue::Parameters { 128 | scattering_r_size: 8, 129 | scattering_mu_size: 32, 130 | scattering_mu_s_size: 8, 131 | scattering_nu_size: 2, 132 | ..Default::default() 133 | }, 134 | ); 135 | 136 | device.end_command_buffer(cmd).unwrap(); 137 | 138 | bench.iter(|| { 139 | device 140 | .queue_submit( 141 | queue, 142 | &[vk::SubmitInfo::builder().command_buffers(&[cmd]).build()], 143 | vk::Fence::null(), 144 | ) 145 | .unwrap(); 146 | 147 | device.device_wait_idle().unwrap(); 148 | }); 149 | 150 | drop(pending); 151 | 152 | if let Some(ref mut rd) = rd { 153 | rd.end_frame_capture(ptr::null(), ptr::null()); 154 | } 155 | 156 | device.destroy_command_pool(pool, None); 157 | device.destroy_device(None); 158 | debug_report_loader.destroy_debug_report_callback(debug_call_back, None); 159 | instance.destroy_instance(None); 160 | } 161 | } 162 | 163 | unsafe extern "system" fn vulkan_debug_callback( 164 | flags: vk::DebugReportFlagsEXT, 165 | _: vk::DebugReportObjectTypeEXT, 166 | _: u64, 167 | _: usize, 168 | _: i32, 169 | _: *const c_char, 170 | p_message: *const c_char, 171 | user_data: *mut c_void, 172 | ) -> u32 { 173 | eprintln!( 174 | "{:?} {}", 175 | flags, 176 | CStr::from_ptr(p_message).to_string_lossy() 177 | ); 178 | if flags.contains(vk::DebugReportFlagsEXT::ERROR) { 179 | let had_error = &*(user_data as *const Cell); 180 | had_error.set(true); 181 | } 182 | vk::FALSE 183 | } 184 | 185 | benchmark_group!(benches, precompute); 186 | benchmark_main!(benches); 187 | -------------------------------------------------------------------------------- /examples/dump.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::fs::File; 3 | use std::sync::Arc; 4 | use std::{mem, ptr, slice}; 5 | 6 | use ash::version::{DeviceV1_0, EntryV1_0, InstanceV1_0}; 7 | use ash::{vk, Device, Entry}; 8 | use half::f16; 9 | use openexr::frame_buffer::PixelStruct; 10 | use renderdoc::{RenderDoc, V100}; 11 | 12 | fn main() { 13 | let mut rd = RenderDoc::::new().ok(); 14 | unsafe { 15 | let entry = Entry::new().unwrap(); 16 | let app_name = CStr::from_bytes_with_nul(b"fuzzyblue smoke test\0").unwrap(); 17 | let instance = entry 18 | .create_instance( 19 | &vk::InstanceCreateInfo::builder().application_info( 20 | &vk::ApplicationInfo::builder() 21 | .application_name(&app_name) 22 | .application_version(0) 23 | .engine_name(&app_name) 24 | .engine_version(0) 25 | .api_version(vk::make_version(1, 0, 36)), 26 | ), 27 | None, 28 | ) 29 | .unwrap(); 30 | 31 | let (pdevice, queue_family_index) = instance 32 | .enumerate_physical_devices() 33 | .unwrap() 34 | .iter() 35 | .map(|pdevice| { 36 | instance 37 | .get_physical_device_queue_family_properties(*pdevice) 38 | .iter() 39 | .enumerate() 40 | .filter_map(|(index, ref info)| { 41 | if info.queue_flags.contains(vk::QueueFlags::GRAPHICS) { 42 | Some((*pdevice, index as u32)) 43 | } else { 44 | None 45 | } 46 | }) 47 | .next() 48 | }) 49 | .filter_map(|v| v) 50 | .next() 51 | .expect("no graphics device available"); 52 | 53 | let memory_props = instance.get_physical_device_memory_properties(pdevice); 54 | 55 | let device = Arc::new( 56 | instance 57 | .create_device( 58 | pdevice, 59 | &vk::DeviceCreateInfo::builder() 60 | .queue_create_infos(&[vk::DeviceQueueCreateInfo::builder() 61 | .queue_family_index(queue_family_index) 62 | .queue_priorities(&[1.0]) 63 | .build()]) 64 | .enabled_features(&vk::PhysicalDeviceFeatures { 65 | robust_buffer_access: vk::TRUE, 66 | ..Default::default() 67 | }), 68 | None, 69 | ) 70 | .unwrap(), 71 | ); 72 | let queue = device.get_device_queue(queue_family_index as u32, 0); 73 | 74 | let pool = device 75 | .create_command_pool( 76 | &vk::CommandPoolCreateInfo::builder() 77 | .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER) 78 | .queue_family_index(queue_family_index), 79 | None, 80 | ) 81 | .unwrap(); 82 | 83 | if let Some(ref mut rd) = rd { 84 | rd.start_frame_capture(renderdoc::DevicePointer::from(ptr::null()), ptr::null()); 85 | } 86 | 87 | let cmd = device 88 | .allocate_command_buffers( 89 | &vk::CommandBufferAllocateInfo::builder() 90 | .command_buffer_count(1) 91 | .command_pool(pool) 92 | .level(vk::CommandBufferLevel::PRIMARY), 93 | ) 94 | .unwrap()[0]; 95 | 96 | let params = fuzzyblue::Parameters { 97 | usage: vk::ImageUsageFlags::TRANSFER_SRC, 98 | dst_stage_mask: vk::PipelineStageFlags::TRANSFER, 99 | dst_access_mask: vk::AccessFlags::TRANSFER_READ, 100 | layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL, 101 | // Simplified for speed 102 | order: 4, 103 | scattering_r_size: 16, 104 | scattering_mu_size: 64, 105 | scattering_mu_s_size: 16, 106 | scattering_nu_size: 4, 107 | ..Default::default() 108 | }; 109 | 110 | let transmittance_buf = Buffer::<[f32; 4]>::new( 111 | &device, 112 | &memory_props, 113 | params.transmittance_extent().width * params.transmittance_extent().height, 114 | ); 115 | let irradiance_buf = Buffer::<[f32; 4]>::new( 116 | &device, 117 | &memory_props, 118 | params.irradiance_extent().width * params.irradiance_extent().height, 119 | ); 120 | let scattering_buf = Buffer::<[f16; 4]>::new( 121 | &device, 122 | &memory_props, 123 | params.scattering_extent().width 124 | * params.scattering_extent().height 125 | * params.scattering_extent().depth, 126 | ); 127 | 128 | let builder = Arc::new(fuzzyblue::Builder::new( 129 | &instance, 130 | device.clone(), 131 | vk::PipelineCache::null(), 132 | pdevice, 133 | queue_family_index, 134 | None, 135 | )); 136 | 137 | // Precompute look-up tables 138 | device 139 | .begin_command_buffer( 140 | cmd, 141 | &vk::CommandBufferBeginInfo::builder() 142 | .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), 143 | ) 144 | .unwrap(); 145 | 146 | let pending = fuzzyblue::Atmosphere::build(builder, cmd, ¶ms); 147 | 148 | // Pipeline barriers of build ensure this is blocked until the images are fully written 149 | let atmosphere = pending.atmosphere(); 150 | for &(image, buf, extent) in &[ 151 | ( 152 | atmosphere.transmittance(), 153 | transmittance_buf.handle, 154 | vk::Extent3D { 155 | width: atmosphere.transmittance_extent().width, 156 | height: atmosphere.transmittance_extent().height, 157 | depth: 1, 158 | }, 159 | ), 160 | ( 161 | atmosphere.irradiance(), 162 | irradiance_buf.handle, 163 | vk::Extent3D { 164 | width: atmosphere.irradiance_extent().width, 165 | height: atmosphere.irradiance_extent().height, 166 | depth: 1, 167 | }, 168 | ), 169 | ( 170 | atmosphere.scattering(), 171 | scattering_buf.handle, 172 | atmosphere.scattering_extent(), 173 | ), 174 | ] { 175 | device.cmd_copy_image_to_buffer( 176 | cmd, 177 | image, 178 | vk::ImageLayout::TRANSFER_SRC_OPTIMAL, 179 | buf, 180 | &[vk::BufferImageCopy { 181 | buffer_offset: 0, 182 | buffer_row_length: 0, 183 | buffer_image_height: 0, 184 | image_subresource: vk::ImageSubresourceLayers { 185 | aspect_mask: vk::ImageAspectFlags::COLOR, 186 | mip_level: 0, 187 | base_array_layer: 0, 188 | layer_count: 1, 189 | }, 190 | image_offset: vk::Offset3D { x: 0, y: 0, z: 0 }, 191 | image_extent: extent, 192 | }], 193 | ); 194 | } 195 | 196 | device.end_command_buffer(cmd).unwrap(); 197 | 198 | device 199 | .queue_submit( 200 | queue, 201 | &[vk::SubmitInfo::builder().command_buffers(&[cmd]).build()], 202 | vk::Fence::null(), 203 | ) 204 | .unwrap(); 205 | 206 | device.device_wait_idle().unwrap(); 207 | 208 | write_image( 209 | "transmittance", 210 | &*transmittance_buf.ptr, 211 | atmosphere.transmittance_extent().width, 212 | atmosphere.transmittance_extent().height, 213 | 1, 214 | ); 215 | write_image( 216 | "irradiance", 217 | &*irradiance_buf.ptr, 218 | atmosphere.irradiance_extent().width, 219 | atmosphere.irradiance_extent().height, 220 | 1, 221 | ); 222 | write_image( 223 | "scattering", 224 | &*scattering_buf.ptr, 225 | atmosphere.scattering_extent().width, 226 | atmosphere.scattering_extent().height, 227 | atmosphere.scattering_extent().depth, 228 | ); 229 | 230 | drop(pending); 231 | 232 | transmittance_buf.destroy(&device); 233 | irradiance_buf.destroy(&device); 234 | scattering_buf.destroy(&device); 235 | 236 | if let Some(ref mut rd) = rd { 237 | rd.end_frame_capture(renderdoc::DevicePointer::from(ptr::null()), ptr::null()); 238 | } 239 | 240 | device.destroy_command_pool(pool, None); 241 | device.destroy_device(None); 242 | instance.destroy_instance(None); 243 | } 244 | } 245 | 246 | fn find_memory_type( 247 | device_props: &vk::PhysicalDeviceMemoryProperties, 248 | type_bits: u32, 249 | flags: vk::MemoryPropertyFlags, 250 | ) -> Option { 251 | for i in 0..device_props.memory_type_count { 252 | if type_bits & (1 << i) != 0 253 | && device_props.memory_types[i as usize] 254 | .property_flags 255 | .contains(flags) 256 | { 257 | return Some(i); 258 | } 259 | } 260 | None 261 | } 262 | 263 | unsafe fn allocate( 264 | device: &Device, 265 | device_props: &vk::PhysicalDeviceMemoryProperties, 266 | reqs: vk::MemoryRequirements, 267 | flags: vk::MemoryPropertyFlags, 268 | ) -> Option { 269 | let ty = find_memory_type(device_props, reqs.memory_type_bits, flags)?; 270 | Some( 271 | device 272 | .allocate_memory( 273 | &vk::MemoryAllocateInfo { 274 | allocation_size: reqs.size, 275 | memory_type_index: ty, 276 | ..Default::default() 277 | }, 278 | None, 279 | ) 280 | .unwrap(), 281 | ) 282 | } 283 | 284 | struct Buffer { 285 | handle: vk::Buffer, 286 | mem: vk::DeviceMemory, 287 | ptr: *mut [T], 288 | } 289 | 290 | impl Buffer { 291 | unsafe fn new( 292 | device: &Device, 293 | memory_props: &vk::PhysicalDeviceMemoryProperties, 294 | pixels: u32, 295 | ) -> Self { 296 | let bytes = u64::from(pixels) * mem::size_of::() as u64; 297 | let handle = device 298 | .create_buffer( 299 | &vk::BufferCreateInfo { 300 | size: bytes, 301 | usage: vk::BufferUsageFlags::TRANSFER_DST, 302 | ..Default::default() 303 | }, 304 | None, 305 | ) 306 | .unwrap(); 307 | let mem = { 308 | let reqs = device.get_buffer_memory_requirements(handle); 309 | allocate( 310 | device, 311 | memory_props, 312 | reqs, 313 | vk::MemoryPropertyFlags::HOST_VISIBLE, 314 | ) 315 | .unwrap() 316 | }; 317 | device.bind_buffer_memory(handle, mem, 0).unwrap(); 318 | let ptr = device 319 | .map_memory(mem, 0, bytes, Default::default()) 320 | .unwrap() as _; 321 | let ptr = slice::from_raw_parts_mut(ptr, pixels as usize); 322 | Self { handle, mem, ptr } 323 | } 324 | 325 | unsafe fn destroy(&self, device: &Device) { 326 | device.destroy_buffer(self.handle, None); 327 | device.free_memory(self.mem, None); 328 | } 329 | } 330 | 331 | fn write_image(name: &str, data: &[T], width: u32, height: u32, depth: u32) { 332 | use openexr::{FrameBuffer, Header, ScanlineOutputFile}; 333 | let mut file = File::create(format!("{}.exr", name)).unwrap(); 334 | 335 | let mut header = Header::new(); 336 | header.set_resolution(width, height); 337 | for layer in 0..depth { 338 | for (&channel, ty) in ['R', 'G', 'B', 'A'] 339 | .iter() 340 | .zip((0..).map(|i| T::channel(i).0)) 341 | { 342 | header.add_channel(&channel_name(channel, layer, depth), ty); 343 | } 344 | } 345 | let mut exr_file = ScanlineOutputFile::new(&mut file, &header).unwrap(); 346 | 347 | let mut fb = FrameBuffer::new(width, height); 348 | for (layer, data) in data.chunks(width as usize * height as usize).enumerate() { 349 | let channels = ['R', 'G', 'B', 'A'] 350 | .iter() 351 | .map(|&c| channel_name(c, layer as u32, depth)) 352 | .collect::>(); 353 | fb.insert_channels( 354 | &[&channels[0], &channels[1], &channels[2], &channels[3]], 355 | &data, 356 | ); 357 | } 358 | exr_file.write_pixels(&fb).unwrap(); 359 | } 360 | 361 | fn channel_name(color: char, layer: u32, layer_count: u32) -> String { 362 | if layer_count == 1 { 363 | color.to_string() 364 | } else { 365 | format!("{}.{}", layer, color) 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /shaders/direct_irradiance.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 8, local_size_y = 8) in; 4 | 5 | #include "irradiance.h" 6 | 7 | #include "params.h" 8 | #include "transmittance.h" 9 | 10 | vec3 ComputeDirectIrradiance( 11 | AtmosphereParameters atmosphere, 12 | sampler2D transmittance_texture, 13 | float r, float mu_s) { 14 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 15 | // assert(mu_s >= -1.0 && mu_s <= 1.0); 16 | 17 | float alpha_s = atmosphere.sun_angular_radius; 18 | // Approximate average of the cosine factor mu_s over the visible fraction of 19 | // the Sun disc. 20 | float average_cosine_factor = 21 | mu_s < -alpha_s ? 0.0 : (mu_s > alpha_s ? mu_s : 22 | (mu_s + alpha_s) * (mu_s + alpha_s) / (4.0 * alpha_s)); 23 | 24 | return atmosphere.solar_irradiance * 25 | GetTransmittanceToTopAtmosphereBoundary( 26 | atmosphere, transmittance_texture, r, mu_s) * average_cosine_factor; 27 | } 28 | 29 | layout (set=0, binding=0) uniform Params { 30 | AtmosphereParameters atmosphere; 31 | }; 32 | 33 | layout (set=1, binding=0) uniform sampler2D transmittance_texture; 34 | layout (set=1, binding=1, rgba16f) uniform writeonly image2D delta_irradiance; 35 | 36 | void main() { 37 | if (any(greaterThanEqual(gl_GlobalInvocationID.xy, uvec2(atmosphere.irradiance_texture_mu_s_size, atmosphere.irradiance_texture_r_size)))) { 38 | return; 39 | } 40 | float x_mu_s = gl_GlobalInvocationID.x / float(atmosphere.irradiance_texture_mu_s_size - 1); 41 | float x_r = gl_GlobalInvocationID.y / float(atmosphere.irradiance_texture_r_size - 1); 42 | float r, mu_s; 43 | GetRMuSFromIrradianceUnitRange(atmosphere, x_mu_s, x_r, r, mu_s); 44 | vec3 result = ComputeDirectIrradiance(atmosphere, transmittance_texture, r, mu_s); 45 | imageStore(delta_irradiance, ivec2(gl_GlobalInvocationID), vec4(result, 0)); 46 | } 47 | -------------------------------------------------------------------------------- /shaders/fullscreen.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) out vec2 screen_coords; 4 | 5 | void main() { 6 | screen_coords = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); 7 | gl_Position = vec4(screen_coords * 2.0f + -1.0f, 0.0, 1.0f); 8 | } 9 | -------------------------------------------------------------------------------- /shaders/indirect_irradiance.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 8, local_size_y = 8) in; 4 | 5 | #include "irradiance.h" 6 | 7 | #include "params.h" 8 | #include "scattering.h" 9 | 10 | vec3 ComputeIndirectIrradiance( 11 | AtmosphereParameters atmosphere, 12 | sampler3D single_rayleigh_scattering_texture, 13 | sampler3D single_mie_scattering_texture, 14 | sampler3D multiple_scattering_texture, 15 | float r, float mu_s, int scattering_order) { 16 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 17 | // assert(mu_s >= -1.0 && mu_s <= 1.0); 18 | // assert(scattering_order >= 1); 19 | 20 | const int SAMPLE_COUNT = 32; 21 | const float dphi = PI / float(SAMPLE_COUNT); 22 | const float dtheta = PI / float(SAMPLE_COUNT); 23 | 24 | // watt_per_square_meter_per_nm 25 | vec3 result = vec3(0.0); 26 | vec3 omega_s = vec3(sqrt(1.0 - mu_s * mu_s), 0.0, mu_s); 27 | for (int j = 0; j < SAMPLE_COUNT / 2; ++j) { 28 | float theta = (float(j) + 0.5) * dtheta; 29 | for (int i = 0; i < 2 * SAMPLE_COUNT; ++i) { 30 | float phi = (float(i) + 0.5) * dphi; 31 | vec3 omega = 32 | vec3(cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta)); 33 | float domega = dtheta * dphi * sin(theta); 34 | 35 | float nu = dot(omega, omega_s); 36 | result += GetScattering(atmosphere, single_rayleigh_scattering_texture, 37 | single_mie_scattering_texture, multiple_scattering_texture, 38 | r, omega.z, mu_s, nu, false /* ray_r_theta_intersects_ground */, 39 | scattering_order) * 40 | omega.z * domega; 41 | } 42 | } 43 | return result; 44 | } 45 | 46 | layout (set=0, binding=0) uniform Params { 47 | AtmosphereParameters atmosphere; 48 | }; 49 | 50 | layout (set=1, binding=0) uniform sampler3D single_rayleigh_scattering_texture; 51 | layout (set=1, binding=1) uniform sampler3D single_mie_scattering_texture; 52 | layout (set=1, binding=2) uniform sampler3D multiple_scattering_texture; 53 | layout (set=1, binding=3, rgba16f) uniform writeonly image2D delta_irradiance; 54 | layout (set=1, binding=4, rgba16f) uniform image2D irradiance; 55 | layout (push_constant) uniform PerOrder { 56 | int scattering_order; 57 | }; 58 | 59 | void main() { 60 | if (any(greaterThanEqual(gl_GlobalInvocationID.xy, uvec2(atmosphere.irradiance_texture_mu_s_size, atmosphere.irradiance_texture_r_size)))) { 61 | return; 62 | } 63 | float x_mu_s = gl_GlobalInvocationID.x / float(atmosphere.irradiance_texture_mu_s_size - 1); 64 | float x_r = gl_GlobalInvocationID.y / float(atmosphere.irradiance_texture_r_size - 1); 65 | float r, mu_s; 66 | GetRMuSFromIrradianceUnitRange(atmosphere, x_mu_s, x_r, r, mu_s); 67 | vec3 result = ComputeIndirectIrradiance( 68 | atmosphere, 69 | single_rayleigh_scattering_texture, single_mie_scattering_texture, 70 | multiple_scattering_texture, r, mu_s, scattering_order); 71 | ivec2 coords = ivec2(gl_GlobalInvocationID); 72 | imageStore(delta_irradiance, coords, vec4(result, 0)); 73 | imageStore(irradiance, coords, vec4(result, 0) + imageLoad(irradiance, coords)); 74 | } 75 | -------------------------------------------------------------------------------- /shaders/irradiance.h: -------------------------------------------------------------------------------- 1 | #ifndef FUZZYBLUE_IRRADIANCE_H_ 2 | #define FUZZYBLUE_IRRADIANCE_H_ 3 | 4 | #include "params.h" 5 | #include "util.h" 6 | 7 | void GetRMuSFromIrradianceUnitRange( 8 | AtmosphereParameters atmosphere, 9 | float x_mu_s, float x_r, 10 | out float r, out float mu_s) { 11 | // assert(uv.x >= 0.0 && uv.x <= 1.0); 12 | // assert(uv.y >= 0.0 && uv.y <= 1.0); 13 | // Number x_mu_s = GetUnitRangeFromTextureCoord(uv.x, IRRADIANCE_TEXTURE_WIDTH); 14 | // Number x_r = GetUnitRangeFromTextureCoord(uv.y, IRRADIANCE_TEXTURE_HEIGHT); 15 | r = atmosphere.bottom_radius + 16 | x_r * (atmosphere.top_radius - atmosphere.bottom_radius); 17 | mu_s = ClampCosine(2.0 * x_mu_s - 1.0); 18 | } 19 | 20 | vec2 GetIrradianceTextureUvFromRMuS( 21 | AtmosphereParameters atmosphere, 22 | float r, float mu_s) { 23 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 24 | // assert(mu_s >= -1.0 && mu_s <= 1.0); 25 | float x_r = (r - atmosphere.bottom_radius) / 26 | (atmosphere.top_radius - atmosphere.bottom_radius); 27 | float x_mu_s = mu_s * 0.5 + 0.5; 28 | return vec2(GetTextureCoordFromUnitRange(x_mu_s, atmosphere.irradiance_texture_mu_s_size), 29 | GetTextureCoordFromUnitRange(x_r, atmosphere.irradiance_texture_r_size)); 30 | } 31 | 32 | vec3 GetIrradiance( 33 | AtmosphereParameters atmosphere, 34 | sampler2D irradiance_texture, 35 | float r, float mu_s) { 36 | vec2 uv = GetIrradianceTextureUvFromRMuS(atmosphere, r, mu_s); 37 | return texture(irradiance_texture, uv).rgb; 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /shaders/multiple_scattering.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; 4 | 5 | #include "params.h" 6 | #include "scattering.h" 7 | #include "transmittance.h" 8 | 9 | vec3 ComputeMultipleScattering( 10 | AtmosphereParameters atmosphere, 11 | sampler2D transmittance_texture, 12 | sampler3D scattering_density_texture, 13 | float r, float mu, float mu_s, float nu, 14 | bool ray_r_mu_intersects_ground) { 15 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 16 | // assert(mu >= -1.0 && mu <= 1.0); 17 | // assert(mu_s >= -1.0 && mu_s <= 1.0); 18 | // assert(nu >= -1.0 && nu <= 1.0); 19 | 20 | // float of intervals for the numerical integration. 21 | const int SAMPLE_COUNT = 50; 22 | // The integration step, i.e. the length of each integration interval. 23 | float dx = 24 | DistanceToNearestAtmosphereBoundary( 25 | atmosphere, r, mu, ray_r_mu_intersects_ground) / 26 | float(SAMPLE_COUNT); 27 | // Integration loop. 28 | // watt_per_square_meter_per_sr_per_nm 29 | vec3 rayleigh_mie_sum = vec3(0.0); 30 | for (int i = 0; i <= SAMPLE_COUNT; ++i) { 31 | float d_i = float(i) * dx; 32 | 33 | // The r, mu and mu_s parameters at the current integration point (see the 34 | // single scattering section for a detailed explanation). 35 | float r_i = 36 | ClampRadius(atmosphere, sqrt(d_i * d_i + 2.0 * r * mu * d_i + r * r)); 37 | float mu_i = ClampCosine((r * mu + d_i) / r_i); 38 | float mu_s_i = ClampCosine((r * mu_s + d_i * nu) / r_i); 39 | 40 | // The Rayleigh and Mie multiple scattering at the current sample point. 41 | vec3 rayleigh_mie_i = 42 | GetScattering( 43 | atmosphere, scattering_density_texture, r_i, mu_i, mu_s_i, nu, 44 | ray_r_mu_intersects_ground) * 45 | GetTransmittance( 46 | atmosphere, transmittance_texture, r, mu, d_i, 47 | ray_r_mu_intersects_ground) * 48 | dx; 49 | // Sample weight (from the trapezoidal rule). 50 | float weight_i = (i == 0 || i == SAMPLE_COUNT) ? 0.5 : 1.0; 51 | rayleigh_mie_sum += rayleigh_mie_i * weight_i; 52 | } 53 | return rayleigh_mie_sum; 54 | } 55 | 56 | vec3 ComputeMultipleScatteringTexture( 57 | AtmosphereParameters atmosphere, 58 | sampler2D transmittance_texture, 59 | sampler3D scattering_density_texture, 60 | vec3 frag_coord, out float nu) { 61 | float r; 62 | float mu; 63 | float mu_s; 64 | bool ray_r_mu_intersects_ground; 65 | GetRMuMuSNuFromScatteringTextureFragCoord(atmosphere, frag_coord, 66 | r, mu, mu_s, nu, ray_r_mu_intersects_ground); 67 | return ComputeMultipleScattering(atmosphere, transmittance_texture, 68 | scattering_density_texture, r, mu, mu_s, nu, 69 | ray_r_mu_intersects_ground); 70 | } 71 | 72 | layout (set=0, binding=0) uniform Params { 73 | AtmosphereParameters atmosphere; 74 | }; 75 | 76 | layout (set=1, binding=0) uniform sampler2D transmittance_texture; 77 | layout (set=1, binding=1) uniform sampler3D scattering_density_texture; 78 | layout (set=1, binding=2, rgba16f) uniform writeonly image3D delta_multiple_scattering; 79 | layout (set=1, binding=3, rgba16f) uniform image3D scattering; 80 | 81 | void main() { 82 | vec3 frag_coord; 83 | if (!GetScatteringFragCoord(atmosphere, gl_GlobalInvocationID, frag_coord)) { 84 | return; 85 | } 86 | float nu; 87 | ivec3 coords = ivec3(gl_GlobalInvocationID); 88 | vec3 ms = ComputeMultipleScatteringTexture( 89 | atmosphere, transmittance_texture, scattering_density_texture, 90 | frag_coord, nu); 91 | imageStore(delta_multiple_scattering, coords, vec4(ms, 0)); 92 | imageStore(scattering, coords, vec4(ms / RayleighPhaseFunction(nu), 0) + imageLoad(scattering, coords)); 93 | } 94 | -------------------------------------------------------------------------------- /shaders/params.h: -------------------------------------------------------------------------------- 1 | #ifndef FUZZYBLUE_PARAMS_H_ 2 | #define FUZZYBLUE_PARAMS_H_ 3 | 4 | #include "util.h" 5 | 6 | // An atmosphere layer of width 'width', and whose density is defined as 7 | // 'exp_term' * exp('exp_scale' * h) + 'linear_term' * h + 'constant_term', 8 | // clamped to [0,1], and where h is the altitude. 9 | struct DensityProfileLayer { 10 | float width; 11 | float exp_term; 12 | float exp_scale; 13 | float linear_term; 14 | float constant_term; 15 | }; 16 | 17 | // An atmosphere density profile made of several layers on top of each other 18 | // (from bottom to top). The width of the last layer is ignored, i.e. it always 19 | // extend to the top atmosphere boundary. The profile values vary between 0 20 | // (null density) to 1 (maximum density). 21 | struct DensityProfile { 22 | DensityProfileLayer layers[2]; 23 | }; 24 | 25 | // Fields ordered for density 26 | struct AtmosphereParameters { 27 | // The solar irradiance at the top of the atmosphere. 28 | vec3 solar_irradiance; 29 | // The sun's angular radius. Warning: the implementation uses approximations 30 | // that are valid only if this angle is smaller than 0.1 radians. 31 | float sun_angular_radius; 32 | // The scattering coefficient of air molecules at the altitude where their 33 | // density is maximum (usually the bottom of the atmosphere), as a function of 34 | // wavelength. The scattering coefficient at altitude h is equal to 35 | // 'rayleigh_scattering' times 'rayleigh_density' at this altitude. 36 | vec3 rayleigh_scattering; 37 | // The distance between the planet center and the bottom of the atmosphere. 38 | float bottom_radius; 39 | // The scattering coefficient of aerosols at the altitude where their density 40 | // is maximum (usually the bottom of the atmosphere), as a function of 41 | // wavelength. The scattering coefficient at altitude h is equal to 42 | // 'mie_scattering' times 'mie_density' at this altitude. 43 | vec3 mie_scattering; 44 | // The distance between the planet center and the top of the atmosphere. 45 | float top_radius; 46 | // The extinction coefficient of aerosols at the altitude where their density 47 | // is maximum (usually the bottom of the atmosphere), as a function of 48 | // wavelength. The extinction coefficient at altitude h is equal to 49 | // 'mie_extinction' times 'mie_density' at this altitude. 50 | vec3 mie_extinction; 51 | // The asymetry parameter for the Cornette-Shanks phase function for the 52 | // aerosols. 53 | float mie_phase_function_g; 54 | // The average albedo of the ground. 55 | vec3 ground_albedo; 56 | // The cosine of the maximum Sun zenith angle for which atmospheric scattering 57 | // must be precomputed (for maximum precision, use the smallest Sun zenith 58 | // angle yielding negligible sky light radiance values. For instance, for the 59 | // Earth case, 102 degrees is a good choice - yielding mu_s_min = -0.2). 60 | float mu_s_min; 61 | // The extinction coefficient of molecules that absorb light (e.g. ozone) at 62 | // the altitude where their density is maximum, as a function of wavelength. 63 | // The extinction coefficient at altitude h is equal to 64 | // 'absorption_extinction' times 'absorption_density' at this altitude. 65 | vec3 absorption_extinction; 66 | 67 | // Texture size parameters 68 | int transmittance_texture_mu_size; 69 | int transmittance_texture_r_size; 70 | int scattering_texture_r_size; 71 | int scattering_texture_mu_size; 72 | int scattering_texture_mu_s_size; 73 | int scattering_texture_nu_size; 74 | int irradiance_texture_mu_s_size; 75 | int irradiance_texture_r_size; 76 | 77 | // The density profile of air molecules, i.e. a function from altitude to 78 | // dimensionless values between 0 (null density) and 1 (maximum density). 79 | DensityProfile rayleigh_density; 80 | // The density profile of aerosols, i.e. a function from altitude to 81 | // dimensionless values between 0 (null density) and 1 (maximum density). 82 | DensityProfile mie_density; 83 | // The density profile of air molecules that absorb light (e.g. ozone), i.e. 84 | // a function from altitude to dimensionless values between 0 (null density) 85 | // and 1 (maximum density). 86 | DensityProfile absorption_density; 87 | }; 88 | 89 | float GetLayerDensity(DensityProfileLayer layer, float altitude) { 90 | float density = layer.exp_term * exp(layer.exp_scale * altitude) + 91 | layer.linear_term * altitude + layer.constant_term; 92 | return clamp(density, 0.0, 1.0); 93 | } 94 | 95 | float GetProfileDensity(DensityProfile profile, float altitude) { 96 | return altitude < profile.layers[0].width ? 97 | GetLayerDensity(profile.layers[0], altitude) : 98 | GetLayerDensity(profile.layers[1], altitude); 99 | } 100 | 101 | float ClampRadius(AtmosphereParameters atmosphere, float r) { 102 | return clamp(r, atmosphere.bottom_radius, atmosphere.top_radius); 103 | } 104 | 105 | float DistanceToTopAtmosphereBoundary(AtmosphereParameters atmosphere, float r, float mu) { 106 | // assert(r <= atmosphere.top_radius); 107 | // assert(mu >= -1.0 && mu <= 1.0); 108 | float discriminant = r * r * (mu * mu - 1.0) + atmosphere.top_radius * atmosphere.top_radius; 109 | return ClampDistance(-r * mu + SafeSqrt(discriminant)); 110 | } 111 | 112 | float DistanceToBottomAtmosphereBoundary(AtmosphereParameters atmosphere, float r, float mu) { 113 | // assert(r >= atmosphere.bottom_radius); 114 | // assert(mu >= -1.0 && mu <= 1.0); 115 | float discriminant = r * r * (mu * mu - 1.0) + atmosphere.bottom_radius * atmosphere.bottom_radius; 116 | return ClampDistance(-r * mu - SafeSqrt(discriminant)); 117 | } 118 | 119 | bool RayIntersectsGround(AtmosphereParameters atmosphere, float r, float mu) { 120 | // assert(r >= atmosphere.bottom_radius); 121 | // assert(mu >= -1.0 && mu <= 1.0); 122 | return mu < 0.0 && r * r * (mu * mu - 1.0) + 123 | atmosphere.bottom_radius * atmosphere.bottom_radius >= 0.0; 124 | } 125 | 126 | float DistanceToNearestAtmosphereBoundary(AtmosphereParameters atmosphere, 127 | float r, float mu, bool ray_r_mu_intersects_ground) { 128 | if (ray_r_mu_intersects_ground) { 129 | return DistanceToBottomAtmosphereBoundary(atmosphere, r, mu); 130 | } else { 131 | return DistanceToTopAtmosphereBoundary(atmosphere, r, mu); 132 | } 133 | } 134 | 135 | #endif 136 | -------------------------------------------------------------------------------- /shaders/render_lighting.h: -------------------------------------------------------------------------------- 1 | // Efficiently compute approximate illumination of a surface within the atmosphere 2 | #ifndef FUZZYBLUE_RENDER_LIGHTING_H_ 3 | #define FUZZYBLUE_RENDER_LIGHTING_H_ 4 | 5 | #include "params.h" 6 | #include "transmittance.h" 7 | #include "irradiance.h" 8 | 9 | // Returns direct illumination, last argument outputs indirect illumination 10 | vec3 GetSunAndSkyIrradiance( 11 | AtmosphereParameters atmosphere, 12 | sampler2D transmittance_texture, 13 | sampler2D irradiance_texture, 14 | vec3 point, vec3 normal, vec3 sun_direction, 15 | out vec3 sky_irradiance) { 16 | float r = length(point); 17 | float mu_s = dot(point, sun_direction) / r; 18 | 19 | // Indirect irradiance (approximated if the surface is not horizontal). 20 | sky_irradiance = GetIrradiance(atmosphere, irradiance_texture, r, mu_s) * 21 | (1.0 + dot(normal, point) / r) * 0.5; 22 | 23 | // Direct irradiance. 24 | return atmosphere.solar_irradiance * 25 | GetTransmittanceToSun( 26 | atmosphere, transmittance_texture, r, mu_s) * 27 | max(dot(normal, sun_direction), 0.0); 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /shaders/render_sky.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "params.h" 4 | #include "render_sky.h" 5 | 6 | layout (location=0) in vec2 screen_coords; 7 | 8 | layout (location=0, index=0) out vec4 color_out; 9 | layout (location=0, index=1) out vec4 transmittance_out; 10 | 11 | layout (set=0, binding=0) uniform Params { 12 | AtmosphereParameters atmosphere; 13 | }; 14 | layout (set=0, binding=1) uniform sampler2D transmittance_texture; 15 | layout (set=0, binding=2) uniform sampler3D scattering_texture; 16 | layout (push_constant) uniform DrawParams { 17 | mat4 inverse_viewproj; 18 | vec3 camera_position; 19 | vec3 sun_direction; 20 | }; 21 | 22 | layout (set=1, binding=0, input_attachment_index=0) uniform subpassInput depth_buffer; 23 | 24 | void main() { 25 | vec3 view = normalize((inverse_viewproj * vec4(2*screen_coords - 1, 0, 1)).xyz); 26 | vec4 world_pre = (inverse_viewproj * vec4(2*screen_coords - 1, subpassLoad(depth_buffer).x, 1)); 27 | vec3 world = (world_pre.xyz / world_pre.w) * 1e-3; 28 | vec3 transmittance; 29 | vec3 color = GetSkyRadianceToPoint( 30 | atmosphere, transmittance_texture, scattering_texture, 31 | camera_position, view, world, sun_direction, 32 | transmittance); 33 | color_out = vec4(color, 0); 34 | transmittance_out = vec4(transmittance, 1); 35 | } 36 | -------------------------------------------------------------------------------- /shaders/render_sky.h: -------------------------------------------------------------------------------- 1 | // Efficiently compute incoming light from atmospheric scattering 2 | #ifndef FUZZYBLUE_RENDER_SKY_H_ 3 | #define FUZZYBLUE_RENDER_SKY_H_ 4 | 5 | #include "params.h" 6 | #include "transmittance.h" 7 | #include "scattering.h" 8 | 9 | vec3 GetExtrapolatedSingleMieScattering( 10 | AtmosphereParameters atmosphere, vec4 scattering) { 11 | // Algebraically this can never be negative, but rounding errors can produce that effect for 12 | // sufficiently short view rays. 13 | if (scattering.r <= 0.0) { 14 | return vec3(0.0); 15 | } 16 | return scattering.rgb * scattering.a / scattering.r * 17 | (atmosphere.rayleigh_scattering.r / atmosphere.mie_scattering.r) * 18 | (atmosphere.mie_scattering / atmosphere.rayleigh_scattering); 19 | } 20 | 21 | vec3 GetCombinedScattering( 22 | AtmosphereParameters atmosphere, 23 | sampler3D scattering_texture, 24 | float r, float mu, float mu_s, float nu, 25 | bool ray_r_mu_intersects_ground, 26 | out vec3 single_mie_scattering) { 27 | vec4 uvwz = GetScatteringTextureUvwzFromRMuMuSNu( 28 | atmosphere, r, mu, mu_s, nu, ray_r_mu_intersects_ground); 29 | float tex_coord_x = uvwz.x * float(atmosphere.scattering_texture_nu_size - 1); 30 | float tex_x = floor(tex_coord_x); 31 | float lerp = tex_coord_x - tex_x; 32 | vec3 uvw0 = vec3((tex_x + uvwz.y) / float(atmosphere.scattering_texture_nu_size), 33 | uvwz.z, uvwz.w); 34 | vec3 uvw1 = vec3((tex_x + 1.0 + uvwz.y) / float(atmosphere.scattering_texture_nu_size), 35 | uvwz.z, uvwz.w); 36 | vec4 combined_scattering = mix( 37 | texture(scattering_texture, uvw0), 38 | texture(scattering_texture, uvw1), 39 | lerp); 40 | single_mie_scattering = 41 | GetExtrapolatedSingleMieScattering(atmosphere, combined_scattering); 42 | return combined_scattering.rgb; 43 | } 44 | 45 | vec3 GetSkyRadiance( 46 | AtmosphereParameters atmosphere, 47 | sampler2D transmittance_texture, 48 | sampler3D scattering_texture, 49 | vec3 camera, vec3 view_ray, 50 | vec3 sun_direction, out vec3 transmittance) { 51 | // Compute the distance to the top atmosphere boundary along the view ray, 52 | // assuming the viewer is in space (or NaN if the view ray does not intersect 53 | // the atmosphere). 54 | float r = length(camera); 55 | float rmu = dot(camera, view_ray); 56 | float distance_to_top_atmosphere_boundary = -rmu - 57 | sqrt(rmu * rmu - r * r + atmosphere.top_radius * atmosphere.top_radius); 58 | // If the viewer is in space and the view ray intersects the atmosphere, move 59 | // the viewer to the top atmosphere boundary (along the view ray): 60 | if (distance_to_top_atmosphere_boundary > 0.0) { 61 | camera = camera + view_ray * distance_to_top_atmosphere_boundary; 62 | r = atmosphere.top_radius; 63 | rmu += distance_to_top_atmosphere_boundary; 64 | } else if (r > atmosphere.top_radius) { 65 | // If the view ray does not intersect the atmosphere, simply return 0. 66 | transmittance = vec3(1.0); 67 | // watt_per_square_meter_per_sr_per_nm 68 | return vec3(0.0); 69 | } 70 | // Compute the r, mu, mu_s and nu parameters needed for the texture lookups. 71 | float mu = rmu / r; 72 | float mu_s = dot(camera, sun_direction) / r; 73 | float nu = dot(view_ray, sun_direction); 74 | bool ray_r_mu_intersects_ground = RayIntersectsGround(atmosphere, r, mu); 75 | 76 | transmittance = ray_r_mu_intersects_ground ? vec3(0.0) : 77 | GetTransmittanceToTopAtmosphereBoundary( 78 | atmosphere, transmittance_texture, r, mu); 79 | vec3 single_mie_scattering; 80 | vec3 scattering; 81 | // if (shadow_length == 0.0 * m) { 82 | scattering = GetCombinedScattering( 83 | atmosphere, scattering_texture, 84 | r, mu, mu_s, nu, ray_r_mu_intersects_ground, 85 | single_mie_scattering); 86 | // } else { 87 | // // Case of light shafts (shadow_length is the total length noted l in our 88 | // // paper): we omit the scattering between the camera and the point at 89 | // // distance l, by implementing Eq. (18) of the paper (shadow_transmittance 90 | // // is the T(x,x_s) term, scattering is the S|x_s=x+lv term). 91 | // Length d = shadow_length; 92 | // Length r_p = 93 | // ClampRadius(atmosphere, sqrt(d * d + 2.0 * r * mu * d + r * r)); 94 | // Number mu_p = (r * mu + d) / r_p; 95 | // Number mu_s_p = (r * mu_s + d * nu) / r_p; 96 | 97 | // scattering = GetCombinedScattering( 98 | // atmosphere, scattering_texture, single_mie_scattering_texture, 99 | // r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground, 100 | // single_mie_scattering); 101 | // DimensionlessSpectrum shadow_transmittance = 102 | // GetTransmittance(atmosphere, transmittance_texture, 103 | // r, mu, shadow_length, ray_r_mu_intersects_ground); 104 | // scattering = scattering * shadow_transmittance; 105 | // single_mie_scattering = single_mie_scattering * shadow_transmittance; 106 | // } 107 | return scattering * RayleighPhaseFunction(nu) + single_mie_scattering * 108 | MiePhaseFunction(atmosphere.mie_phase_function_g, nu); 109 | } 110 | 111 | vec3 GetSkyRadianceToPoint( 112 | AtmosphereParameters atmosphere, 113 | sampler2D transmittance_texture, 114 | sampler3D scattering_texture, 115 | vec3 camera, vec3 view_ray, vec3 point, 116 | vec3 sun_direction, out vec3 transmittance) { 117 | // Compute the distance to the top atmosphere boundary along the view ray, 118 | // assuming the viewer is in space (or NaN if the view ray does not intersect 119 | // the atmosphere). 120 | float r = length(camera); 121 | float rmu = dot(camera, view_ray); 122 | float distance_to_top_atmosphere_boundary = -rmu - 123 | sqrt(rmu * rmu - r * r + atmosphere.top_radius * atmosphere.top_radius); 124 | 125 | // If the viewer is in space and the view ray intersects the atmosphere, move 126 | // the viewer to the top atmosphere boundary (along the view ray): 127 | if (distance_to_top_atmosphere_boundary > 0.0) { 128 | camera = camera + view_ray * distance_to_top_atmosphere_boundary; 129 | r = atmosphere.top_radius; 130 | rmu += distance_to_top_atmosphere_boundary; 131 | } else if (r > atmosphere.top_radius) { 132 | // If the view ray does not intersect the atmosphere, simply return 0. 133 | transmittance = vec3(1.0); 134 | // watt_per_square_meter_per_sr_per_nm 135 | return vec3(0.0); 136 | } 137 | 138 | // Compute the r, mu, mu_s and nu parameters for the first texture lookup. 139 | float mu = rmu / r; 140 | float mu_s = dot(camera, sun_direction) / r; 141 | float nu = dot(view_ray, sun_direction); 142 | float d = length(point - camera); 143 | bool ray_r_mu_intersects_ground = RayIntersectsGround(atmosphere, r, mu); 144 | 145 | transmittance = GetTransmittance(atmosphere, transmittance_texture, 146 | r, mu, d, ray_r_mu_intersects_ground); 147 | 148 | vec3 single_mie_scattering; 149 | vec3 scattering = GetCombinedScattering( 150 | atmosphere, scattering_texture, 151 | r, mu, mu_s, nu, ray_r_mu_intersects_ground, 152 | single_mie_scattering); 153 | 154 | if (!isinf(d)) { 155 | // Compute the r, mu, mu_s and nu parameters for the second texture lookup. 156 | // If shadow_length is not 0 (case of light shafts), we want to ignore the 157 | // scattering along the last shadow_length meters of the view ray, which we 158 | // do by subtracting shadow_length from d (this way scattering_p is equal to 159 | // the S|x_s=x_0-lv term in Eq. (17) of our paper). 160 | // d = max(d - shadow_length, 0.0 * m); 161 | float r_p = ClampRadius(atmosphere, sqrt(d * d + 2.0 * r * mu * d + r * r)); 162 | float mu_p = (r * mu + d) / r_p; 163 | float mu_s_p = (r * mu_s + d * nu) / r_p; 164 | 165 | vec3 single_mie_scattering_p; 166 | vec3 scattering_p = GetCombinedScattering( 167 | atmosphere, scattering_texture, 168 | r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground, 169 | single_mie_scattering_p); 170 | 171 | // Combine the lookup results to get the scattering between camera and point. 172 | vec3 shadow_transmittance = transmittance; 173 | // if (shadow_length > 0.0 * m) { 174 | // // This is the T(x,x_s) term in Eq. (17) of our paper, for light shafts. 175 | // shadow_transmittance = GetTransmittance(atmosphere, transmittance_texture, 176 | // r, mu, d, ray_r_mu_intersects_ground); 177 | // } 178 | scattering = scattering - shadow_transmittance * scattering_p; 179 | single_mie_scattering = 180 | single_mie_scattering - shadow_transmittance * single_mie_scattering_p; 181 | single_mie_scattering = GetExtrapolatedSingleMieScattering( 182 | atmosphere, vec4(scattering, single_mie_scattering.r)); 183 | 184 | // Hack to avoid rendering artifacts when the sun is below the horizon. 185 | single_mie_scattering = single_mie_scattering * 186 | smoothstep(0.0, 0.01, mu_s); 187 | } 188 | 189 | return scattering * RayleighPhaseFunction(nu) + single_mie_scattering * 190 | MiePhaseFunction(atmosphere.mie_phase_function_g, nu); 191 | } 192 | 193 | #endif 194 | -------------------------------------------------------------------------------- /shaders/scattering.h: -------------------------------------------------------------------------------- 1 | #ifndef FUZZYBLUE_SCATTERING_H_ 2 | #define FUZZYBLUE_SCATTERING_H_ 3 | 4 | #include "params.h" 5 | #include "util.h" 6 | 7 | vec4 GetScatteringTextureUvwzFromRMuMuSNu(AtmosphereParameters atmosphere, 8 | float r, float mu, float mu_s, float nu, 9 | bool ray_r_mu_intersects_ground) { 10 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 11 | // assert(mu >= -1.0 && mu <= 1.0); 12 | // assert(mu_s >= -1.0 && mu_s <= 1.0); 13 | // assert(nu >= -1.0 && nu <= 1.0); 14 | 15 | // Distance to top atmosphere boundary for a horizontal ray at ground level. 16 | float H = sqrt(atmosphere.top_radius * atmosphere.top_radius - 17 | atmosphere.bottom_radius * atmosphere.bottom_radius); 18 | // Distance to the horizon. 19 | float rho = 20 | SafeSqrt(r * r - atmosphere.bottom_radius * atmosphere.bottom_radius); 21 | float u_r = GetTextureCoordFromUnitRange(rho / H, atmosphere.scattering_texture_r_size); 22 | 23 | // Discriminant of the quadratic equation for the intersections of the ray 24 | // (r,mu) with the ground (see RayIntersectsGround). 25 | float r_mu = r * mu; 26 | float discriminant = 27 | r_mu * r_mu - r * r + atmosphere.bottom_radius * atmosphere.bottom_radius; 28 | float u_mu; 29 | if (ray_r_mu_intersects_ground) { 30 | // Distance to the ground for the ray (r,mu), and its minimum and maximum 31 | // values over all mu - obtained for (r,-1) and (r,mu_horizon). 32 | float d = -r_mu - SafeSqrt(discriminant); 33 | float d_min = r - atmosphere.bottom_radius; 34 | float d_max = rho; 35 | u_mu = 0.5 - 0.5 * GetTextureCoordFromUnitRange(d_max == d_min ? 0.0 : 36 | (d - d_min) / (d_max - d_min), atmosphere.scattering_texture_mu_size / 2); 37 | } else { 38 | // Distance to the top atmosphere boundary for the ray (r,mu), and its 39 | // minimum and maximum values over all mu - obtained for (r,1) and 40 | // (r,mu_horizon). 41 | float d = -r_mu + SafeSqrt(discriminant + H * H); 42 | float d_min = atmosphere.top_radius - r; 43 | float d_max = rho + H; 44 | u_mu = 0.5 + 0.5 * GetTextureCoordFromUnitRange( 45 | (d - d_min) / (d_max - d_min), atmosphere.scattering_texture_mu_size / 2); 46 | } 47 | 48 | float d = DistanceToTopAtmosphereBoundary( 49 | atmosphere, atmosphere.bottom_radius, mu_s); 50 | float d_min = atmosphere.top_radius - atmosphere.bottom_radius; 51 | float d_max = H; 52 | float a = (d - d_min) / (d_max - d_min); 53 | float A = 54 | -2.0 * atmosphere.mu_s_min * atmosphere.bottom_radius / (d_max - d_min); 55 | float u_mu_s = GetTextureCoordFromUnitRange( 56 | max(1.0 - a / A, 0.0) / (1.0 + a), atmosphere.scattering_texture_mu_s_size); 57 | 58 | float u_nu = (nu + 1.0) / 2.0; 59 | return vec4(u_nu, u_mu_s, u_mu, u_r); 60 | } 61 | 62 | void GetRMuMuSNuFromScatteringTextureUvwz(AtmosphereParameters atmosphere, 63 | vec4 uvwz, out float r, out float mu, out float mu_s, 64 | out float nu, out bool ray_r_mu_intersects_ground) { 65 | // assert(uvwz.x >= 0.0 && uvwz.x <= 1.0); 66 | // assert(uvwz.y >= 0.0 && uvwz.y <= 1.0); 67 | // assert(uvwz.z >= 0.0 && uvwz.z <= 1.0); 68 | // assert(uvwz.w >= 0.0 && uvwz.w <= 1.0); 69 | 70 | // Distance to top atmosphere boundary for a horizontal ray at ground level. 71 | float H = sqrt(atmosphere.top_radius * atmosphere.top_radius - 72 | atmosphere.bottom_radius * atmosphere.bottom_radius); 73 | // Distance to the horizon. 74 | float rho = 75 | H * GetUnitRangeFromTextureCoord(uvwz.w, atmosphere.scattering_texture_r_size); 76 | r = sqrt(rho * rho + atmosphere.bottom_radius * atmosphere.bottom_radius); 77 | 78 | if (uvwz.z < 0.5) { 79 | // Distance to the ground for the ray (r,mu), and its minimum and maximum 80 | // values over all mu - obtained for (r,-1) and (r,mu_horizon) - from which 81 | // we can recover mu: 82 | float d_min = r - atmosphere.bottom_radius; 83 | float d_max = rho; 84 | float d = d_min + (d_max - d_min) * GetUnitRangeFromTextureCoord( 85 | 1.0 - 2.0 * uvwz.z, atmosphere.scattering_texture_mu_size / 2); 86 | mu = d == 0.0 ? -1.0 : 87 | ClampCosine(-(rho * rho + d * d) / (2.0 * r * d)); 88 | ray_r_mu_intersects_ground = true; 89 | } else { 90 | // Distance to the top atmosphere boundary for the ray (r,mu), and its 91 | // minimum and maximum values over all mu - obtained for (r,1) and 92 | // (r,mu_horizon) - from which we can recover mu: 93 | float d_min = atmosphere.top_radius - r; 94 | float d_max = rho + H; 95 | float d = d_min + (d_max - d_min) * GetUnitRangeFromTextureCoord( 96 | 2.0 * uvwz.z - 1.0, atmosphere.scattering_texture_mu_size / 2); 97 | mu = d == 0.0 ? 1.0 : 98 | ClampCosine((H * H - rho * rho - d * d) / (2.0 * r * d)); 99 | ray_r_mu_intersects_ground = false; 100 | } 101 | 102 | float x_mu_s = 103 | GetUnitRangeFromTextureCoord(uvwz.y, atmosphere.scattering_texture_mu_s_size); 104 | float d_min = atmosphere.top_radius - atmosphere.bottom_radius; 105 | float d_max = H; 106 | float A = 107 | -2.0 * atmosphere.mu_s_min * atmosphere.bottom_radius / (d_max - d_min); 108 | float a = (A - x_mu_s * A) / (1.0 + x_mu_s * A); 109 | float d = d_min + min(a, A) * (d_max - d_min); 110 | mu_s = d == 0.0 ? 1.0 : 111 | ClampCosine((H * H - d * d) / (2.0 * atmosphere.bottom_radius * d)); 112 | 113 | nu = ClampCosine(uvwz.x * 2.0 - 1.0); 114 | } 115 | 116 | void GetRMuMuSNuFromScatteringTextureFragCoord( 117 | AtmosphereParameters atmosphere, vec3 frag_coord, 118 | out float r, out float mu, out float mu_s, out float nu, 119 | out bool ray_r_mu_intersects_ground) { 120 | const vec4 SCATTERING_TEXTURE_SIZE = vec4( 121 | atmosphere.scattering_texture_nu_size - 1, 122 | atmosphere.scattering_texture_mu_s_size, 123 | atmosphere.scattering_texture_mu_size, 124 | atmosphere.scattering_texture_r_size); 125 | float frag_coord_nu = 126 | floor(frag_coord.x / float(atmosphere.scattering_texture_mu_s_size)); 127 | float frag_coord_mu_s = 128 | mod(frag_coord.x, float(atmosphere.scattering_texture_mu_s_size)); 129 | vec4 uvwz = 130 | vec4(frag_coord_nu, frag_coord_mu_s, frag_coord.y, frag_coord.z) / 131 | SCATTERING_TEXTURE_SIZE; 132 | GetRMuMuSNuFromScatteringTextureUvwz( 133 | atmosphere, uvwz, r, mu, mu_s, nu, ray_r_mu_intersects_ground); 134 | // Clamp nu to its valid range of values, given mu and mu_s. 135 | nu = clamp(nu, mu * mu_s - sqrt((1.0 - mu * mu) * (1.0 - mu_s * mu_s)), 136 | mu * mu_s + sqrt((1.0 - mu * mu) * (1.0 - mu_s * mu_s))); 137 | } 138 | 139 | vec3 GetScattering( 140 | AtmosphereParameters atmosphere, 141 | sampler3D scattering_texture, 142 | float r, float mu, float mu_s, float nu, 143 | bool ray_r_mu_intersects_ground) { 144 | vec4 uvwz = GetScatteringTextureUvwzFromRMuMuSNu( 145 | atmosphere, r, mu, mu_s, nu, ray_r_mu_intersects_ground); 146 | float tex_coord_x = uvwz.x * float(atmosphere.scattering_texture_nu_size - 1); 147 | float tex_x = floor(tex_coord_x); 148 | float lerp = tex_coord_x - tex_x; 149 | vec3 uvw0 = vec3((tex_x + uvwz.y) / float(atmosphere.scattering_texture_nu_size), 150 | uvwz.z, uvwz.w); 151 | vec3 uvw1 = vec3((tex_x + 1.0 + uvwz.y) / float(atmosphere.scattering_texture_nu_size), 152 | uvwz.z, uvwz.w); 153 | return vec3(texture(scattering_texture, uvw0) * (1.0 - lerp) + 154 | texture(scattering_texture, uvw1) * lerp); 155 | } 156 | 157 | vec3 GetScattering( 158 | AtmosphereParameters atmosphere, 159 | sampler3D single_rayleigh_scattering_texture, 160 | sampler3D single_mie_scattering_texture, 161 | sampler3D multiple_scattering_texture, 162 | float r, float mu, float mu_s, float nu, 163 | bool ray_r_mu_intersects_ground, 164 | int scattering_order) { 165 | if (scattering_order == 1) { 166 | vec3 rayleigh = GetScattering( 167 | atmosphere, single_rayleigh_scattering_texture, r, mu, mu_s, nu, 168 | ray_r_mu_intersects_ground); 169 | vec3 mie = GetScattering( 170 | atmosphere, single_mie_scattering_texture, r, mu, mu_s, nu, 171 | ray_r_mu_intersects_ground); 172 | return rayleigh * RayleighPhaseFunction(nu) + 173 | mie * MiePhaseFunction(atmosphere.mie_phase_function_g, nu); 174 | } else { 175 | return GetScattering( 176 | atmosphere, multiple_scattering_texture, r, mu, mu_s, nu, 177 | ray_r_mu_intersects_ground); 178 | } 179 | } 180 | 181 | bool GetScatteringFragCoord(AtmosphereParameters atmosphere, uvec3 id, out vec3 coord) { 182 | uvec3 ref = uvec3(atmosphere.scattering_texture_nu_size * atmosphere.scattering_texture_mu_s_size, 183 | atmosphere.scattering_texture_mu_size, 184 | atmosphere.scattering_texture_r_size); 185 | if (any(greaterThanEqual(id, ref))) { 186 | return false; 187 | } 188 | coord = GetFragCoordFromTexel(id, ref); 189 | return true; 190 | } 191 | 192 | #endif 193 | -------------------------------------------------------------------------------- /shaders/scattering_density.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; 4 | 5 | #include "params.h" 6 | #include "scattering.h" 7 | #include "transmittance.h" 8 | #include "irradiance.h" 9 | 10 | vec3 ComputeScatteringDensity( 11 | AtmosphereParameters atmosphere, 12 | sampler2D transmittance_texture, 13 | sampler3D single_rayleigh_scattering_texture, 14 | sampler3D single_mie_scattering_texture, 15 | sampler3D multiple_scattering_texture, 16 | sampler2D irradiance_texture, 17 | float r, float mu, float mu_s, float nu, int scattering_order) { 18 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 19 | // assert(mu >= -1.0 && mu <= 1.0); 20 | // assert(mu_s >= -1.0 && mu_s <= 1.0); 21 | // assert(nu >= -1.0 && nu <= 1.0); 22 | // assert(scattering_order >= 2); 23 | 24 | // Compute unit direction vectors for the zenith, the view direction omega and 25 | // and the sun direction omega_s, such that the cosine of the view-zenith 26 | // angle is mu, the cosine of the sun-zenith angle is mu_s, and the cosine of 27 | // the view-sun angle is nu. The goal is to simplify computations below. 28 | vec3 zenith_direction = vec3(0.0, 0.0, 1.0); 29 | vec3 omega = vec3(sqrt(1.0 - mu * mu), 0.0, mu); 30 | float sun_dir_x = omega.x == 0.0 ? 0.0 : (nu - mu * mu_s) / omega.x; 31 | float sun_dir_y = sqrt(max(1.0 - sun_dir_x * sun_dir_x - mu_s * mu_s, 0.0)); 32 | vec3 omega_s = vec3(sun_dir_x, sun_dir_y, mu_s); 33 | 34 | const int SAMPLE_COUNT = 16; 35 | const float dphi = PI / float(SAMPLE_COUNT); 36 | const float dtheta = PI / float(SAMPLE_COUNT); 37 | // watt_per_cubic_meter_per_sr_per_nm 38 | vec3 rayleigh_mie = vec3(0.0); 39 | 40 | // Nested loops for the integral over all the incident directions omega_i. 41 | for (int l = 0; l < SAMPLE_COUNT; ++l) { 42 | float theta = (float(l) + 0.5) * dtheta; 43 | float cos_theta = cos(theta); 44 | float sin_theta = sin(theta); 45 | bool ray_r_theta_intersects_ground = 46 | RayIntersectsGround(atmosphere, r, cos_theta); 47 | 48 | // The distance and transmittance to the ground only depend on theta, so we 49 | // can compute them in the outer loop for efficiency. 50 | float distance_to_ground = 0.0; 51 | vec3 transmittance_to_ground = vec3(0.0); 52 | vec3 ground_albedo = vec3(0.0); 53 | if (ray_r_theta_intersects_ground) { 54 | distance_to_ground = 55 | DistanceToBottomAtmosphereBoundary(atmosphere, r, cos_theta); 56 | transmittance_to_ground = 57 | GetTransmittance(atmosphere, transmittance_texture, r, cos_theta, 58 | distance_to_ground, true /* ray_intersects_ground */); 59 | ground_albedo = atmosphere.ground_albedo; 60 | } 61 | 62 | for (int m = 0; m < 2 * SAMPLE_COUNT; ++m) { 63 | float phi = (float(m) + 0.5) * dphi; 64 | vec3 omega_i = 65 | vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta); 66 | float domega_i = dtheta * dphi * sin(theta); 67 | 68 | // The radiance L_i arriving from direction omega_i after n-1 bounces is 69 | // the sum of a term given by the precomputed scattering texture for the 70 | // (n-1)-th order: 71 | float nu1 = dot(omega_s, omega_i); 72 | vec3 incident_radiance = GetScattering(atmosphere, 73 | single_rayleigh_scattering_texture, single_mie_scattering_texture, 74 | multiple_scattering_texture, r, omega_i.z, mu_s, nu1, 75 | ray_r_theta_intersects_ground, scattering_order - 1); 76 | 77 | // and of the contribution from the light paths with n-1 bounces and whose 78 | // last bounce is on the ground. This contribution is the product of the 79 | // transmittance to the ground, the ground albedo, the ground BRDF, and 80 | // the irradiance received on the ground after n-2 bounces. 81 | vec3 ground_normal = 82 | normalize(zenith_direction * r + omega_i * distance_to_ground); 83 | vec3 ground_irradiance = GetIrradiance( 84 | atmosphere, irradiance_texture, atmosphere.bottom_radius, 85 | dot(ground_normal, omega_s)); 86 | incident_radiance += transmittance_to_ground * 87 | ground_albedo * (1.0 / PI) * ground_irradiance; 88 | 89 | // The radiance finally scattered from direction omega_i towards direction 90 | // -omega is the product of the incident radiance, the scattering 91 | // coefficient, and the phase function for directions omega and omega_i 92 | // (all this summed over all particle types, i.e. Rayleigh and Mie). 93 | float nu2 = dot(omega, omega_i); 94 | float rayleigh_density = GetProfileDensity( 95 | atmosphere.rayleigh_density, r - atmosphere.bottom_radius); 96 | float mie_density = GetProfileDensity( 97 | atmosphere.mie_density, r - atmosphere.bottom_radius); 98 | rayleigh_mie += incident_radiance * ( 99 | atmosphere.rayleigh_scattering * rayleigh_density * 100 | RayleighPhaseFunction(nu2) + 101 | atmosphere.mie_scattering * mie_density * 102 | MiePhaseFunction(atmosphere.mie_phase_function_g, nu2)) * 103 | domega_i; 104 | } 105 | } 106 | return rayleigh_mie; 107 | } 108 | 109 | vec3 ComputeScatteringDensityTexture( 110 | AtmosphereParameters atmosphere, 111 | sampler2D transmittance_texture, 112 | sampler3D single_rayleigh_scattering_texture, 113 | sampler3D single_mie_scattering_texture, 114 | sampler3D multiple_scattering_texture, 115 | sampler2D irradiance_texture, 116 | vec3 frag_coord, int scattering_order) { 117 | float r; 118 | float mu; 119 | float mu_s; 120 | float nu; 121 | bool ray_r_mu_intersects_ground; 122 | GetRMuMuSNuFromScatteringTextureFragCoord(atmosphere, frag_coord, 123 | r, mu, mu_s, nu, ray_r_mu_intersects_ground); 124 | return ComputeScatteringDensity(atmosphere, transmittance_texture, 125 | single_rayleigh_scattering_texture, single_mie_scattering_texture, 126 | multiple_scattering_texture, irradiance_texture, r, mu, mu_s, nu, 127 | scattering_order); 128 | } 129 | 130 | layout (set=0, binding=0) uniform Params { 131 | AtmosphereParameters atmosphere; 132 | }; 133 | 134 | layout (set=1, binding=0) uniform sampler2D transmittance_texture; 135 | layout (set=1, binding=1) uniform sampler3D single_rayleigh_scattering_texture; 136 | layout (set=1, binding=2) uniform sampler3D single_mie_scattering_texture; 137 | layout (set=1, binding=3) uniform sampler3D multiple_scattering_texture; 138 | layout (set=1, binding=4) uniform sampler2D irradiance_texture; 139 | layout (set=1, binding=5, rgba16f) uniform writeonly image3D scattering_density; 140 | layout (push_constant) uniform PerOrder { 141 | int scattering_order; 142 | }; 143 | 144 | void main() { 145 | vec3 frag_coord; 146 | if (!GetScatteringFragCoord(atmosphere, gl_GlobalInvocationID, frag_coord)) { 147 | return; 148 | } 149 | vec3 density = ComputeScatteringDensityTexture( 150 | atmosphere, transmittance_texture, single_rayleigh_scattering_texture, 151 | single_mie_scattering_texture, multiple_scattering_texture, 152 | irradiance_texture, frag_coord, 153 | scattering_order); 154 | imageStore(scattering_density, ivec3(gl_GlobalInvocationID), vec4(density, 0.0)); 155 | } 156 | -------------------------------------------------------------------------------- /shaders/single_scattering.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; 4 | 5 | #include "params.h" 6 | #include "util.h" 7 | #include "transmittance.h" 8 | #include "scattering.h" 9 | 10 | void ComputeSingleScatteringIntegrand( 11 | AtmosphereParameters atmosphere, 12 | sampler2D transmittance_texture, 13 | float r, float mu, float mu_s, float nu, float d, 14 | bool ray_r_mu_intersects_ground, 15 | out vec3 rayleigh, out vec3 mie) { 16 | float r_d = ClampRadius(atmosphere, sqrt(d * d + 2.0 * r * mu * d + r * r)); 17 | float mu_s_d = ClampCosine((r * mu_s + d * nu) / r_d); 18 | vec3 transmittance = 19 | GetTransmittance( 20 | atmosphere, transmittance_texture, r, mu, d, 21 | ray_r_mu_intersects_ground) * 22 | GetTransmittanceToSun( 23 | atmosphere, transmittance_texture, r_d, mu_s_d); 24 | rayleigh = transmittance * GetProfileDensity( 25 | atmosphere.rayleigh_density, r_d - atmosphere.bottom_radius); 26 | mie = transmittance * GetProfileDensity( 27 | atmosphere.mie_density, r_d - atmosphere.bottom_radius); 28 | } 29 | 30 | void ComputeSingleScattering( 31 | AtmosphereParameters atmosphere, 32 | sampler2D transmittance_texture, 33 | float r, float mu, float mu_s, float nu, 34 | bool ray_r_mu_intersects_ground, 35 | out vec3 rayleigh, out vec3 mie) { 36 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 37 | // assert(mu >= -1.0 && mu <= 1.0); 38 | // assert(mu_s >= -1.0 && mu_s <= 1.0); 39 | // assert(nu >= -1.0 && nu <= 1.0); 40 | 41 | // Number of intervals for the numerical integration. 42 | const int SAMPLE_COUNT = 50; 43 | // The integration step, i.e. the length of each integration interval. 44 | float dx = 45 | DistanceToNearestAtmosphereBoundary(atmosphere, r, mu, 46 | ray_r_mu_intersects_ground) / float(SAMPLE_COUNT); 47 | // Integration loop. 48 | vec3 rayleigh_sum = vec3(0.0); 49 | vec3 mie_sum = vec3(0.0); 50 | for (int i = 0; i <= SAMPLE_COUNT; ++i) { 51 | float d_i = float(i) * dx; 52 | // The Rayleigh and Mie single scattering at the current sample point. 53 | vec3 rayleigh_i; 54 | vec3 mie_i; 55 | ComputeSingleScatteringIntegrand(atmosphere, transmittance_texture, 56 | r, mu, mu_s, nu, d_i, ray_r_mu_intersects_ground, rayleigh_i, mie_i); 57 | // Sample weight (from the trapezoidal rule). 58 | float weight_i = (i == 0 || i == SAMPLE_COUNT) ? 0.5 : 1.0; 59 | rayleigh_sum += rayleigh_i * weight_i; 60 | mie_sum += mie_i * weight_i; 61 | } 62 | rayleigh = rayleigh_sum * dx * atmosphere.solar_irradiance * 63 | atmosphere.rayleigh_scattering; 64 | mie = mie_sum * dx * atmosphere.solar_irradiance * atmosphere.mie_scattering; 65 | } 66 | 67 | void ComputeSingleScatteringTexture(AtmosphereParameters atmosphere, 68 | sampler2D transmittance_texture, vec3 frag_coord, 69 | out vec3 rayleigh, out vec3 mie) { 70 | float r; 71 | float mu; 72 | float mu_s; 73 | float nu; 74 | bool ray_r_mu_intersects_ground; 75 | GetRMuMuSNuFromScatteringTextureFragCoord(atmosphere, frag_coord, 76 | r, mu, mu_s, nu, ray_r_mu_intersects_ground); 77 | ComputeSingleScattering(atmosphere, transmittance_texture, 78 | r, mu, mu_s, nu, ray_r_mu_intersects_ground, rayleigh, mie); 79 | } 80 | 81 | layout (set=0, binding=0) uniform Params { 82 | AtmosphereParameters atmosphere; 83 | }; 84 | layout (set=1, binding=0) uniform sampler2D transmittance; 85 | layout (set=1, binding=1, rgba16f) uniform writeonly image3D delta_rayleigh; 86 | layout (set=1, binding=2, rgba16f) uniform writeonly image3D delta_mie; 87 | layout (set=1, binding=3, rgba16f) uniform writeonly image3D scattering; 88 | 89 | void main() { 90 | vec3 frag_coord; 91 | if (!GetScatteringFragCoord(atmosphere, gl_GlobalInvocationID, frag_coord)) { 92 | return; 93 | } 94 | vec3 rayleigh; 95 | vec3 mie; 96 | ComputeSingleScatteringTexture(atmosphere, transmittance, frag_coord, rayleigh, mie); 97 | ivec3 coords = ivec3(gl_GlobalInvocationID); 98 | imageStore(delta_rayleigh, coords, vec4(rayleigh, 0)); 99 | imageStore(delta_mie, coords, vec4(mie, 0)); 100 | imageStore(scattering, coords, vec4(rayleigh, mie.r)); 101 | } 102 | -------------------------------------------------------------------------------- /shaders/transmittance.comp: -------------------------------------------------------------------------------- 1 | // Precompute atmospheric transmittance 2 | #version 450 3 | 4 | layout(local_size_x = 8, local_size_y = 8) in; 5 | 6 | #include "transmittance.h" 7 | 8 | float ComputeOpticalLengthToTopAtmosphereBoundary( 9 | AtmosphereParameters atmosphere, DensityProfile profile, 10 | float r, float mu) { 11 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 12 | // assert(mu >= -1.0 && mu <= 1.0); 13 | // Number of intervals for the numerical integration. 14 | const int SAMPLE_COUNT = 500; 15 | // The integration step, i.e. the length of each integration interval. 16 | float dx = 17 | DistanceToTopAtmosphereBoundary(atmosphere, r, mu) / SAMPLE_COUNT; 18 | // Integration loop. 19 | float result = 0.0; 20 | for (int i = 0; i <= SAMPLE_COUNT; ++i) { 21 | float d_i = i * dx; 22 | // Distance between the current sample point and the planet center. 23 | float r_i = sqrt(d_i * d_i + 2.0 * r * mu * d_i + r * r); 24 | // Number density at the current sample point (divided by the number density 25 | // at the bottom of the atmosphere, yielding a dimensionless number). 26 | float y_i = GetProfileDensity(profile, r_i - atmosphere.bottom_radius); 27 | // Sample weight (from the trapezoidal rule). 28 | float weight_i = i == 0 || i == SAMPLE_COUNT ? 0.5 : 1.0; 29 | result += y_i * weight_i * dx; 30 | } 31 | return result; 32 | } 33 | 34 | vec3 ComputeTransmittanceToTopAtmosphereBoundary(AtmosphereParameters atmosphere, float r, float mu) { 35 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 36 | // assert(mu >= -1.0 && mu <= 1.0); 37 | return exp(-(atmosphere.rayleigh_scattering * 38 | ComputeOpticalLengthToTopAtmosphereBoundary( 39 | atmosphere, atmosphere.rayleigh_density, r, mu) + 40 | atmosphere.mie_extinction * 41 | ComputeOpticalLengthToTopAtmosphereBoundary( 42 | atmosphere, atmosphere.mie_density, r, mu) + 43 | atmosphere.absorption_extinction * 44 | ComputeOpticalLengthToTopAtmosphereBoundary( 45 | atmosphere, atmosphere.absorption_density, r, mu))); 46 | } 47 | 48 | vec2 GetRMuFromUnitRanges(AtmosphereParameters atmosphere, float x_mu, float x_r) { 49 | // Distance to top atmosphere boundary for a horizontal ray at ground level. 50 | float H = sqrt(atmosphere.top_radius * atmosphere.top_radius - 51 | atmosphere.bottom_radius * atmosphere.bottom_radius); 52 | // Distance to the horizon, from which we can compute r: 53 | float rho = H * x_r; 54 | float r = sqrt(rho * rho + atmosphere.bottom_radius * atmosphere.bottom_radius); 55 | // Distance to the top atmosphere boundary for the ray (r,mu), and its minimum 56 | // and maximum values over all mu - obtained for (r,1) and (r,mu_horizon) - 57 | // from which we can recover mu: 58 | float d_min = atmosphere.top_radius - r; 59 | float d_max = rho + H; 60 | float d = d_min + x_mu * (d_max - d_min); 61 | float mu = d == 0.0 ? 1.0 : (H * H - rho * rho - d * d) / (2.0 * r * d); 62 | mu = ClampCosine(mu); 63 | return vec2(r, mu); 64 | } 65 | 66 | layout (set=0, binding=0) uniform Params { 67 | AtmosphereParameters atmosphere; 68 | }; 69 | layout (set=1, binding=0, rgba16f) uniform writeonly image2D table; 70 | 71 | void main() { 72 | if (any(greaterThanEqual(gl_GlobalInvocationID.xy, uvec2(atmosphere.transmittance_texture_mu_size, atmosphere.transmittance_texture_r_size)))) { 73 | return; 74 | } 75 | float x_mu = gl_GlobalInvocationID.x / float(atmosphere.transmittance_texture_mu_size - 1); 76 | float x_r = gl_GlobalInvocationID.y / float(atmosphere.transmittance_texture_r_size - 1); 77 | vec2 rmu = GetRMuFromUnitRanges(atmosphere, x_mu, x_r); 78 | vec3 result = ComputeTransmittanceToTopAtmosphereBoundary(atmosphere, rmu.x, rmu.y); 79 | imageStore(table, ivec2(gl_GlobalInvocationID.xy), vec4(result, 1)); 80 | } 81 | -------------------------------------------------------------------------------- /shaders/transmittance.h: -------------------------------------------------------------------------------- 1 | #ifndef TRANSMITTANCE_H_ 2 | #define TRANSMITTANCE_H_ 3 | 4 | #include "util.h" 5 | #include "params.h" 6 | 7 | vec2 GetTransmittanceTextureUvFromRMu(AtmosphereParameters atmosphere, float r, float mu) { 8 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 9 | // assert(mu >= -1.0 && mu <= 1.0); 10 | // Distance to top atmosphere boundary for a horizontal ray at ground level. 11 | float H = sqrt(atmosphere.top_radius * atmosphere.top_radius - 12 | atmosphere.bottom_radius * atmosphere.bottom_radius); 13 | // Distance to the horizon. 14 | float rho = SafeSqrt(r * r - atmosphere.bottom_radius * atmosphere.bottom_radius); 15 | // Distance to the top atmosphere boundary for the ray (r,mu), and its minimum 16 | // and maximum values over all mu - obtained for (r,1) and (r,mu_horizon). 17 | float d = DistanceToTopAtmosphereBoundary(atmosphere, r, mu); 18 | float d_min = atmosphere.top_radius - r; 19 | float d_max = rho + H; 20 | float x_mu = (d - d_min) / (d_max - d_min); 21 | float x_r = rho / H; 22 | return vec2(GetTextureCoordFromUnitRange(x_mu, atmosphere.transmittance_texture_mu_size), 23 | GetTextureCoordFromUnitRange(x_r, atmosphere.transmittance_texture_r_size)); 24 | } 25 | 26 | vec3 GetTransmittanceToTopAtmosphereBoundary( 27 | AtmosphereParameters atmosphere, 28 | sampler2D transmittance_texture, 29 | float r, float mu) { 30 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 31 | vec2 uv = GetTransmittanceTextureUvFromRMu(atmosphere, r, mu); 32 | return texture(transmittance_texture, uv).rgb; 33 | } 34 | 35 | vec3 GetTransmittance( 36 | AtmosphereParameters atmosphere, 37 | sampler2D transmittance_texture, 38 | float r, float mu, float d, bool ray_r_mu_intersects_ground) { 39 | // assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius); 40 | // assert(mu >= -1.0 && mu <= 1.0); 41 | // assert(d >= 0.0 * m); 42 | 43 | float r_d = ClampRadius(atmosphere, sqrt(d * d + 2.0 * r * mu * d + r * r)); 44 | float mu_d = ClampCosine((r * mu + d) / r_d); 45 | 46 | vec3 quotient; 47 | if (ray_r_mu_intersects_ground) { 48 | quotient = 49 | GetTransmittanceToTopAtmosphereBoundary( 50 | atmosphere, transmittance_texture, r_d, -mu_d) / 51 | GetTransmittanceToTopAtmosphereBoundary( 52 | atmosphere, transmittance_texture, r, -mu); 53 | } else { 54 | quotient = 55 | GetTransmittanceToTopAtmosphereBoundary( 56 | atmosphere, transmittance_texture, r, mu) / 57 | GetTransmittanceToTopAtmosphereBoundary( 58 | atmosphere, transmittance_texture, r_d, mu_d); 59 | } 60 | return min(quotient, vec3(1.0)); 61 | } 62 | 63 | vec3 GetTransmittanceToSun( 64 | AtmosphereParameters atmosphere, 65 | sampler2D transmittance_texture, 66 | float r, float mu_s) { 67 | float sin_theta_h = atmosphere.bottom_radius / r; 68 | float cos_theta_h = -sqrt(max(1.0 - sin_theta_h * sin_theta_h, 0.0)); 69 | return GetTransmittanceToTopAtmosphereBoundary( 70 | atmosphere, transmittance_texture, r, mu_s) * 71 | smoothstep(-sin_theta_h * atmosphere.sun_angular_radius, 72 | sin_theta_h * atmosphere.sun_angular_radius, 73 | mu_s - cos_theta_h); 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /shaders/util.h: -------------------------------------------------------------------------------- 1 | #ifndef FUZZYBLUE_UTIL_H_ 2 | #define FUZZYBLUE_UTIL_H_ 3 | 4 | const float PI = 3.14159265358979323846; 5 | 6 | float ClampCosine(float mu) { 7 | return clamp(mu, -1.0, 1.0); 8 | } 9 | 10 | float ClampDistance(float d) { 11 | return max(d, 0.0); 12 | } 13 | 14 | float SafeSqrt(float area) { 15 | return sqrt(max(area, 0.0)); 16 | } 17 | 18 | float GetTextureCoordFromUnitRange(float x, int texture_size) { 19 | return 0.5 / float(texture_size) + x * (1.0 - 1.0 / float(texture_size)); 20 | } 21 | 22 | float GetUnitRangeFromTextureCoord(float u, int texture_size) { 23 | return (u - 0.5 / float(texture_size)) / (1.0 - 1.0 / float(texture_size)); 24 | } 25 | 26 | float RayleighPhaseFunction(float nu) { 27 | float k = 3.0 / (16.0 * PI); 28 | return k * (1.0 + nu * nu); 29 | } 30 | 31 | float MiePhaseFunction(float g, float nu) { 32 | float k = 3.0 / (8.0 * PI) * (1.0 - g * g) / (2.0 + g * g); 33 | return k * (1.0 + nu * nu) / pow(1.0 + g * g - 2.0 * g * nu, 1.5); 34 | } 35 | 36 | float GetFragCoordFromTexel(uint x, uint texture_size) { 37 | return texture_size * GetTextureCoordFromUnitRange(x / float(texture_size - 1), int(texture_size)); 38 | } 39 | 40 | vec3 GetFragCoordFromTexel(uvec3 v, uvec3 size) { 41 | return vec3( 42 | GetFragCoordFromTexel(v.x, size.x), 43 | GetFragCoordFromTexel(v.y, size.y), 44 | GetFragCoordFromTexel(v.z, size.z)); 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Real-time physically based atmospheric scattering rendering 2 | //! 3 | //! Based on [E. Bruneton and F. Neyret's "Precomputed atmospheric 4 | //! scattering"](https://ebruneton.github.io/precomputed_atmospheric_scattering/) 5 | 6 | #![allow(clippy::missing_safety_doc)] 7 | 8 | mod precompute; 9 | pub use precompute::{Atmosphere, Builder, Parameters, PendingAtmosphere}; 10 | 11 | mod render; 12 | pub use render::{DrawParameters, Renderer}; 13 | -------------------------------------------------------------------------------- /src/precompute.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, ptr, sync::Arc}; 2 | 3 | use ash::version::{DeviceV1_0, InstanceV1_0}; 4 | use ash::{vk, Device, Instance}; 5 | use vk_shader_macros::include_glsl; 6 | 7 | const TRANSMITTANCE: &[u32] = include_glsl!("shaders/transmittance.comp"); 8 | const SINGLE_SCATTERING: &[u32] = include_glsl!("shaders/single_scattering.comp"); 9 | const SCATTERING_DENSITY: &[u32] = include_glsl!("shaders/scattering_density.comp"); 10 | const MULTIPLE_SCATTERING: &[u32] = include_glsl!("shaders/multiple_scattering.comp"); 11 | const DIRECT_IRRADIANCE: &[u32] = include_glsl!("shaders/direct_irradiance.comp"); 12 | const INDIRECT_IRRADIANCE: &[u32] = include_glsl!("shaders/indirect_irradiance.comp"); 13 | 14 | /// Constructs `Atmosphere`s 15 | pub struct Builder { 16 | device: Arc, 17 | memory_props: vk::PhysicalDeviceMemoryProperties, 18 | gfx_queue_family: u32, 19 | compute_queue_family: Option, 20 | sampler: vk::Sampler, 21 | params_ds_layout: vk::DescriptorSetLayout, 22 | render_ds_layout: vk::DescriptorSetLayout, 23 | frame_ds_layout: vk::DescriptorSetLayout, 24 | transmittance: Pass, 25 | single_scattering: Pass, 26 | direct_irradiance: Pass, 27 | indirect_irradiance: Pass, 28 | scattering_density: Pass, 29 | multiple_scattering: Pass, 30 | } 31 | 32 | impl Drop for Builder { 33 | fn drop(&mut self) { 34 | unsafe { 35 | self.device.destroy_sampler(self.sampler, None); 36 | self.device 37 | .destroy_descriptor_set_layout(self.params_ds_layout, None); 38 | self.device 39 | .destroy_descriptor_set_layout(self.render_ds_layout, None); 40 | self.device 41 | .destroy_descriptor_set_layout(self.frame_ds_layout, None); 42 | for &pass in &[ 43 | &self.transmittance, 44 | &self.single_scattering, 45 | &self.direct_irradiance, 46 | &self.indirect_irradiance, 47 | &self.scattering_density, 48 | &self.multiple_scattering, 49 | ] { 50 | self.device.destroy_pipeline(pass.pipeline, None); 51 | self.device.destroy_pipeline_layout(pass.layout, None); 52 | self.device 53 | .destroy_descriptor_set_layout(pass.ds_layout, None); 54 | self.device.destroy_shader_module(pass.shader, None); 55 | } 56 | } 57 | } 58 | } 59 | 60 | impl Builder { 61 | pub fn new( 62 | instance: &Instance, 63 | device: Arc, 64 | cache: vk::PipelineCache, 65 | physical: vk::PhysicalDevice, 66 | gfx_queue_family: u32, 67 | compute_queue_family: Option, 68 | ) -> Self { 69 | unsafe { 70 | let params_ds_layout = device 71 | .create_descriptor_set_layout( 72 | &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ 73 | vk::DescriptorSetLayoutBinding { 74 | binding: 0, 75 | descriptor_type: vk::DescriptorType::UNIFORM_BUFFER, 76 | descriptor_count: 1, 77 | stage_flags: vk::ShaderStageFlags::COMPUTE, 78 | p_immutable_samplers: ptr::null(), 79 | }, 80 | ]), 81 | None, 82 | ) 83 | .unwrap(); 84 | 85 | let sampler = device 86 | .create_sampler( 87 | &vk::SamplerCreateInfo { 88 | min_filter: vk::Filter::LINEAR, 89 | mag_filter: vk::Filter::LINEAR, 90 | mipmap_mode: vk::SamplerMipmapMode::NEAREST, 91 | address_mode_u: vk::SamplerAddressMode::CLAMP_TO_EDGE, 92 | address_mode_v: vk::SamplerAddressMode::CLAMP_TO_EDGE, 93 | address_mode_w: vk::SamplerAddressMode::CLAMP_TO_EDGE, 94 | ..Default::default() 95 | }, 96 | None, 97 | ) 98 | .unwrap(); 99 | 100 | let transmittance_ds_layout = device 101 | .create_descriptor_set_layout( 102 | &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ 103 | vk::DescriptorSetLayoutBinding { 104 | binding: 0, 105 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 106 | descriptor_count: 1, 107 | stage_flags: vk::ShaderStageFlags::COMPUTE, 108 | p_immutable_samplers: ptr::null(), 109 | }, 110 | ]), 111 | None, 112 | ) 113 | .unwrap(); 114 | let transmittance_layout = device 115 | .create_pipeline_layout( 116 | &vk::PipelineLayoutCreateInfo::builder() 117 | .set_layouts(&[params_ds_layout, transmittance_ds_layout]), 118 | None, 119 | ) 120 | .unwrap(); 121 | let transmittance_shader = device 122 | .create_shader_module( 123 | &vk::ShaderModuleCreateInfo::builder().code(&TRANSMITTANCE), 124 | None, 125 | ) 126 | .unwrap(); 127 | 128 | let direct_irradiance_ds_layout = device 129 | .create_descriptor_set_layout( 130 | &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ 131 | // transmittance 132 | vk::DescriptorSetLayoutBinding { 133 | binding: 0, 134 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 135 | descriptor_count: 1, 136 | stage_flags: vk::ShaderStageFlags::COMPUTE, 137 | p_immutable_samplers: &sampler, 138 | }, 139 | // delta_irradiance 140 | vk::DescriptorSetLayoutBinding { 141 | binding: 1, 142 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 143 | descriptor_count: 1, 144 | stage_flags: vk::ShaderStageFlags::COMPUTE, 145 | p_immutable_samplers: ptr::null(), 146 | }, 147 | ]), 148 | None, 149 | ) 150 | .unwrap(); 151 | let direct_irradiance_layout = device 152 | .create_pipeline_layout( 153 | &vk::PipelineLayoutCreateInfo::builder() 154 | .set_layouts(&[params_ds_layout, direct_irradiance_ds_layout]), 155 | None, 156 | ) 157 | .unwrap(); 158 | let direct_irradiance_shader = device 159 | .create_shader_module( 160 | &vk::ShaderModuleCreateInfo::builder().code(&DIRECT_IRRADIANCE), 161 | None, 162 | ) 163 | .unwrap(); 164 | 165 | let indirect_irradiance_ds_layout = device 166 | .create_descriptor_set_layout( 167 | &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ 168 | // single_rayleigh 169 | vk::DescriptorSetLayoutBinding { 170 | binding: 0, 171 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 172 | descriptor_count: 1, 173 | stage_flags: vk::ShaderStageFlags::COMPUTE, 174 | p_immutable_samplers: &sampler, 175 | }, 176 | // single_mie 177 | vk::DescriptorSetLayoutBinding { 178 | binding: 1, 179 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 180 | descriptor_count: 1, 181 | stage_flags: vk::ShaderStageFlags::COMPUTE, 182 | p_immutable_samplers: &sampler, 183 | }, 184 | // multiple 185 | vk::DescriptorSetLayoutBinding { 186 | binding: 2, 187 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 188 | descriptor_count: 1, 189 | stage_flags: vk::ShaderStageFlags::COMPUTE, 190 | p_immutable_samplers: &sampler, 191 | }, 192 | // delta_irradiance 193 | vk::DescriptorSetLayoutBinding { 194 | binding: 3, 195 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 196 | descriptor_count: 1, 197 | stage_flags: vk::ShaderStageFlags::COMPUTE, 198 | p_immutable_samplers: ptr::null(), 199 | }, 200 | // irradiance 201 | vk::DescriptorSetLayoutBinding { 202 | binding: 4, 203 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 204 | descriptor_count: 1, 205 | stage_flags: vk::ShaderStageFlags::COMPUTE, 206 | p_immutable_samplers: ptr::null(), 207 | }, 208 | ]), 209 | None, 210 | ) 211 | .unwrap(); 212 | let indirect_irradiance_layout = device 213 | .create_pipeline_layout( 214 | &vk::PipelineLayoutCreateInfo::builder() 215 | .set_layouts(&[params_ds_layout, indirect_irradiance_ds_layout]) 216 | .push_constant_ranges(&[vk::PushConstantRange { 217 | stage_flags: vk::ShaderStageFlags::COMPUTE, 218 | offset: 0, 219 | size: 4, 220 | }]), 221 | None, 222 | ) 223 | .unwrap(); 224 | let indirect_irradiance_shader = device 225 | .create_shader_module( 226 | &vk::ShaderModuleCreateInfo::builder().code(&INDIRECT_IRRADIANCE), 227 | None, 228 | ) 229 | .unwrap(); 230 | 231 | let scattering_ds_layout = device 232 | .create_descriptor_set_layout( 233 | &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ 234 | // transmittance 235 | vk::DescriptorSetLayoutBinding { 236 | binding: 0, 237 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 238 | descriptor_count: 1, 239 | stage_flags: vk::ShaderStageFlags::COMPUTE, 240 | p_immutable_samplers: &sampler, 241 | }, 242 | // delta_rayleigh 243 | vk::DescriptorSetLayoutBinding { 244 | binding: 1, 245 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 246 | descriptor_count: 1, 247 | stage_flags: vk::ShaderStageFlags::COMPUTE, 248 | p_immutable_samplers: ptr::null(), 249 | }, 250 | // delta_mie 251 | vk::DescriptorSetLayoutBinding { 252 | binding: 2, 253 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 254 | descriptor_count: 1, 255 | stage_flags: vk::ShaderStageFlags::COMPUTE, 256 | p_immutable_samplers: ptr::null(), 257 | }, 258 | // scattering 259 | vk::DescriptorSetLayoutBinding { 260 | binding: 3, 261 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 262 | descriptor_count: 1, 263 | stage_flags: vk::ShaderStageFlags::COMPUTE, 264 | p_immutable_samplers: ptr::null(), 265 | }, 266 | ]), 267 | None, 268 | ) 269 | .unwrap(); 270 | let scattering_layout = device 271 | .create_pipeline_layout( 272 | &vk::PipelineLayoutCreateInfo::builder() 273 | .set_layouts(&[params_ds_layout, scattering_ds_layout]), 274 | None, 275 | ) 276 | .unwrap(); 277 | let scattering_shader = device 278 | .create_shader_module( 279 | &vk::ShaderModuleCreateInfo::builder().code(&SINGLE_SCATTERING), 280 | None, 281 | ) 282 | .unwrap(); 283 | 284 | let scattering_density_ds_layout = device 285 | .create_descriptor_set_layout( 286 | &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ 287 | // transmittance 288 | vk::DescriptorSetLayoutBinding { 289 | binding: 0, 290 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 291 | descriptor_count: 1, 292 | stage_flags: vk::ShaderStageFlags::COMPUTE, 293 | p_immutable_samplers: &sampler, 294 | }, 295 | // single_rayleigh 296 | vk::DescriptorSetLayoutBinding { 297 | binding: 1, 298 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 299 | descriptor_count: 1, 300 | stage_flags: vk::ShaderStageFlags::COMPUTE, 301 | p_immutable_samplers: &sampler, 302 | }, 303 | // single_mie 304 | vk::DescriptorSetLayoutBinding { 305 | binding: 2, 306 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 307 | descriptor_count: 1, 308 | stage_flags: vk::ShaderStageFlags::COMPUTE, 309 | p_immutable_samplers: &sampler, 310 | }, 311 | // multiple_scattering 312 | vk::DescriptorSetLayoutBinding { 313 | binding: 3, 314 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 315 | descriptor_count: 1, 316 | stage_flags: vk::ShaderStageFlags::COMPUTE, 317 | p_immutable_samplers: &sampler, 318 | }, 319 | // irradiance 320 | vk::DescriptorSetLayoutBinding { 321 | binding: 4, 322 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 323 | descriptor_count: 1, 324 | stage_flags: vk::ShaderStageFlags::COMPUTE, 325 | p_immutable_samplers: &sampler, 326 | }, 327 | // scattering_density 328 | vk::DescriptorSetLayoutBinding { 329 | binding: 5, 330 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 331 | descriptor_count: 1, 332 | stage_flags: vk::ShaderStageFlags::COMPUTE, 333 | p_immutable_samplers: ptr::null(), 334 | }, 335 | ]), 336 | None, 337 | ) 338 | .unwrap(); 339 | let scattering_density_layout = device 340 | .create_pipeline_layout( 341 | &vk::PipelineLayoutCreateInfo::builder() 342 | .set_layouts(&[params_ds_layout, scattering_density_ds_layout]) 343 | .push_constant_ranges(&[vk::PushConstantRange { 344 | stage_flags: vk::ShaderStageFlags::COMPUTE, 345 | offset: 0, 346 | size: 4, 347 | }]), 348 | None, 349 | ) 350 | .unwrap(); 351 | let scattering_density_shader = device 352 | .create_shader_module( 353 | &vk::ShaderModuleCreateInfo::builder().code(&SCATTERING_DENSITY), 354 | None, 355 | ) 356 | .unwrap(); 357 | 358 | let multiple_scattering_ds_layout = device 359 | .create_descriptor_set_layout( 360 | &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ 361 | // transmittance 362 | vk::DescriptorSetLayoutBinding { 363 | binding: 0, 364 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 365 | descriptor_count: 1, 366 | stage_flags: vk::ShaderStageFlags::COMPUTE, 367 | p_immutable_samplers: &sampler, 368 | }, 369 | // scattering_density 370 | vk::DescriptorSetLayoutBinding { 371 | binding: 1, 372 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 373 | descriptor_count: 1, 374 | stage_flags: vk::ShaderStageFlags::COMPUTE, 375 | p_immutable_samplers: &sampler, 376 | }, 377 | // delta_multiple_scattering 378 | vk::DescriptorSetLayoutBinding { 379 | binding: 2, 380 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 381 | descriptor_count: 1, 382 | stage_flags: vk::ShaderStageFlags::COMPUTE, 383 | p_immutable_samplers: ptr::null(), 384 | }, 385 | // scattering 386 | vk::DescriptorSetLayoutBinding { 387 | binding: 3, 388 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 389 | descriptor_count: 1, 390 | stage_flags: vk::ShaderStageFlags::COMPUTE, 391 | p_immutable_samplers: ptr::null(), 392 | }, 393 | ]), 394 | None, 395 | ) 396 | .unwrap(); 397 | let multiple_scattering_layout = device 398 | .create_pipeline_layout( 399 | &vk::PipelineLayoutCreateInfo::builder() 400 | .set_layouts(&[params_ds_layout, multiple_scattering_ds_layout]) 401 | .push_constant_ranges(&[vk::PushConstantRange { 402 | stage_flags: vk::ShaderStageFlags::COMPUTE, 403 | offset: 0, 404 | size: 4, 405 | }]), 406 | None, 407 | ) 408 | .unwrap(); 409 | let multiple_scattering_shader = device 410 | .create_shader_module( 411 | &vk::ShaderModuleCreateInfo::builder().code(&MULTIPLE_SCATTERING), 412 | None, 413 | ) 414 | .unwrap(); 415 | 416 | let render_ds_layout = device 417 | .create_descriptor_set_layout( 418 | &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ 419 | vk::DescriptorSetLayoutBinding { 420 | binding: 0, 421 | descriptor_type: vk::DescriptorType::UNIFORM_BUFFER, 422 | descriptor_count: 1, 423 | stage_flags: vk::ShaderStageFlags::FRAGMENT, 424 | p_immutable_samplers: ptr::null(), 425 | }, 426 | vk::DescriptorSetLayoutBinding { 427 | binding: 1, 428 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 429 | descriptor_count: 1, 430 | stage_flags: vk::ShaderStageFlags::FRAGMENT, 431 | p_immutable_samplers: &sampler, 432 | }, 433 | vk::DescriptorSetLayoutBinding { 434 | binding: 2, 435 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 436 | descriptor_count: 1, 437 | stage_flags: vk::ShaderStageFlags::FRAGMENT, 438 | p_immutable_samplers: &sampler, 439 | }, 440 | ]), 441 | None, 442 | ) 443 | .unwrap(); 444 | 445 | let frame_ds_layout = device 446 | .create_descriptor_set_layout( 447 | &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ 448 | vk::DescriptorSetLayoutBinding { 449 | binding: 0, 450 | descriptor_type: vk::DescriptorType::INPUT_ATTACHMENT, 451 | descriptor_count: 1, 452 | stage_flags: vk::ShaderStageFlags::FRAGMENT, 453 | p_immutable_samplers: ptr::null(), 454 | }, 455 | ]), 456 | None, 457 | ) 458 | .unwrap(); 459 | 460 | let p_name = b"main\0".as_ptr() as *const i8; 461 | 462 | let mut pipelines = device 463 | .create_compute_pipelines( 464 | cache, 465 | &[ 466 | vk::ComputePipelineCreateInfo { 467 | stage: vk::PipelineShaderStageCreateInfo { 468 | stage: vk::ShaderStageFlags::COMPUTE, 469 | module: transmittance_shader, 470 | p_name, 471 | ..Default::default() 472 | }, 473 | layout: transmittance_layout, 474 | ..Default::default() 475 | }, 476 | vk::ComputePipelineCreateInfo { 477 | stage: vk::PipelineShaderStageCreateInfo { 478 | stage: vk::ShaderStageFlags::COMPUTE, 479 | module: direct_irradiance_shader, 480 | p_name, 481 | ..Default::default() 482 | }, 483 | layout: direct_irradiance_layout, 484 | ..Default::default() 485 | }, 486 | vk::ComputePipelineCreateInfo { 487 | stage: vk::PipelineShaderStageCreateInfo { 488 | stage: vk::ShaderStageFlags::COMPUTE, 489 | module: indirect_irradiance_shader, 490 | p_name, 491 | ..Default::default() 492 | }, 493 | layout: indirect_irradiance_layout, 494 | ..Default::default() 495 | }, 496 | vk::ComputePipelineCreateInfo { 497 | stage: vk::PipelineShaderStageCreateInfo { 498 | stage: vk::ShaderStageFlags::COMPUTE, 499 | module: scattering_shader, 500 | p_name, 501 | ..Default::default() 502 | }, 503 | layout: scattering_layout, 504 | ..Default::default() 505 | }, 506 | vk::ComputePipelineCreateInfo { 507 | stage: vk::PipelineShaderStageCreateInfo { 508 | stage: vk::ShaderStageFlags::COMPUTE, 509 | module: scattering_density_shader, 510 | p_name, 511 | ..Default::default() 512 | }, 513 | layout: scattering_density_layout, 514 | ..Default::default() 515 | }, 516 | vk::ComputePipelineCreateInfo { 517 | stage: vk::PipelineShaderStageCreateInfo { 518 | stage: vk::ShaderStageFlags::COMPUTE, 519 | module: multiple_scattering_shader, 520 | p_name, 521 | ..Default::default() 522 | }, 523 | layout: multiple_scattering_layout, 524 | ..Default::default() 525 | }, 526 | ], 527 | None, 528 | ) 529 | .unwrap() 530 | .into_iter(); 531 | 532 | let transmittance = Pass { 533 | shader: transmittance_shader, 534 | pipeline: pipelines.next().unwrap(), 535 | layout: transmittance_layout, 536 | ds_layout: transmittance_ds_layout, 537 | }; 538 | let direct_irradiance = Pass { 539 | shader: direct_irradiance_shader, 540 | pipeline: pipelines.next().unwrap(), 541 | layout: direct_irradiance_layout, 542 | ds_layout: direct_irradiance_ds_layout, 543 | }; 544 | let indirect_irradiance = Pass { 545 | shader: indirect_irradiance_shader, 546 | pipeline: pipelines.next().unwrap(), 547 | layout: indirect_irradiance_layout, 548 | ds_layout: indirect_irradiance_ds_layout, 549 | }; 550 | let single_scattering = Pass { 551 | shader: scattering_shader, 552 | pipeline: pipelines.next().unwrap(), 553 | layout: scattering_layout, 554 | ds_layout: scattering_ds_layout, 555 | }; 556 | let scattering_density = Pass { 557 | shader: scattering_density_shader, 558 | pipeline: pipelines.next().unwrap(), 559 | layout: scattering_density_layout, 560 | ds_layout: scattering_density_ds_layout, 561 | }; 562 | let multiple_scattering = Pass { 563 | shader: multiple_scattering_shader, 564 | pipeline: pipelines.next().unwrap(), 565 | layout: multiple_scattering_layout, 566 | ds_layout: multiple_scattering_ds_layout, 567 | }; 568 | debug_assert!(pipelines.next().is_none()); 569 | 570 | Self { 571 | device, 572 | memory_props: instance.get_physical_device_memory_properties(physical), 573 | gfx_queue_family, 574 | compute_queue_family, 575 | sampler, 576 | params_ds_layout, 577 | render_ds_layout, 578 | frame_ds_layout, 579 | transmittance, 580 | direct_irradiance, 581 | indirect_irradiance, 582 | single_scattering, 583 | scattering_density, 584 | multiple_scattering, 585 | } 586 | } 587 | } 588 | 589 | unsafe fn alloc_image(&self, info: &vk::ImageCreateInfo) -> Image { 590 | let handle = self.device.create_image(info, None).unwrap(); 591 | let reqs = self.device.get_image_memory_requirements(handle); 592 | let memory = allocate( 593 | &self.device, 594 | &self.memory_props, 595 | reqs, 596 | vk::MemoryPropertyFlags::DEVICE_LOCAL, 597 | ) 598 | .unwrap(); 599 | self.device.bind_image_memory(handle, memory, 0).unwrap(); 600 | let view = self 601 | .device 602 | .create_image_view( 603 | &vk::ImageViewCreateInfo { 604 | image: handle, 605 | view_type: match info.image_type { 606 | vk::ImageType::TYPE_1D => vk::ImageViewType::TYPE_1D, 607 | vk::ImageType::TYPE_2D => vk::ImageViewType::TYPE_2D, 608 | vk::ImageType::TYPE_3D => vk::ImageViewType::TYPE_3D, 609 | _ => unreachable!("unknown image type"), 610 | }, 611 | format: info.format, 612 | components: vk::ComponentMapping { 613 | r: vk::ComponentSwizzle::IDENTITY, 614 | g: vk::ComponentSwizzle::IDENTITY, 615 | b: vk::ComponentSwizzle::IDENTITY, 616 | a: vk::ComponentSwizzle::IDENTITY, 617 | }, 618 | subresource_range: vk::ImageSubresourceRange { 619 | aspect_mask: vk::ImageAspectFlags::COLOR, 620 | base_mip_level: 0, 621 | level_count: 1, 622 | base_array_layer: 0, 623 | layer_count: 1, 624 | }, 625 | ..Default::default() 626 | }, 627 | None, 628 | ) 629 | .unwrap(); 630 | Image { 631 | handle, 632 | view, 633 | memory, 634 | } 635 | } 636 | 637 | pub(crate) fn device(&self) -> &Arc { 638 | &self.device 639 | } 640 | 641 | pub(crate) fn render_ds_layout(&self) -> vk::DescriptorSetLayout { 642 | self.render_ds_layout 643 | } 644 | pub(crate) fn frame_ds_layout(&self) -> vk::DescriptorSetLayout { 645 | self.frame_ds_layout 646 | } 647 | } 648 | 649 | struct Image { 650 | handle: vk::Image, 651 | view: vk::ImageView, 652 | memory: vk::DeviceMemory, 653 | } 654 | 655 | /// A single layer of a `DensityProfile` 656 | /// 657 | /// An atmosphere layer of width 'width', and whose density is defined as 658 | /// 'exp_term' * exp('exp_scale' * h) + 'linear_term' * h + 'constant_term', 659 | /// clamped to [0,1], and where h is the altitude. 660 | pub struct DensityProfileLayer { 661 | pub width: f32, 662 | pub exp_term: f32, 663 | pub exp_scale: f32, 664 | pub linear_term: f32, 665 | pub constant_term: f32, 666 | } 667 | 668 | /// A collection of `DensityProfileLayer`s 669 | /// 670 | /// An atmosphere density profile made of several layers on top of each other 671 | /// (from bottom to top). The width of the last layer is ignored, i.e. it always 672 | /// extend to the top atmosphere boundary. The profile values vary between 0 673 | /// (null density) to 1 (maximum density). 674 | pub struct DensityProfile { 675 | pub layers: [DensityProfileLayer; 2], 676 | } 677 | 678 | /// Parameters governing generated skies 679 | /// 680 | /// Distances in km. 681 | /// 682 | /// # LUT dimensions 683 | /// 684 | /// All values are encoded in various ways to achieve a useful distribution of precision. 685 | /// 686 | /// - μ (mu): view angle from vertical 687 | /// - μ_s (mu_s): sun angle from vertical 688 | /// - r: distance from planet origin 689 | /// - ν (nu): view angle from sun 690 | pub struct Parameters { 691 | /// Extra usage flags for the generated look-up tables 692 | pub usage: vk::ImageUsageFlags, 693 | /// Stage mask for synchronizing precompute 694 | pub dst_stage_mask: vk::PipelineStageFlags, 695 | /// Access mask for synchronizing precompute 696 | pub dst_access_mask: vk::AccessFlags, 697 | /// Layout the look-up tables should end in 698 | pub layout: vk::ImageLayout, 699 | 700 | /// Number of light bounces to simulate 701 | pub order: u32, 702 | 703 | /// View angle precision for the transmittance look-up table 704 | pub transmittance_mu_size: u32, 705 | /// Height precision for the transmittance look-up table 706 | pub transmittance_r_size: u32, 707 | /// Height precision for the scattering look-up table 708 | pub scattering_r_size: u32, 709 | /// View angle precision for the scattering look-up table 710 | pub scattering_mu_size: u32, 711 | /// Sun angle precision for the scattering look-up table 712 | pub scattering_mu_s_size: u32, 713 | /// Sun azimuth precision for the scattering look-up table 714 | pub scattering_nu_size: u32, 715 | /// Sun angle precision for the lighting look-up table 716 | pub irradiance_mu_s_size: u32, 717 | /// Height precision for the lighting look-up table 718 | pub irradiance_r_size: u32, 719 | 720 | /// The solar irradiance at the top of the atmosphere. 721 | pub solar_irradiance: [f32; 3], 722 | /// The sun's angular radius. Warning: the implementation uses approximations 723 | /// that are valid only if this angle is smaller than 0.1 radians. 724 | pub sun_angular_radius: f32, 725 | /// The distance between the planet center and the bottom of the atmosphere. 726 | pub bottom_radius: f32, 727 | /// The distance between the planet center and the top of the atmosphere. 728 | pub top_radius: f32, 729 | /// The density profile of air molecules, i.e. a function from altitude to 730 | /// dimensionless values between 0 (null density) and 1 (maximum density). 731 | pub rayleigh_density: DensityProfile, 732 | /// The scattering coefficient of air molecules at the altitude where their 733 | /// density is maximum (usually the bottom of the atmosphere), as a function of 734 | /// wavelength. The scattering coefficient at altitude h is equal to 735 | /// 'rayleigh_scattering' times 'rayleigh_density' at this altitude. 736 | pub rayleigh_scattering: [f32; 3], 737 | /// The density profile of aerosols, i.e. a function from altitude to 738 | /// dimensionless values between 0 (null density) and 1 (maximum density). 739 | pub mie_density: DensityProfile, 740 | /// The scattering coefficient of aerosols at the altitude where their density 741 | /// is maximum (usually the bottom of the atmosphere), as a function of 742 | /// wavelength. The scattering coefficient at altitude h is equal to 743 | /// 'mie_scattering' times 'mie_density' at this altitude. 744 | pub mie_scattering: [f32; 3], 745 | /// The extinction coefficient of aerosols at the altitude where their density 746 | /// is maximum (usually the bottom of the atmosphere), as a function of 747 | /// wavelength. The extinction coefficient at altitude h is equal to 748 | /// 'mie_extinction' times 'mie_density' at this altitude. 749 | pub mie_extinction: [f32; 3], 750 | /// The asymetry parameter for the Cornette-Shanks phase function for the 751 | /// aerosols. 752 | pub mie_phase_function_g: f32, 753 | /// The density profile of air molecules that absorb light (e.g. ozone), i.e. 754 | /// a function from altitude to dimensionless values between 0 (null density) 755 | /// and 1 (maximum density). 756 | pub absorbtion_density: DensityProfile, 757 | /// The extinction coefficient of molecules that absorb light (e.g. ozone) at 758 | /// the altitude where their density is maximum, as a function of wavelength. 759 | /// The extinction coefficient at altitude h is equal to 760 | /// 'absorption_extinction' times 'absorption_density' at this altitude. 761 | pub absorbtion_extinction: [f32; 3], 762 | /// The average albedo of the ground. 763 | pub ground_albedo: [f32; 3], 764 | /// The cosine of the maximum Sun zenith angle for which atmospheric scattering 765 | /// must be precomputed (for maximum precision, use the smallest Sun zenith 766 | /// angle yielding negligible sky light radiance values. For instance, for the 767 | /// Earth case, 102 degrees is a good choice - yielding mu_s_min = -0.2). 768 | pub mu_s_min: f32, 769 | } 770 | 771 | impl Parameters { 772 | pub fn transmittance_extent(&self) -> vk::Extent2D { 773 | vk::Extent2D { 774 | width: self.transmittance_mu_size, 775 | height: self.transmittance_r_size, 776 | } 777 | } 778 | 779 | pub fn irradiance_extent(&self) -> vk::Extent2D { 780 | vk::Extent2D { 781 | width: self.irradiance_mu_s_size, 782 | height: self.irradiance_r_size, 783 | } 784 | } 785 | 786 | pub fn scattering_extent(&self) -> vk::Extent3D { 787 | vk::Extent3D { 788 | width: self.scattering_nu_size * self.scattering_mu_s_size, 789 | height: self.scattering_mu_size, 790 | depth: self.scattering_r_size, 791 | } 792 | } 793 | } 794 | 795 | // Taken from Bruneton's paper 796 | // /// Wavelength of red light 797 | // pub const LAMBDA_R: f32 = 680e-9; 798 | // /// Wavelength of green light 799 | // pub const LAMBDA_G: f32 = 550e-9; 800 | // /// Wavelength of blue light 801 | // pub const LAMBDA_B: f32 = 440e-9; 802 | 803 | // /// Average index of refraction Earth's atmosphere, used to compute `Params::default().beta_r` 804 | // pub const IOR_AIR: f32 = 1.0003; 805 | 806 | // /// Number density of Earth's atmosphere at sea level (molecules/m^3) 807 | // pub const DENSITY_AIR: f32 = 2.545e25; 808 | 809 | // /// Extinction coefficients for ozone on Earth 810 | // pub const OZONE_ABSORBTION_COEFFICIENT: [f32; 3] = [0.000650, 0.001881, 0.000085]; 811 | 812 | // /// Compute the Rayleigh scattering factor at a certain wavelength 813 | // /// 814 | // /// `ior` - index of refraction 815 | // /// `molecular_density` - number of Rayleigh particles (i.e. molecules) per cubic m at sea level 816 | // /// `wavelength` - wavelength to compute β_R for 817 | // pub fn beta_rayleigh(ior: f32, molecular_density: f32, wavelength: f32) -> f32 { 818 | // 8.0 * std::f32::consts::PI.powi(3) * (ior.powi(2) - 1.0).powi(2) 819 | // / (3.0 * molecular_density * wavelength.powi(4)) 820 | // } 821 | 822 | // /// Compute the wavelength-independent Mie scattering factor 823 | // /// 824 | // /// `ior` - index of refraction of the aerosol particle 825 | // /// `molecular_density` - number of Mie particles (i.e. aerosols) per cubic meter at sea level 826 | // /// `wavelength` - wavelength to compute β_R for 827 | // pub fn beta_mie(ior: f32, particle_density: f32) -> f32 { 828 | // 8.0 * std::f32::consts::PI.powi(3) * (ior.powi(2) - 1.0).powi(2) / (3.0 * particle_density) 829 | // } 830 | 831 | // impl Default for Params { 832 | // fn default() -> Self { 833 | // // from Bruneton 834 | // let beta_m = 2.2e-5; 835 | // let beta_e_m = beta_m / 0.9; 836 | // Self { 837 | // h_atm: 80_000.0, 838 | // r_planet: 6371e3, 839 | // h_r: 8_000.0, 840 | // h_m: 1_200.0, 841 | // beta_r: [r, g, b], 842 | // beta_m, 843 | // beta_e_o: OZONE_EXTINCTION_COEFFICIENT, 844 | // beta_e_m, 845 | // } 846 | // } 847 | // } 848 | 849 | impl Default for Parameters { 850 | fn default() -> Self { 851 | Self { 852 | usage: vk::ImageUsageFlags::default(), 853 | dst_stage_mask: vk::PipelineStageFlags::FRAGMENT_SHADER, 854 | dst_access_mask: vk::AccessFlags::SHADER_READ, 855 | layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 856 | 857 | order: 4, 858 | 859 | transmittance_mu_size: 256, 860 | transmittance_r_size: 64, 861 | scattering_r_size: 32, 862 | scattering_mu_size: 128, 863 | scattering_mu_s_size: 32, 864 | scattering_nu_size: 8, 865 | irradiance_mu_s_size: 64, 866 | irradiance_r_size: 16, 867 | 868 | solar_irradiance: [1.474, 1.850, 1.91198], 869 | sun_angular_radius: 0.004675, 870 | bottom_radius: 6360.0, 871 | top_radius: 6420.0, 872 | rayleigh_density: DensityProfile { 873 | layers: [ 874 | DensityProfileLayer { 875 | width: 0.0, 876 | exp_term: 0.0, 877 | exp_scale: 0.0, 878 | linear_term: 0.0, 879 | constant_term: 0.0, 880 | }, 881 | DensityProfileLayer { 882 | width: 0.0, 883 | exp_term: 1.0, 884 | exp_scale: -0.125, 885 | linear_term: 0.0, 886 | constant_term: 0.0, 887 | }, 888 | ], 889 | }, 890 | rayleigh_scattering: [0.005802, 0.013558, 0.033100], 891 | mie_density: DensityProfile { 892 | layers: [ 893 | DensityProfileLayer { 894 | width: 0.0, 895 | exp_term: 0.0, 896 | exp_scale: 0.0, 897 | linear_term: 0.0, 898 | constant_term: 0.0, 899 | }, 900 | DensityProfileLayer { 901 | width: 0.0, 902 | exp_term: 1.0, 903 | exp_scale: -0.833333, 904 | linear_term: 0.0, 905 | constant_term: 0.0, 906 | }, 907 | ], 908 | }, 909 | mie_scattering: [0.003996, 0.003996, 0.003996], 910 | mie_extinction: [0.004440, 0.004440, 0.004440], 911 | mie_phase_function_g: 0.8, 912 | absorbtion_density: DensityProfile { 913 | layers: [ 914 | DensityProfileLayer { 915 | width: 25.0, 916 | exp_term: 0.0, 917 | exp_scale: 0.0, 918 | linear_term: 0.066667, 919 | constant_term: -0.666667, 920 | }, 921 | DensityProfileLayer { 922 | width: 0.0, 923 | exp_term: 0.0, 924 | exp_scale: 0.0, 925 | linear_term: -0.066667, 926 | constant_term: 2.666667, 927 | }, 928 | ], 929 | }, 930 | absorbtion_extinction: [6.5e-4, 1.881e-3, 8.5e-5], 931 | ground_albedo: [0.1, 0.1, 0.1], 932 | mu_s_min: -0.207912, 933 | } 934 | } 935 | } 936 | 937 | #[repr(C)] 938 | #[derive(Copy, Clone)] 939 | struct ParamsRaw { 940 | solar_irradiance: [f32; 3], 941 | sun_angular_radius: f32, 942 | rayleigh_scattering: [f32; 3], 943 | bottom_radius: f32, 944 | mie_scattering: [f32; 3], 945 | top_radius: f32, 946 | mie_extinction: [f32; 3], 947 | mie_phase_function_g: f32, 948 | ground_albedo: [f32; 3], 949 | mu_s_min: f32, 950 | absorbtion_extinction: [f32; 3], 951 | 952 | transmittance_mu_size: u32, 953 | transmittance_r_size: u32, 954 | scattering_r_size: u32, 955 | scattering_mu_size: u32, 956 | scattering_mu_s_size: u32, 957 | scattering_nu_size: u32, 958 | irradiance_mu_s_size: u32, 959 | irradiance_r_size: u32, 960 | 961 | rayleigh_density: DensityProfileRaw, 962 | mie_density: DensityProfileRaw, 963 | absorbtion_density: DensityProfileRaw, 964 | } 965 | 966 | impl ParamsRaw { 967 | fn new(x: &Parameters) -> Self { 968 | Self { 969 | solar_irradiance: x.solar_irradiance, 970 | sun_angular_radius: x.sun_angular_radius, 971 | rayleigh_scattering: x.rayleigh_scattering, 972 | bottom_radius: x.bottom_radius, 973 | mie_scattering: x.mie_scattering, 974 | top_radius: x.top_radius, 975 | mie_extinction: x.mie_extinction, 976 | mie_phase_function_g: x.mie_phase_function_g, 977 | ground_albedo: x.ground_albedo, 978 | mu_s_min: x.mu_s_min, 979 | absorbtion_extinction: x.absorbtion_extinction, 980 | transmittance_mu_size: x.transmittance_mu_size, 981 | transmittance_r_size: x.transmittance_r_size, 982 | scattering_r_size: x.scattering_r_size, 983 | scattering_mu_size: x.scattering_mu_size, 984 | scattering_mu_s_size: x.scattering_mu_s_size, 985 | scattering_nu_size: x.scattering_nu_size, 986 | irradiance_mu_s_size: x.irradiance_mu_s_size, 987 | irradiance_r_size: x.irradiance_r_size, 988 | rayleigh_density: DensityProfileRaw::new(&x.rayleigh_density), 989 | mie_density: DensityProfileRaw::new(&x.mie_density), 990 | absorbtion_density: DensityProfileRaw::new(&x.absorbtion_density), 991 | } 992 | } 993 | } 994 | 995 | #[repr(C)] 996 | #[derive(Copy, Clone)] 997 | struct DensityProfileRaw { 998 | layers: [DensityProfileLayerRaw; 2], 999 | } 1000 | 1001 | impl DensityProfileRaw { 1002 | fn new(x: &DensityProfile) -> Self { 1003 | Self { 1004 | layers: [ 1005 | DensityProfileLayerRaw::new(&x.layers[0]), 1006 | DensityProfileLayerRaw::new(&x.layers[1]), 1007 | ], 1008 | } 1009 | } 1010 | } 1011 | 1012 | #[repr(C)] 1013 | #[repr(align(16))] 1014 | #[derive(Copy, Clone)] 1015 | struct DensityProfileLayerRaw { 1016 | width: f32, 1017 | exp_term: f32, 1018 | exp_scale: f32, 1019 | linear_term: f32, 1020 | constant_term: f32, 1021 | } 1022 | 1023 | impl DensityProfileLayerRaw { 1024 | fn new(x: &DensityProfileLayer) -> Self { 1025 | Self { 1026 | width: x.width, 1027 | exp_term: x.exp_term, 1028 | exp_scale: x.exp_scale, 1029 | linear_term: x.linear_term, 1030 | constant_term: x.constant_term, 1031 | } 1032 | } 1033 | } 1034 | 1035 | struct Pass { 1036 | shader: vk::ShaderModule, 1037 | pipeline: vk::Pipeline, 1038 | layout: vk::PipelineLayout, 1039 | ds_layout: vk::DescriptorSetLayout, 1040 | } 1041 | 1042 | /// An atmosphere that's ready for rendering 1043 | /// 1044 | /// As with any Vulkan object, this must not be dropped while in use by a rendering operation. 1045 | pub struct Atmosphere { 1046 | builder: Arc, 1047 | descriptor_pool: vk::DescriptorPool, 1048 | ds: vk::DescriptorSet, 1049 | transmittance: Image, 1050 | transmittance_extent: vk::Extent2D, 1051 | scattering: Image, 1052 | scattering_extent: vk::Extent3D, 1053 | irradiance: Image, 1054 | irradiance_extent: vk::Extent2D, 1055 | params: vk::Buffer, 1056 | params_mem: vk::DeviceMemory, 1057 | } 1058 | 1059 | impl Drop for Atmosphere { 1060 | fn drop(&mut self) { 1061 | let device = &*self.builder.device; 1062 | unsafe { 1063 | for &image in &[&self.transmittance, &self.scattering, &self.irradiance] { 1064 | device.destroy_image_view(image.view, None); 1065 | device.destroy_image(image.handle, None); 1066 | device.free_memory(image.memory, None); 1067 | } 1068 | device.destroy_buffer(self.params, None); 1069 | device.free_memory(self.params_mem, None); 1070 | device.destroy_descriptor_pool(self.descriptor_pool, None); 1071 | } 1072 | } 1073 | } 1074 | 1075 | impl Atmosphere { 1076 | /// Build an `Atmosphere` that will be usable when `cmd` is fully executed. 1077 | pub fn build( 1078 | builder: Arc, 1079 | cmd: vk::CommandBuffer, 1080 | atmosphere_params: &Parameters, 1081 | ) -> PendingAtmosphere { 1082 | let device = &*builder.device; 1083 | unsafe { 1084 | // common: 1 uniform 1085 | // transmittance: 1 storage image 1086 | // direct irradiance: 1 image-sampler, 1 storage image 1087 | // indirect irradiance: 3 image-samplers, 2 storage images 1088 | // single scattering: 1 image-sampler, 3 storage images 1089 | // scattering density: 5 image-samplers, 1 storage image 1090 | // multiple scattering: 2 image-samplers, 2 storage images 1091 | let descriptor_pool = device 1092 | .create_descriptor_pool( 1093 | &vk::DescriptorPoolCreateInfo::builder() 1094 | .max_sets(7) 1095 | .pool_sizes(&[ 1096 | vk::DescriptorPoolSize { 1097 | ty: vk::DescriptorType::UNIFORM_BUFFER, 1098 | descriptor_count: 1, 1099 | }, 1100 | vk::DescriptorPoolSize { 1101 | ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1102 | descriptor_count: 12, 1103 | }, 1104 | vk::DescriptorPoolSize { 1105 | ty: vk::DescriptorType::STORAGE_IMAGE, 1106 | descriptor_count: 10, 1107 | }, 1108 | ]), 1109 | None, 1110 | ) 1111 | .unwrap(); 1112 | 1113 | let mut descriptor_sets = device 1114 | .allocate_descriptor_sets( 1115 | &vk::DescriptorSetAllocateInfo::builder() 1116 | .descriptor_pool(descriptor_pool) 1117 | .set_layouts(&[ 1118 | builder.params_ds_layout, 1119 | builder.transmittance.ds_layout, 1120 | builder.direct_irradiance.ds_layout, 1121 | builder.indirect_irradiance.ds_layout, 1122 | builder.single_scattering.ds_layout, 1123 | builder.scattering_density.ds_layout, 1124 | builder.multiple_scattering.ds_layout, 1125 | ]), 1126 | ) 1127 | .unwrap() 1128 | .into_iter(); 1129 | let params_ds = descriptor_sets.next().unwrap(); 1130 | let transmittance_ds = descriptor_sets.next().unwrap(); 1131 | let direct_irradiance_ds = descriptor_sets.next().unwrap(); 1132 | let indirect_irradiance_ds = descriptor_sets.next().unwrap(); 1133 | let single_scattering_ds = descriptor_sets.next().unwrap(); 1134 | let scattering_density_ds = descriptor_sets.next().unwrap(); 1135 | let multiple_scattering_ds = descriptor_sets.next().unwrap(); 1136 | debug_assert!(descriptor_sets.next().is_none()); 1137 | 1138 | let persistent_pool = device 1139 | .create_descriptor_pool( 1140 | &vk::DescriptorPoolCreateInfo::builder() 1141 | .max_sets(1) 1142 | .pool_sizes(&[ 1143 | vk::DescriptorPoolSize { 1144 | ty: vk::DescriptorType::UNIFORM_BUFFER, 1145 | descriptor_count: 1, 1146 | }, 1147 | vk::DescriptorPoolSize { 1148 | ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1149 | descriptor_count: 2, 1150 | }, 1151 | ]), 1152 | None, 1153 | ) 1154 | .unwrap(); 1155 | 1156 | let render_ds = device 1157 | .allocate_descriptor_sets( 1158 | &vk::DescriptorSetAllocateInfo::builder() 1159 | .descriptor_pool(persistent_pool) 1160 | .set_layouts(&[builder.render_ds_layout]), 1161 | ) 1162 | .unwrap() 1163 | .into_iter() 1164 | .next() 1165 | .unwrap(); 1166 | 1167 | let transmittance_extent = atmosphere_params.transmittance_extent(); 1168 | let transmittance = builder.alloc_image(&vk::ImageCreateInfo { 1169 | image_type: vk::ImageType::TYPE_2D, 1170 | format: vk::Format::R32G32B32A32_SFLOAT, 1171 | extent: vk::Extent3D { 1172 | width: transmittance_extent.width, 1173 | height: transmittance_extent.height, 1174 | depth: 1, 1175 | }, 1176 | mip_levels: 1, 1177 | array_layers: 1, 1178 | samples: vk::SampleCountFlags::TYPE_1, 1179 | tiling: vk::ImageTiling::OPTIMAL, 1180 | usage: vk::ImageUsageFlags::STORAGE 1181 | | vk::ImageUsageFlags::SAMPLED 1182 | | atmosphere_params.usage, 1183 | sharing_mode: vk::SharingMode::EXCLUSIVE, 1184 | initial_layout: vk::ImageLayout::UNDEFINED, 1185 | ..Default::default() 1186 | }); 1187 | 1188 | let irradiance_extent = atmosphere_params.irradiance_extent(); 1189 | let irradiance_image_info = vk::ImageCreateInfo { 1190 | image_type: vk::ImageType::TYPE_2D, 1191 | format: vk::Format::R32G32B32A32_SFLOAT, 1192 | extent: vk::Extent3D { 1193 | width: irradiance_extent.width, 1194 | height: irradiance_extent.height, 1195 | depth: 1, 1196 | }, 1197 | mip_levels: 1, 1198 | array_layers: 1, 1199 | samples: vk::SampleCountFlags::TYPE_1, 1200 | tiling: vk::ImageTiling::OPTIMAL, 1201 | usage: vk::ImageUsageFlags::STORAGE 1202 | | vk::ImageUsageFlags::SAMPLED 1203 | | vk::ImageUsageFlags::TRANSFER_DST 1204 | | atmosphere_params.usage, 1205 | sharing_mode: vk::SharingMode::EXCLUSIVE, 1206 | initial_layout: vk::ImageLayout::UNDEFINED, 1207 | ..Default::default() 1208 | }; 1209 | let delta_irradiance = builder.alloc_image(&irradiance_image_info); 1210 | let irradiance = builder.alloc_image(&irradiance_image_info); 1211 | 1212 | let scattering_extent = atmosphere_params.scattering_extent(); 1213 | let scattering_image_info = vk::ImageCreateInfo { 1214 | image_type: vk::ImageType::TYPE_3D, 1215 | format: vk::Format::R16G16B16A16_SFLOAT, 1216 | extent: scattering_extent, 1217 | mip_levels: 1, 1218 | array_layers: 1, 1219 | samples: vk::SampleCountFlags::TYPE_1, 1220 | tiling: vk::ImageTiling::OPTIMAL, 1221 | usage: vk::ImageUsageFlags::STORAGE 1222 | | vk::ImageUsageFlags::SAMPLED 1223 | | atmosphere_params.usage, 1224 | sharing_mode: vk::SharingMode::EXCLUSIVE, 1225 | initial_layout: vk::ImageLayout::UNDEFINED, 1226 | ..Default::default() 1227 | }; 1228 | // TODO: These could be merged 1229 | let delta_rayleigh = builder.alloc_image(&scattering_image_info); 1230 | let delta_mie = builder.alloc_image(&scattering_image_info); 1231 | let scattering = builder.alloc_image(&scattering_image_info); 1232 | // TODO: This could overlap with delta_rayleigh/mie, since they are not used simultaneously 1233 | let delta_multiple_scattering = builder.alloc_image(&scattering_image_info); 1234 | let scattering_density = builder.alloc_image(&scattering_image_info); 1235 | 1236 | let params = device 1237 | .create_buffer( 1238 | &vk::BufferCreateInfo { 1239 | size: mem::size_of::() as vk::DeviceSize, 1240 | usage: vk::BufferUsageFlags::UNIFORM_BUFFER 1241 | | vk::BufferUsageFlags::TRANSFER_DST, 1242 | ..Default::default() 1243 | }, 1244 | None, 1245 | ) 1246 | .unwrap(); 1247 | let params_mem = { 1248 | let reqs = device.get_buffer_memory_requirements(params); 1249 | allocate( 1250 | device, 1251 | &builder.memory_props, 1252 | reqs, 1253 | vk::MemoryPropertyFlags::DEVICE_LOCAL, 1254 | ) 1255 | .unwrap() 1256 | }; 1257 | device.bind_buffer_memory(params, params_mem, 0).unwrap(); 1258 | 1259 | device.update_descriptor_sets( 1260 | &[ 1261 | vk::WriteDescriptorSet { 1262 | dst_set: params_ds, 1263 | dst_binding: 0, 1264 | dst_array_element: 0, 1265 | descriptor_count: 1, 1266 | descriptor_type: vk::DescriptorType::UNIFORM_BUFFER, 1267 | p_buffer_info: &vk::DescriptorBufferInfo { 1268 | buffer: params, 1269 | offset: 0, 1270 | range: vk::WHOLE_SIZE, 1271 | }, 1272 | ..Default::default() 1273 | }, 1274 | vk::WriteDescriptorSet { 1275 | dst_set: transmittance_ds, 1276 | dst_binding: 0, 1277 | dst_array_element: 0, 1278 | descriptor_count: 1, 1279 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1280 | p_image_info: &vk::DescriptorImageInfo { 1281 | sampler: vk::Sampler::null(), 1282 | image_view: transmittance.view, 1283 | image_layout: vk::ImageLayout::GENERAL, 1284 | }, 1285 | ..Default::default() 1286 | }, 1287 | vk::WriteDescriptorSet { 1288 | dst_set: direct_irradiance_ds, 1289 | dst_binding: 0, 1290 | dst_array_element: 0, 1291 | descriptor_count: 1, 1292 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1293 | p_image_info: &vk::DescriptorImageInfo { 1294 | sampler: vk::Sampler::null(), 1295 | image_view: transmittance.view, 1296 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1297 | }, 1298 | ..Default::default() 1299 | }, 1300 | vk::WriteDescriptorSet { 1301 | dst_set: direct_irradiance_ds, 1302 | dst_binding: 1, 1303 | dst_array_element: 0, 1304 | descriptor_count: 1, 1305 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1306 | p_image_info: &vk::DescriptorImageInfo { 1307 | sampler: vk::Sampler::null(), 1308 | image_view: delta_irradiance.view, 1309 | image_layout: vk::ImageLayout::GENERAL, 1310 | }, 1311 | ..Default::default() 1312 | }, 1313 | vk::WriteDescriptorSet { 1314 | dst_set: single_scattering_ds, 1315 | dst_binding: 0, 1316 | dst_array_element: 0, 1317 | descriptor_count: 1, 1318 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1319 | p_image_info: &vk::DescriptorImageInfo { 1320 | sampler: vk::Sampler::null(), 1321 | image_view: transmittance.view, 1322 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1323 | }, 1324 | ..Default::default() 1325 | }, 1326 | vk::WriteDescriptorSet { 1327 | dst_set: single_scattering_ds, 1328 | dst_binding: 1, 1329 | dst_array_element: 0, 1330 | descriptor_count: 1, 1331 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1332 | p_image_info: &vk::DescriptorImageInfo { 1333 | sampler: vk::Sampler::null(), 1334 | image_view: delta_rayleigh.view, 1335 | image_layout: vk::ImageLayout::GENERAL, 1336 | }, 1337 | ..Default::default() 1338 | }, 1339 | vk::WriteDescriptorSet { 1340 | dst_set: single_scattering_ds, 1341 | dst_binding: 2, 1342 | dst_array_element: 0, 1343 | descriptor_count: 1, 1344 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1345 | p_image_info: &vk::DescriptorImageInfo { 1346 | sampler: vk::Sampler::null(), 1347 | image_view: delta_mie.view, 1348 | image_layout: vk::ImageLayout::GENERAL, 1349 | }, 1350 | ..Default::default() 1351 | }, 1352 | vk::WriteDescriptorSet { 1353 | dst_set: single_scattering_ds, 1354 | dst_binding: 3, 1355 | dst_array_element: 0, 1356 | descriptor_count: 1, 1357 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1358 | p_image_info: &vk::DescriptorImageInfo { 1359 | sampler: vk::Sampler::null(), 1360 | image_view: scattering.view, 1361 | image_layout: vk::ImageLayout::GENERAL, 1362 | }, 1363 | ..Default::default() 1364 | }, 1365 | vk::WriteDescriptorSet { 1366 | dst_set: scattering_density_ds, 1367 | dst_binding: 0, 1368 | dst_array_element: 0, 1369 | descriptor_count: 1, 1370 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1371 | p_image_info: &vk::DescriptorImageInfo { 1372 | sampler: vk::Sampler::null(), 1373 | image_view: transmittance.view, 1374 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1375 | }, 1376 | ..Default::default() 1377 | }, 1378 | vk::WriteDescriptorSet { 1379 | dst_set: scattering_density_ds, 1380 | dst_binding: 1, 1381 | dst_array_element: 0, 1382 | descriptor_count: 1, 1383 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1384 | p_image_info: &vk::DescriptorImageInfo { 1385 | sampler: vk::Sampler::null(), 1386 | image_view: delta_rayleigh.view, 1387 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1388 | }, 1389 | ..Default::default() 1390 | }, 1391 | vk::WriteDescriptorSet { 1392 | dst_set: scattering_density_ds, 1393 | dst_binding: 2, 1394 | dst_array_element: 0, 1395 | descriptor_count: 1, 1396 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1397 | p_image_info: &vk::DescriptorImageInfo { 1398 | sampler: vk::Sampler::null(), 1399 | image_view: delta_mie.view, 1400 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1401 | }, 1402 | ..Default::default() 1403 | }, 1404 | vk::WriteDescriptorSet { 1405 | dst_set: scattering_density_ds, 1406 | dst_binding: 3, 1407 | dst_array_element: 0, 1408 | descriptor_count: 1, 1409 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1410 | p_image_info: &vk::DescriptorImageInfo { 1411 | sampler: vk::Sampler::null(), 1412 | image_view: delta_multiple_scattering.view, 1413 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1414 | }, 1415 | ..Default::default() 1416 | }, 1417 | vk::WriteDescriptorSet { 1418 | dst_set: scattering_density_ds, 1419 | dst_binding: 4, 1420 | dst_array_element: 0, 1421 | descriptor_count: 1, 1422 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1423 | p_image_info: &vk::DescriptorImageInfo { 1424 | sampler: vk::Sampler::null(), 1425 | image_view: delta_irradiance.view, 1426 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1427 | }, 1428 | ..Default::default() 1429 | }, 1430 | vk::WriteDescriptorSet { 1431 | dst_set: scattering_density_ds, 1432 | dst_binding: 5, 1433 | dst_array_element: 0, 1434 | descriptor_count: 1, 1435 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1436 | p_image_info: &vk::DescriptorImageInfo { 1437 | sampler: vk::Sampler::null(), 1438 | image_view: scattering_density.view, 1439 | image_layout: vk::ImageLayout::GENERAL, 1440 | }, 1441 | ..Default::default() 1442 | }, 1443 | vk::WriteDescriptorSet { 1444 | dst_set: indirect_irradiance_ds, 1445 | dst_binding: 0, 1446 | dst_array_element: 0, 1447 | descriptor_count: 1, 1448 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1449 | p_image_info: &vk::DescriptorImageInfo { 1450 | sampler: vk::Sampler::null(), 1451 | image_view: delta_rayleigh.view, 1452 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1453 | }, 1454 | ..Default::default() 1455 | }, 1456 | vk::WriteDescriptorSet { 1457 | dst_set: indirect_irradiance_ds, 1458 | dst_binding: 1, 1459 | dst_array_element: 0, 1460 | descriptor_count: 1, 1461 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1462 | p_image_info: &vk::DescriptorImageInfo { 1463 | sampler: vk::Sampler::null(), 1464 | image_view: delta_mie.view, 1465 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1466 | }, 1467 | ..Default::default() 1468 | }, 1469 | vk::WriteDescriptorSet { 1470 | dst_set: indirect_irradiance_ds, 1471 | dst_binding: 2, 1472 | dst_array_element: 0, 1473 | descriptor_count: 1, 1474 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1475 | p_image_info: &vk::DescriptorImageInfo { 1476 | sampler: vk::Sampler::null(), 1477 | image_view: delta_multiple_scattering.view, 1478 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1479 | }, 1480 | ..Default::default() 1481 | }, 1482 | vk::WriteDescriptorSet { 1483 | dst_set: indirect_irradiance_ds, 1484 | dst_binding: 3, 1485 | dst_array_element: 0, 1486 | descriptor_count: 1, 1487 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1488 | p_image_info: &vk::DescriptorImageInfo { 1489 | sampler: vk::Sampler::null(), 1490 | image_view: delta_irradiance.view, 1491 | image_layout: vk::ImageLayout::GENERAL, 1492 | }, 1493 | ..Default::default() 1494 | }, 1495 | vk::WriteDescriptorSet { 1496 | dst_set: indirect_irradiance_ds, 1497 | dst_binding: 4, 1498 | dst_array_element: 0, 1499 | descriptor_count: 1, 1500 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1501 | p_image_info: &vk::DescriptorImageInfo { 1502 | sampler: vk::Sampler::null(), 1503 | image_view: irradiance.view, 1504 | image_layout: vk::ImageLayout::GENERAL, 1505 | }, 1506 | ..Default::default() 1507 | }, 1508 | vk::WriteDescriptorSet { 1509 | dst_set: multiple_scattering_ds, 1510 | dst_binding: 0, 1511 | dst_array_element: 0, 1512 | descriptor_count: 1, 1513 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1514 | p_image_info: &vk::DescriptorImageInfo { 1515 | sampler: vk::Sampler::null(), 1516 | image_view: transmittance.view, 1517 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1518 | }, 1519 | ..Default::default() 1520 | }, 1521 | vk::WriteDescriptorSet { 1522 | dst_set: multiple_scattering_ds, 1523 | dst_binding: 1, 1524 | dst_array_element: 0, 1525 | descriptor_count: 1, 1526 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1527 | p_image_info: &vk::DescriptorImageInfo { 1528 | sampler: vk::Sampler::null(), 1529 | image_view: scattering_density.view, 1530 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1531 | }, 1532 | ..Default::default() 1533 | }, 1534 | vk::WriteDescriptorSet { 1535 | dst_set: multiple_scattering_ds, 1536 | dst_binding: 2, 1537 | dst_array_element: 0, 1538 | descriptor_count: 1, 1539 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1540 | p_image_info: &vk::DescriptorImageInfo { 1541 | sampler: vk::Sampler::null(), 1542 | image_view: delta_multiple_scattering.view, 1543 | image_layout: vk::ImageLayout::GENERAL, 1544 | }, 1545 | ..Default::default() 1546 | }, 1547 | vk::WriteDescriptorSet { 1548 | dst_set: multiple_scattering_ds, 1549 | dst_binding: 3, 1550 | dst_array_element: 0, 1551 | descriptor_count: 1, 1552 | descriptor_type: vk::DescriptorType::STORAGE_IMAGE, 1553 | p_image_info: &vk::DescriptorImageInfo { 1554 | sampler: vk::Sampler::null(), 1555 | image_view: scattering.view, 1556 | image_layout: vk::ImageLayout::GENERAL, 1557 | }, 1558 | ..Default::default() 1559 | }, 1560 | vk::WriteDescriptorSet { 1561 | dst_set: render_ds, 1562 | dst_binding: 0, 1563 | dst_array_element: 0, 1564 | descriptor_count: 1, 1565 | descriptor_type: vk::DescriptorType::UNIFORM_BUFFER, 1566 | p_buffer_info: &vk::DescriptorBufferInfo { 1567 | buffer: params, 1568 | offset: 0, 1569 | range: vk::WHOLE_SIZE, 1570 | }, 1571 | ..Default::default() 1572 | }, 1573 | vk::WriteDescriptorSet { 1574 | dst_set: render_ds, 1575 | dst_binding: 1, 1576 | dst_array_element: 0, 1577 | descriptor_count: 1, 1578 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1579 | p_image_info: &vk::DescriptorImageInfo { 1580 | sampler: vk::Sampler::null(), 1581 | image_view: transmittance.view, 1582 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1583 | }, 1584 | ..Default::default() 1585 | }, 1586 | vk::WriteDescriptorSet { 1587 | dst_set: render_ds, 1588 | dst_binding: 2, 1589 | dst_array_element: 0, 1590 | descriptor_count: 1, 1591 | descriptor_type: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1592 | p_image_info: &vk::DescriptorImageInfo { 1593 | sampler: vk::Sampler::null(), 1594 | image_view: scattering.view, 1595 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1596 | }, 1597 | ..Default::default() 1598 | }, 1599 | ], 1600 | &[], 1601 | ); 1602 | 1603 | let init_barrier = vk::ImageMemoryBarrier { 1604 | dst_access_mask: vk::AccessFlags::SHADER_WRITE, 1605 | old_layout: vk::ImageLayout::UNDEFINED, 1606 | new_layout: vk::ImageLayout::GENERAL, 1607 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1608 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1609 | subresource_range: vk::ImageSubresourceRange { 1610 | aspect_mask: vk::ImageAspectFlags::COLOR, 1611 | base_mip_level: 0, 1612 | level_count: 1, 1613 | base_array_layer: 0, 1614 | layer_count: 1, 1615 | }, 1616 | ..Default::default() 1617 | }; 1618 | let write_read_barrier = vk::ImageMemoryBarrier { 1619 | src_access_mask: vk::AccessFlags::SHADER_WRITE, 1620 | dst_access_mask: vk::AccessFlags::SHADER_READ, 1621 | old_layout: vk::ImageLayout::GENERAL, 1622 | new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1623 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1624 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1625 | subresource_range: vk::ImageSubresourceRange { 1626 | aspect_mask: vk::ImageAspectFlags::COLOR, 1627 | base_mip_level: 0, 1628 | level_count: 1, 1629 | base_array_layer: 0, 1630 | layer_count: 1, 1631 | }, 1632 | ..Default::default() 1633 | }; 1634 | let read_write_barrier = vk::ImageMemoryBarrier { 1635 | src_access_mask: vk::AccessFlags::SHADER_READ, 1636 | dst_access_mask: vk::AccessFlags::SHADER_WRITE, 1637 | old_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 1638 | new_layout: vk::ImageLayout::GENERAL, 1639 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1640 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1641 | subresource_range: vk::ImageSubresourceRange { 1642 | aspect_mask: vk::ImageAspectFlags::COLOR, 1643 | base_mip_level: 0, 1644 | level_count: 1, 1645 | base_array_layer: 0, 1646 | layer_count: 1, 1647 | }, 1648 | ..Default::default() 1649 | }; 1650 | let write_barrier = vk::ImageMemoryBarrier { 1651 | src_access_mask: vk::AccessFlags::SHADER_WRITE, 1652 | dst_access_mask: vk::AccessFlags::SHADER_READ | vk::AccessFlags::SHADER_WRITE, 1653 | old_layout: vk::ImageLayout::GENERAL, 1654 | new_layout: vk::ImageLayout::GENERAL, 1655 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1656 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1657 | subresource_range: vk::ImageSubresourceRange { 1658 | aspect_mask: vk::ImageAspectFlags::COLOR, 1659 | base_mip_level: 0, 1660 | level_count: 1, 1661 | base_array_layer: 0, 1662 | layer_count: 1, 1663 | }, 1664 | ..Default::default() 1665 | }; 1666 | 1667 | // 1668 | // Write commands 1669 | // 1670 | 1671 | device.cmd_update_buffer( 1672 | cmd, 1673 | params, 1674 | 0, 1675 | &mem::transmute::<_, [u8; 320]>(ParamsRaw::new(atmosphere_params)), 1676 | ); 1677 | device.cmd_pipeline_barrier( 1678 | cmd, 1679 | vk::PipelineStageFlags::TRANSFER, 1680 | vk::PipelineStageFlags::COMPUTE_SHADER, 1681 | Default::default(), 1682 | &[], 1683 | &[vk::BufferMemoryBarrier { 1684 | src_access_mask: vk::AccessFlags::TRANSFER_WRITE, 1685 | dst_access_mask: vk::AccessFlags::UNIFORM_READ, 1686 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1687 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 1688 | buffer: params, 1689 | offset: 0, 1690 | size: vk::WHOLE_SIZE, 1691 | ..Default::default() 1692 | }], 1693 | &[ 1694 | vk::ImageMemoryBarrier { 1695 | image: transmittance.handle, 1696 | ..init_barrier 1697 | }, 1698 | vk::ImageMemoryBarrier { 1699 | image: delta_rayleigh.handle, 1700 | ..init_barrier 1701 | }, 1702 | vk::ImageMemoryBarrier { 1703 | image: delta_mie.handle, 1704 | ..init_barrier 1705 | }, 1706 | vk::ImageMemoryBarrier { 1707 | image: scattering.handle, 1708 | ..init_barrier 1709 | }, 1710 | vk::ImageMemoryBarrier { 1711 | image: irradiance.handle, 1712 | new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, 1713 | ..init_barrier 1714 | }, 1715 | vk::ImageMemoryBarrier { 1716 | image: delta_irradiance.handle, 1717 | ..init_barrier 1718 | }, 1719 | vk::ImageMemoryBarrier { 1720 | image: delta_multiple_scattering.handle, 1721 | ..init_barrier 1722 | }, 1723 | ], 1724 | ); 1725 | 1726 | // Transmittance 1727 | device.cmd_bind_pipeline( 1728 | cmd, 1729 | vk::PipelineBindPoint::COMPUTE, 1730 | builder.transmittance.pipeline, 1731 | ); 1732 | device.cmd_bind_descriptor_sets( 1733 | cmd, 1734 | vk::PipelineBindPoint::COMPUTE, 1735 | builder.transmittance.layout, 1736 | 0, 1737 | &[params_ds, transmittance_ds], 1738 | &[], 1739 | ); 1740 | device.cmd_dispatch( 1741 | cmd, 1742 | transmittance_extent.width / WORKGROUP_SIZE_2D, 1743 | transmittance_extent.height / WORKGROUP_SIZE_2D, 1744 | 1, 1745 | ); 1746 | 1747 | device.cmd_pipeline_barrier( 1748 | cmd, 1749 | vk::PipelineStageFlags::COMPUTE_SHADER, 1750 | vk::PipelineStageFlags::COMPUTE_SHADER, 1751 | Default::default(), 1752 | &[], 1753 | &[], 1754 | &[vk::ImageMemoryBarrier { 1755 | image: transmittance.handle, 1756 | ..write_read_barrier 1757 | }], 1758 | ); 1759 | 1760 | // Direct irradiance 1761 | device.cmd_bind_pipeline( 1762 | cmd, 1763 | vk::PipelineBindPoint::COMPUTE, 1764 | builder.direct_irradiance.pipeline, 1765 | ); 1766 | device.cmd_bind_descriptor_sets( 1767 | cmd, 1768 | vk::PipelineBindPoint::COMPUTE, 1769 | builder.direct_irradiance.layout, 1770 | 1, 1771 | &[direct_irradiance_ds], 1772 | &[], 1773 | ); 1774 | device.cmd_dispatch( 1775 | cmd, 1776 | irradiance_extent.width / WORKGROUP_SIZE_2D, 1777 | irradiance_extent.height / WORKGROUP_SIZE_2D, 1778 | 1, 1779 | ); 1780 | 1781 | // Single scattering 1782 | device.cmd_bind_pipeline( 1783 | cmd, 1784 | vk::PipelineBindPoint::COMPUTE, 1785 | builder.single_scattering.pipeline, 1786 | ); 1787 | device.cmd_bind_descriptor_sets( 1788 | cmd, 1789 | vk::PipelineBindPoint::COMPUTE, 1790 | builder.single_scattering.layout, 1791 | 1, 1792 | &[single_scattering_ds], 1793 | &[], 1794 | ); 1795 | device.cmd_dispatch( 1796 | cmd, 1797 | scattering_extent.width / WORKGROUP_SIZE_3D, 1798 | scattering_extent.height / WORKGROUP_SIZE_3D, 1799 | scattering_extent.depth / WORKGROUP_SIZE_3D, 1800 | ); 1801 | 1802 | device.cmd_clear_color_image( 1803 | cmd, 1804 | irradiance.handle, 1805 | vk::ImageLayout::TRANSFER_DST_OPTIMAL, 1806 | &vk::ClearColorValue { 1807 | float32: [0.0, 0.0, 0.0, 0.0], 1808 | }, 1809 | &[vk::ImageSubresourceRange { 1810 | aspect_mask: vk::ImageAspectFlags::COLOR, 1811 | base_mip_level: 0, 1812 | level_count: 1, 1813 | base_array_layer: 0, 1814 | layer_count: 1, 1815 | }], 1816 | ); 1817 | 1818 | device.cmd_pipeline_barrier( 1819 | cmd, 1820 | vk::PipelineStageFlags::TRANSFER, 1821 | vk::PipelineStageFlags::COMPUTE_SHADER, 1822 | Default::default(), 1823 | &[], 1824 | &[], 1825 | &[vk::ImageMemoryBarrier { 1826 | image: irradiance.handle, 1827 | src_access_mask: vk::AccessFlags::TRANSFER_WRITE, 1828 | old_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, 1829 | ..write_barrier 1830 | }], 1831 | ); 1832 | 1833 | device.cmd_pipeline_barrier( 1834 | cmd, 1835 | vk::PipelineStageFlags::COMPUTE_SHADER, 1836 | vk::PipelineStageFlags::COMPUTE_SHADER, 1837 | Default::default(), 1838 | &[], 1839 | &[], 1840 | &[ 1841 | vk::ImageMemoryBarrier { 1842 | image: delta_rayleigh.handle, 1843 | ..write_read_barrier 1844 | }, 1845 | vk::ImageMemoryBarrier { 1846 | image: delta_mie.handle, 1847 | ..write_read_barrier 1848 | }, 1849 | ], 1850 | ); 1851 | 1852 | // Compute higher-order effects 1853 | for order in 2..=atmosphere_params.order { 1854 | device.cmd_pipeline_barrier( 1855 | cmd, 1856 | vk::PipelineStageFlags::COMPUTE_SHADER, 1857 | vk::PipelineStageFlags::COMPUTE_SHADER, 1858 | Default::default(), 1859 | &[], 1860 | &[], 1861 | &[ 1862 | vk::ImageMemoryBarrier { 1863 | image: scattering_density.handle, 1864 | src_access_mask: vk::AccessFlags::SHADER_READ, 1865 | ..init_barrier 1866 | }, 1867 | vk::ImageMemoryBarrier { 1868 | image: delta_irradiance.handle, 1869 | ..write_read_barrier 1870 | }, 1871 | vk::ImageMemoryBarrier { 1872 | image: delta_multiple_scattering.handle, 1873 | ..write_read_barrier 1874 | }, 1875 | ], 1876 | ); 1877 | 1878 | // Scattering density 1879 | device.cmd_bind_pipeline( 1880 | cmd, 1881 | vk::PipelineBindPoint::COMPUTE, 1882 | builder.scattering_density.pipeline, 1883 | ); 1884 | device.cmd_bind_descriptor_sets( 1885 | cmd, 1886 | vk::PipelineBindPoint::COMPUTE, 1887 | builder.scattering_density.layout, 1888 | 0, 1889 | &[params_ds, scattering_density_ds], 1890 | &[], 1891 | ); 1892 | device.cmd_push_constants( 1893 | cmd, 1894 | builder.scattering_density.layout, 1895 | vk::ShaderStageFlags::COMPUTE, 1896 | 0, 1897 | &order.to_ne_bytes(), 1898 | ); 1899 | device.cmd_dispatch( 1900 | cmd, 1901 | scattering_extent.width / WORKGROUP_SIZE_3D, 1902 | scattering_extent.height / WORKGROUP_SIZE_3D, 1903 | scattering_extent.depth / WORKGROUP_SIZE_3D, 1904 | ); 1905 | 1906 | device.cmd_pipeline_barrier( 1907 | cmd, 1908 | vk::PipelineStageFlags::COMPUTE_SHADER, 1909 | vk::PipelineStageFlags::COMPUTE_SHADER, 1910 | Default::default(), 1911 | &[], 1912 | &[], 1913 | &[ 1914 | // Scattering density reads this 1915 | vk::ImageMemoryBarrier { 1916 | image: delta_irradiance.handle, 1917 | ..read_write_barrier 1918 | }, 1919 | // Previous irradiance pass output must be written 1920 | vk::ImageMemoryBarrier { 1921 | image: irradiance.handle, 1922 | ..write_barrier 1923 | }, 1924 | ], 1925 | ); 1926 | 1927 | // Indirect irradiance 1928 | device.cmd_bind_pipeline( 1929 | cmd, 1930 | vk::PipelineBindPoint::COMPUTE, 1931 | builder.indirect_irradiance.pipeline, 1932 | ); 1933 | device.cmd_bind_descriptor_sets( 1934 | cmd, 1935 | vk::PipelineBindPoint::COMPUTE, 1936 | builder.indirect_irradiance.layout, 1937 | 0, 1938 | &[params_ds, indirect_irradiance_ds], 1939 | &[], 1940 | ); 1941 | device.cmd_push_constants( 1942 | cmd, 1943 | builder.indirect_irradiance.layout, 1944 | vk::ShaderStageFlags::COMPUTE, 1945 | 0, 1946 | &(order - 1).to_ne_bytes(), 1947 | ); 1948 | device.cmd_dispatch( 1949 | cmd, 1950 | irradiance_extent.width / WORKGROUP_SIZE_2D, 1951 | irradiance_extent.height / WORKGROUP_SIZE_2D, 1952 | 1, 1953 | ); 1954 | 1955 | device.cmd_pipeline_barrier( 1956 | cmd, 1957 | vk::PipelineStageFlags::COMPUTE_SHADER, 1958 | vk::PipelineStageFlags::COMPUTE_SHADER, 1959 | Default::default(), 1960 | &[], 1961 | &[], 1962 | &[ 1963 | vk::ImageMemoryBarrier { 1964 | image: scattering_density.handle, 1965 | ..write_read_barrier 1966 | }, 1967 | vk::ImageMemoryBarrier { 1968 | image: scattering.handle, 1969 | ..write_barrier 1970 | }, 1971 | vk::ImageMemoryBarrier { 1972 | image: delta_multiple_scattering.handle, 1973 | src_access_mask: vk::AccessFlags::SHADER_READ, 1974 | ..init_barrier 1975 | }, 1976 | ], 1977 | ); 1978 | 1979 | // Multiscattering 1980 | device.cmd_bind_pipeline( 1981 | cmd, 1982 | vk::PipelineBindPoint::COMPUTE, 1983 | builder.multiple_scattering.pipeline, 1984 | ); 1985 | device.cmd_bind_descriptor_sets( 1986 | cmd, 1987 | vk::PipelineBindPoint::COMPUTE, 1988 | builder.multiple_scattering.layout, 1989 | 0, 1990 | &[params_ds, multiple_scattering_ds], 1991 | &[], 1992 | ); 1993 | device.cmd_dispatch( 1994 | cmd, 1995 | scattering_extent.width / WORKGROUP_SIZE_3D, 1996 | scattering_extent.height / WORKGROUP_SIZE_3D, 1997 | scattering_extent.depth / WORKGROUP_SIZE_3D, 1998 | ); 1999 | } 2000 | 2001 | // Finalize layouts and transfer to graphics queue 2002 | let src_queue_family_index = builder 2003 | .compute_queue_family 2004 | .unwrap_or(builder.gfx_queue_family); 2005 | device.cmd_pipeline_barrier( 2006 | cmd, 2007 | vk::PipelineStageFlags::COMPUTE_SHADER, 2008 | atmosphere_params.dst_stage_mask, 2009 | Default::default(), 2010 | &[], 2011 | &[vk::BufferMemoryBarrier { 2012 | src_access_mask: vk::AccessFlags::UNIFORM_READ, 2013 | src_queue_family_index, 2014 | dst_queue_family_index: builder.gfx_queue_family, 2015 | buffer: params, 2016 | offset: 0, 2017 | size: vk::WHOLE_SIZE, 2018 | ..Default::default() 2019 | }], 2020 | &[ 2021 | vk::ImageMemoryBarrier { 2022 | image: scattering.handle, 2023 | dst_access_mask: atmosphere_params.dst_access_mask, 2024 | new_layout: atmosphere_params.layout, 2025 | src_queue_family_index, 2026 | dst_queue_family_index: builder.gfx_queue_family, 2027 | ..write_read_barrier 2028 | }, 2029 | vk::ImageMemoryBarrier { 2030 | image: irradiance.handle, 2031 | dst_access_mask: atmosphere_params.dst_access_mask, 2032 | new_layout: atmosphere_params.layout, 2033 | src_queue_family_index, 2034 | dst_queue_family_index: builder.gfx_queue_family, 2035 | ..write_read_barrier 2036 | }, 2037 | vk::ImageMemoryBarrier { 2038 | image: transmittance.handle, 2039 | src_access_mask: vk::AccessFlags::default(), 2040 | dst_access_mask: atmosphere_params.dst_access_mask, 2041 | old_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 2042 | new_layout: atmosphere_params.layout, 2043 | src_queue_family_index, 2044 | dst_queue_family_index: builder.gfx_queue_family, 2045 | ..write_read_barrier 2046 | }, 2047 | ], 2048 | ); 2049 | 2050 | PendingAtmosphere { 2051 | device: builder.device.clone(), 2052 | descriptor_pool, 2053 | inner: Some(Self { 2054 | builder, 2055 | descriptor_pool: persistent_pool, 2056 | ds: render_ds, 2057 | transmittance, 2058 | transmittance_extent, 2059 | scattering, 2060 | scattering_extent, 2061 | irradiance, 2062 | irradiance_extent, 2063 | params, 2064 | params_mem, 2065 | }), 2066 | delta_irradiance, 2067 | delta_mie, 2068 | delta_rayleigh, 2069 | scattering_density, 2070 | delta_multiple_scattering, 2071 | } 2072 | } 2073 | } 2074 | 2075 | pub fn transmittance(&self) -> vk::Image { 2076 | self.transmittance.handle 2077 | } 2078 | pub fn transmittance_view(&self) -> vk::ImageView { 2079 | self.transmittance.view 2080 | } 2081 | pub fn transmittance_extent(&self) -> vk::Extent2D { 2082 | self.transmittance_extent 2083 | } 2084 | pub fn scattering(&self) -> vk::Image { 2085 | self.scattering.handle 2086 | } 2087 | pub fn scattering_view(&self) -> vk::ImageView { 2088 | self.scattering.view 2089 | } 2090 | pub fn scattering_extent(&self) -> vk::Extent3D { 2091 | self.scattering_extent 2092 | } 2093 | pub fn irradiance(&self) -> vk::Image { 2094 | self.irradiance.handle 2095 | } 2096 | pub fn irradiance_view(&self) -> vk::ImageView { 2097 | self.irradiance.view 2098 | } 2099 | pub fn irradiance_extent(&self) -> vk::Extent2D { 2100 | self.irradiance_extent 2101 | } 2102 | 2103 | pub(crate) fn descriptor_set(&self) -> vk::DescriptorSet { 2104 | self.ds 2105 | } 2106 | } 2107 | 2108 | /// An atmosphere being prepared by the GPU 2109 | /// 2110 | /// Must not be dropped before the `vk::CommandBuffer` passed to `Builder::build` has completed execution 2111 | pub struct PendingAtmosphere { 2112 | device: Arc, 2113 | descriptor_pool: vk::DescriptorPool, 2114 | inner: Option, 2115 | delta_irradiance: Image, 2116 | delta_rayleigh: Image, 2117 | delta_mie: Image, 2118 | scattering_density: Image, 2119 | delta_multiple_scattering: Image, 2120 | } 2121 | 2122 | impl Drop for PendingAtmosphere { 2123 | fn drop(&mut self) { 2124 | unsafe { 2125 | for &image in &[ 2126 | &self.delta_irradiance, 2127 | &self.delta_rayleigh, 2128 | &self.delta_mie, 2129 | &self.scattering_density, 2130 | &self.delta_multiple_scattering, 2131 | ] { 2132 | self.device.destroy_image_view(image.view, None); 2133 | self.device.destroy_image(image.handle, None); 2134 | self.device.free_memory(image.memory, None); 2135 | } 2136 | self.device 2137 | .destroy_descriptor_pool(self.descriptor_pool, None); 2138 | } 2139 | } 2140 | } 2141 | 2142 | impl PendingAtmosphere { 2143 | /// Call if precompute completed on a different queue family than that of the gfx queue that 2144 | /// will be used for drawing 2145 | /// 2146 | /// `cmd` is the command buffer that this `Atmosphere` will be drawn with. 2147 | pub unsafe fn acquire_ownership( 2148 | &self, 2149 | cmd: vk::CommandBuffer, 2150 | compute_queue_family: u32, 2151 | gfx_queue_family: u32, 2152 | ) { 2153 | debug_assert!(compute_queue_family != gfx_queue_family); 2154 | let inner = self.inner.as_ref().unwrap(); 2155 | let barrier = vk::ImageMemoryBarrier { 2156 | dst_access_mask: vk::AccessFlags::SHADER_READ, 2157 | old_layout: vk::ImageLayout::GENERAL, 2158 | new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 2159 | src_queue_family_index: compute_queue_family, 2160 | dst_queue_family_index: gfx_queue_family, 2161 | subresource_range: vk::ImageSubresourceRange { 2162 | aspect_mask: vk::ImageAspectFlags::COLOR, 2163 | base_mip_level: 0, 2164 | level_count: 1, 2165 | base_array_layer: 0, 2166 | layer_count: 1, 2167 | }, 2168 | ..Default::default() 2169 | }; 2170 | self.device.cmd_pipeline_barrier( 2171 | cmd, 2172 | Default::default(), 2173 | vk::PipelineStageFlags::FRAGMENT_SHADER, 2174 | Default::default(), 2175 | &[], 2176 | &[vk::BufferMemoryBarrier { 2177 | dst_access_mask: vk::AccessFlags::UNIFORM_READ, 2178 | src_queue_family_index: compute_queue_family, 2179 | dst_queue_family_index: gfx_queue_family, 2180 | buffer: inner.params, 2181 | offset: 0, 2182 | size: vk::WHOLE_SIZE, 2183 | ..Default::default() 2184 | }], 2185 | &[ 2186 | vk::ImageMemoryBarrier { 2187 | image: inner.transmittance.handle, 2188 | old_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 2189 | ..barrier 2190 | }, 2191 | vk::ImageMemoryBarrier { 2192 | image: inner.scattering.handle, 2193 | ..barrier 2194 | }, 2195 | vk::ImageMemoryBarrier { 2196 | image: inner.irradiance.handle, 2197 | ..barrier 2198 | }, 2199 | ], 2200 | ); 2201 | } 2202 | 2203 | /// Access the `Atmosphere` while it may not yet be ready 2204 | pub unsafe fn atmosphere(&self) -> &Atmosphere { 2205 | self.inner.as_ref().unwrap() 2206 | } 2207 | 2208 | /// Call when the `vk::CommandBuffer` passed to `Builder::build` has completed execution 2209 | pub unsafe fn assert_ready(mut self) -> Atmosphere { 2210 | self.inner.take().unwrap() 2211 | } 2212 | } 2213 | 2214 | fn find_memory_type( 2215 | device_props: &vk::PhysicalDeviceMemoryProperties, 2216 | type_bits: u32, 2217 | flags: vk::MemoryPropertyFlags, 2218 | ) -> Option { 2219 | for i in 0..device_props.memory_type_count { 2220 | if type_bits & (1 << i) != 0 2221 | && device_props.memory_types[i as usize] 2222 | .property_flags 2223 | .contains(flags) 2224 | { 2225 | return Some(i); 2226 | } 2227 | } 2228 | None 2229 | } 2230 | 2231 | unsafe fn allocate( 2232 | device: &Device, 2233 | device_props: &vk::PhysicalDeviceMemoryProperties, 2234 | reqs: vk::MemoryRequirements, 2235 | flags: vk::MemoryPropertyFlags, 2236 | ) -> Option { 2237 | let ty = find_memory_type(device_props, reqs.memory_type_bits, flags)?; 2238 | Some( 2239 | device 2240 | .allocate_memory( 2241 | &vk::MemoryAllocateInfo { 2242 | allocation_size: reqs.size, 2243 | memory_type_index: ty, 2244 | ..Default::default() 2245 | }, 2246 | None, 2247 | ) 2248 | .unwrap(), 2249 | ) 2250 | } 2251 | 2252 | const WORKGROUP_SIZE_2D: u32 = 8; 2253 | const WORKGROUP_SIZE_3D: u32 = 4; 2254 | -------------------------------------------------------------------------------- /src/render.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, sync::Arc}; 2 | 3 | use ash::version::DeviceV1_0; 4 | use ash::{vk, Device}; 5 | use vk_shader_macros::include_glsl; 6 | 7 | use crate::{Atmosphere, Builder}; 8 | 9 | const FULLSCREEN: &[u32] = include_glsl!("shaders/fullscreen.vert"); 10 | const RENDER_SKY: &[u32] = include_glsl!("shaders/render_sky.frag"); 11 | 12 | // TODO: Rasterize icospheres rather than raytracing 13 | pub struct Renderer { 14 | device: Arc, 15 | pipeline_layout: vk::PipelineLayout, 16 | pipeline: vk::Pipeline, 17 | frame_pool: vk::DescriptorPool, 18 | frames: Vec, 19 | } 20 | 21 | impl Drop for Renderer { 22 | fn drop(&mut self) { 23 | unsafe { 24 | self.device.destroy_descriptor_pool(self.frame_pool, None); 25 | self.device.destroy_pipeline(self.pipeline, None); 26 | self.device 27 | .destroy_pipeline_layout(self.pipeline_layout, None); 28 | } 29 | } 30 | } 31 | 32 | impl Renderer { 33 | /// Construct an atmosphere renderer 34 | pub fn new( 35 | builder: &Builder, 36 | cache: vk::PipelineCache, 37 | render_pass: vk::RenderPass, 38 | subpass: u32, 39 | frames: u32, 40 | ) -> Self { 41 | let device = builder.device().clone(); 42 | unsafe { 43 | let vert = device 44 | .create_shader_module( 45 | &vk::ShaderModuleCreateInfo::builder().code(&FULLSCREEN), 46 | None, 47 | ) 48 | .unwrap(); 49 | 50 | let frag = device 51 | .create_shader_module( 52 | &vk::ShaderModuleCreateInfo::builder().code(&RENDER_SKY), 53 | None, 54 | ) 55 | .unwrap(); 56 | 57 | let pipeline_layout = device 58 | .create_pipeline_layout( 59 | &vk::PipelineLayoutCreateInfo::builder() 60 | .set_layouts(&[builder.render_ds_layout(), builder.frame_ds_layout()]) 61 | .push_constant_ranges(&[vk::PushConstantRange { 62 | stage_flags: vk::ShaderStageFlags::FRAGMENT, 63 | offset: 0, 64 | size: mem::size_of::() as u32, 65 | }]), 66 | None, 67 | ) 68 | .unwrap(); 69 | 70 | let entry_point = b"main\0".as_ptr() as *const i8; 71 | let noop_stencil_state = vk::StencilOpState { 72 | fail_op: vk::StencilOp::KEEP, 73 | pass_op: vk::StencilOp::KEEP, 74 | depth_fail_op: vk::StencilOp::KEEP, 75 | compare_op: vk::CompareOp::ALWAYS, 76 | compare_mask: 0, 77 | write_mask: 0, 78 | reference: 0, 79 | }; 80 | let mut pipelines = device 81 | .create_graphics_pipelines( 82 | cache, 83 | &[vk::GraphicsPipelineCreateInfo::builder() 84 | .stages(&[ 85 | vk::PipelineShaderStageCreateInfo { 86 | stage: vk::ShaderStageFlags::VERTEX, 87 | module: vert, 88 | p_name: entry_point, 89 | ..Default::default() 90 | }, 91 | vk::PipelineShaderStageCreateInfo { 92 | stage: vk::ShaderStageFlags::FRAGMENT, 93 | module: frag, 94 | p_name: entry_point, 95 | ..Default::default() 96 | }, 97 | ]) 98 | .vertex_input_state(&Default::default()) 99 | .input_assembly_state( 100 | &vk::PipelineInputAssemblyStateCreateInfo::builder() 101 | .topology(vk::PrimitiveTopology::TRIANGLE_LIST), 102 | ) 103 | .viewport_state( 104 | &vk::PipelineViewportStateCreateInfo::builder() 105 | .scissor_count(1) 106 | .viewport_count(1), 107 | ) 108 | .rasterization_state( 109 | &vk::PipelineRasterizationStateCreateInfo::builder() 110 | .cull_mode(vk::CullModeFlags::NONE) 111 | .polygon_mode(vk::PolygonMode::FILL) 112 | .line_width(1.0), 113 | ) 114 | .multisample_state( 115 | &vk::PipelineMultisampleStateCreateInfo::builder() 116 | .rasterization_samples(vk::SampleCountFlags::TYPE_1), 117 | ) 118 | .depth_stencil_state( 119 | &vk::PipelineDepthStencilStateCreateInfo::builder() 120 | .depth_test_enable(false) 121 | .front(noop_stencil_state) 122 | .back(noop_stencil_state), 123 | ) 124 | .color_blend_state( 125 | &vk::PipelineColorBlendStateCreateInfo::builder().attachments(&[ 126 | vk::PipelineColorBlendAttachmentState { 127 | blend_enable: vk::TRUE, 128 | src_color_blend_factor: vk::BlendFactor::ONE, 129 | dst_color_blend_factor: vk::BlendFactor::SRC1_COLOR, 130 | color_blend_op: vk::BlendOp::ADD, 131 | src_alpha_blend_factor: vk::BlendFactor::ZERO, 132 | dst_alpha_blend_factor: vk::BlendFactor::ONE, 133 | alpha_blend_op: vk::BlendOp::ADD, 134 | color_write_mask: vk::ColorComponentFlags::all(), 135 | }, 136 | ]), 137 | ) 138 | .dynamic_state( 139 | &vk::PipelineDynamicStateCreateInfo::builder().dynamic_states(&[ 140 | vk::DynamicState::VIEWPORT, 141 | vk::DynamicState::SCISSOR, 142 | ]), 143 | ) 144 | .layout(pipeline_layout) 145 | .render_pass(render_pass) 146 | .subpass(subpass) 147 | .build()], 148 | None, 149 | ) 150 | .unwrap() 151 | .into_iter(); 152 | 153 | device.destroy_shader_module(vert, None); 154 | device.destroy_shader_module(frag, None); 155 | 156 | let pipeline = pipelines.next().unwrap(); 157 | 158 | let frame_pool = device 159 | .create_descriptor_pool( 160 | &vk::DescriptorPoolCreateInfo::builder() 161 | .max_sets(frames) 162 | .pool_sizes(&[vk::DescriptorPoolSize { 163 | ty: vk::DescriptorType::INPUT_ATTACHMENT, 164 | descriptor_count: frames, 165 | }]), 166 | None, 167 | ) 168 | .unwrap(); 169 | let frames = device 170 | .allocate_descriptor_sets( 171 | &vk::DescriptorSetAllocateInfo::builder() 172 | .descriptor_pool(frame_pool) 173 | .set_layouts( 174 | &(0..frames) 175 | .map(|_| builder.frame_ds_layout()) 176 | .collect::>(), 177 | ), 178 | ) 179 | .unwrap() 180 | .into_iter() 181 | .map(|ds| Frame { ds }) 182 | .collect(); 183 | 184 | Self { 185 | device, 186 | pipeline_layout, 187 | pipeline, 188 | frame_pool, 189 | frames, 190 | } 191 | } 192 | } 193 | 194 | pub unsafe fn set_depth_buffer(&mut self, frame: u32, image: &vk::DescriptorImageInfo) { 195 | self.device.update_descriptor_sets( 196 | &[vk::WriteDescriptorSet { 197 | dst_set: self.frames[frame as usize].ds, 198 | dst_binding: 0, 199 | dst_array_element: 0, 200 | descriptor_count: 1, 201 | descriptor_type: vk::DescriptorType::INPUT_ATTACHMENT, 202 | p_image_info: image, 203 | ..Default::default() 204 | }], 205 | &[], 206 | ); 207 | } 208 | 209 | pub fn draw( 210 | &self, 211 | cmd: vk::CommandBuffer, 212 | atmosphere: &Atmosphere, 213 | frame: u32, 214 | params: &DrawParameters, 215 | ) { 216 | unsafe { 217 | self.device 218 | .cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline); 219 | self.device.cmd_bind_descriptor_sets( 220 | cmd, 221 | vk::PipelineBindPoint::GRAPHICS, 222 | self.pipeline_layout, 223 | 0, 224 | &[atmosphere.descriptor_set(), self.frames[frame as usize].ds], 225 | &[], 226 | ); 227 | self.device.cmd_push_constants( 228 | cmd, 229 | self.pipeline_layout, 230 | vk::ShaderStageFlags::FRAGMENT, 231 | 0, 232 | &mem::transmute::<_, [u8; 92]>(DrawParamsRaw::new(params)), 233 | ); 234 | self.device.cmd_draw(cmd, 3, 1, 0, 0); 235 | } 236 | } 237 | } 238 | 239 | struct Frame { 240 | ds: vk::DescriptorSet, 241 | } 242 | 243 | /// Rendering parameters for an individual frame 244 | /// 245 | /// All coordinates are in the planet's reference frame. 246 | #[derive(Debug, Copy, Clone)] 247 | pub struct DrawParameters { 248 | /// (projection * view)^-1 249 | pub inverse_viewproj: [[f32; 4]; 4], 250 | pub camera_position: [f32; 3], 251 | pub sun_direction: [f32; 3], 252 | } 253 | 254 | #[repr(C)] 255 | struct DrawParamsRaw { 256 | inverse_viewproj: [[f32; 4]; 4], 257 | camera_position: [f32; 3], 258 | _padding: u32, 259 | sun_direction: [f32; 3], 260 | } 261 | 262 | impl DrawParamsRaw { 263 | fn new(x: &DrawParameters) -> Self { 264 | Self { 265 | inverse_viewproj: x.inverse_viewproj, 266 | camera_position: x.camera_position, 267 | _padding: 0, 268 | sun_direction: x.sun_direction, 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /tests/smoke.rs: -------------------------------------------------------------------------------- 1 | use std::cell::Cell; 2 | use std::ffi::CStr; 3 | use std::os::raw::{c_char, c_void}; 4 | use std::ptr; 5 | use std::sync::Arc; 6 | 7 | use ash::version::{DeviceV1_0, EntryV1_0, InstanceV1_0}; 8 | use ash::{extensions::ext::DebugReport, vk, Entry}; 9 | use renderdoc::{RenderDoc, V100}; 10 | 11 | #[test] 12 | #[ignore] 13 | fn smoke() { 14 | let had_error = Cell::new(false); 15 | let mut rd = RenderDoc::::new().ok(); 16 | 17 | unsafe { 18 | let entry = Entry::new().unwrap(); 19 | let app_name = CStr::from_bytes_with_nul(b"fuzzyblue smoke test\0").unwrap(); 20 | let instance = entry 21 | .create_instance( 22 | &vk::InstanceCreateInfo::builder() 23 | .application_info( 24 | &vk::ApplicationInfo::builder() 25 | .application_name(&app_name) 26 | .application_version(0) 27 | .engine_name(&app_name) 28 | .engine_version(0) 29 | .api_version(vk::make_version(1, 0, 36)), 30 | ) 31 | .enabled_extension_names(&[DebugReport::name().as_ptr()]), 32 | None, 33 | ) 34 | .unwrap(); 35 | 36 | let debug_report_loader = DebugReport::new(&entry, &instance); 37 | let debug_call_back = debug_report_loader 38 | .create_debug_report_callback( 39 | &vk::DebugReportCallbackCreateInfoEXT::builder() 40 | .flags( 41 | vk::DebugReportFlagsEXT::ERROR 42 | | vk::DebugReportFlagsEXT::WARNING 43 | | vk::DebugReportFlagsEXT::PERFORMANCE_WARNING 44 | | vk::DebugReportFlagsEXT::INFORMATION, 45 | ) 46 | .pfn_callback(Some(vulkan_debug_callback)) 47 | .user_data(&had_error as *const _ as *mut _), 48 | None, 49 | ) 50 | .unwrap(); 51 | 52 | let (pdevice, queue_family_index) = instance 53 | .enumerate_physical_devices() 54 | .unwrap() 55 | .iter() 56 | .map(|pdevice| { 57 | instance 58 | .get_physical_device_queue_family_properties(*pdevice) 59 | .iter() 60 | .enumerate() 61 | .filter_map(|(index, ref info)| { 62 | if info.queue_flags.contains(vk::QueueFlags::GRAPHICS) { 63 | Some((*pdevice, index as u32)) 64 | } else { 65 | None 66 | } 67 | }) 68 | .next() 69 | }) 70 | .filter_map(|v| v) 71 | .next() 72 | .expect("no graphics device available"); 73 | 74 | let device = Arc::new( 75 | instance 76 | .create_device( 77 | pdevice, 78 | &vk::DeviceCreateInfo::builder() 79 | .queue_create_infos(&[vk::DeviceQueueCreateInfo::builder() 80 | .queue_family_index(queue_family_index) 81 | .queue_priorities(&[1.0]) 82 | .build()]) 83 | .enabled_features(&vk::PhysicalDeviceFeatures { 84 | robust_buffer_access: vk::TRUE, 85 | ..Default::default() 86 | }), 87 | None, 88 | ) 89 | .unwrap(), 90 | ); 91 | let queue = device.get_device_queue(queue_family_index as u32, 0); 92 | 93 | let pool = device 94 | .create_command_pool( 95 | &vk::CommandPoolCreateInfo::builder() 96 | .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER) 97 | .queue_family_index(queue_family_index), 98 | None, 99 | ) 100 | .unwrap(); 101 | 102 | if let Some(ref mut rd) = rd { 103 | rd.start_frame_capture(renderdoc::DevicePointer::from(ptr::null()), ptr::null()); 104 | } 105 | 106 | let cmd = device 107 | .allocate_command_buffers( 108 | &vk::CommandBufferAllocateInfo::builder() 109 | .command_buffer_count(1) 110 | .command_pool(pool) 111 | .level(vk::CommandBufferLevel::PRIMARY), 112 | ) 113 | .unwrap()[0]; 114 | 115 | let builder = Arc::new(fuzzyblue::Builder::new( 116 | &instance, 117 | device.clone(), 118 | vk::PipelineCache::null(), 119 | pdevice, 120 | queue_family_index, 121 | None, 122 | )); 123 | 124 | device 125 | .begin_command_buffer( 126 | cmd, 127 | &vk::CommandBufferBeginInfo::builder() 128 | .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), 129 | ) 130 | .unwrap(); 131 | 132 | let pending = fuzzyblue::Atmosphere::build( 133 | builder, 134 | cmd, 135 | // Simplified for speed 136 | &fuzzyblue::Parameters { 137 | scattering_r_size: 8, 138 | scattering_mu_size: 32, 139 | scattering_mu_s_size: 8, 140 | scattering_nu_size: 2, 141 | ..Default::default() 142 | }, 143 | ); 144 | 145 | device.end_command_buffer(cmd).unwrap(); 146 | 147 | device 148 | .queue_submit( 149 | queue, 150 | &[vk::SubmitInfo::builder().command_buffers(&[cmd]).build()], 151 | vk::Fence::null(), 152 | ) 153 | .unwrap(); 154 | 155 | device.device_wait_idle().unwrap(); 156 | 157 | drop(pending); 158 | 159 | if let Some(ref mut rd) = rd { 160 | rd.end_frame_capture(renderdoc::DevicePointer::from(ptr::null()), ptr::null()); 161 | } 162 | 163 | device.destroy_command_pool(pool, None); 164 | device.destroy_device(None); 165 | debug_report_loader.destroy_debug_report_callback(debug_call_back, None); 166 | instance.destroy_instance(None); 167 | } 168 | 169 | if had_error.get() { 170 | panic!("vulkan reported an error"); 171 | } 172 | } 173 | 174 | unsafe extern "system" fn vulkan_debug_callback( 175 | flags: vk::DebugReportFlagsEXT, 176 | _: vk::DebugReportObjectTypeEXT, 177 | _: u64, 178 | _: usize, 179 | _: i32, 180 | _: *const c_char, 181 | p_message: *const c_char, 182 | user_data: *mut c_void, 183 | ) -> u32 { 184 | eprintln!( 185 | "{:?} {}", 186 | flags, 187 | CStr::from_ptr(p_message).to_string_lossy() 188 | ); 189 | if flags.contains(vk::DebugReportFlagsEXT::ERROR) { 190 | let had_error = &*(user_data as *const Cell); 191 | had_error.set(true); 192 | } 193 | vk::FALSE 194 | } 195 | --------------------------------------------------------------------------------