master
max.nuding 2022-06-30 09:54:25 +02:00
commit a86dbb516c
Failed to extract signature
9 changed files with 721 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
*.ppm
.idea
*.iml

75
Cargo.lock generated Normal file
View File

@ -0,0 +1,75 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rustracer"
version = "0.1.0"
dependencies = [
"rand",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "rustracer"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"

45
src/camera.rs Normal file
View File

@ -0,0 +1,45 @@
use crate::{Point3, Ray, Vec3};
pub struct Camera {
aspect_ratio: f64,
viewport_height: f64,
viewport_width: f64,
focal_length: f64,
origin: Point3,
horizontal: Vec3,
vertical: Vec3,
lower_left_corner: Vec3
}
impl Camera {
pub fn get_ray(&self, u: f64, v: f64) -> Ray {
Ray::new(
self.origin,
self.lower_left_corner + u*self.horizontal + v*self.vertical - self.origin)
}
pub fn new(aspect_ratio: f64) -> Self {
let viewport_height = 2.0;
let viewport_width = aspect_ratio * viewport_height;
let origin = Point3::default();
let horizontal = Vec3::new(viewport_width, 0.0, 0.0);
let vertical = Vec3::new(0.0, viewport_height, 0.0);
let focal_length = 1.0;
Camera {
aspect_ratio,
viewport_height,
viewport_width,
focal_length,
origin,
horizontal,
vertical,
lower_left_corner: origin - horizontal/2.0 - vertical/2.0 - Vec3::new(0.0, 0.0, focal_length)
}
}
}
impl Default for Camera {
fn default() -> Self {
let aspect_ratio = 16.0 / 9.0;
Camera::new(aspect_ratio)
}
}

64
src/hittable.rs Normal file
View File

@ -0,0 +1,64 @@
use crate::{Point3, Ray, Vec3};
use crate::material::{Material};
#[derive(Debug, Clone, Copy)]
pub struct HitRecord<'material> {
pub point: Point3,
pub normal: Vec3,
pub t: f64,
pub front_face: bool,
pub material: &'material Material
}
pub trait Hittable {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
}
pub struct Sphere {
pub center: Point3,
pub radius: f64,
pub material: Material
}
impl Sphere {
pub fn new(center: Point3, radius: f64, material: Material) -> Sphere {
Sphere { center, radius, material }
}
}
impl Default for Sphere {
fn default() -> Self {
Sphere {
center: Point3::default(),
radius: 0.0,
material: Material::default()
}
}
}
impl Hittable for Sphere {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let oc = &ray.origin() - &self.center;
let a = ray.direction().length_squared();
let half_b = oc.dot(&ray.direction());
let c = oc.length_squared() - &self.radius * &self.radius;
let discriminant = half_b * half_b - a * c;
if discriminant < 0.0 { return None; }
let sqrtd = f64::sqrt(discriminant);
let root = (-half_b - sqrtd) / a;
if root < t_min || t_max < root {
let root = (-half_b + sqrtd) / a;
if root < t_min || t_max < root { return None; }
}
let point = ray.at(root);
let normal = (point - self.center) / self.radius;
let front_face = ray.direction().dot(&normal) < 0.0;
let normal = if front_face { normal } else { -normal };
Some(HitRecord {
point,
normal,
t: root,
front_face,
material: &self.material
})
}
}

81
src/main.rs Normal file
View File

