Added materials

This commit is contained in:
k 2026-04-10 09:06:55 -04:00
parent 0d8db1ffc0
commit 38fd5264cd
4 changed files with 254 additions and 73 deletions

View file

@ -2,69 +2,47 @@ mod image;
mod object; mod object;
mod render; mod render;
mod vec3; mod vec3;
mod material;
use image::Image;
use object::Sphere; use object::Sphere;
use render::World; use render::{World,Camera};
use vec3::{Point3, Ray, Vec3}; use vec3::{Point3,Vec3};
use material::{Metal,Lambertian,Material};
use crate::vec3::Interval;
fn main() { fn main() {
let aspect_ratio = 16.0 / 9.0; let aspect_ratio = 16.0 / 9.0;
let image_width = 800; let image_width = 800;
let image_height = (image_width as f64 / aspect_ratio) as i32;
let focal_length = 1.0; let focal_length = 1.0;
let viewport_height = 2.0; let viewport_height = 2.0;
let viewport_width = viewport_height * (image_width as f64 / image_height as f64);
let camera_center = Point3::new(0.0, 0.0, 0.0); let camera_center = Point3::new(0.0, 0.0, 0.0);
let viewport_u = Vec3::new(viewport_width as f64, 0.0, 0.0);
let viewport_v = Vec3::new(0.0, -viewport_height as f64, 0.0);
let pixel_delta_u = viewport_u / image_width as f64; let m = Lambertian::new(Vec3::new(0.5,0.5,0.5));
let pixel_delta_v = viewport_v / image_height as f64; let b: Box <dyn Material> = Box::new(m);
let world_m = std::rc::Rc::new(b);
let viewport_upper_left = (camera_center.to_vec() let m = Lambertian::new(Vec3::new(0.1, 0.2, 0.5));
- Vec3::new(0.0, 0.0, focal_length) let b: Box <dyn Material> = Box::new(m);
- viewport_u / 2.0 let center_m = std::rc::Rc::new(b);
- viewport_v / 2.0)
.to_point();
let pixel00_loc = viewport_upper_left + ((pixel_delta_u + pixel_delta_v) * 0.5).to_point(); let m = Metal::new(Vec3::new(0.8, 0.8, 0.8), 0.3);
let b: Box <dyn Material> = Box::new(m);
let left_m = std::rc::Rc::new(b);
let mut img = Image::new(image_width, image_height);
let m = Metal::new(Vec3::new(0.8, 0.6, 0.2), 0.5);
let b: Box <dyn Material> = Box::new(m);
let right_m = std::rc::Rc::new(b);
let mut camera = Camera::new(aspect_ratio,image_width,focal_length,viewport_height,camera_center);
let mut world = World::new(); let mut world = World::new();
world.add(Sphere::new(0.5, Point3::new(0.0, 0.0, -1.0))); world.add(Sphere::new(0.5, Point3::new(0.0, 0.0, -1.0),center_m));
world.add(Sphere::new(100.0, Point3::new(0.0, -100.5, -1.0))); world.add(Sphere::new(0.5, Point3::new(-1.0, 0.0, -1.0),left_m));
world.add(Sphere::new(0.5, Point3::new(1.0, 0.0, -1.0),right_m));
world.add(Sphere::new(100.0, Point3::new(0.0, -100.5, -1.0),world_m));
for y in 0..img.height { camera.render(&world);
for x in 0..img.width {
if let Some(px) = img.get_pixel(x, y) {
let pixel_center = pixel00_loc
+ ((pixel_delta_u * x as f64) + (pixel_delta_v * y as f64)).to_point();
let ray_direction = (pixel_center - camera_center).to_vec();
let ray = Ray::new(camera_center, ray_direction);
let (r, g, b) = color_ray(ray);
let i = Interval::new(0.001,f64::INFINITY);
if let Some(hit) = world.hit(ray, i) {
let (r,g,b) = hit.color();
px.set_color(r,g,b);
} else {
px.set_color(r, g, b);
}
} else {
}
}
}
img.save("./foo.ppm").unwrap();
}
fn color_ray(ray: Ray) -> (f64, f64, f64) {
let unit_direction = ray.direction().unit();
let a = 0.5 * unit_direction.y() + 1.0;
let b = 1.0 - a;
(b + 0.5 * a, b + 0.7 * a, b + 1.0 * a)
} }

92
src/material.rs Normal file
View file

