use crate::mesh3d::Vec3;
#[derive(Debug, Clone)]
pub struct PBRMaterial {
pub albedo: [f32; 3], pub metallic: f32, pub roughness: f32, pub ao: f32, pub emissive: [f32; 3], pub emissive_strength: f32, }
impl PBRMaterial {
pub fn new() -> Self {
Self {
albedo: [0.8, 0.8, 0.8],
metallic: 0.0,
roughness: 0.5,
ao: 1.0,
emissive: [0.0, 0.0, 0.0],
emissive_strength: 0.0,
}
}
pub fn metal(mut self, metallic: f32) -> Self {
self.metallic = metallic.clamp(0.0, 1.0);
self
}
pub fn rough(mut self, roughness: f32) -> Self {
self.roughness = roughness.clamp(0.0, 1.0);
self
}
pub fn color(mut self, r: f32, g: f32, b: f32) -> Self {
self.albedo = [r, g, b];
self
}
pub fn emissive(mut self, r: f32, g: f32, b: f32, strength: f32) -> Self {
self.emissive = [r, g, b];
self.emissive_strength = strength;
self
}
pub fn gold() -> Self {
Self::new()
.color(1.0, 0.766, 0.336)
.metal(1.0)
.rough(0.2)
}
pub fn silver() -> Self {
Self::new()
.color(0.972, 0.960, 0.915)
.metal(1.0)
.rough(0.1)
}
pub fn copper() -> Self {
Self::new()
.color(0.955, 0.637, 0.538)
.metal(1.0)
.rough(0.3)
}
pub fn plastic() -> Self {
Self::new()
.color(0.8, 0.2, 0.2)
.metal(0.0)
.rough(0.5)
}
pub fn rubber() -> Self {
Self::new()
.color(0.1, 0.1, 0.1)
.metal(0.0)
.rough(0.9)
}
pub fn glass() -> Self {
Self::new()
.color(0.95, 0.95, 0.95)
.metal(0.0)
.rough(0.0)
}
}
impl Default for PBRMaterial {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PBRLight {
pub position: Vec3,
pub color: [f32; 3],
pub intensity: f32,
pub radius: f32,
}
impl PBRLight {
pub fn new(position: Vec3, color: [f32; 3], intensity: f32) -> Self {
Self {
position,
color,
intensity,
radius: 10.0,
}
}
pub fn point(x: f32, y: f32, z: f32) -> Self {
Self::new(Vec3::new(x, y, z), [1.0, 1.0, 1.0], 1.0)
}
pub fn with_color(mut self, r: f32, g: f32, b: f32) -> Self {
self.color = [r, g, b];
self
}
pub fn with_intensity(mut self, intensity: f32) -> Self {
self.intensity = intensity;
self
}
pub fn with_radius(mut self, radius: f32) -> Self {
self.radius = radius;
self
}
}
pub struct PBRShader {
pub lights: Vec<PBRLight>,
ambient: [f32; 3],
}
impl PBRShader {
pub fn new() -> Self {
Self {
lights: Vec::new(),
ambient: [0.03, 0.03, 0.03],
}
}
pub fn add_light(&mut self, light: PBRLight) {
self.lights.push(light);
}
pub fn set_ambient(&mut self, r: f32, g: f32, b: f32) {
self.ambient = [r, g, b];
}
pub fn shade(
&self,
material: &PBRMaterial,
position: &Vec3,
normal: &Vec3,
view_dir: &Vec3,
) -> [f32; 3] {
let mut color = [0.0, 0.0, 0.0];
color[0] += material.albedo[0] * self.ambient[0] * material.ao;
color[1] += material.albedo[1] * self.ambient[1] * material.ao;
color[2] += material.albedo[2] * self.ambient[2] * material.ao;
for light in &self.lights {
let light_dir = Vec3::new(
light.position.x - position.x,
light.position.y - position.y,
light.position.z - position.z,
)
.normalize();
let distance = ((light.position.x - position.x).powi(2)
+ (light.position.y - position.y).powi(2)
+ (light.position.z - position.z).powi(2))
.sqrt();
let attenuation = 1.0 / (1.0 + distance * distance / (light.radius * light.radius));
let n_dot_l = normal.dot(&light_dir).max(0.0);
let half_dir = (light_dir + *view_dir).normalize();
let n_dot_h = normal.dot(&half_dir).max(0.0);
let specular = n_dot_h.powf(1.0 / (material.roughness + 0.001));
let f0 = if material.metallic > 0.5 {
material.albedo
} else {
[0.04, 0.04, 0.04]
};
let v_dot_h = view_dir.dot(&half_dir).max(0.0);
let fresnel = [
f0[0] + (1.0 - f0[0]) * (1.0 - v_dot_h).powi(5),
f0[1] + (1.0 - f0[1]) * (1.0 - v_dot_h).powi(5),
f0[2] + (1.0 - f0[2]) * (1.0 - v_dot_h).powi(5),
];
let k_d = [
(1.0 - fresnel[0]) * (1.0 - material.metallic),
(1.0 - fresnel[1]) * (1.0 - material.metallic),
(1.0 - fresnel[2]) * (1.0 - material.metallic),
];
let diffuse = [
k_d[0] * material.albedo[0] * n_dot_l,
k_d[1] * material.albedo[1] * n_dot_l,
k_d[2] * material.albedo[2] * n_dot_l,
];
let spec = [
fresnel[0] * specular,
fresnel[1] * specular,
fresnel[2] * specular,
];
let radiance = [
light.color[0] * light.intensity * attenuation,
light.color[1] * light.intensity * attenuation,
light.color[2] * light.intensity * attenuation,
];
color[0] += (diffuse[0] + spec[0]) * radiance[0];
color[1] += (diffuse[1] + spec[1]) * radiance[1];
color[2] += (diffuse[2] + spec[2]) * radiance[2];
}
color[0] += material.emissive[0] * material.emissive_strength;
color[1] += material.emissive[1] * material.emissive_strength;
color[2] += material.emissive[2] * material.emissive_strength;
color[0] = color[0].min(1.0);
color[1] = color[1].min(1.0);
color[2] = color[2].min(1.0);
color
}
}
impl Default for PBRShader {
fn default() -> Self {
Self::new()
}
}
pub struct ShadowMap {
pub width: u32,
pub height: u32,
pub depth_buffer: Vec<f32>,
pub light_position: Vec3,
pub light_target: Vec3,
}
impl ShadowMap {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
depth_buffer: vec![f32::INFINITY; (width * height) as usize],
light_position: Vec3::new(0.0, 10.0, 0.0),
light_target: Vec3::new(0.0, 0.0, 0.0),
}
}
pub fn clear(&mut self) {
for depth in &mut self.depth_buffer {
*depth = f32::INFINITY;
}
}
pub fn set_light(&mut self, position: Vec3, target: Vec3) {
self.light_position = position;
self.light_target = target;
}
pub fn is_in_shadow(&self, world_pos: &Vec3) -> bool {
let light_dir = Vec3::new(
self.light_target.x - self.light_position.x,
self.light_target.y - self.light_position.y,
self.light_target.z - self.light_position.z,
)
.normalize();
let to_point = Vec3::new(
world_pos.x - self.light_position.x,
world_pos.y - self.light_position.y,
world_pos.z - self.light_position.z,
);
let distance = to_point.length();
let u = ((world_pos.x - self.light_position.x + 10.0) / 20.0 * self.width as f32) as u32;
let v = ((world_pos.z - self.light_position.z + 10.0) / 20.0 * self.height as f32) as u32;
if u < self.width && v < self.height {
let idx = (v * self.width + u) as usize;
if idx < self.depth_buffer.len() {
return distance > self.depth_buffer[idx] + 0.1; }
}
false
}
}
pub struct SSAO {
pub enabled: bool,
pub radius: f32,
pub bias: f32,
pub samples: u32,
}
impl SSAO {
pub fn new() -> Self {
Self {
enabled: true,
radius: 0.5,
bias: 0.025,
samples: 16,
}
}
pub fn calculate_occlusion(
&self,
position: &Vec3,
normal: &Vec3,
depth_buffer: &[f32],
width: u32,
height: u32,
) -> f32 {
if !self.enabled {
return 1.0;
}
let mut occlusion = 0.0;
for i in 0..self.samples {
let angle = (i as f32 / self.samples as f32) * std::f32::consts::PI * 2.0;
let offset = Vec3::new(
angle.cos() * self.radius,
angle.sin() * self.radius,
0.0,
);
let sample_pos = *position + offset;
let u = ((sample_pos.x + 10.0) / 20.0 * width as f32) as u32;
let v = ((sample_pos.z + 10.0) / 20.0 * height as f32) as u32;
if u < width && v < height {
let idx = (v * width + u) as usize;
if idx < depth_buffer.len() {
let sample_depth = depth_buffer[idx];
let depth_diff = position.y - sample_depth;
if depth_diff > self.bias && depth_diff < self.radius {
occlusion += 1.0;
}
}
}
}
1.0 - (occlusion / self.samples as f32)
}
}
impl Default for SSAO {
fn default() -> Self {
Self::new()
}
}
pub struct HDRProcessor {
pub enabled: bool,
pub exposure: f32,
pub gamma: f32,
}
impl HDRProcessor {
pub fn new() -> Self {
Self {
enabled: true,
exposure: 1.0,
gamma: 2.2,
}
}
pub fn tone_map_reinhard(&self, color: [f32; 3]) -> [f32; 3] {
if !self.enabled {
return color;
}
let exposed = [
color[0] * self.exposure,
color[1] * self.exposure,
color[2] * self.exposure,
];
let mapped = [
exposed[0] / (1.0 + exposed[0]),
exposed[1] / (1.0 + exposed[1]),
exposed[2] / (1.0 + exposed[2]),
];
[
mapped[0].powf(1.0 / self.gamma),
mapped[1].powf(1.0 / self.gamma),
mapped[2].powf(1.0 / self.gamma),
]
}
pub fn tone_map_aces(&self, color: [f32; 3]) -> [f32; 3] {
if !self.enabled {
return color;
}
let exposed = [
color[0] * self.exposure,
color[1] * self.exposure,
color[2] * self.exposure,
];
let a = 2.51;
let b = 0.03;
let c = 2.43;
let d = 0.59;
let e = 0.14;
let mapped = [
((exposed[0] * (a * exposed[0] + b)) / (exposed[0] * (c * exposed[0] + d) + e))
.clamp(0.0, 1.0),
((exposed[1] * (a * exposed[1] + b)) / (exposed[1] * (c * exposed[1] + d) + e))
.clamp(0.0, 1.0),
((exposed[2] * (a * exposed[2] + b)) / (exposed[2] * (c * exposed[2] + d) + e))
.clamp(0.0, 1.0),
];
[
mapped[0].powf(1.0 / self.gamma),
mapped[1].powf(1.0 / self.gamma),
mapped[2].powf(1.0 / self.gamma),
]
}
}
impl Default for HDRProcessor {
fn default() -> Self {
Self::new()
}
}
pub struct Bloom {
pub enabled: bool,
pub threshold: f32,
pub intensity: f32,
pub radius: u32,
}
impl Bloom {
pub fn new() -> Self {
Self {
enabled: true,
threshold: 1.0,
intensity: 0.5,
radius: 5,
}
}
pub fn extract_bright(&self, color: [f32; 3]) -> [f32; 3] {
if !self.enabled {
return [0.0, 0.0, 0.0];
}
let luminance = color[0] * 0.2126 + color[1] * 0.7152 + color[2] * 0.0722;
if luminance > self.threshold {
let factor = (luminance - self.threshold) * self.intensity;
[color[0] * factor, color[1] * factor, color[2] * factor]
} else {
[0.0, 0.0, 0.0]
}
}
pub fn apply(&self, original: [f32; 3], bloom: [f32; 3]) -> [f32; 3] {
if !self.enabled {
return original;
}
[
(original[0] + bloom[0]).min(1.0),
(original[1] + bloom[1]).min(1.0),
(original[2] + bloom[2]).min(1.0),
]
}
}
impl Default for Bloom {
fn default() -> Self {
Self::new()
}
}