use crate::{
core::{
algebra::{Isometry3, Matrix4, Point3, Translation, Vector3},
math::Rect,
sstorage::ImmutableString,
},
graphics::{
buffer::BufferUsage, error::FrameworkError, framebuffer::GpuFrameBuffer,
geometry_buffer::GpuGeometryBuffer, server::GraphicsServer,
},
renderer::{
bundle::{LightSource, LightSourceKind},
cache::{
shader::{binding, property, PropertyGroup, RenderMaterial},
uniform::UniformBufferCache,
},
framework::GeometryBufferExt,
gbuffer::GBuffer,
make_viewport_matrix,
resources::RendererResources,
RenderPassStatistics,
},
scene::{graph::Graph, mesh::surface::SurfaceData},
};
pub struct LightVolumeRenderer {
cone: GpuGeometryBuffer,
sphere: GpuGeometryBuffer,
}
impl LightVolumeRenderer {
pub fn new(server: &dyn GraphicsServer) -> Result<Self, FrameworkError> {
Ok(Self {
cone: GpuGeometryBuffer::from_surface_data(
"Cone",
&SurfaceData::make_cone(
16,
1.0,
1.0,
&Matrix4::new_translation(&Vector3::new(0.0, -1.0, 0.0)),
),
BufferUsage::StaticDraw,
server,
)?,
sphere: GpuGeometryBuffer::from_surface_data(
"Sphere",
&SurfaceData::make_sphere(8, 8, 1.0, &Matrix4::identity()),
BufferUsage::StaticDraw,
server,
)?,
})
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn render_volume(
&mut self,
server: &dyn GraphicsServer,
light: &LightSource,
gbuffer: &GBuffer,
view: Matrix4<f32>,
inv_proj: Matrix4<f32>,
view_proj: Matrix4<f32>,
viewport: Rect<i32>,
graph: &Graph,
frame_buffer: &GpuFrameBuffer,
uniform_buffer_cache: &mut UniformBufferCache,
renderer_resources: &RendererResources,
) -> Result<RenderPassStatistics, FrameworkError> {
let _debug_scope = server.begin_scope("LightVolume");
let mut stats = RenderPassStatistics::default();
let frame_matrix = make_viewport_matrix(viewport);
let position = view.transform_point(&Point3::from(light.position)).coords;
let color = light.color.srgb_to_linear_f32().xyz();
match light.kind {
LightSourceKind::Spot {
distance,
full_cone_angle,
..
} => {
let direction = view.transform_vector(
&(-light
.up_vector
.try_normalize(f32::EPSILON)
.unwrap_or_else(Vector3::z)),
);
let k = (full_cone_angle * 0.5 + 1.0f32.to_radians()).tan() * distance;
let light_shape_matrix = Isometry3 {
rotation: graph.global_rotation(light.handle),
translation: Translation {
vector: light.position,
},
}
.to_homogeneous()
* Matrix4::new_nonuniform_scaling(&Vector3::new(k, distance, k));
let mvp = view_proj * light_shape_matrix;
frame_buffer.clear(viewport, None, None, Some(0));
let properties = PropertyGroup::from([property("worldViewProjection", &mvp)]);
let material = RenderMaterial::from([binding("properties", &properties)]);
stats += renderer_resources.shaders.volume_marker_vol.run_pass(
1,
&ImmutableString::new("Primary"),
frame_buffer,
&self.cone,
viewport,
&material,
uniform_buffer_cache,
Default::default(),
None,
)?;
let cone_angle_cos = (full_cone_angle * 0.5).cos();
let properties = PropertyGroup::from([
property("worldViewProjection", &frame_matrix),
property("invProj", &inv_proj),
property("lightPosition", &position),
property("lightDirection", &direction),
property("lightColor", &color),
property("scatterFactor", &light.scatter),
property("intensity", &light.intensity),
property("coneAngleCos", &cone_angle_cos),
]);
let material = RenderMaterial::from([
binding(
"depthSampler",
(gbuffer.depth(), &renderer_resources.nearest_clamp_sampler),
),
binding("properties", &properties),
]);
stats += renderer_resources.shaders.spot_light_volume.run_pass(
1,
&ImmutableString::new("Primary"),
frame_buffer,
&renderer_resources.quad,
viewport,
&material,
uniform_buffer_cache,
Default::default(),
None,
)?;
}
LightSourceKind::Point { radius, .. } => {
frame_buffer.clear(viewport, None, None, Some(0));
let bias = 1.05;
let k = bias * radius;
let light_shape_matrix = Matrix4::new_translation(&light.position)
* Matrix4::new_nonuniform_scaling(&Vector3::new(k, k, k));
let mvp = view_proj * light_shape_matrix;
let properties = PropertyGroup::from([property("worldViewProjection", &mvp)]);
let material = RenderMaterial::from([binding("properties", &properties)]);
stats += renderer_resources.shaders.volume_marker_vol.run_pass(
1,
&ImmutableString::new("Primary"),
frame_buffer,
&self.sphere,
viewport,
&material,
uniform_buffer_cache,
Default::default(),
None,
)?;
let properties = PropertyGroup::from([
property("worldViewProjection", &frame_matrix),
property("invProj", &inv_proj),
property("lightPosition", &position),
property("lightColor", &color),
property("scatterFactor", &light.scatter),
property("intensity", &light.intensity),
property("lightRadius", &radius),
]);
let material = RenderMaterial::from([
binding(
"depthSampler",
(gbuffer.depth(), &renderer_resources.nearest_clamp_sampler),
),
binding("properties", &properties),
]);
stats += renderer_resources.shaders.point_light_volume.run_pass(
1,
&ImmutableString::new("Primary"),
frame_buffer,
&renderer_resources.quad,
viewport,
&material,
uniform_buffer_cache,
Default::default(),
None,
)?;
}
_ => (),
}
Ok(stats)
}
}