@ -0,0 +1,92 @@
use crate::vec3::{Ray,Vec3};
use crate::object::HitRecord;
use rand::prelude::*;
pub struct Scatter {
pub attenuation: Vec3,
pub ray: Ray,
}
pub trait Material {
fn scatter(&self, r_in: &Ray, rec: &HitRecord, rng: &mut ThreadRng) -> Option<Scatter>;
}
#[derive(Clone, Copy)]
pub struct Metal {
pub albedo: Vec3,
pub fuzz: f64,
}
impl Metal {
pub fn new(albedo: Vec3, f: f64) -> Self {
Self {
albedo,
fuzz: if f < 1.0 { f } else { 1.0 },
}
}
}
impl Material for Metal {
fn scatter(&self, r_in: &Ray, rec: &HitRecord, rng: &mut ThreadRng) -> Option<Scatter> {
let reflected = reflect(r_in.direction().unit(), rec.normal());
let scattered_ray = Ray::new(
rec.point(),
reflected + random_in_unit_sphere(rng) * self.fuzz
);
if scattered_ray.direction().dot(rec.normal()) > 0.0 {
Some(Scatter {
attenuation: self.albedo,
ray: scattered_ray,
})
} else {
None
}
}
}
#[derive(Clone, Copy)]
pub struct Lambertian {
pub albedo: Vec3,
}
impl Lambertian {
pub fn new(albedo: Vec3) -> Self {
Self {
albedo,
}
}
}
impl Material for Lambertian {
fn scatter(&self, _r_in: &Ray, rec: &HitRecord, rng: &mut ThreadRng) -> Option<Scatter> {
let target = rec.point().to_vec() + rec.normal() + random_in_unit_sphere(rng);
Some(Scatter {
attenuation: self.albedo,
ray: Ray::new(rec.point(), target - rec.point().to_vec()),
})
}
}
fn reflect(v: Vec3, n: Vec3) -> Vec3 {
v - n * 2.0 * v.dot(n)
}
fn random_in_unit_sphere(rng: &mut ThreadRng) -> Vec3 {
loop {
let p = Vec3::new(
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
rng.random_range(-1.0..1.0),
);
if p.length_squared() < 1.0 {
return p;
}
}
}

View file

@ -1,49 +1,54 @@
use crate::vec3::{Point3, Ray, Vec3, Interval}; use crate::vec3::{Point3, Ray, Vec3, Interval};
use crate::material::{Material,Scatter};
use rand::prelude::*;
pub trait Hittable { pub trait Hittable {
fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord>; fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord>;
} }
#[derive(Clone, Copy)]
pub struct HitRecord { pub struct HitRecord {
point: Point3, point: Point3,
normal: Vec3, normal: Vec3,
root: f64, root: f64,
front_face: bool, front_face: bool,
color: (f64,f64,f64) material: std::rc::Rc<Box<dyn Material>>,
} }
impl HitRecord { impl HitRecord {
pub fn normal(self) -> Vec3 { pub fn normal(&self) -> Vec3 {
self.normal self.normal
} }
pub fn point(self) -> Point3 { pub fn point(&self) -> Point3 {
self.point self.point
} }
pub fn root(self) -> f64 { pub fn root(&self) -> f64 {
self.root self.root
} }
pub fn color(self) -> (f64,f64,f64) {
self.color
}
pub fn set_face_normal(&mut self, ray: Ray, norm: Vec3) { pub fn set_face_normal(&mut self, ray: Ray, norm: Vec3) {
self.front_face = ray.direction().dot(norm) < 0.0; self.front_face = ray.direction().dot(norm) < 0.0;
self.normal = if self.front_face { norm } else { norm * -1.0 }; self.normal = if self.front_face { norm } else { norm * -1.0 };
} }
pub fn scatter(&self, r_in: &Ray, rng: &mut ThreadRng) -> Option<Scatter>{
self.material.scatter(r_in,&self,rng)
}
} }
pub struct Sphere { pub struct Sphere {
radius: f64, radius: f64,
center: Point3, center: Point3,
material: std::rc::Rc<Box<dyn Material>>
} }
impl Sphere { impl Sphere {
pub fn new(radius: f64, center: Point3) -> Self { pub fn new(radius: f64, center: Point3, material:std::rc::Rc<Box<dyn Material>>) -> Self {
Sphere { radius, center } Sphere { radius, center , material}
} }
} }
@ -74,7 +79,7 @@ impl Hittable for Sphere {
point, point,
normal, normal,
front_face: false, front_face: false,
color: (normal.x(),normal.y(),normal.z()) material: self.material.clone()
}; };
tmp.set_face_normal(ray, normal); tmp.set_face_normal(ray, normal);
Some(tmp) Some(tmp)

