use std::sync::Arc;
use crate::assets::EnvironmentDesc;
use crate::scene::Vec3;
use super::environment_prefilter::{build_brdf_lut, prefilter_specular_cubemap_mips};
use super::pbr_contract::{PbrMaterial, environment_split_sum_contribution, reflect_vec3};
pub(in crate::render) const PREFILTER_MIP_COUNT: u32 = 5;
pub(in crate::render) const BRDF_LUT_SIZE: u32 = 64;
#[derive(Debug, Clone, PartialEq)]
pub(in crate::render) struct PreparedEnvironmentLighting {
diffuse_rgb: Vec3,
specular_rgb: Vec3,
intensity: f32,
cubemap: Option<Arc<PreparedEnvironmentCubemap>>,
}
#[derive(Debug, Clone, PartialEq)]
pub(in crate::render) struct PreparedEnvironmentCubemap {
pub(in crate::render) resolution: u32,
pub(in crate::render) mips: Vec<[Vec<f32>; 6]>,
pub(in crate::render) mip_count: u32,
pub(in crate::render) brdf_lut: Vec<f32>,
pub(in crate::render) brdf_lut_size: u32,
}
impl Default for PreparedEnvironmentLighting {
fn default() -> Self {
Self {
diffuse_rgb: Vec3::ZERO,
specular_rgb: Vec3::ZERO,
intensity: 0.0,
cubemap: None,
}
}
}
impl PreparedEnvironmentLighting {
pub(in crate::render) fn from_environment(environment: Option<&EnvironmentDesc>) -> Self {
let Some(environment) = environment else {
return Self::default();
};
let cubemap_faces = environment.cubemap_faces();
let cubemap = cubemap_faces.map(|faces| {
let resolution = faces.resolution();
let source_pixels = faces.build_face_pixels_rgba32f();
let mips =
prefilter_specular_cubemap_mips(&source_pixels, resolution, PREFILTER_MIP_COUNT);
Arc::new(PreparedEnvironmentCubemap {
resolution,
mips,
mip_count: PREFILTER_MIP_COUNT,
brdf_lut: build_brdf_lut(BRDF_LUT_SIZE),
brdf_lut_size: BRDF_LUT_SIZE,
})
});
let irradiance = match environment.preview_irradiance_rgb() {
Some(stored) => stored,
None => match cubemap.as_ref() {
Some(prepared) => average_cubemap_radiance(prepared),
None => {
return Self {
diffuse_rgb: Vec3::ZERO,
specular_rgb: Vec3::ZERO,
intensity: 0.0,
cubemap,
};
}
},
};
let diffuse_rgb = Vec3::new(
sanitize_environment_channel(irradiance[0]),
sanitize_environment_channel(irradiance[1]),
sanitize_environment_channel(irradiance[2]),
);
if diffuse_rgb.x <= f32::EPSILON
&& diffuse_rgb.y <= f32::EPSILON
&& diffuse_rgb.z <= f32::EPSILON
{
return Self {
diffuse_rgb: Vec3::ZERO,
specular_rgb: Vec3::ZERO,
intensity: 0.0,
cubemap,
};
}
Self {
diffuse_rgb,
specular_rgb: diffuse_rgb,
intensity: 1.0,
cubemap,
}
}
pub(in crate::render) fn cubemap(&self) -> Option<&PreparedEnvironmentCubemap> {
self.cubemap.as_deref()
}
pub(in crate::render::prepare) fn is_active(&self) -> bool {
self.intensity > 0.0
&& (self.diffuse_rgb.x > f32::EPSILON
|| self.diffuse_rgb.y > f32::EPSILON
|| self.diffuse_rgb.z > f32::EPSILON
|| self.specular_rgb.x > f32::EPSILON
|| self.specular_rgb.y > f32::EPSILON
|| self.specular_rgb.z > f32::EPSILON)
}
pub(in crate::render::prepare) fn gpu_diffuse_intensity(&self) -> [f32; 4] {
[
self.diffuse_rgb.x,
self.diffuse_rgb.y,
self.diffuse_rgb.z,
self.intensity,
]
}
pub(in crate::render::prepare) fn gpu_specular_intensity(&self) -> [f32; 4] {
[
self.specular_rgb.x,
self.specular_rgb.y,
self.specular_rgb.z,
self.intensity,
]
}
pub(in crate::render::prepare) fn pbr_contribution(
&self,
material: PbrMaterial,
normal: Vec3,
view: Vec3,
) -> Vec3 {
if !self.is_active() {
return Vec3::ZERO;
}
let diffuse = self
.cubemap
.as_deref()
.map(|cubemap| sample_cubemap_mip(cubemap, 0, normal))
.unwrap_or(self.diffuse_rgb);
let reflection = reflect_vec3(Vec3::new(-view.x, -view.y, -view.z), normal);
let prefiltered = self
.cubemap
.as_deref()
.map(|cubemap| sample_prefiltered_specular(cubemap, reflection, material.roughness))
.unwrap_or(self.specular_rgb);
let brdf = self
.cubemap
.as_deref()
.map(|cubemap| sample_brdf_lut(cubemap, dot_vec3(normal, view), material.roughness))
.unwrap_or((1.0, 0.0));
scale_vec3(
environment_split_sum_contribution(material, normal, view, diffuse, prefiltered, brdf),
self.intensity,
)
}
}
pub(in crate::render) fn collect_environment_lighting(
environment: Option<&EnvironmentDesc>,
) -> PreparedEnvironmentLighting {
PreparedEnvironmentLighting::from_environment(environment)
}
fn average_cubemap_radiance(cubemap: &PreparedEnvironmentCubemap) -> [f32; 3] {
let Some(faces) = cubemap.mips.first() else {
return [0.0; 3];
};
let mut total = [0.0_f64; 3];
let mut count = 0u64;
for face in faces {
for pixel in face.chunks_exact(4) {
total[0] += f64::from(pixel[0]);
total[1] += f64::from(pixel[1]);
total[2] += f64::from(pixel[2]);
count += 1;
}
}
if count == 0 {
return [0.0; 3];
}
let count = count as f64;
[
(total[0] / count) as f32,
(total[1] / count) as f32,
(total[2] / count) as f32,
]
}
fn sanitize_environment_channel(value: f32) -> f32 {
if value.is_finite() {
value.clamp(0.0, 64.0)
} else {
0.0
}
}
fn sample_prefiltered_specular(
cubemap: &PreparedEnvironmentCubemap,
direction: Vec3,
roughness: f32,
) -> Vec3 {
let max_mip = cubemap.mip_count.saturating_sub(1);
let mip = (roughness.clamp(0.0, 1.0) * max_mip as f32).round() as u32;
sample_cubemap_mip(cubemap, mip, direction)
}
fn sample_cubemap_mip(cubemap: &PreparedEnvironmentCubemap, mip: u32, direction: Vec3) -> Vec3 {
let Some(faces) = cubemap.mips.get(mip as usize) else {
return Vec3::ZERO;
};
let resolution = (cubemap.resolution >> mip).max(1);
let (face_index, u, v) = cubemap_face_uv(direction);
let x = (u.clamp(0.0, 1.0) * (resolution - 1) as f32).round() as u32;
let y = (v.clamp(0.0, 1.0) * (resolution - 1) as f32).round() as u32;
let pixel = ((y * resolution + x) * 4) as usize;
let face = &faces[face_index];
if pixel + 2 >= face.len() {
return Vec3::ZERO;
}
Vec3::new(face[pixel], face[pixel + 1], face[pixel + 2])
}
fn cubemap_face_uv(direction: Vec3) -> (usize, f32, f32) {
let ax = direction.x.abs();
let ay = direction.y.abs();
let az = direction.z.abs();
let (face, sc, tc, major) = if ax >= ay && ax >= az {
if direction.x >= 0.0 {
(0, -direction.z, -direction.y, ax)
} else {
(1, direction.z, -direction.y, ax)
}
} else if ay >= ax && ay >= az {
if direction.y >= 0.0 {
(2, direction.x, direction.z, ay)
} else {
(3, direction.x, -direction.z, ay)
}
} else if direction.z >= 0.0 {
(4, direction.x, -direction.y, az)
} else {
(5, -direction.x, -direction.y, az)
};
if major <= f32::EPSILON || !major.is_finite() {
return (4, 0.5, 0.5);
}
(face, 0.5 * (sc / major + 1.0), 0.5 * (tc / major + 1.0))
}
fn sample_brdf_lut(
cubemap: &PreparedEnvironmentCubemap,
n_dot_v: f32,
roughness: f32,
) -> (f32, f32) {
let size = cubemap.brdf_lut_size.max(1);
let x = (n_dot_v.clamp(0.0, 1.0) * (size - 1) as f32).round() as u32;
let y = (roughness.clamp(0.0, 1.0) * (size - 1) as f32).round() as u32;
let index = ((y * size + x) * 2) as usize;
if index + 1 >= cubemap.brdf_lut.len() {
return (1.0, 0.0);
}
(cubemap.brdf_lut[index], cubemap.brdf_lut[index + 1])
}
fn dot_vec3(left: Vec3, right: Vec3) -> f32 {
left.x * right.x + left.y * right.y + left.z * right.z
}
fn scale_vec3(value: Vec3, scale: f32) -> Vec3 {
Vec3::new(value.x * scale, value.y * scale, value.z * scale)
}