From bceb4ae244eaf8e76591a56e3b8efaa86eb5b805 Mon Sep 17 00:00:00 2001 From: dengqn Date: Sat, 9 Aug 2025 16:10:52 +0800 Subject: [PATCH] 10.5.A Scene with Metal Spheres --- src/camera.rs | 65 +++++++++++++++++++++++++++++++++++----- src/hittable.rs | 21 ++++++++----- src/main.rs | 24 ++++++++++----- src/material.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++ src/math_utils.rs | 11 +++++++ src/sphere.rs | 19 ++++++++++-- src/types_defined.rs | 7 +++-- src/vec3.rs | 9 ++++++ 8 files changed, 200 insertions(+), 27 deletions(-) create mode 100644 src/material.rs diff --git a/src/camera.rs b/src/camera.rs index ab556af..a0ce7b9 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -7,6 +7,7 @@ use crate::ppm_writer::PPMWriter; use crate::types_defined::{Camera, Color, HitRecord, Point, Ray, Vec3}; use rand::rngs::ThreadRng; use rand::{Rng, thread_rng}; +use crate::material::{Material, MaterialKind}; impl<'a> Camera<'a> { pub fn new( @@ -61,9 +62,9 @@ impl<'a> Camera<'a> { // let mut img_content = format!("P3\n{} {}\n255\n", self.image_width, self.image_height); for j in 0..self.image_height { - if j % 10 == 0 { + // if j % 10 == 0 { println!("scan line {}/{} ", j + 1, self.image_height); - } + // } for i in 0..self.image_width { // color @@ -105,17 +106,65 @@ impl<'a> Camera<'a> { // 限制出射光线的角度(0.001经验值) let hit_record = &mut HitRecord{ t: 0.0, + p: Vec3::new(0.0, 0.0, 0.0), normal: Vec3::new(0.0, 0.0, 0.0), front_face: false, + material: None, }; let hit = world.hit(&ray, 0.001, f32::MAX, hit_record); - if hit_record.t >= 0.0 { - // diffuse normal vec - let diffuse_vec = Vec3::random_unit(); - let lambertian_vec = diffuse_vec + hit_record.normal; - // 每次反射按0.5计算颜色(反射率 ) - return 0.5 * self.ray_color(&Ray::new(ray.point, lambertian_vec), depth - 1, world); + if hit { + let scatted = &mut Ray::new(Point::new(0.0, 0.0, 0.0), Vec3::random()); + let attenuation = &mut Color::new(1.0, 1.0, 1.0); + let hit_m = &hit_record.material; + let hc = &mut HitRecord{ + t: hit_record.t, + p: hit_record.p, + normal: hit_record.normal, + front_face: hit_record.front_face, + material: None, + }; + let hit_c = match hit_m { + Some(mk) => { + let mc = match mk { + MaterialKind::Lambertian(l) => { + if l.scatter(ray, hc, attenuation, scatted) { + let r_c = self.ray_color(scatted, depth - 1, world); + let sc_color = Vec3::new(attenuation.x * r_c.x, attenuation.y * r_c.y, attenuation.z * r_c.z); + // *attenuation * ; + Color::new(sc_color.x, sc_color.y, sc_color.z) + } else { + Color::new(0.0, 0.0, 0.0) + } + }, + MaterialKind::Metal(m) => { + if m.scatter(ray, hc, attenuation, scatted) { + let r_c = self.ray_color(scatted, depth - 1, world); + let sc_color = Vec3::new(attenuation.x * r_c.x, attenuation.y * r_c.y, attenuation.z * r_c.z); + // *attenuation * ; + + Color::new(sc_color.x, sc_color.y, sc_color.z) + } else { + Color::new(0.0, 0.0, 0.0) + } + } + }; + return mc + }, + None => { + // if hit_record.t >= 0.0 { + // // diffuse normal vec + // let diffuse_vec = Vec3::random_unit(); + // let lambertian_vec = diffuse_vec + hit_record.normal; + // // 每次反射按0.5计算颜色(反射率 ) + // return 0.5 * self.ray_color(&Ray::new(ray.point, lambertian_vec), depth - 1, world); + // } + Color::new(0.0, 0.0, 0.0) + } + }; + + return hit_c; } + // v / |v| let unit_direction = ray.direction / ray.direction.length(); let a = 0.5 * (unit_direction.y + 1.0); diff --git a/src/hittable.rs b/src/hittable.rs index 070f451..013920b 100644 --- a/src/hittable.rs +++ b/src/hittable.rs @@ -1,15 +1,14 @@ +use crate::material::MaterialKind; use crate::types_defined::{HitRecord, Ray, Vec3}; pub trait Hittable { fn hit(&self, r: &Ray, t_min: f32, t_max: f32, hit_record: &mut HitRecord) -> bool; } - pub struct HittableList { - pub objects: Vec> + pub objects: Vec>, } - impl HittableList { pub fn new() -> Self { HittableList { objects: vec![] } @@ -20,14 +19,15 @@ impl HittableList { } pub fn hit(&self, r: &Ray, t_min: f32, t_max: f32, hit_record: &mut HitRecord) -> bool { - let mut hits = false; let mut closest_so_far = t_max; - let temp_hit_record = &mut HitRecord{ + let temp_hit_record = &mut HitRecord { t: 0.0, normal: Vec3::new(0.0, 0.0, 0.0), + p: Vec3::new(0.0, 0.0, 0.0), front_face: false, + material: None, }; for object in &self.objects { @@ -35,10 +35,18 @@ impl HittableList { if hit { hits = true; closest_so_far = temp_hit_record.t; - hit_record.t = temp_hit_record.t; + hit_record.p = temp_hit_record.p; hit_record.front_face = temp_hit_record.front_face; hit_record.normal = temp_hit_record.normal; + hit_record.material = if let Some(ref m) = temp_hit_record.material { + match m { + MaterialKind::Lambertian(l) => Some(MaterialKind::Lambertian(l.clone())), + MaterialKind::Metal(m) => Some(MaterialKind::Metal(m.clone())), + } + } else { + None + }; if temp_hit_record.t >= 0.0 { return true; } @@ -48,4 +56,3 @@ impl HittableList { hits } } - diff --git a/src/main.rs b/src/main.rs index 7f644fa..7971d12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,9 @@ use std::fs::File; use std::io::BufWriter; use crate::hittable::HittableList; +use crate::material::{Lambertian, MaterialKind, Metal}; use crate::ppm_writer::PPMWriter; -use crate::types_defined::{Camera, Point, Sphere, Vec3}; +use crate::types_defined::{Camera, Color, Point, Sphere, Vec3}; mod camera; mod color; mod hittable; @@ -14,6 +15,7 @@ mod vec3; mod write_file_util; mod math_utils; mod ppm_writer; +mod material; fn main() { camera_render(); @@ -37,20 +39,26 @@ fn camera_render() { width, 16.0 / 9.0, 2.0, - Point::new(0.0, 0.0, 1.0), + Point::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, -1.0), - 50, + 100, 50, pw ); // world // world objects(spheres) let mut world = HittableList::new(); - world.put(Box::new(Sphere::new(Point::new(0.0, 0.0, -1.0), 0.5))); - world.put(Box::new(Sphere::new(Point::new(0.3, 0.1, -1.0), 0.1))); - world.put(Box::new(Sphere::new(Point::new(-0.5, 0.0, -1.0), 0.3))); - world.put(Box::new(Sphere::new(Point::new(0.0, -1000.0, -1.0), 1000.0))); - world.put(Box::new(Sphere::new(Point::new(-0.5, 0.2, -0.5), 0.4))); + + let plane_m = Some(MaterialKind::Lambertian(Lambertian{albedo: Color::new(0.3, 0.2, 0.5)})); + let plane2_m = Some(MaterialKind::Metal(Metal{albedo: Color::new(0.3, 0.2, 0.5)})); + let center_m = Some(MaterialKind::Lambertian(Lambertian{albedo: Color::new(0.1, 0.2, 0.5)})); + let left_m = Some(MaterialKind::Metal(Metal{albedo: Color::new(0.8, 0.8, 0.8)})); + let right_m = Some(MaterialKind::Metal(Metal{albedo: Color::new(0.8, 0.6, 0.2)})); + + world.put(Box::new(Sphere::new(Point::new(0.0, -100.5, -1.2), 100.0, plane2_m))); + world.put(Box::new(Sphere::new(Point::new(0.0, 0.0, -1.2), 0.5, center_m))); + world.put(Box::new(Sphere::new(Point::new(-1.0, 0.0, -1.0), 0.5, left_m))); + world.put(Box::new(Sphere::new(Point::new(1.0, 0.0, -1.0), 0.5, right_m))); camera.render(&world); // write_image( diff --git a/src/material.rs b/src/material.rs new file mode 100644 index 0000000..5f3ffb0 --- /dev/null +++ b/src/material.rs @@ -0,0 +1,71 @@ +use crate::math_utils::{near_zero, reflect}; +use crate::types_defined::{Color, HitRecord, Ray, Vec3}; + + +#[derive(Debug)] +pub enum MaterialKind { + Lambertian(Lambertian), + Metal(Metal), +} + +pub trait Material { + fn scatter(&self, r_in: &Ray, hit_record: &mut HitRecord, attenuation: &mut Color, ray: &mut Ray) -> bool; +} + +#[derive(Debug)] +pub struct Lambertian { + pub albedo: Color, +} + +impl Clone for Lambertian { + fn clone(&self) -> Self { + Lambertian { + albedo: self.albedo, + } + } +} + +impl Material for Lambertian { + fn scatter(&self, r_in: &Ray, hit_record: &mut HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { + + let scatter_direction = hit_record.normal + Vec3::random_unit(); + // let scatter_direction = reflect(r_in.direction, hit_record.normal); + + if near_zero(scatter_direction) { + scattered.direction = hit_record.normal; + } + + *scattered = Ray::new(hit_record.p, scatter_direction); + *attenuation = self.albedo.clone(); + + true + } +} + +#[derive(Debug)] +pub struct Metal { + pub albedo: Color, +} + +impl Clone for Metal { + fn clone(&self) -> Self { + Metal { + albedo: self.albedo, + } + } +} + +impl Material for Metal { + fn scatter(&self, r_in: &Ray, hit_record: &mut HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { + /* + vec3 reflected = reflect(r_in.direction(), rec.normal); + scattered = ray(rec.p, reflected); + attenuation = albedo; + return true; + */ + let reflected = reflect(r_in.direction, hit_record.normal); + *scattered = Ray::new(hit_record.p, reflected); + *attenuation = self.albedo.clone(); + true + } +} \ No newline at end of file diff --git a/src/math_utils.rs b/src/math_utils.rs index 56a60cf..f93f297 100644 --- a/src/math_utils.rs +++ b/src/math_utils.rs @@ -1,3 +1,4 @@ +use crate::types_defined::Vec3; pub fn clamp(value: f32, low: f32, high: f32) -> f32 { if value < low { @@ -7,4 +8,14 @@ pub fn clamp(value: f32, low: f32, high: f32) -> f32 { return high; } return value; +} + +pub fn near_zero(v: Vec3) -> bool { + const EPSILON: f32 = 1e-8; + + v.x.abs() < EPSILON && v.y.abs() < EPSILON && v.z.abs() < EPSILON +} + +pub fn reflect(v: Vec3, n: Vec3) -> Vec3 { + v - ((2.0 * v.dot(n)) * n) } \ No newline at end of file diff --git a/src/sphere.rs b/src/sphere.rs index 388efa0..80ef1b1 100644 --- a/src/sphere.rs +++ b/src/sphere.rs @@ -1,11 +1,13 @@ use crate::hittable; -use crate::types_defined::{HitRecord, Point, Ray, Sphere}; +use crate::material::{Lambertian, Material, MaterialKind, Metal}; +use crate::types_defined::{Color, HitRecord, Point, Ray, Sphere}; impl Sphere { - pub fn new(c: Point, r: f32) -> Self { + pub fn new(c: Point, r: f32, m: Option) -> Self { Self { center: c, radius: r, + material: m, } } } @@ -19,6 +21,10 @@ impl hittable::Hittable for Sphere { let discriminant = h * h - a * c; + if discriminant < 0.0 { + return false; + } + // // 两个交点 let disc_sqrt = discriminant.sqrt(); let near = (h - disc_sqrt) / a; @@ -37,8 +43,17 @@ impl hittable::Hittable for Sphere { let normal = (p - self.center) / self.radius; hit_record.t = root; + hit_record.p = p; hit_record.normal = normal; hit_record.front_face = r.direction.dot(normal) < 0.0; + hit_record.material = if let Some(ref m) = self.material { + match m { + MaterialKind::Lambertian(l) => Some(MaterialKind::Lambertian(l.clone())), + MaterialKind::Metal(m) => Some(MaterialKind::Metal(m.clone())), + } + } else { + None + }; true } diff --git a/src/types_defined.rs b/src/types_defined.rs index b51b585..a92b699 100644 --- a/src/types_defined.rs +++ b/src/types_defined.rs @@ -1,5 +1,6 @@ use std::{fs::File, io::{BufWriter, Write}}; - +use rand::distributions::Open01; +use crate::material::{Material, MaterialKind}; use crate::ppm_writer::PPMWriter; /* @@ -25,9 +26,10 @@ pub struct Ray { pub struct HitRecord { pub t: f32, - // pub p: Vec3, + pub p: Vec3, pub normal: Vec3, pub front_face: bool, + pub material: Option, } pub struct Camera<'a> { @@ -60,4 +62,5 @@ pub struct Camera<'a> { pub struct Sphere { pub center: Point, pub radius: f32, + pub material: Option, } diff --git a/src/vec3.rs b/src/vec3.rs index 6464960..24ce443 100644 --- a/src/vec3.rs +++ b/src/vec3.rs @@ -125,6 +125,15 @@ impl Div for Vec3 { } } +// Vec * Vec +impl Mul for Vec3 { + type Output = Self; + + fn mul(self, rhs: Vec3) -> Self::Output { + Vec3::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z) + } +} + // 加法赋值: Vec3 += Vec3 impl AddAssign for Vec3 { fn add_assign(&mut self, other: Self) {