From 977b0e4152433b2a68e2b97fe5fe2c0ff6fb20d8 Mon Sep 17 00:00:00 2001 From: Julian T Date: Wed, 3 Feb 2021 17:29:27 +0100 Subject: Can render a simple sphere, without shading --- Cargo.lock | 332 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/camera/camera.rs | 9 +- src/camera/film.rs | 40 +++++-- src/camera/mod.rs | 1 + src/core/spectrum.rs | 6 +- src/core/vector2.rs | 7 ++ src/lib.rs | 3 +- src/main.rs | 38 ++++-- src/render.rs | 51 ++++++++ 10 files changed, 465 insertions(+), 23 deletions(-) create mode 100644 src/render.rs diff --git a/Cargo.lock b/Cargo.lock index 169dc89..a6626c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,337 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bytemuck" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a4bad0c5981acc24bc09e532f35160f952e35422603f0563cd7a73c2c2e65a0" + +[[package]] +name = "byteorder" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "const_fn" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" +dependencies = [ + "cfg-if", + "const_fn", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "gif" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02efba560f227847cb41463a7395c514d127d4f74fff12ef0137fff1b84b96c4" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "image" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce04077ead78e39ae8610ad26216aed811996b043d47beed5090db674f9e9b5" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +dependencies = [ + "rayon", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" + +[[package]] +name = "memoffset" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "pathtrace" version = "0.1.0" +dependencies = [ + "image", +] + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.3", + "weezl", +] + +[[package]] +name = "weezl" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a32b378380f4e9869b22f0b5177c68a5519f03b3454fde0b291455ddbae266c" diff --git a/Cargo.toml b/Cargo.toml index 621e7e3..63e31c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +image = "0.23.12" diff --git a/src/camera/camera.rs b/src/camera/camera.rs index 69fd4e0..7822e92 100644 --- a/src/camera/camera.rs +++ b/src/camera/camera.rs @@ -7,13 +7,13 @@ //! //! ``` //! use pathtrace::camera::Camera; -//! use pathtrace::core::{Vector3f, Vector2f}; +//! use pathtrace::core::{Vector3f, Vector2f, Vector2i}; //! //! let cam = Camera::new( //! Vector3f::new(10.0), //! Vector3f::new(0.0), //! Vector3f::new_xyz(0.0, 1.0, 0.0), -//! 90.0, Vector2f::new(10.0), +//! 90.0, Vector2i::new(10.0), //! ); //! //! let (r, _) = cam.generate_ray(&Vector2f::new(5.0)); @@ -27,7 +27,7 @@ //! //! ``` use crate::Float; -use crate::core::{Vector3f, Vector2f, Ray}; +use crate::core::{Vector3f, Vector2f, Vector2i, Ray}; /// A simple perspective camera pub struct Camera { @@ -45,7 +45,8 @@ impl Camera { /// /// The field of view specifies how wide the image should be. /// Currently must be between [0; 180[. - pub fn new(origin: Vector3f, target: Vector3f, up: Vector3f, fov: Float, screensize: Vector2f) -> Camera { + pub fn new(origin: Vector3f, target: Vector3f, up: Vector3f, fov: Float, screensize: Vector2i) -> Camera { + let screensize = Vector2f::from(screensize); // Calculate translation vectors let forward = (target - origin).norm(); let right = up.cross(&origin).norm(); diff --git a/src/camera/film.rs b/src/camera/film.rs index 30fd2fe..3586374 100644 --- a/src/camera/film.rs +++ b/src/camera/film.rs @@ -1,5 +1,6 @@ use crate::core::*; use crate::Float; +use image::{RgbImage, Rgb}; /// Contains the necesary values when doing calculations /// @@ -14,7 +15,7 @@ pub struct Pixel { pub struct Film { size: Vector2i, - drawing_bound: Bound2i, + pub frame: Bound2i, pixels: Vec, } @@ -24,14 +25,12 @@ pub struct Film { /// This means that multiple threads can work on the same area and commit their changed when they /// are done. pub struct FilmTile { - bounds: Bound2i, - size: Vector2i, + pub bounds: Bound2i, + pub size: Vector2i, pixels: Vec, } -//const HalfPixel = Vector2f::new(0.5); - impl Pixel { fn new() -> Pixel { Pixel { @@ -44,6 +43,15 @@ impl Pixel { self.rgb += &(c * weight); self.samples += 1; } + + fn finalize_rgb(&self) -> [u8; 3] { + let (r, g, b) = self.rgb.to_rgb(255.0); + [ + r as u8, + g as u8, + b as u8, + ] + } } impl std::ops::AddAssign<&Self> for Pixel { @@ -58,7 +66,7 @@ impl Film { let area = size.x * size.y; Film { size, - drawing_bound: Bound2i::new(&Vector2i::new(0), &size), + frame: Bound2i::new(&Vector2i::new(0), &size), pixels: vec![Pixel::new(); area as usize], } } @@ -79,18 +87,34 @@ impl Film { pub fn commit_tile(&mut self, tile: &FilmTile) { let offset = tile.bounds.min; - for y in 0 ..= tile.size.y { + for y in 0 .. tile.size.y { let rowindex = (offset.y + y) * self.size.x; let prowindex = y * tile.size.x; - for x in 0 ..= tile.size.x { + for x in 0 .. tile.size.x { let index = offset.x + x + rowindex; let pindex: i32 = x + prowindex; self.pixels[index as usize] += &tile.pixels[pindex as usize]; } } + } + + pub fn finalize_image(&self) -> RgbImage { + let mut img = RgbImage::new(self.size.x as u32, self.size.y as u32); + + for y in 0..self.size.y { + let index = y * self.size.x; + for x in 0..self.size.x { + img.put_pixel( + x as u32, + y as u32, + Rgb(self.pixels[(index + x) as usize].finalize_rgb()), + ); + } + } + img } } diff --git a/src/camera/mod.rs b/src/camera/mod.rs index b7c982b..1391cea 100644 --- a/src/camera/mod.rs +++ b/src/camera/mod.rs @@ -10,3 +10,4 @@ pub mod film; pub mod camera; pub use camera::Camera; +pub use film::Film; diff --git a/src/core/spectrum.rs b/src/core/spectrum.rs index c72a251..fb82a9e 100644 --- a/src/core/spectrum.rs +++ b/src/core/spectrum.rs @@ -12,9 +12,13 @@ pub struct Spectrum { } impl Spectrum { - fn new_rgb(r: Float, g: Float, b: Float) -> Spectrum { + pub fn new_rgb(r: Float, g: Float, b: Float) -> Spectrum { Spectrum { c: [r, g, b] } } + + pub fn to_rgb(&self, scale: Float) -> (Float, Float, Float) { + (self.c[0] * scale, self.c[1] * scale, self.c[2] * scale) + } } impl std::ops::Mul for &Spectrum { diff --git a/src/core/vector2.rs b/src/core/vector2.rs index 5afa0f2..3aadb46 100644 --- a/src/core/vector2.rs +++ b/src/core/vector2.rs @@ -3,6 +3,7 @@ //! This is implemented generictly with types that fit in the Number trait use crate::{Float, Number}; use std::ops::{Sub, Add}; +use std::fmt; #[derive(Clone, Copy)] pub struct Vector2 { @@ -43,6 +44,12 @@ impl Add for Vector2 { } } +impl fmt::Display for Vector2 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("[{}, {}]", self.x, self.y)) + } +} + impl Vector2f { pub fn ceil(&self) -> Self { Self::new_xy( diff --git a/src/lib.rs b/src/lib.rs index c3025b3..50d0435 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub mod core; pub mod camera; -mod scene; +pub mod render; +pub mod scene; use std::ops::{Add, Sub, Mul, DivAssign}; use std::cmp; diff --git a/src/main.rs b/src/main.rs index 22998dd..05d6617 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,37 @@ -use pathtrace::camera::Camera; -use pathtrace::core::{Vector3f, Vector2f}; - +use pathtrace::camera::{Camera, Film}; +use pathtrace::scene::Scene; +use pathtrace::scene::shapes::Sphere; +use pathtrace::core::{Vector2i, Vector3f}; +use pathtrace::render::{RenderContext, RenderTask}; fn main() { + let res = Vector2i::new_xy(500, 500); let cam = Camera::new( - Vector3f::new(10.0), - Vector3f::new(0.0), - Vector3f::new_xyz(0.0, 1.0, 0.0), - 90.0, Vector2f::new(10.0), + Vector3f::new_xyz(10.0, 0.0, 0.0), + Vector3f::new(0.0), + Vector3f::new_xyz(0.0, 0.1, 0.0), + 90.0, res, + ); + + let mut scn = Scene::new(); + scn.add_shape( + Box::new(Sphere::new(4.0, Vector3f::new(0.0))), ); - let (r, _) = cam.generate_ray(Vector2f::new(5.0)); + let ctx = RenderContext { cam: &cam, scn: &scn }; + + let mut film = Film::new(res); + let tile = film.get_tile(&film.frame); + + let mut task = RenderTask::new(Box::new(tile), 1); + task.render(&ctx); + + film.commit_tile(&task.tile); + + let image = film.finalize_image(); + if let Err(e) = image.save("test.png") { + println!("Failed to save {}", e); + } - println!("r: {}, norm: {}", r.direction, r.direction.norm()); } diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..dcda672 --- /dev/null +++ b/src/render.rs @@ -0,0 +1,51 @@ +//! Implements the main render loop +//! +//! This is not a final design +use crate::camera::film::FilmTile; +use crate::camera::Camera; +use crate::scene::Scene; + +use crate::core::{Vector2f, Spectrum}; +use crate::Float; + +const HALF_PIXEL: Vector2f = Vector2f {x: 0.5, y: 0.5 }; + +pub struct RenderTask { + pub tile: Box, + samples: u32, +} + +pub struct RenderContext<'a> { + pub scn: &'a Scene, + pub cam: &'a Camera, +} + +impl RenderTask { + pub fn new(tile: Box, samples: u32) -> Self { + Self { tile, samples } + } + + fn render_at(&self, ctx: &RenderContext, x: i32, y: i32) -> Spectrum { + // Create a ray + let (r, _) = ctx.cam.generate_ray(&Vector2f::new_xy(x as Float, y as Float)); + + // Trace ray + if let Some(_) = ctx.scn.intersect(r) { + return Spectrum::new_rgb(0.5, 0.5, 0.0); + } + + Spectrum::new_rgb(0.0, 0.0, 0.0) + + } + + pub fn render(&mut self, ctx: &RenderContext) { + let b = self.tile.bounds.clone(); + for x in b.min.x .. b.max.x { + for y in b.min.y .. b.max.y { + let p = Vector2f::new_xy(x as Float, y as Float); + + self.tile.add_sample(&p, self.render_at(ctx, x, y)) + } + } + } +} -- cgit v1.2.3