use crate::render3d::color::Rgb;
use crate::render3d::light::Light;
use crate::render3d::material::Material;
use crate::render3d::math::Vec3;
#[allow(clippy::too_many_arguments)]
pub fn shade_fragment(
world_pos: Vec3,
world_normal: Vec3,
base_color: Rgb,
material: &Material,
lights: &[Light],
camera_pos: Vec3,
fog: Option<(f32, f32)>,
background: Rgb,
) -> Rgb {
let normal = world_normal;
let view_dir = (camera_pos - world_pos).normalize_or_zero();
let mut total_r: f32 = 0.0;
let mut total_g: f32 = 0.0;
let mut total_b: f32 = 0.0;
let lit = material.diffuse != 0.0 || material.specular != 0.0;
for light in lights {
match light {
Light::Ambient { color, intensity } => {
let factor = intensity * material.ambient;
total_r += (color.0 as f32 / 255.0) * factor;
total_g += (color.1 as f32 / 255.0) * factor;
total_b += (color.2 as f32 / 255.0) * factor;
}
_ if !lit => {}
Light::Directional {
direction,
color,
intensity,
} => {
let light_dir = -*direction;
let (dr, dg, db) =
diffuse_specular(normal, light_dir, view_dir, material, *color, *intensity);
total_r += dr;
total_g += dg;
total_b += db;
}
Light::Point {
position,
color,
intensity,
} => {
let to_light = *position - world_pos;
let distance = to_light.length();
let light_dir = to_light / distance;
let attenuation = intensity / (1.0 + 0.09 * distance + 0.032 * distance * distance);
let (dr, dg, db) =
diffuse_specular(normal, light_dir, view_dir, material, *color, attenuation);
total_r += dr;
total_g += dg;
total_b += db;
}
}
}
let r = (total_r * base_color.0 as f32).clamp(0.0, 255.0) as u8;
let g = (total_g * base_color.1 as f32).clamp(0.0, 255.0) as u8;
let b = (total_b * base_color.2 as f32).clamp(0.0, 255.0) as u8;
let color = Rgb(r, g, b);
if let Some((start, end)) = fog {
let dist = (camera_pos - world_pos).length();
let t = fog_smoothstep(start, end, dist);
if t > 0.0 {
return color.lerp(background, t);
}
}
color
}
#[inline(always)]
fn fog_smoothstep(e0: f32, e1: f32, x: f32) -> f32 {
let t = ((x - e0) / (e1 - e0)).clamp(0.0, 1.0);
t * t * (3.0 - 2.0 * t)
}
fn diffuse_specular(
normal: Vec3,
light_dir: Vec3,
view_dir: Vec3,
material: &Material,
light_color: Rgb,
intensity: f32,
) -> (f32, f32, f32) {
let n_dot_l = normal.dot(light_dir).max(0.0);
let diffuse = n_dot_l * material.diffuse * intensity;
let specular = if material.specular != 0.0 {
let halfway = (light_dir + view_dir).normalize_or_zero();
let n_dot_h = normal.dot(halfway).max(0.0);
n_dot_h.powf(material.shininess) * material.specular * intensity
} else {
0.0
};
let total = diffuse + specular;
(
(light_color.0 as f32 / 255.0) * total,
(light_color.1 as f32 / 255.0) * total,
(light_color.2 as f32 / 255.0) * total,
)
}