added metals and dielectrics

This commit is contained in:
2023-02-02 09:41:11 +01:00
parent 72fb4ce3fd
commit 32cb8b4554
7 changed files with 145 additions and 42 deletions

14
img.ppm

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,5 @@
use crate::ray::Ray;
use crate::rtweekend::degrees_to_radians;
use crate::vec3::*;
pub struct Camera {
@@ -9,10 +10,12 @@ pub struct Camera {
}
impl Camera {
pub fn new() -> Self {
let aspect_ratio = 1.0 / 1.0;
let viewport_height = 2.0;
pub fn new(vfov: f64, aspect_ratio: f64) -> Self {
let theta = degrees_to_radians(vfov);
let h = f64::tan(theta/2.0);
let viewport_height = 2.0 * h;
let viewport_width = aspect_ratio * viewport_height;
let focal_length = 1.0;
let origin = Point3::new(0.0, 0.0, 0.0);

View File

@@ -12,7 +12,7 @@ pub struct HitRecord<'a> {
}
impl HitRecord<'_> {
pub fn new_empty<T: Material>(material: &T) -> Self {
pub fn new_empty<T: Material>(material: &'static T) -> Self {
HitRecord { p: Point3::new(0.0, 0.0, 0.0), normal: Vec3::new(0.0, 0.0, 0.0), t: 0.0, front_face: false, mat_ptr: material }
}

View File

@@ -20,22 +20,23 @@ use crate::{
vec3::*,
ray::*,
hittable::*,
camera::*,
camera::*, material::{Lambertian, Dielectric},
};
fn ray_color(r: &Ray, world: &dyn Hittable, depth: i32) -> Color {
let mut rec = HitRecord::new_empty(&Metal{ albedo: Color::new_empty() });
fn ray_color(r: &Ray, world: &mut dyn Hittable, depth: i32) -> Color {
// Limit the bounces
if depth <= 0 {
return Color::new(0.0, 0.0, 0.0);
}
if let Some(rec) = world.hit(r, 0.001, INFINITY) {
if let Some(rec) = world.hit(r, 0.001, INFINITY) {
let mut scattered = Ray::new_empty();
let mut attenuation = Color::new_empty();
//if rec.mat_ptr
if rec.mat_ptr.scatter(r, rec, &mut attenuation, &mut scattered) {
return attenuation * ray_color(&scattered, world, depth - 1);
}
return Color::new_empty();
}
let unit_direction: Vec3 = r.direction().unit_vector();
@@ -54,11 +55,18 @@ fn main() {
// World
let mut world = HittableList::new_empty();
world.add(Box::new(Sphere::new(Point3::new(0.0, 0.0, -1.0), 0.5)));
world.add(Box::new(Sphere::new(Point3::new(0.0, -100.5, -1.0), 100.0)));
static MATERIAL_GROUND: Lambertian = Lambertian::new(Color::new(0.3, 0.8, 0.0));
static MATERIAL_CENTER: Lambertian = Lambertian::new(Color::new(0.1, 0.2, 0.5));
static MATERIAL_LEFT: Dielectric = Dielectric::new(1.5);
static MATERIAL_RIGHT: Metal = Metal::new(Color::new(0.8, 0.6, 0.2), 0.0);
world.add(Box::new(Sphere::new(Point3::new(0.0, -100.5, -1.0), 100.0, &MATERIAL_GROUND)));
world.add(Box::new(Sphere::new(Point3::new(0.0, 0.0, -1.0), 0.5, &MATERIAL_CENTER)));
world.add(Box::new(Sphere::new(Point3::new(-1.0, 0.0, -1.0), -0.5, &MATERIAL_LEFT)));
world.add(Box::new(Sphere::new(Point3::new(1.0, 0.0, -1.0), 0.5, &MATERIAL_RIGHT)));
// Camera
let cam = Camera::new();
let cam = Camera::new(120.0, aspect_ratio);
// Render
//let mut rng = rand::thread_rng();
@@ -90,4 +98,4 @@ fn main() {
println!("Took {:.6}s", now.elapsed().as_secs_f64());
println!("Took {:.6}m", now.elapsed().as_secs_f64() / 60.0);
println!("Took {:.6}h", now.elapsed().as_secs_f64() / 3600.0);
}
}

View File

@@ -1,51 +1,97 @@
use crate::hittable::HitRecord;
use crate::ray::Ray;
use crate::vec3::{Color, random_unit_vector, reflect};
use crate::rtweekend::random_f64;
use crate::vec3::{Color, random_unit_vector, reflect, refract};
pub trait Material: {
fn scatter(&self, r_in: &Ray, rec: HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool;
}
pub struct Lambertian {
albedo: Color,
pub albedo: Color,
}
impl Lambertian {
pub fn new(a: &Color) -> Self {
Lambertian { albedo: *a }
pub const fn new(albedo: Color) -> Self {
Lambertian { albedo }
}
}
impl Material for Lambertian {
fn scatter(&self, r_in: &Ray, rec: HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool {
let scatter_direction = rec.normal + random_unit_vector();
fn scatter(&self, _r_in: &Ray, rec: HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool {
let mut scatter_direction = rec.normal + random_unit_vector();
if scatter_direction.near_zero() {
scatter_direction = rec.normal;
}
scattered = &mut Ray::new(rec.p, scatter_direction);
attenuation = &mut self.albedo;
*scattered = Ray::new(rec.p, scatter_direction);
*attenuation = self.albedo;
return true;
}
}
pub struct Metal {
pub albedo: Color
pub albedo: Color,
pub fuzz: f64,
}
impl Metal {
pub fn new(a: &Color) -> Self {
Metal { albedo: *a }
pub const fn new(albedo: Color, fuzz: f64) -> Self {
Metal { albedo, fuzz }
}
}
impl Material for Metal {
fn scatter(&self, r_in: &Ray, rec: HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool {
let reflected = reflect(&r_in.direction(), &rec.normal);
scattered = &mut Ray::new(rec.p, rec.normal);
attenuation = &mut self.albedo;
*scattered = Ray::new(rec.p, reflected + self.fuzz*random_unit_vector());
*attenuation = self.albedo;
scattered.direction().dot(&rec.normal) > 0.0
}
}
pub struct Dielectric {
ir: f64,
}
impl Dielectric {
pub const fn new(ir: f64) -> Self {
Dielectric { ir }
}
fn reflectance(&self, cosine: f64, ref_idx: f64) -> f64 {
// Schlick
let mut r0 = (1.0-ref_idx) / (1.0+ref_idx);
r0 = r0*r0;
r0 + (1.0-r0)*f64::powf(1.0 - cosine, 5.0)
}
}
impl Material for Dielectric {
fn scatter(&self, r_in: &Ray, rec: HitRecord, attenuation: &mut Color, scattered: &mut Ray) -> bool {
*attenuation = Color::new(1.0, 1.0, 1.0);
let refraction_ratio: f64 = if rec.front_face {
1.0/self.ir
} else {
self.ir
};
let unit_direction = r_in.direction().unit_vector();
let cos_theta = f64::min(-unit_direction.dot(&rec.normal), 1.0);
let sin_theta = f64::sqrt(1.0 - cos_theta*cos_theta);
let cannot_refract = refraction_ratio * sin_theta > 1.0;
let direction = if cannot_refract || Dielectric::reflectance(self, cos_theta, refraction_ratio) > random_f64() {
reflect(&unit_direction, &rec.normal)
} else {
refract(&unit_direction, &rec.normal, refraction_ratio)
};
*scattered = Ray::new(rec.p, direction);
true
}
}

View File

@@ -9,20 +9,22 @@ pub struct Sphere<'a> {
}
impl Sphere<'_> {
pub fn new<T: Material>(center: Point3, radius: f64, mat_ptr: &T) -> Self {
pub fn new<T: Material>(center: Point3, radius: f64, mat_ptr: &'static T) -> Self {
Sphere {center, radius, mat_ptr}
}
}
impl Hittable for Sphere<'_> {
fn hit(&mut self, r: &crate::ray::Ray, t_min: f64, t_max: f64, rec: &mut HitRecord) -> bool {
fn hit(&mut self, r: &crate::ray::Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let oc = r.origin() - self.center;
let a = r.direction().length_squared();
let half_b = oc.dot(&r.direction());
let c = oc.length_squared() - self.radius*self.radius;
let c = oc.length_squared() - self.radius * self.radius;
let discriminant = half_b*half_b - a*c;
if discriminant < 0.0 { return false; }
let discriminant = half_b * half_b - a * c;
if discriminant < 0.0 {
return None;
}
let sqrtd = discriminant.sqrt();
// Find the nearest root that lies in the acceptable range
@@ -30,16 +32,26 @@ impl Hittable for Sphere<'_> {
if root < t_min || t_max < root {
root = (-half_b + sqrtd) / a;
if root < t_min || t_max < root {
return false;
return None;
}
}
rec.t = root;
rec.p = r.at(rec.t);
let outward_normal = (rec.p - self.center) / self.radius;
rec.set_face_normal(r, &outward_normal);
rec.mat_ptr = self.mat_ptr;
let p = r.at(root);
let outward_normal = (p - self.center) / self.radius;
let front_face = r.direction().dot(&outward_normal) < 0.0;
return true;
let outward_normal = if front_face {
outward_normal
} else {
outward_normal * -1.0
};
Some(HitRecord {
t: root,
p: p,
normal: outward_normal,
front_face,
mat_ptr: self.mat_ptr,
})
}
}

View File

@@ -1,4 +1,4 @@
use std::ops::{Add, Sub, AddAssign, SubAssign, Mul, Div, MulAssign, DivAssign, Neg};
use std::{ops::{Add, Sub, AddAssign, SubAssign, Mul, Div, MulAssign, DivAssign, Neg}};
use crate::rtweekend::{random_f64, random_range_f64};
@@ -8,7 +8,7 @@ pub struct Vec3 {
}
impl Vec3 {
pub fn new(x: f64, y: f64, z: f64) -> Self {
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Vec3 { elements: [x, y, z] }
}
@@ -126,6 +126,20 @@ impl Mul<f64> for Vec3 {
}
}
impl Mul<Self> for Vec3 {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
elements: [
self.x() * rhs.x(),
self.y() * rhs.y(),
self.z() * rhs.z()
]
}
}
}
impl Mul<Vec3> for f64 {
type Output = Vec3;
@@ -225,4 +239,12 @@ pub fn random_in_hemisphere(normal: &Vec3) -> Vec3 {
pub fn reflect(v: &Vec3, n: &Vec3) -> Vec3 {
*v - 2.0 * v.dot(n) * *n
}
pub fn refract(uv: &Vec3, n: &Vec3, etai_over_etat: f64) -> Vec3 {
let cos_theta = f64::min(-uv.dot(&n), 1.0);
let r_out_perp = etai_over_etat * (*uv + cos_theta * *n);
let r_out_parallel = -f64::sqrt(f64::abs(1.0 - r_out_perp.length_squared())) * *n;
r_out_perp + r_out_parallel
}