ft: 10: metals

This commit is contained in:
2026-04-16 14:04:55 +02:00
parent e8f84b590b
commit b756cc394a
12 changed files with 199 additions and 41 deletions

10
Cargo.lock generated
View File

@@ -674,6 +674,15 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "is_close"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4d49e01f14871e71a0dce6f09eb72308bca74a4adb1bab5a8c34ee26979a8a2"
dependencies = [
"num-traits",
]
[[package]]
name = "itertools"
version = "0.14.0"
@@ -1219,6 +1228,7 @@ name = "raytracing"
version = "0.1.0"
dependencies = [
"image",
"is_close",
"log",
"ops",
"pretty_env_logger",

View File

@@ -5,6 +5,7 @@ edition = "2024"
[dependencies]
image = "0.25.10"
is_close = "0.1.3"
log = "0.4.29"
ops = "0.6.0"
pretty_env_logger = "0.5.0"

View File

@@ -20,10 +20,7 @@ pub struct Camera {
}
impl Camera {
pub fn new(aspect_ratio: f32, image_width: u32) -> Camera {
Camera::new_aa(aspect_ratio, image_width, 1)
}
pub fn new_aa(aspect_ratio: f32, image_width: u32, anti_alias_rate: u32) -> Camera {
pub fn new(aspect_ratio: f32, image_width: u32, anti_alias_rate: u32, max_depth: u32) -> Self {
let image_height = max(1, (image_width as f32 / aspect_ratio) as u32);
//camera
@@ -42,11 +39,11 @@ impl Camera {
let pixel00_loc =
camera_center - Vec3::new(0., 0., focal_length) - viewport_u / 2 - viewport_v / 2;
Camera {
Self {
image_width: image_width,
image_height: image_height,
anti_alias_rate: anti_alias_rate,
max_depth: 50,
max_depth: max_depth,
center: camera_center,
pixel00_loc: pixel00_loc,
pixel_delta_u: pixel_delta_u,
@@ -93,8 +90,10 @@ impl Camera {
let closest = Hit::hit_list(hittables, r);
if let Some(hit) = closest {
let dir = *hit.n() + Vec3::random_unit_hemisphere(hit.n());
return 0.5 * self.ray_colour(hittables, &Ray::new(*hit.p(), dir), depth - 1);
if let Some((scattered, att)) = hit.mat().scatter(&hit, r) {
return att * self.ray_colour(hittables, &scattered, depth - 1);
}
return Colour::nil();
}
// background

View File

@@ -5,7 +5,10 @@ mod objects;
mod ray;
mod vec3;
use std::sync::Arc;
use crate::camera::Camera;
use crate::objects::materials::lambertian::{Lambertian, Metal};
use crate::objects::sphere::Sphere;
use crate::ray::Ray;
use crate::vec3::Vec3;
@@ -15,10 +18,18 @@ fn main() {
pretty_env_logger::init();
// setup objects
let mut world = vec![Sphere::new(Vec3::new(0., 0., -1.), 0.5)];
world.push(Sphere::new(Vec3::new(0., 0., -0.1), 0.01));
world.push(Sphere::new(Vec3::new(0., -100.5, -1.), 100.));
let metal = Arc::new(Metal::rgb(0.7, 0.4, 0.2, 1., 0.1));
let ground = Arc::new(Lambertian::rgb(0.8, 0.8, 0., 1.0));
let center = Arc::new(Lambertian::rgb(0.1, 0.2, 0.5, 1.));
let left = Arc::new(Metal::rgb(0.8, 0.8, 0.8, 1., 0.3));
let right = Arc::new(Metal::rgb(0.8, 0.6, 0.2, 1., 1.0));
let c = Camera::new_aa(16. / 9., 400, 3);
let mut world = vec![Sphere::xyz(0., 0.5, -0.8, 0.1, metal.clone())];
world.push(Sphere::xyz(0., -100.5, -1., 100., ground.clone()));
world.push(Sphere::xyz(0., 0., -1.2, 0.5, center.clone()));
world.push(Sphere::xyz(-1., 0., -1.0, 0.5, left.clone()));
world.push(Sphere::xyz(1., 0., -1.0, 0.5, right.clone()));
let c = Camera::new(16. / 9., 800, 2, 50);
c.render(&world);
}

View File

@@ -1,3 +1,4 @@
pub mod hit;
pub mod materials;
pub mod sphere;
pub mod traits;

View File

@@ -1,14 +1,26 @@
use crate::{objects::traits::Hittable, ray::Ray, vec3::Vec3};
use std::sync::Arc;
use crate::{
objects::{materials::traits::Material, traits::Hittable},
ray::Ray,
vec3::Vec3,
};
pub struct Hit {
t: f32,
p: Vec3,
n: Vec3,
mat: Arc<dyn Material>,
}
impl Hit {
pub fn new(t: f32, p: Vec3, n: Vec3) -> Hit {
Hit { t: t, p: p, n: n }
pub fn new(t: f32, p: Vec3, n: Vec3, mat: Arc<dyn Material>) -> Self {
Self {
t: t,
p: p,
n: n,
mat: mat,
}
}
pub fn t(&self) -> &f32 {
@@ -23,6 +35,10 @@ impl Hit {
&self.n
}
pub fn mat(&self) -> Arc<dyn Material> {
self.mat.clone()
}
pub fn hit_list<T: Hittable>(hittables: &Vec<T>, r: &Ray) -> Option<Hit> {
let mut closest: Option<Hit> = None;
for hittable in hittables {

2
src/objects/materials.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod lambertian;
pub mod traits;

View File

@@ -0,0 +1,79 @@
use rand::RngExt;
use crate::{
objects::{hit::Hit, materials::traits::Material},
ray::Ray,
vec3::{Colour, Vec3},
};
pub struct Lambertian {
albedo: Colour,
prob: f32,
}
impl Lambertian {
pub fn new(albedo: Colour, prob: f32) -> Self {
Self {
albedo: albedo,
prob: prob,
}
}
pub fn rgb(r: f32, g: f32, b: f32, prob: f32) -> Self {
Self {
albedo: Colour::new(r, g, b),
prob: prob,
}
}
}
impl Material for Lambertian {
fn scatter(&self, hit: &Hit, ray: &Ray) -> Option<(Ray, Colour)> {
let mut rng = rand::rng();
if self.prob >= rng.random::<f32>() {
let mut dir = *hit.n() + Vec3::random_unit();
if dir.near_zero() {
dir = *hit.n();
}
let scattered = Ray::new(*hit.p(), dir);
return Some((scattered, self.albedo));
}
return None;
}
}
pub struct Metal {
albedo: Colour,
prob: f32,
fuzz: f32,
}
impl Metal {
pub fn new(albedo: Colour, prob: f32, fuzz: f32) -> Self {
Self {
albedo: albedo,
prob: prob,
fuzz: fuzz,
}
}
pub fn rgb(r: f32, g: f32, b: f32, prob: f32, fuzz: f32) -> Self {
Self {
albedo: Colour::new(r, g, b),
prob: prob,
fuzz: fuzz,
}
}
}
impl Material for Metal {
fn scatter(&self, hit: &Hit, ray: &Ray) -> Option<(Ray, Colour)> {
let mut rng = rand::rng();
if self.prob >= rng.random::<f32>() {
let mut refl = ray.dir().reflect(hit.n());
refl = refl.get_unit() + self.fuzz * Vec3::random_unit();
return Some((Ray::new(*hit.p(), refl), self.albedo));
}
return None;
}
}

View File

@@ -0,0 +1,5 @@
use crate::{objects::hit::Hit, ray::Ray, vec3::Colour};
pub trait Material {
fn scatter(&self, hit: &Hit, ray: &Ray) -> Option<(Ray, Colour)>;
}

View File

@@ -1,19 +1,31 @@
use core::f32::math::sqrt;
use std::sync::Arc;
use crate::objects::hit::Hit;
use crate::objects::materials::traits::Material;
use crate::objects::traits::Hittable;
use crate::Vec3;
pub struct Sphere {
center: Vec3,
radius: f32,
material: Arc<dyn Material>,
}
impl Sphere {
pub fn new(center: Vec3, radius: f32) -> Sphere {
Sphere {
pub fn new(center: Vec3, r: f32, mat: Arc<dyn Material>) -> Self {
Self {
center: center,
radius: radius,
radius: r,
material: mat,
}
}
pub fn xyz(x: f32, y: f32, z: f32, r: f32, mat: Arc<dyn Material>) -> Self {
Self {
center: Vec3::new(x, y, z),
radius: r,
material: mat,
}
}
}
@@ -31,7 +43,7 @@ impl Hittable for Sphere {
} else {
let t = (h - sqrt(d)) / a;
let p = r.at(t);
Some(Hit::new(t, p, self.normal_at(&p)))
Some(Hit::new(t, p, self.normal_at(&p), self.material.clone()))
}
}

View File

@@ -6,17 +6,17 @@ pub struct Ray {
}
impl Ray {
pub fn at(&self, t: f32) -> Vec3 {
self.origin + t * self.dir
}
pub fn new(origin: Vec3, dir: Vec3) -> Ray {
Ray {
pub fn new(origin: Vec3, dir: Vec3) -> Self {
Self {
origin: origin,
dir: dir,
}
}
pub fn at(&self, t: f32) -> Vec3 {
self.origin + t * self.dir
}
pub fn origin(&self) -> &Vec3 {
&self.origin
}

View File

@@ -1,11 +1,11 @@
use core::f32::math::sqrt;
use is_close::default;
use rand::RngExt;
use std::{
fmt::Display,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
};
use rand::RngExt;
#[derive(Copy, Clone)]
pub struct Vec3 {
r: f32,
@@ -16,11 +16,11 @@ pub struct Vec3 {
pub type Colour = Vec3;
impl Vec3 {
pub fn new(r: f32, g: f32, b: f32) -> Vec3 {
pub fn new(r: f32, g: f32, b: f32) -> Self {
Self { r: r, g: g, b: b }
}
pub fn nil() -> Vec3 {
pub fn nil() -> Self {
Self {
r: 0.,
g: 0.,
@@ -28,26 +28,26 @@ impl Vec3 {
}
}
pub fn random() -> Vec3 {
pub fn random() -> Self {
let mut rng = rand::rng();
Vec3 {
Self {
r: rng.random_range(-1.0..1.),
g: rng.random_range(-1.0..1.),
b: rng.random_range(-1.0..1.),
}
}
pub fn random_unit() -> Vec3 {
pub fn random_unit() -> Self {
loop {
let v = Vec3::random();
let v = Self::random();
if v.length_squared() <= 1. {
return v / v.length();
}
}
}
pub fn random_unit_hemisphere(n: &Vec3) -> Vec3 {
let v = Vec3::random_unit();
pub fn random_unit_hemisphere(n: &Self) -> Self {
let v = Self::random_unit();
if n.dot(&v) > 0.0 {
v
} else {
@@ -75,12 +75,12 @@ impl Vec3 {
(self.r * self.r + self.g * self.g + self.b * self.b) as f32
}
pub fn dot(&self, other: &Vec3) -> f32 {
pub fn dot(&self, other: &Self) -> f32 {
self.r * other.r + self.g * other.g + self.b * other.b
}
pub fn cross(&self, other: &Vec3) -> Vec3 {
Vec3 {
pub fn cross(&self, other: &Self) -> Self {
Self {
r: self.g * other.b - self.b * other.g,
g: self.b * other.r - self.r * other.b,
b: self.r * other.g - self.g * other.r,
@@ -91,10 +91,20 @@ impl Vec3 {
self /= self.length();
}
pub fn get_unit(self) -> Vec3 {
pub fn get_unit(self) -> Self {
self / self.length()
}
pub fn near_zero(&self) -> bool {
default().is_close(self.r, 0.)
&& default().is_close(self.g, 0.)
&& default().is_close(self.b, 0.)
}
pub fn reflect(&self, o: &Vec3) -> Vec3 {
*self - 2. * self.dot(o) * o
}
pub fn output(self) -> image::Rgb<u8> {
// gamma correction
let r = if self.r > 0. {
@@ -120,8 +130,8 @@ impl Vec3 {
image::Rgb([ir, ig, ib])
}
pub fn clone(&self) -> Vec3 {
Vec3 {
pub fn clone(&self) -> Self {
Self {
r: self.r,
g: self.g,
b: self.b,
@@ -289,6 +299,18 @@ impl Mul<Vec3> for f32 {
}
}
impl Mul<&Vec3> for f32 {
type Output = Vec3;
fn mul(self, rhs: &Vec3) -> Self::Output {
Vec3 {
r: rhs.r * self,
g: rhs.g * self,
b: rhs.b * self,
}
}
}
impl MulAssign<Vec3> for Vec3 {
fn mul_assign(&mut self, rhs: Self) {
*self = Self {