use crate::{
core::{
algebra::{Matrix3, Matrix4, Vector2, Vector3},
color::Color,
math::{lerpf, Rect},
sstorage::ImmutableString,
},
graphics::{
error::FrameworkError,
framebuffer::{Attachment, GpuFrameBuffer},
gpu_texture::{GpuTexture, GpuTextureDescriptor, GpuTextureKind, PixelKind},
server::GraphicsServer,
},
rand::Rng,
renderer::{
cache::{
shader::{binding, property, PropertyGroup, RenderMaterial},
uniform::UniformBufferCache,
},
gbuffer::GBuffer,
make_viewport_matrix,
resources::RendererResources,
ssao::blur::Blur,
RenderPassStatistics,
},
};
mod blur;
const KERNEL_SIZE: usize = 32;
const NOISE_SIZE: usize = 4;
pub struct ScreenSpaceAmbientOcclusionRenderer {
blur: Blur,
framebuffer: GpuFrameBuffer,
width: i32,
height: i32,
noise: GpuTexture,
kernel: [Vector3<f32>; KERNEL_SIZE],
radius: f32,
}
impl ScreenSpaceAmbientOcclusionRenderer {
pub fn new(
server: &dyn GraphicsServer,
frame_width: usize,
frame_height: usize,
) -> Result<Self, FrameworkError> {
let width = (frame_width / 2).max(1);
let height = (frame_height / 2).max(1);
let occlusion =
server.create_2d_render_target("SsaoTexture", PixelKind::R32F, width, height)?;
let mut rng = crate::rand::thread_rng();
Ok(Self {
blur: Blur::new(server, width, height)?,
framebuffer: server.create_frame_buffer(None, vec![Attachment::color(occlusion)])?,
width: width as i32,
height: height as i32,
kernel: {
let mut kernel = [Default::default(); KERNEL_SIZE];
for (i, v) in kernel.iter_mut().enumerate() {
let k = i as f32 / KERNEL_SIZE as f32;
let scale = lerpf(0.1, 1.0, k * k);
*v = Vector3::new(
rng.gen_range(-1.0..1.0),
rng.gen_range(-1.0..1.0),
rng.gen_range(0.0..1.0),
)
.try_normalize(f32::EPSILON)
.unwrap()
.scale(scale);
}
kernel
},
noise: {
const RGB_PIXEL_SIZE: usize = 3;
let mut pixels = [0u8; RGB_PIXEL_SIZE * NOISE_SIZE * NOISE_SIZE];
for pixel in pixels.chunks_exact_mut(RGB_PIXEL_SIZE) {
pixel[0] = rng.gen_range(0u8..255u8); pixel[1] = rng.gen_range(0u8..255u8); pixel[2] = 0u8; }
server.create_texture(GpuTextureDescriptor {
name: "SsaoNoise",
kind: GpuTextureKind::Rectangle {
width: NOISE_SIZE,
height: NOISE_SIZE,
},
pixel_kind: PixelKind::RGB8,
data: Some(&pixels),
..Default::default()
})?
},
radius: 0.5,
})
}
pub fn set_radius(&mut self, radius: f32) {
self.radius = radius.abs();
}
fn raw_ao_map(&self) -> GpuTexture {
self.framebuffer.color_attachments()[0].texture.clone()
}
pub fn ao_map(&self) -> GpuTexture {
self.blur.result()
}
pub(crate) fn render(
&self,
server: &dyn GraphicsServer,
gbuffer: &GBuffer,
projection_matrix: Matrix4<f32>,
view_matrix: Matrix3<f32>,
uniform_buffer_cache: &mut UniformBufferCache,
renderer_resources: &RendererResources,
) -> Result<RenderPassStatistics, FrameworkError> {
let _debug_scope = server.begin_scope("SSAO");
let mut stats = RenderPassStatistics::default();
let viewport = Rect::new(0, 0, self.width, self.height);
let frame_matrix = make_viewport_matrix(viewport);
self.framebuffer.clear(
viewport,
Some(Color::from_rgba(0, 0, 0, 0)),
Some(1.0),
None,
);
let noise_scale = Vector2::new(
self.width as f32 / NOISE_SIZE as f32,
self.height as f32 / NOISE_SIZE as f32,
);
let inv_projection = projection_matrix.try_inverse().unwrap_or_default();
let properties = PropertyGroup::from([
property("worldViewProjection", &frame_matrix),
property("inverseProjectionMatrix", &inv_projection),
property("projectionMatrix", &projection_matrix),
property("kernel", self.kernel.as_slice()),
property("noiseScale", &noise_scale),
property("viewMatrix", &view_matrix),
property("radius", &self.radius),
]);
let material = RenderMaterial::from([
binding(
"depthSampler",
(gbuffer.depth(), &renderer_resources.nearest_clamp_sampler),
),
binding(
"normalSampler",
(
gbuffer.normal_texture(),
&renderer_resources.nearest_clamp_sampler,
),
),
binding(
"noiseSampler",
(&self.noise, &renderer_resources.nearest_wrap_sampler),
),
binding("properties", &properties),
]);
stats += renderer_resources.shaders.ssao.run_pass(
1,
&ImmutableString::new("Primary"),
&self.framebuffer,
&renderer_resources.quad,
viewport,
&material,
uniform_buffer_cache,
Default::default(),
None,
)?;
stats += self.blur.render(
server,
self.raw_ao_map(),
uniform_buffer_cache,
renderer_resources,
)?;
Ok(stats)
}
}