View file

@ -1,5 +1,8 @@
use crate::object::{HitRecord, Hittable}; use crate::object::{HitRecord, Hittable};
use crate::vec3::{Interval, Ray}; use crate::vec3::{Interval, Ray, Vec3, Point3};
use crate::image::Image;
use rand::prelude::*;
pub struct World { pub struct World {
pub objects: Vec<Box<dyn Hittable>>, pub objects: Vec<Box<dyn Hittable>>,
@ -30,3 +33,106 @@ impl World {
hit_anything hit_anything
} }
} }
pub struct Camera {
img: Image,
pixel00_loc: Point3,
camera_center: Point3,
pixel_delta_u: Vec3,
pixel_delta_v: Vec3
}
impl Camera {
pub fn new(aspect_ratio:f64,image_width: i32, focal_length:f64, viewport_height:f64, camera_center: Point3) -> Self{
let image_height = (image_width as f64 / aspect_ratio) as i32;
let viewport_width = viewport_height * (image_width as f64 / image_height as f64);
let viewport_u = Vec3::new(viewport_width as f64, 0.0, 0.0);
let viewport_v = Vec3::new(0.0, -viewport_height as f64, 0.0);
let pixel_delta_u = viewport_u / image_width as f64;
let pixel_delta_v = viewport_v / image_height as f64;
let viewport_upper_left = (camera_center.to_vec()
- Vec3::new(0.0, 0.0, focal_length)
- viewport_u / 2.0
- viewport_v / 2.0).to_point();
let pixel00_loc = viewport_upper_left + ((pixel_delta_u + pixel_delta_v) * 0.5).to_point();
let img = Image::new(image_width, image_height);
Self{img,pixel00_loc,camera_center,pixel_delta_u,pixel_delta_v}
}
pub fn render(&mut self, world: &World){
let mut rng:ThreadRng = rand::rng();
for y in 0..self.img.height {
for x in 0..self.img.width {
let (mut r,mut g,mut b) = (0.0,0.0,0.0);
let num_samples=10.0;
let depth=10;
for _ in 0..num_samples as i32{
let ray = self.get_ray(x,y,&mut rng);
let (tr,tg,tb)= trace(ray, &world,depth,&mut rng);
r+= tr;
g+= tg;
b+= tb;
}
r /= num_samples;
g /= num_samples;
b /= num_samples;
r = r.clamp(0.0,1.0).sqrt();
g = g.clamp(0.0,1.0).sqrt();
b = b.clamp(0.0,1.0).sqrt();
if let Some(px) = self.img.get_pixel(x,y){
px.set_color(r,g,b);
} else{/*ignore*/}
}
}
self.img.save("./foo.ppm").unwrap();
}
fn get_ray(&self, x:i32,y:i32,rng:&mut ThreadRng) -> Ray{
let offset = 0.005;
let rnd_vec = Vec3::new(rng.random_range(-offset..offset),rng.random_range(-offset..offset),0.0);
let pixel_center = self.pixel00_loc
+ ((self.pixel_delta_u * (x as f64 + rnd_vec.x())) + (self.pixel_delta_v * (y as f64 + rnd_vec.y()))).to_point();
let ray_direction = (pixel_center - self.camera_center).to_vec();
Ray::new(self.camera_center, ray_direction)//+rnd_vec)
}
}
fn trace(ray: Ray, world: &World, depth: i32, rng: &mut ThreadRng) -> (f64, f64, f64) {
let i = Interval::new(0.001, f64::INFINITY);
if depth <= 0 {
return (0.0, 0.0, 0.0);
}
if let Some(hit) = world.hit(ray, i) {
if let Some(scatter_record) = hit.scatter(&ray, rng) {
let (tr, tg, tb) = trace(scatter_record.ray, world, depth - 1, rng);
let r = scatter_record.attenuation.x() * tr;
let g = scatter_record.attenuation.y() * tg;
let b = scatter_record.attenuation.z() * tb;
return (r, g, b);
} else {
return (0.0, 0.0, 0.0);
}
} else {
color_ray(ray)
}
}
fn color_ray(ray: Ray) -> (f64, f64, f64) {
let unit_direction = ray.direction().unit();
let a = 0.5 * unit_direction.y() + 1.0;
let b = 1.0 - a;
(b + 0.5 * a, b + 0.7 * a, b + 1.0 * a)
}