|
@@ -1,6 +1,7 @@
|
|
|
use std::fs::File;
|
|
|
use std::io::BufReader;
|
|
|
use std::io::Read;
|
|
|
+use std::io::BufRead;
|
|
|
|
|
|
// ┏━┓┏━┓┏┓╻╺┳┓┏━┓┏┳┓ ┏┓╻╻ ╻┏┳┓┏┓ ┏━╸┏━┓ ┏━╸┏━╸┏┓╻┏━╸┏━┓┏━┓╺┳╸╻┏━┓┏┓╻
|
|
|
// ┣┳┛┣━┫┃┗┫ ┃┃┃ ┃┃┃┃ ┃┗┫┃ ┃┃┃┃┣┻┓┣╸ ┣┳┛ ┃╺┓┣╸ ┃┗┫┣╸ ┣┳┛┣━┫ ┃ ┃┃ ┃┃┗┫
|
|
@@ -17,9 +18,8 @@ impl RNG {
|
|
|
self.random_device
|
|
|
.read(&mut buf)
|
|
|
.expect("Read from /dev/random failed");
|
|
|
-
|
|
|
let val: f32 = u32::from_be_bytes(buf) as f32;
|
|
|
- val as f32 / u32::max_value() as f32
|
|
|
+ f32::from(val) / (u32::max_value() as f32 + 1.0)
|
|
|
}
|
|
|
|
|
|
pub fn create() -> RNG {
|
|
@@ -43,13 +43,257 @@ pub struct Vec3 {
|
|
|
z: f32,
|
|
|
}
|
|
|
|
|
|
+impl Vec3 {
|
|
|
+ pub fn new(x: f32, y: f32, z: f32) -> Vec3 {
|
|
|
+ return Vec3 {
|
|
|
+ x: x,
|
|
|
+ y: y,
|
|
|
+ z: z
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn length(&self) -> f32 {
|
|
|
+ return (*self * *self).sqrt();
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn distance_to(&self, other: Vec3) -> f32 {
|
|
|
+ return (*self - other).length();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn normalize(&mut self) {
|
|
|
+ let l = self.length();
|
|
|
+ self.x /= l;
|
|
|
+ self.y /= l;
|
|
|
+ self.z /= l;
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn normalized(&self) -> Vec3 {
|
|
|
+ let mut other = self.clone();
|
|
|
+ other.normalize();
|
|
|
+ return other;
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn cross(&self, _rhs: Vec3) -> Vec3 {
|
|
|
+ return Vec3 {
|
|
|
+ x: self.y * _rhs.z - self.z * _rhs.y,
|
|
|
+ y: self.z * _rhs.x - self.x * _rhs.z,
|
|
|
+ z: self.x * _rhs.y - self.y * _rhs.x,
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl std::fmt::Display for Vec3 {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ write!(f, "({}, {}, {})", self.x, self.y, self.z);
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl std::ops::Add<Vec3> for Vec3 {
|
|
|
+ type Output = Vec3;
|
|
|
+
|
|
|
+ fn add(self, _rhs: Vec3) -> Vec3 {
|
|
|
+ return Vec3 {
|
|
|
+ x: self.x + _rhs.x,
|
|
|
+ y: self.y + _rhs.y,
|
|
|
+ z: self.z + _rhs.z,
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl std::ops::AddAssign for Vec3 {
|
|
|
+ fn add_assign(&mut self, other: Vec3) {
|
|
|
+ self.x += other.x;
|
|
|
+ self.y += other.y;
|
|
|
+ self.z += other.z;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+impl std::ops::Neg for Vec3 {
|
|
|
+ type Output = Vec3;
|
|
|
+
|
|
|
+ fn neg(self) -> Vec3 {
|
|
|
+ return self * -1.0f32;
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl std::ops::Sub for Vec3 {
|
|
|
+ type Output = Vec3;
|
|
|
+
|
|
|
+ fn sub(self, _rhs: Vec3) -> Vec3 {
|
|
|
+ return self + (-_rhs);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+// Dot product
|
|
|
+impl std::ops::Mul<Vec3> for Vec3 {
|
|
|
+ type Output = f32;
|
|
|
+
|
|
|
+ fn mul(self, _rhs: Vec3) -> f32 {
|
|
|
+ self.x * _rhs.x + self.y * _rhs.y + self.z * _rhs.z
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Scalar product
|
|
|
+impl std::ops::Mul<f32> for Vec3 {
|
|
|
+ type Output = Vec3;
|
|
|
+
|
|
|
+ fn mul(self, _rhs: f32) -> Vec3 {
|
|
|
+ return Vec3 {
|
|
|
+ x: _rhs * self.x,
|
|
|
+ y: _rhs * self.y,
|
|
|
+ z: _rhs * self.z
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+impl std::ops::Mul<Vec3> for f32 {
|
|
|
+ type Output = Vec3;
|
|
|
+
|
|
|
+ fn mul(self, _rhs: Vec3) -> Vec3 {
|
|
|
+ return _rhs * self;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub struct Ray {
|
|
|
+ origin: Vec3,
|
|
|
+ direction: Vec3,
|
|
|
+}
|
|
|
+
|
|
|
+pub struct Intersection {
|
|
|
+ t: f32,
|
|
|
+ pos: Vec3,
|
|
|
+ normal: Vec3,
|
|
|
+}
|
|
|
+
|
|
|
+type IndexedVertex = [usize; 3];
|
|
|
+
|
|
|
+struct Face {
|
|
|
+ a: IndexedVertex,
|
|
|
+ b: IndexedVertex,
|
|
|
+ c: IndexedVertex
|
|
|
+}
|
|
|
+
|
|
|
+struct Material {
|
|
|
+
|
|
|
+}
|
|
|
+struct Mesh {
|
|
|
+ vertices: Vec<Vec3>,
|
|
|
+ color: Vec<Vec3>,
|
|
|
+ texture_coordinates: Vec<Vec3>,
|
|
|
+ faces: Vec<[usize; 3]>,
|
|
|
+ material: Material
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// ┏┳┓┏━╸┏━┓╻ ╻ ╻┏━┓
|
|
|
+// ┃┃┃┣╸ ┗━┓┣━┫ ┃┃ ┃
|
|
|
+// ╹ ╹┗━╸┗━┛╹ ╹ ╹┗━┛
|
|
|
+impl Mesh {
|
|
|
+ fn load(obj_path: &str) -> Mesh {
|
|
|
+ let file = File::open(obj_path).expect("Could not open OBJ file");
|
|
|
+ let reader = BufReader::new(file);
|
|
|
+
|
|
|
+ let mut line_counter = 0;
|
|
|
+
|
|
|
+ let mut vertices : Vec<Vec3> = Vec::new();
|
|
|
+ let mut normals : Vec<Vec3>= Vec::new();
|
|
|
+ let mut texture_coords : Vec<Vec3> = Vec::new();
|
|
|
+ let mut faces : Vec<Face>= Vec::new();
|
|
|
+
|
|
|
+ for l in reader.lines() {
|
|
|
+ line_counter += 1;
|
|
|
+ let line = l.expect("Could not read line from file");
|
|
|
+ let comment_offset = line.find('#').unwrap_or(line.len());
|
|
|
+
|
|
|
+ // Remove comments
|
|
|
+ let mut stripped_line = line.clone();
|
|
|
+ let instruction = stripped_line.drain(0..comment_offset);
|
|
|
+
|
|
|
+ // Skip if line is empty
|
|
|
+ if instruction.as_str().is_empty() {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ let arguments : Vec<&str>= instruction.as_str().split(' ').collect();
|
|
|
+ let instruction_code = arguments.get(0).expect(
|
|
|
+ format!("Did not find instruction code in line {}: '{}'", line_counter, line).as_str())
|
|
|
+ .clone();
|
|
|
+
|
|
|
+ fn parse_vec3(a: &[&str]) -> Option<Vec3> {
|
|
|
+ let x = f32::from_str(a[0]?)?;
|
|
|
+ let y = f32::from_str(a[1]?)?;
|
|
|
+ let z = f32::from_str(a[2]?)?;
|
|
|
+
|
|
|
+ return Some(Vec3::new(x,y,z));
|
|
|
+ }
|
|
|
+
|
|
|
+ fn parse_uv(a: &[&str]) -> Option<Vec3> {
|
|
|
+ let x = f32::from_str(a[0])?;
|
|
|
+ let y = f32::from_str(a[1])?;
|
|
|
+
|
|
|
+ return Some(Vec3::new(x,y, 0.0));
|
|
|
+ }
|
|
|
+
|
|
|
+ fn parse_triple(a : &str) -> Option<IndexedVertex> {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ match instruction_code {
|
|
|
+ "v" | "vn" => {
|
|
|
+ let coords = parse_vec3(&arguments[1..4]).expect(
|
|
|
+ format!("Could not parse coords in line {}", line_counter).as_str()
|
|
|
+ );
|
|
|
+
|
|
|
+ match instruction_code {
|
|
|
+ "v" => &vertices,
|
|
|
+ "vt" => texture,
|
|
|
+ _ => {}
|
|
|
+ }.push(coords)
|
|
|
+ }
|
|
|
+ "vt" => {
|
|
|
+ let coords = parse_uv(&arguments[1..3]).expect(
|
|
|
+ format!("Could not parse uv coords in line {}", line_counter).as_str()
|
|
|
+ );
|
|
|
+
|
|
|
+ texture_coords.push(coords);
|
|
|
+ }
|
|
|
+ "f" => {
|
|
|
+ faces.push()
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ _ => {}
|
|
|
+ }
|
|
|
+
|
|
|
+ //let args = line.split(' ').collect();
|
|
|
+ println!("Line {}", arguments.get(0).unwrap_or(&"<EMPTY>"));
|
|
|
+ }
|
|
|
+
|
|
|
+ return Mesh{
|
|
|
+ vertices: vec!() ,
|
|
|
+ color: vec!(),
|
|
|
+ texture_coordinates: vec!(),
|
|
|
+ faces: vec!(),
|
|
|
+ material: Material {}
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
// ╻┏┳┓┏━┓┏━╸┏━╸ ╻┏━┓
|
|
|
// ┃┃┃┃┣━┫┃╺┓┣╸ ┃┃ ┃
|
|
|
// ╹╹ ╹╹ ╹┗━┛┗━╸ ╹┗━┛
|
|
|
+
|
|
|
use std::convert::From;
|
|
|
use std::io::BufWriter;
|
|
|
use std::io::Write;
|
|
|
+use std::ops::Index;
|
|
|
use std::path::Path;
|
|
|
+use std::str::FromStr;
|
|
|
|
|
|
pub struct Image {
|
|
|
width: u16,
|
|
@@ -83,6 +327,39 @@ impl Image {
|
|
|
data: data,
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ fn save_bmp(self: &Image, p: &Path) -> std::io::Result<()> {
|
|
|
+ let f: File = File::create(p)?;
|
|
|
+ let mut w = BufWriter::new(f);
|
|
|
+
|
|
|
+ // File header
|
|
|
+ w.write_all(&['B' as u8, 'M' as u8])?;
|
|
|
+ write_int(&mut w, 0)?; // size, left blank for now
|
|
|
+ write_int(&mut w, 0)?; // reserved bytes
|
|
|
+ write_int(&mut w, 54)?; // offset
|
|
|
+
|
|
|
+ // Bitmap info header
|
|
|
+ write_int(&mut w, 40)?; // header size in bytes
|
|
|
+ write_int(&mut w, self.width as u32)?;
|
|
|
+ write_int(&mut w, self.height as u32)?;
|
|
|
+ write_int16(&mut w, 1)?; // number of color planes
|
|
|
+ write_int16(&mut w, 24)?; // bits per pixel
|
|
|
+ write_int(&mut w, 0)?; // no compression
|
|
|
+ write_int(&mut w, 0)?; // image size
|
|
|
+ write_int(&mut w, 1000)?; // horizontal resolution in pixels per metre
|
|
|
+ write_int(&mut w, 1000)?; // vertical resolution
|
|
|
+ write_int(&mut w, 0)?; // number of colors
|
|
|
+ write_int(&mut w, 0)?; // number of important colors
|
|
|
+
|
|
|
+ // Pixel data
|
|
|
+ for y in (0..self.height).rev() {
|
|
|
+ for x in 0..self.width {
|
|
|
+ write_vec3(&mut w, self.get_pixel(x.into(), y.into()))?;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
fn read_int(v: &[u8]) -> u32 {
|
|
@@ -183,66 +460,132 @@ fn write_vec3(f: &mut dyn Write, v: &Vec3) -> std::io::Result<()> {
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
-fn save_bmp(p: &Path, i: &Image) -> std::io::Result<()> {
|
|
|
- let f: File = File::create(p)?;
|
|
|
- let mut w = BufWriter::new(f);
|
|
|
-
|
|
|
- // File header
|
|
|
- w.write_all(&['B' as u8, 'M' as u8])?;
|
|
|
- write_int(&mut w, 0)?; // size, left blank for now
|
|
|
- write_int(&mut w, 0)?; // reserved bytes
|
|
|
- write_int(&mut w, 54)?; // offset
|
|
|
-
|
|
|
- // Bitmap info header
|
|
|
- write_int(&mut w, 40)?; // header size in bytes
|
|
|
- write_int(&mut w, i.width as u32)?;
|
|
|
- write_int(&mut w, i.height as u32)?;
|
|
|
- write_int16(&mut w, 1)?; // number of color planes
|
|
|
- write_int16(&mut w, 24)?; // bits per pixel
|
|
|
- write_int(&mut w, 0)?; // no compression
|
|
|
- write_int(&mut w, 0)?; // image size
|
|
|
- write_int(&mut w, 1000)?; // horizontal resolution in pixels per metre
|
|
|
- write_int(&mut w, 1000)?; // vertical resolution
|
|
|
- write_int(&mut w, 0)?; // number of colors
|
|
|
- write_int(&mut w, 0)?; // number of important colors
|
|
|
-
|
|
|
- // Pixel data
|
|
|
- for y in (0..i.height).rev() {
|
|
|
- for x in 0..i.width {
|
|
|
- write_vec3(&mut w, i.get_pixel(x.into(), y.into()))?;
|
|
|
- }
|
|
|
+
|
|
|
+// ╺┳╸┏━┓┏━┓┏━╸┏━╸ ┏━┓┏━┓╻ ╻┏━┓
|
|
|
+// ┃ ┣┳┛┣━┫┃ ┣╸ ┣┳┛┣━┫┗┳┛┗━┓
|
|
|
+// ╹ ╹┗╸╹ ╹┗━╸┗━╸ ╹┗╸╹ ╹ ╹ ┗━┛
|
|
|
+
|
|
|
+pub fn intersect_sphere(ray: &Ray, center: &Vec3, radius: f32) -> Option<Intersection> {
|
|
|
+ let v: Vec3 = *center - ray.origin;
|
|
|
+ let cos_alpha = v.normalized() * ray.direction;
|
|
|
+
|
|
|
+ let t = cos_alpha * v.length();
|
|
|
+
|
|
|
+ let closest_point = ray.origin + t * ray.direction;
|
|
|
+ let min_distance = center.distance_to(closest_point);
|
|
|
+
|
|
|
+ if min_distance < radius {
|
|
|
+ let x = (radius * radius - min_distance * min_distance).sqrt();
|
|
|
+ let intersection = closest_point - x * ray.direction;
|
|
|
+ let normal = (intersection - *center).normalized();
|
|
|
+
|
|
|
+ return Some(Intersection {
|
|
|
+ t: t,
|
|
|
+ pos: intersection,
|
|
|
+ normal: normal,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ return None;
|
|
|
}
|
|
|
|
|
|
- Ok(())
|
|
|
}
|
|
|
|
|
|
+fn intersect(r: &Ray) -> Option<Intersection> {
|
|
|
+ // Have a simple scene with a singular sphere at (0,0,5) with radius r=1
|
|
|
+ return intersect_sphere(r, &Vec3 {x: 0.0, y: 0.0, z: -5.0}, 1.8);
|
|
|
+}
|
|
|
+
|
|
|
+fn raytrace(r: &Ray) -> Vec3 {
|
|
|
+ let i = intersect(r);
|
|
|
+
|
|
|
+ match i {
|
|
|
+ None => Vec3 { x: 0.0, y: 0.0, z: 0.0 },
|
|
|
+ Some(intersect) => Vec3 { x: 1.0, y: 1.0, z: 0.0 }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ┏┓ ┏━┓╺┳┓┏━╸
|
|
|
+// ┣┻┓┗━┓ ┃┃┣╸
|
|
|
+// ┗━┛┗━┛╺┻┛╹
|
|
|
+
|
|
|
+trait BSDF {
|
|
|
+ fn eval(i: Vec3, o: Vec3) -> f32;
|
|
|
+ fn sample(r: &mut RNG) -> Vec3;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+struct Object {
|
|
|
+ mesh: Mesh,
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+struct Scene {
|
|
|
+ objects: Vec<Object>
|
|
|
+}
|
|
|
+
|
|
|
+// ┏┳┓┏━┓╻┏┓╻
|
|
|
+// ┃┃┃┣━┫┃┃┗┫
|
|
|
+// ╹ ╹╹ ╹╹╹ ╹
|
|
|
+
|
|
|
fn main() {
|
|
|
+
|
|
|
+ let m = Mesh::load("benchmarks/suzanne.obj");
|
|
|
+ return;
|
|
|
let mut rng = RNG::create();
|
|
|
- println!("{}", rng.next_float());
|
|
|
-
|
|
|
- let path = Path::new("skymap.bmp");
|
|
|
- let image = load_bmp(&path);
|
|
|
- println!(
|
|
|
- "Image length: {}, {}x{}",
|
|
|
- image.data.len(),
|
|
|
- image.width,
|
|
|
- image.height
|
|
|
- );
|
|
|
|
|
|
- let pix = image.get_pixel(0, 0);
|
|
|
- println!("RGB Val: ({}, {}, {})", pix.x, pix.y, pix.z);
|
|
|
-
|
|
|
- // Create a new pixel from scratch
|
|
|
- let mut rand_bitmap = Image::new(1024, 1024);
|
|
|
- for x in 0..1024 {
|
|
|
- for y in 0..1024 {
|
|
|
- let mut p = rand_bitmap.mod_pixel(x, y);
|
|
|
- p.x = rng.next_float() * 255.0;
|
|
|
- p.y = rng.next_float() * 255.0;
|
|
|
- p.z = rng.next_float() * 255.0;
|
|
|
+ // Generate camera rays
|
|
|
+ const SPP : u16 = 1;
|
|
|
+ const WIDTH : u16 = 1024;
|
|
|
+ const HEIGHT : u16 = 1024;
|
|
|
+ const D: f32 = 1.0; // distance of image plane to camera origin
|
|
|
+ const FOV : f32 = 80.0; // opening angle of camera
|
|
|
+
|
|
|
+ let mut img = Image::new(WIDTH, HEIGHT);
|
|
|
+
|
|
|
+ for x in 0..WIDTH {
|
|
|
+ for y in 0..HEIGHT {
|
|
|
+ let target_x = (f32::from(x) / f32::from(WIDTH) - 0.5) * (FOV / 2.0).tan() * D;
|
|
|
+ let target_y = (f32::from(y) / f32::from(HEIGHT) - 0.5) * (FOV / 2.0).tan() * D;
|
|
|
+ let pixel = img.mod_pixel(x.into(), y.into());
|
|
|
+ let r = Ray {
|
|
|
+ origin: Vec3 {
|
|
|
+ x: 0.0,
|
|
|
+ y: 0.0,
|
|
|
+ z: 0.0,
|
|
|
+ },
|
|
|
+ direction: Vec3 {
|
|
|
+ x: target_x,
|
|
|
+ y: target_y,
|
|
|
+ z: D
|
|
|
+ }
|
|
|
+ };
|
|
|
+ //println!("Shooting ray {} {} {}", target_x, target_y, D);
|
|
|
+
|
|
|
+ for _ in 0..SPP {
|
|
|
+ let sample : Vec3= 255.0 / f32::from(SPP) * raytrace(&r);
|
|
|
+ //println!("{} --> {}", r.direction, sample);
|
|
|
+ *pixel += sample;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- let outpath = Path::new("random.bmp");
|
|
|
- save_bmp(&outpath, &rand_bitmap).expect("Could not save bitmap :(");
|
|
|
+ img.save_bmp(&Path::new("Render.bmp")).expect("Failed to save rendered image");
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// ╺┳╸┏━╸┏━┓╺┳╸┏━┓
|
|
|
+// ┃ ┣╸ ┗━┓ ┃ ┗━┓
|
|
|
+// ╹ ┗━╸┗━┛ ╹ ┗━┛
|
|
|
+
|
|
|
+#[test]
|
|
|
+fn sphereIntersect() {
|
|
|
+ let result = intersect_sphere(&Ray {
|
|
|
+ origin: Vec3::new(0.0, 0.0, -5.0),
|
|
|
+ direction: Vec3::new(0.0, 0.0, 1.0)
|
|
|
+
|
|
|
+ }, &Vec3::new(0.0, 0.0, 0.0),
|
|
|
+ 1.0);
|
|
|
+
|
|
|
+ assert!(result.is_some(), "Should have found an intersection");
|
|
|
}
|