|
| 1 | +//! Physically based bloom. |
| 2 | +//! |
| 3 | +//! As described in [learnopengl.com's Physically Based Bloom article](https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom). |
| 4 | +use crabslab::{Id, Slab, SlabItem}; |
| 5 | +use glam::{UVec2, Vec2, Vec4, Vec4Swizzles}; |
| 6 | +use spirv_std::{image::Image2d, spirv, Sampler}; |
| 7 | + |
| 8 | +#[cfg(not(target_arch = "spirv"))] |
| 9 | +mod cpu; |
| 10 | +#[cfg(not(target_arch = "spirv"))] |
| 11 | +pub use cpu::*; |
| 12 | + |
| 13 | +#[derive(Clone, Copy, SlabItem)] |
| 14 | +pub struct BloomConfig { |
| 15 | + pub resolution: UVec2, |
| 16 | + pub upsample_filter_radius: Vec2, |
| 17 | +} |
| 18 | + |
| 19 | +impl Default for BloomConfig { |
| 20 | + fn default() -> Self { |
| 21 | + Self { |
| 22 | + resolution: UVec2::ONE, |
| 23 | + upsample_filter_radius: Vec2::ONE, |
| 24 | + } |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +#[cfg(feature = "bloom_vertex")] |
| 29 | +/// A passthru vertex shader to facilitate a bloom effect. |
| 30 | +#[spirv(vertex)] |
| 31 | +pub fn bloom_vertex( |
| 32 | + #[spirv(vertex_index)] vertex_index: u32, |
| 33 | + #[spirv(instance_index)] in_id: u32, |
| 34 | + out_uv: &mut Vec2, |
| 35 | + #[spirv(flat)] out_id: &mut u32, |
| 36 | + #[spirv(position)] out_clip_pos: &mut Vec4, |
| 37 | +) { |
| 38 | + let i = (vertex_index % 6) as usize; |
| 39 | + *out_uv = crate::math::UV_COORD_QUAD_CCW[i]; |
| 40 | + *out_clip_pos = crate::math::CLIP_SPACE_COORD_QUAD_CCW[i]; |
| 41 | + *out_id = in_id; |
| 42 | +} |
| 43 | + |
| 44 | +#[cfg(feature = "bloom_downsample_fragment")] |
| 45 | +/// Performs downsampling on a texture. |
| 46 | +/// |
| 47 | +/// As taken from Call Of Duty method - presented at ACM Siggraph 2014. |
| 48 | +/// |
| 49 | +/// This particular method was customly designed to eliminate |
| 50 | +/// "pulsating artifacts and temporal stability issues". |
| 51 | +#[spirv(fragment)] |
| 52 | +pub fn bloom_downsample_fragment( |
| 53 | + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], |
| 54 | + // Remember to add bilinear minification filter for this texture! |
| 55 | + // Remember to use a floating-point texture format (for HDR)! |
| 56 | + // Remember to use edge clamping for this texture! |
| 57 | + #[spirv(descriptor_set = 0, binding = 1)] texture: &Image2d, |
| 58 | + #[spirv(descriptor_set = 0, binding = 2)] sampler: &Sampler, |
| 59 | + in_uv: Vec2, |
| 60 | + #[spirv(flat)] in_pixel_size_id: Id<Vec2>, |
| 61 | + // frag_color |
| 62 | + downsample: &mut Vec4, |
| 63 | +) { |
| 64 | + use glam::Vec3; |
| 65 | + |
| 66 | + let Vec2 { x, y } = slab.read(in_pixel_size_id); |
| 67 | + |
| 68 | + // Take 13 samples around current texel: |
| 69 | + // a - b - c |
| 70 | + // - j - k - |
| 71 | + // d - e - f |
| 72 | + // - l - m - |
| 73 | + // g - h - i |
| 74 | + // === ('e' is the current texel) === |
| 75 | + let a = texture.sample(*sampler, Vec2::new(in_uv.x - 2.0 * x, in_uv.y + 2.0 * y)); |
| 76 | + let b = texture.sample(*sampler, Vec2::new(in_uv.x, in_uv.y + 2.0 * y)); |
| 77 | + let c = texture.sample(*sampler, Vec2::new(in_uv.x + 2.0 * x, in_uv.y + 2.0 * y)); |
| 78 | + |
| 79 | + let d = texture.sample(*sampler, Vec2::new(in_uv.x - 2.0 * x, in_uv.y)); |
| 80 | + let e = texture.sample(*sampler, Vec2::new(in_uv.x, in_uv.y)); |
| 81 | + let f = texture.sample(*sampler, Vec2::new(in_uv.x + 2.0 * x, in_uv.y)); |
| 82 | + |
| 83 | + let g = texture.sample(*sampler, Vec2::new(in_uv.x - 2.0 * x, in_uv.y - 2.0 * y)); |
| 84 | + let h = texture.sample(*sampler, Vec2::new(in_uv.x, in_uv.y - 2.0 * y)); |
| 85 | + let i = texture.sample(*sampler, Vec2::new(in_uv.x + 2.0 * x, in_uv.y - 2.0 * y)); |
| 86 | + |
| 87 | + let j = texture.sample(*sampler, Vec2::new(in_uv.x - x, in_uv.y + y)); |
| 88 | + let k = texture.sample(*sampler, Vec2::new(in_uv.x + x, in_uv.y + y)); |
| 89 | + let l = texture.sample(*sampler, Vec2::new(in_uv.x - x, in_uv.y - y)); |
| 90 | + let m = texture.sample(*sampler, Vec2::new(in_uv.x + x, in_uv.y - y)); |
| 91 | + |
| 92 | + // Apply weighted distribution: |
| 93 | + // 0.5 + 0.125 + 0.125 + 0.125 + 0.125 = 1 |
| 94 | + // a,b,d,e * 0.125 |
| 95 | + // b,c,e,f * 0.125 |
| 96 | + // d,e,g,h * 0.125 |
| 97 | + // e,f,h,i * 0.125 |
| 98 | + // j,k,l,m * 0.5 |
| 99 | + // This shows 5 square areas that are being sampled. But some of them overlap, |
| 100 | + // so to have an energy preserving downsample we need to make some adjustments. |
| 101 | + // The weights are the distributed so that the sum of j,k,l,m (e.g.) |
| 102 | + // contribute 0.5 to the final color output. The code below is written |
| 103 | + // to effectively yield this sum. We get: |
| 104 | + // 0.125*5 + 0.03125*4 + 0.0625*4 = 1 |
| 105 | + let f1 = 0.125; |
| 106 | + let f2 = 0.0625; |
| 107 | + let f3 = 0.03125; |
| 108 | + let center = e * f1; |
| 109 | + let inner = (j + k + l + m) * f1; |
| 110 | + let outer = (b + d + h + f) * f2; |
| 111 | + let furthest = (a + c + g + i) * f3; |
| 112 | + let min = Vec3::splat(f32::EPSILON).extend(1.0); |
| 113 | + *downsample = (center + inner + outer + furthest).max(min); |
| 114 | +} |
| 115 | + |
| 116 | +#[cfg(feature = "bloom_upsample_fragment")] |
| 117 | +/// This shader performs upsampling on a texture. |
| 118 | +/// Taken from Call Of Duty method, presented at ACM Siggraph 2014. |
| 119 | +#[spirv(fragment)] |
| 120 | +pub fn bloom_upsample_fragment( |
| 121 | + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], |
| 122 | + // Remember to add bilinear minification filter for this texture! |
| 123 | + // Remember to use a floating-point texture format (for HDR)! |
| 124 | + // Remember to use edge clamping for this texture! |
| 125 | + #[spirv(descriptor_set = 0, binding = 1)] texture: &Image2d, |
| 126 | + #[spirv(descriptor_set = 0, binding = 2)] sampler: &Sampler, |
| 127 | + in_uv: Vec2, |
| 128 | + #[spirv(flat)] filter_radius_id: Id<Vec2>, |
| 129 | + // frag_color |
| 130 | + upsample: &mut Vec4, |
| 131 | +) { |
| 132 | + // The filter kernel is applied with a radius, specified in texture |
| 133 | + // coordinates, so that the radius will vary across mip resolutions. |
| 134 | + let Vec2 { x, y } = slab.read(filter_radius_id); |
| 135 | + |
| 136 | + // Take 9 samples around current texel: |
| 137 | + // a - b - c |
| 138 | + // d - e - f |
| 139 | + // g - h - i |
| 140 | + // === ('e' is the current texel) === |
| 141 | + let a = texture |
| 142 | + .sample(*sampler, Vec2::new(in_uv.x - x, in_uv.y + y)) |
| 143 | + .xyz(); |
| 144 | + let b = texture |
| 145 | + .sample(*sampler, Vec2::new(in_uv.x, in_uv.y + y)) |
| 146 | + .xyz(); |
| 147 | + let c = texture |
| 148 | + .sample(*sampler, Vec2::new(in_uv.x + x, in_uv.y + y)) |
| 149 | + .xyz(); |
| 150 | + |
| 151 | + let d = texture |
| 152 | + .sample(*sampler, Vec2::new(in_uv.x - x, in_uv.y)) |
| 153 | + .xyz(); |
| 154 | + let e = texture.sample(*sampler, Vec2::new(in_uv.x, in_uv.y)).xyz(); |
| 155 | + let f = texture |
| 156 | + .sample(*sampler, Vec2::new(in_uv.x + x, in_uv.y)) |
| 157 | + .xyz(); |
| 158 | + |
| 159 | + let g = texture |
| 160 | + .sample(*sampler, Vec2::new(in_uv.x - x, in_uv.y - y)) |
| 161 | + .xyz(); |
| 162 | + let h = texture |
| 163 | + .sample(*sampler, Vec2::new(in_uv.x, in_uv.y - y)) |
| 164 | + .xyz(); |
| 165 | + let i = texture |
| 166 | + .sample(*sampler, Vec2::new(in_uv.x + x, in_uv.y - y)) |
| 167 | + .xyz(); |
| 168 | + |
| 169 | + // Apply weighted distribution, by using a 3x3 tent filter: |
| 170 | + // 1 | 1 2 1 | |
| 171 | + // -- * | 2 4 2 | |
| 172 | + // 16 | 1 2 1 | |
| 173 | + let mut sample = e * 4.0; |
| 174 | + sample += (b + d + f + h) * 2.0; |
| 175 | + sample += a + c + g + i; |
| 176 | + sample *= 1.0 / 16.0; |
| 177 | + *upsample = sample.extend(0.5); |
| 178 | +} |
| 179 | + |
| 180 | +#[cfg(feature = "bloom_mix_fragment")] |
| 181 | +#[spirv(fragment)] |
| 182 | +pub fn bloom_mix_fragment( |
| 183 | + #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32], |
| 184 | + #[spirv(descriptor_set = 0, binding = 1)] hdr_texture: &Image2d, |
| 185 | + #[spirv(descriptor_set = 0, binding = 2)] hdr_sampler: &Sampler, |
| 186 | + #[spirv(descriptor_set = 0, binding = 3)] bloom_texture: &Image2d, |
| 187 | + #[spirv(descriptor_set = 0, binding = 4)] bloom_sampler: &Sampler, |
| 188 | + in_uv: Vec2, |
| 189 | + #[spirv(flat)] in_bloom_strength_id: Id<f32>, |
| 190 | + frag_color: &mut Vec4, |
| 191 | +) { |
| 192 | + let bloom_strength = slab.read(in_bloom_strength_id); |
| 193 | + let hdr = hdr_texture.sample(*hdr_sampler, in_uv).xyz(); |
| 194 | + let bloom = bloom_texture.sample(*bloom_sampler, in_uv).xyz(); |
| 195 | + let color = hdr.lerp(bloom, bloom_strength); |
| 196 | + *frag_color = color.extend(1.0) |
| 197 | +} |
0 commit comments