@ -0,0 +1,81 @@
use crate::camera::Camera;
use crate::hittable::{Hittable, Sphere};
use crate::ray::Ray;
use crate::vec3::{Color, Point3, Vec3};
use rand::distributions::{Distribution, Uniform};
use crate::material::{Dielectric, Lambertian, Material, Metal};
mod vec3;
mod ray;
mod hittable;
mod camera;
mod material;
fn main() {
// Image
const ASPECT_RATIO: f64 = 16.0 / 9.0;
const IMAGE_WIDTH: i32 = 400;
const IMAGE_HEIGHT: i32 = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as i32;
const SAMPLES_PER_PIXEL: i32 = 10;
const MAX_DEPTH: i32 = 50;
// Camera
let cam = Camera::new(ASPECT_RATIO);
// World
let mut world:Vec<Box<dyn Hittable>> = Vec::new();
let center = Box::new(Sphere {
center: Point3::new(0.0, 0.0, -1.0),
radius: 0.5,
material: Material::Lambertian(Lambertian::new(Color::new(0.7, 0.3, 0.3)))
});
let center_glass = Box::new(Sphere {
center: Point3::new(0.0, 0.0, -1.0),
radius: 0.5,
material: Material::Dielectric(Dielectric::new(1.5))
});
world.push(center_glass);
let ground = Sphere {
center: Point3::new(0.0, -100.5, -1.0),
radius: 100.0,
material: Material::Lambertian(Lambertian::new(Color::new(0.8, 0.8, 0.0)))
};
world.push(Box::new(ground));
let left = Box::new(Sphere {
center: Point3::new(-1.0, 0.0, -1.0),
radius: 0.5,
material: Material::Metal(Metal::new(Color::new(0.8, 0.8, 0.8), 0.3))
});
let left_glass = Box::new(Sphere {
center: Point3::new(-1.0, 0.0, -1.0),
radius: 0.5,
material: Material::Dielectric(Dielectric::new(1.5))
});
world.push(left_glass);
let right = Box::new(Sphere {
center: Point3::new(1.0, 0.0, -1.0),
radius: 0.5,
material: Material::Metal(Metal::new(Color::new(0.8, 0.6, 0.2), 1.0))
});
world.push(right);
println!("P3\n{} {}\n255", IMAGE_WIDTH, IMAGE_HEIGHT);
let between = Uniform::from(0.0..1.0);
let mut rng = rand::thread_rng();
for j in (0..IMAGE_HEIGHT).rev() {
eprint!("\rScanlines remaining: {} ", j);
for i in 0..IMAGE_WIDTH {
let mut color = Color::default();
for s in 0..SAMPLES_PER_PIXEL {
let random_number = between.sample(&mut rng);
let u = (i as f64 + random_number) / (IMAGE_WIDTH - 1) as f64;
let v = (j as f64 + random_number) / (IMAGE_HEIGHT - 1) as f64;
let ray = cam.get_ray(u, v);
color += ray.pixel_color(&world, MAX_DEPTH);
}
color.write_color(SAMPLES_PER_PIXEL);
}
}
eprintln!("\nDone");
}

124
src/material.rs Normal file
View File

@ -0,0 +1,124 @@
use rand::Rng;
use crate::hittable::HitRecord;
use crate::{Color, Ray, Vec3};
pub trait Scatterable {
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)>;
}
#[derive(Debug, Clone)]
pub enum Material {
Lambertian(Lambertian),
Metal(Metal),
Dielectric(Dielectric)
}
impl Default for Material {
fn default() -> Self {
Material::Lambertian(Lambertian::new(Color::default()))
}
}
impl Scatterable for Material {
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
match self {
Material::Lambertian(l) => l.scatter(ray, hit_record),
Material::Metal(m) => m.scatter(ray, hit_record),
Material::Dielectric(d) => d.scatter(ray, hit_record)
}
}
}
#[derive(Debug, Clone)]
pub struct Lambertian {
pub albedo: Color
}
impl Lambertian {
pub fn new(albedo: Color) -> Self {
Lambertian { albedo }
}
}
impl Scatterable for Lambertian {
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
let mut direction = hit_record.normal + Vec3::random_unit_vector();
if direction.near_zero() {
direction = hit_record.normal;
}
let scattered = Ray::new(hit_record.point, direction);
Some((Some(scattered), self.albedo))
}
}
#[derive(Debug, Clone)]
pub struct Metal {
pub albedo: Color,
pub fuzz: f64
}
impl Metal {
pub fn new(albedo: Color, fuzz: f64) -> Self {
Metal {
albedo,
fuzz: if fuzz < 1.0 { fuzz} else { 1.0 }
}
}
}
impl Scatterable for Metal {
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
let reflected = ray.direction().unit_vector().reflected(&hit_record.normal);
let scattered = Ray::new(
hit_record.point,
reflected + self.fuzz * Vec3::random_in_unit_sphere());
if scattered.direction().dot(&hit_record.normal) > 0.0 {
Some((Some(scattered), self.albedo))
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct Dielectric { // Glass
pub index_of_refraction: f64
}
impl Dielectric {
pub fn new(index_of_refraction: f64) -> Self {
Dielectric { index_of_refraction }
}
fn reflectance(cosine: f64, ref_idx: f64) -> f64 {
let r0 = (1.0-ref_idx) / (1.0+ref_idx);
let r0 = r0*r0;
r0 + (1.0-r0)*((1.0-cosine).powi(5))
}
}
impl Scatterable for Dielectric {
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
let color = Color::new(1.0, 1.0, 1.0);
let refraction_ratio = if hit_record.front_face {
1.0/self.index_of_refraction
} else {
self.index_of_refraction
};
let unit_direction = ray.direction().unit_vector();
let cos_theta = ((-unit_direction).dot(&hit_record.normal)).min(1.0);
let sin_theta = (1.0 - cos_theta*cos_theta).sqrt();
let cannot_refract = refraction_ratio * sin_theta > 1.0;
let reflectance = Dielectric::reflectance(cos_theta, refraction_ratio);
let mut rng = rand::thread_rng();
if cannot_refract || reflectance > rng.gen::<f64>() {
let reflected = unit_direction.reflected(&hit_record.normal);
let reflected = Vec3::new(-reflected.x(), reflected.y(), -reflected.z());
let scattered = Ray::new(hit_record.point, reflected);
Some((Some(scattered), color))
} else {
let direction = unit_direction.refract_orig(&hit_record.normal, refraction_ratio);
let scattered = Ray::new(hit_record.point, direction);
Some((Some(scattered), color))
}
}
}

66
src/ray.rs Normal file
View File

@ -0,0 +1,66 @@
use crate::{Color, Hittable, Vec3};
use crate::hittable::HitRecord;
use crate::material::Scatterable;
use crate::vec3::Point3;
pub struct Ray {
origin: Point3,
direction: Vec3
}
impl Default for Ray {
fn default() -> Self {
Ray::new(Vec3::default(), Vec3::default())
}
}
impl Ray {
pub fn new(origin: Point3, direction: Point3) -> Ray {
Ray { origin, direction }
}
pub fn at(&self, t: f64) -> Point3 {
self.origin + self.direction * t
}
pub fn direction(&self) -> Vec3 { self.direction }
pub fn origin(&self) -> Point3 { self.origin }
pub fn pixel_color(&self, world: &Vec<Box<dyn Hittable>>, depth: i32) -> Color {
if depth <= 0 {
return Color::default();
}
if let Some(rect) = self.hit_world(world, 0.001, f64::INFINITY) {
let scattered = rect.material.scatter(self, &rect);
return match scattered {
Some((scattered_ray, albedo)) => {
match scattered_ray {
Some(sr) => {
albedo * sr.pixel_color(world, depth-1)
},
None => albedo
}
},
None => { return Color::default() }
};
}
//Hot nothing, display sky color
let unit_direction = self.direction().unit_vector();
let t = 0.5 * (unit_direction.y() + 1.0);
(1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0)
}
fn hit_world<'material>(
&self,
world: &'material Vec<Box<dyn Hittable>>,
t_min: f64,
t_max: f64,
) -> Option<HitRecord<'material>> {
let mut closest_so_far = t_max;
let mut hit_record = None;
for sphere in world {
if let Some(hit) = sphere.hit(self, t_min, closest_so_far) {
closest_so_far = hit.t;
hit_record = Some(hit);
}
}
hit_record
}
}

