Added materials
This commit is contained in:
parent
0d8db1ffc0
commit
38fd5264cd
4 changed files with 254 additions and 73 deletions
72
src/main.rs
72
src/main.rs
|
|
@ -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
92
src/material.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
134
src/render.rs
134
src/render.rs
|
|
@ -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>>,
|
||||||
|
|
@ -7,26 +10,129 @@ pub struct World {
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
World {
|
World {
|
||||||
objects: Vec::new(),
|
objects: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, object: impl Hittable + 'static) {
|
pub fn add(&mut self, object: impl Hittable + 'static) {
|
||||||
self.objects.push(Box::new(object));
|
self.objects.push(Box::new(object));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord> {
|
pub fn hit(&self, ray: Ray, interval:Interval) -> Option<HitRecord> {
|
||||||
let mut hit_anything = None;
|
let mut hit_anything = None;
|
||||||
let mut closest_so_far = interval.max();
|
let mut closest_so_far = interval.max();
|
||||||
|
|
||||||
for object in &self.objects {
|
for object in &self.objects {
|
||||||
let i = Interval::new(interval.min(),closest_so_far);
|
let i = Interval::new(interval.min(),closest_so_far);
|
||||||
if let Some(record) = object.hit(ray, i) {
|
if let Some(record) = object.hit(ray, i) {
|
||||||
closest_so_far = record.root();
|
closest_so_far = record.root();
|
||||||
hit_anything = Some(record);
|
hit_anything = Some(record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue