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 --- 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 +++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 132 insertions(+), 23 deletions(-) create mode 100644 src/render.rs (limited to 'src') 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