diff --git a/src/camera.rs b/src/camera.rs index 535e0d6..69f2914 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -2,7 +2,7 @@ use std::fs::File; use std::io::{BufWriter}; use crate::hittable::HittableList; -use crate::math_utils::clamp; +use crate::math_utils::{clamp, near_zero}; use crate::ppm_writer::PPMWriter; use crate::types_defined::{Camera, Color, HitRecord, Point, Ray, Vec3}; use rand::rngs::ThreadRng; @@ -125,12 +125,12 @@ impl<'a> Camera<'a> { }; let hit_c = match hit_m { Some(mk) => { - let mc = match mk { + match mk { // MaterialKind::Lambertian(l) => l.albedo, MaterialKind::Lambertian(l) => self.scatter_color(ray, hc, l, depth, world), MaterialKind::Metal(m) => self.scatter_color(ray, hc, m, depth, world), - }; - mc + MaterialKind::Dielectric(d) => self.scatter_color(ray, hc, d, depth, world) + } }, None => { if hit_record.t >= 0.0 { @@ -175,7 +175,11 @@ impl<'a> Camera<'a> { attenuation.y * r_c.y, attenuation.z * r_c.z, ); - Color::new(sc_color.x, sc_color.y, sc_color.z) + let color = Color::new(sc_color.x, sc_color.y, sc_color.z); + // if near_zero(color) { + // println!("near zero: {:?}", scatted.direction); + // } + color } else { Color::new(0.0, 0.0, 0.0) } diff --git a/src/hittable.rs b/src/hittable.rs index 3a75c3d..f37fb0b 100644 --- a/src/hittable.rs +++ b/src/hittable.rs @@ -1,4 +1,5 @@ use crate::material::MaterialKind; +use crate::math_utils::{front_face_normal, is_front_face}; use crate::types_defined::{HitRecord, Ray, Vec3}; pub trait Hittable { @@ -37,20 +38,17 @@ impl HittableList { 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.front_face = is_front_face(r, temp_hit_record.normal); + hit_record.normal = front_face_normal(r, 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())), + MaterialKind::Dielectric(d) => Some(MaterialKind::Dielectric(d.clone())) } } else { None }; - - // if temp_hit_record.t >= 0.0 { - // return true; - // } } } diff --git a/src/main.rs b/src/main.rs index 4f3a6ac..2ec00a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::fs::File; use std::io::BufWriter; use crate::hittable::HittableList; -use crate::material::{Lambertian, MaterialKind, Metal}; +use crate::material::{Dielectric, Lambertian, MaterialKind, Metal}; use crate::ppm_writer::PPMWriter; use crate::types_defined::{Camera, Color, Point, Sphere, Vec3}; mod camera; @@ -22,8 +22,13 @@ fn main() { fn camera_render() { - let width: i32 = 800/2; - let height: i32 = 600/2; + let scale = 2; + let width: i32 = 800/scale; + let height: i32 = 600/scale; + let sample_times = 50; + let reflect_depth = 100; + + let pw_r = PPMWriter::new( BufWriter::new(File::create("./target/ray_sphere_normal_scene_render.ppm").unwrap()), width, height); @@ -36,12 +41,12 @@ fn camera_render() { let mut camera: Camera = Camera::new( width, - 4.0 / 3.0, + width as f32 / height as f32, 2.0, Point::new(0.0, -0.0, 0.0), Vec3::new(0.0, 0.0, 1.0), - 127, - 127, + sample_times, + reflect_depth, pw ); // world @@ -50,20 +55,18 @@ fn camera_render() { let plane_m = Some(MaterialKind::Lambertian(Lambertian{albedo: Color::new(0.899, 0.899, 0.999)})); // let plane2_m = Some(MaterialKind::Metal(Metal{albedo: Color::new(0.784,0.784,0.784)})); - let center_m = Some(MaterialKind::Lambertian(Lambertian{albedo: Color::new(0.5, 0.5, 0.5)})); + let center_m = Some(MaterialKind::Lambertian(Lambertian{albedo: Color::new(0.2, 0.5, 0.5)})); let left_m = Some(MaterialKind::Metal(Metal{albedo: Color::new(0.799, 0.599, 0.799), fuzz: 0.0005})); let left_behind_m = Some(MaterialKind::Lambertian(Lambertian{albedo: Color::new(0.799, 0.599, 0.599)})); - let right_m = Some(MaterialKind::Metal(Metal{albedo: Color::new(0.8, 0.6, 0.2), fuzz: 0.3})); + let right_m = Some(MaterialKind::Metal(Metal{albedo: Color::new(0.8, 0.6, 0.2), fuzz: 0.003})); + let left_dia_m = Some(MaterialKind::Dielectric(Dielectric{albedo: Color::new(0.8, 0.6, 0.2), refraction_index: 1.5})); - world.put(Box::new(Sphere::new(Point::new(0.0, 0.0, -1.0), 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(-2.5, 1.5, -3.5), 1.5, left_behind_m))); + // world.put(Box::new(Sphere::new(Point::new(0.0, 0.0, -1.0), 0.5, center_m))); + world.put(Box::new(Sphere::new(Point::new(-1.0, 0.0, -1.0), 0.5, left_dia_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(-3.5, 1.5, -5.5), 1.5, left_behind_m))); world.put(Box::new(Sphere::new(Point::new(1.0, 0.0, -1.0), 0.5, right_m))); - world.put(Box::new(Sphere::new(Point::new(0.0, -25.5, -1.0), 25.0, plane_m))); + world.put(Box::new(Sphere::new(Point::new(0.0, -500.5, -1.0), 500.0, plane_m))); camera.render(&world); - // write_image( - // ppm_content, - // "".to_string(), - // ) } diff --git a/src/material.rs b/src/material.rs index 26b8ff8..69e1358 100644 --- a/src/material.rs +++ b/src/material.rs @@ -1,4 +1,4 @@ -use crate::math_utils::{reflect}; +use crate::math_utils::{near_zero, reflect, refract}; use crate::types_defined::{Color, HitRecord, Ray, Vec3}; @@ -6,6 +6,7 @@ use crate::types_defined::{Color, HitRecord, Ray, Vec3}; pub enum MaterialKind { Lambertian(Lambertian), Metal(Metal), + Dielectric (Dielectric), } pub trait Material { @@ -28,13 +29,12 @@ impl Clone for Lambertian { impl Material for Lambertian { fn scatter(&self, r_in: &Ray, hit_record: &mut HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { - let scatter_direction = r_in.direction + Vec3::random_unit_on_hemisphere(hit_record.normal); - // hit_record.normal + Vec3::random_unit(); - // let scatter_direction = reflect(r_in.direction, hit_record.normal); + let mut scatter_direction = r_in.direction + Vec3::random_unit_on_hemisphere(hit_record.normal); - // if near_zero(scatter_direction) { - // scattered.direction = r_in.direction; - // } + if (near_zero(scatter_direction)) { + scatter_direction = hit_record.normal; + } + *scattered = Ray::new(hit_record.p, scatter_direction); *attenuation = self.albedo.clone(); @@ -66,4 +66,63 @@ impl Material for Metal { *attenuation = self.albedo.clone(); true } +} + +impl Clone for Dielectric { + fn clone(&self) -> Self { + Dielectric { + albedo: self.albedo, + refraction_index: self.refraction_index + } + } +} + +#[derive(Debug)] +pub struct Dielectric { + pub albedo: Color, + pub refraction_index: f32 +} + +impl Material for Dielectric { + fn scatter(&self, r_in: &Ray, hit_record: &mut HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool { + + // attenuation = color(1.0, 1.0, 1.0); + // double ri = rec.front_face ? (1.0/refraction_index) : refraction_index; + + // vec3 unit_direction = unit_vector(r_in.direction()); + // vec3 refracted = refract(unit_direction, rec.normal, ri); + + // scattered = ray(rec.p, refracted); + // return true; + *attenuation = Color::new(1.0, 1.0, 1.0); + let ri = if hit_record.front_face { + 1.0 / self.refraction_index + } else { + self.refraction_index + }; + + let unit_direction = r_in.direction.normalize(); + let refracted = refract(unit_direction, hit_record.normal, ri); + *scattered = Ray::new(hit_record.p, refracted); + + true + + + + + + + + // *attenuation = Color::new(1.0, 1.0, 1.0); + // let ri = if hit_record.front_face { + // 1.0/self.refraction_index + // } else { + // self.refraction_index + // }; + + // let normalized = r_in.direction; + // let refracted = refract(normalized, hit_record.normal, ri); + // *scattered = Ray::new(hit_record.p, refracted); + // true + } } \ No newline at end of file diff --git a/src/math_utils.rs b/src/math_utils.rs index d873f76..d466369 100644 --- a/src/math_utils.rs +++ b/src/math_utils.rs @@ -1,4 +1,4 @@ -use crate::types_defined::Vec3; +use crate::types_defined::{Ray, Vec3}; pub fn clamp(value: f32, low: f32, high: f32) -> f32 { if value < low { @@ -10,12 +10,55 @@ pub fn clamp(value: f32, low: f32, high: f32) -> f32 { return value; } -// pub fn near_zero(v: Vec3) -> bool { -// const EPSILON: f32 = 1e-4; +pub fn near_zero(v: Vec3) -> bool { + const EPSILON: f32 = 1e-4; -// v.x.abs() < EPSILON && v.y.abs() < EPSILON && v.z.abs() < EPSILON -// } + 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) +} + +// v, normal, 折射率 +pub fn refract(uv: Vec3, n: Vec3, etai_over_etat: f32) -> Vec3 { + // auto cos_theta = std::fmin(dot(-uv, n), 1.0); + // vec3 r_out_perp = etai_over_etat * (uv + cos_theta*n); + // vec3 r_out_parallel = -std::sqrt(std::fabs(1.0 - r_out_perp.length_squared())) * n; + // return r_out_perp + r_out_parallel; + let cos_theta = (-uv.dot(n)).min(1.0); + let r_out_perp = etai_over_etat * (uv + cos_theta * n); + let r_out_parallel = -(f32::sqrt(f32::abs(1.0 - r_out_perp.length()))) * n; + r_out_perp + r_out_parallel + + // let v_normalized = v; + // let n_normalized = n.normalize(); + + // let cos_theta = f32::min(-v_normalized.dot(n_normalized), 1.0); + // let sin_theta = (1.0 - cos_theta * cos_theta).sqrt(); + // // 检查是否全内反射(无法折射) + // if etai_over_etat * sin_theta > 1.0 { + // return reflect(v_normalized, n) + // } + + // let r_out_perp = etai_over_etat * (v_normalized + cos_theta * n_normalized); + // let discriminant = 1.0 - r_out_perp.length_squared(); + // // if discriminant < 0.0 { + // // return -1.0 * v; // 全内反射,返回零向量(或改为反射) + // // } + // let r_out_parallel = -discriminant * n_normalized; + + // r_out_perp + r_out_parallel +} + +pub fn is_front_face(r: &Ray, outward_normal: Vec3) -> bool { + r.direction.dot(outward_normal) < 0.0 +} + +pub fn front_face_normal(r: &Ray, outward_normal: Vec3) -> Vec3 { + if is_front_face(r, outward_normal) { + outward_normal + } else { + -1. * outward_normal + } } \ No newline at end of file diff --git a/src/sphere.rs b/src/sphere.rs index 92f7f1e..13f1266 100644 --- a/src/sphere.rs +++ b/src/sphere.rs @@ -1,5 +1,6 @@ use crate::hittable; use crate::material::{MaterialKind}; +use crate::math_utils::{front_face_normal, is_front_face}; use crate::types_defined::{HitRecord, Point, Ray, Sphere}; impl Sphere { @@ -44,12 +45,13 @@ impl hittable::Hittable for Sphere { hit_record.t = root; hit_record.p = p; - hit_record.normal = normal; - hit_record.front_face = r.direction.dot(normal) < 0.0; + hit_record.normal = front_face_normal(r, normal); + hit_record.front_face = is_front_face(r, normal); 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())), + MaterialKind::Dielectric(d) => Some(MaterialKind::Dielectric(d.clone())), } } else { None diff --git a/src/types_defined.rs b/src/types_defined.rs index 8328f6e..ec28bb9 100644 --- a/src/types_defined.rs +++ b/src/types_defined.rs @@ -53,6 +53,7 @@ pub struct Camera<'a> { pub ppm_file_writer: &'a mut PPMWriter> } + /* ///////////// // for test @@ -62,4 +63,4 @@ pub struct Sphere { pub center: Point, pub radius: f32, pub material: Option, -} +} \ No newline at end of file diff --git a/src/vec3.rs b/src/vec3.rs index ebe21ac..05b69e3 100644 --- a/src/vec3.rs +++ b/src/vec3.rs @@ -37,6 +37,18 @@ impl Vec3 { return self.length_squared().sqrt(); } + pub fn normalize(self) -> Vec3 { + let length = self.length_squared().sqrt(); + if length != 0. { + let new_x = self.x / length; + let new_y = self.y / length; + let new_z = self.z / length; + Vec3 { x: new_x, y: new_y, z: new_z } + } else { + Vec3 { x: 0., y: 0., z: 0. } + } + } + pub fn random_range(min: f32, max: f32) -> Self { let rng = &mut thread_rng(); Vec3::new(rng.gen_range(min..max), rng.gen_range(min..max), rng.gen_range(min..max))