253
src/vec3.rs Normal file
View File

@ -0,0 +1,253 @@
use std::fmt::{Display, Formatter};
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
use rand::distributions::{Distribution, Uniform};
#[derive(Debug, Clone, Copy)]
pub struct Vec3 { x: f64, y: f64, z: f64 }
pub type Point3 = Vec3;
pub type Color = Vec3;
impl Vec3 {
pub fn new(x: f64, y: f64, z: f64) -> Vec3 { Vec3 {x, y, z} }
pub fn x(&self) -> f64 { self.x }
pub fn y(&self) -> f64 { self.y }
pub fn z(&self) -> f64 { self.z }
pub fn length_squared(&self) -> f64 {
self.x * self.x + self.y * self.y + self.z * self.z
}
pub fn length(&self) -> f64 { self.distance(&Vec3::default()) }
pub fn unit_vector(&self) -> Vec3 {
let length = self.length();
Vec3::new(self.x / length, self.y / length, self.z / length)
}
pub fn dot(&self, v:&Vec3) -> f64 {
self.x * v.x + self.y * v.y + self.z * v.z
}
pub fn cross(&self, v:&Vec3) -> Vec3 {
Vec3::new(
self.y * v.z - self.z * v.y,
self.z * v.x - self.x * v.z,
self.x * v.y - self.y * v.x
)
}
pub fn distance(&self, other: &Vec3) -> f64 {
let dx = self.x - other.x();
let dy = self.y - other.y();
let dz = self.z - other.z();
(dx * dx + dy * dy + dz * dz).sqrt()
}
pub fn random(min: f64, max:f64) -> Self {
let between = Uniform::from(min..max);
let mut rng = rand::thread_rng();
Vec3::new(
between.sample(&mut rng),
between.sample(&mut rng),
between.sample(&mut rng))
}
pub fn random_in_unit_sphere() -> Self {
loop {
let v = Vec3::random(-1.0, 1.0);
if v.length_squared() < 1.0 {
return v;
}
}
}
pub fn random_in_hemisphere(normal: &Vec3) -> Self {
let vec = Vec3::random_in_unit_sphere();
if vec.dot(normal) > 0.0 {
vec
} else {
-vec
}
}
pub fn random_unit_vector() -> Self { Vec3::random_in_unit_sphere().unit_vector() }
pub fn near_zero(&self) -> bool {
const MAXIMUM_DISTANCE_FROM_ZERO:f64 = 1e-8;
self.x.abs() < MAXIMUM_DISTANCE_FROM_ZERO &&
self.y.abs() < MAXIMUM_DISTANCE_FROM_ZERO &&
self.z.abs() < MAXIMUM_DISTANCE_FROM_ZERO
}
pub fn reflected(&self, normal: &Vec3) -> Vec3 {
let dp = self.dot(normal);
let dp = dp * 2.0 * (*normal);
*self - dp
}
pub fn refract(&self, normal: &Vec3, etai_over_etat: f64) -> Vec3 {
let dot = (-(*self)).dot(normal);
let cos_theta = dot.min(1.0);
let out_perp = etai_over_etat * ((*self) + cos_theta * (*normal));
let inner = 1.0 - out_perp.length_squared();
let abs = inner.abs();
let r = -(abs.sqrt());
out_parallel = r * (*normal);
out_perp + out_parallel
}
pub fn refract_sort_of_works(&self, normal: &Vec3, etai_over_etat: f64) -> Vec3 {
let dot = (-(*self)).dot(normal);
let cos_theta = dot.min(1.0);
let out_perp = etai_over_etat * ((*self) + cos_theta * (*normal));
let inner = 1.0 - out_perp.length_squared();
let abs = inner.abs();
let r = -(abs.sqrt());
let out_parallel = r * (*normal);
// Why?
let mut res = out_perp + out_parallel;
res.x = -res.x;
//res.y = -res.y;
res.z = -res.z;
res
}
}
#[test]
fn test_refract() {
let uv = Point3::new(1.0, 1.0, 0.0);
let n = Point3::new(-1.0, 0.0, 0.0);
let etai_over_etat = 1.0;
let expected = Point3::new(0.0, 1.0, 0.0);
let actual = uv.refract_orig( &n, etai_over_etat);
assert_eq!(actual, expected);
}
impl Color {
pub fn write_color(self: Color, samples_per_pixel: i32) {
let scale = 1.0 / samples_per_pixel as f64;
let r = f64::sqrt(scale * self.x);
let g = f64::sqrt(scale * self.y);
let b = f64::sqrt(scale * self.z);
let r = 256.0 * f64::clamp(r, 0.0, 0.999);
let g = 256.0 * f64::clamp(g, 0.0, 0.999);
let b = 256.0 * f64::clamp(b, 0.0, 0.999);
println!("{} {} {}", r as i32, g as i32, b as i32);
}
}
impl Default for Vec3 {
fn default() -> Self { Vec3 {x: 0.0, y: 0.0, z: 0.0} }
}
impl Display for Vec3 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {} {}", self.x, self.y, self.z)
}
}
impl Add for Vec3 {
type Output = Vec3;
fn add(self, other: Vec3) -> Vec3 {
Vec3 {
x: self.x + other.x(),
y: self.y + other.y(),
z: self.z + other.z(),
}
}
}
impl AddAssign for Vec3 {
fn add_assign(&mut self, rhs: Self) {
self.x += rhs.x;
self.y += rhs.y;
self.z += rhs.z;
}
}
impl Sub for Vec3 {
type Output = Vec3;
fn sub(self, other: Vec3) -> Vec3 {
Vec3 {
x: self.x - other.x(),
y: self.y - other.y(),
z: self.z - other.z(),
}
}
}
impl Sub for &Vec3 {
type Output = Vec3;
fn sub(self, other: &Vec3) -> Vec3 {
Vec3 {
x: self.x - other.x(),
y: self.y - other.y(),
z: self.z - other.z(),
}
}
}
impl Neg for Vec3 {
type Output = Vec3;
fn neg(self) -> Vec3 {
Vec3 {
x: -self.x,
y: -self.y,
z: -self.z,
}
}
}
impl Mul<Vec3> for Vec3 {
type Output = Vec3;
fn mul(self, other: Vec3) -> Vec3 {
Vec3 {
x: self.x * other.x(),
y: self.y * other.y(),
z: self.z * other.z(),
}
}
}
impl Mul<f64> for Vec3 {
type Output = Vec3;
fn mul(self, other: f64) -> Vec3 {
Vec3 {
x: self.x * other,
y: self.y * other,
z: self.z * other,
}
}
}
impl Mul<Vec3> for f64 {
type Output = Vec3;
fn mul(self, other: Vec3) -> Vec3 {
other * self
}
}
impl Div<Vec3> for Vec3 {
type Output = Vec3;
fn div(self, other: Vec3) -> Vec3 {
Vec3 {
x: self.x / other.x(),
y: self.y / other.y(),
z: self.z / other.z(),
}
}
}
impl Div<f64> for Vec3 {
type Output = Vec3;
fn div(self, other: f64) -> Vec3 {
Vec3 {
x: self.x / other,
y: self.y / other,
z: self.z / other,
}
}
}
impl PartialEq for Vec3 {
fn eq(&self, other: &Vec3) -> bool {
self.x == other.x() && self.y == other.y() && self.z == other.z()
}
}