1
0

Complete simple light demo

This commit is contained in:
Ishan Jain 2021-03-06 21:49:16 +05:30
parent 2545765e2a
commit 298e0a2301
No known key found for this signature in database
GPG Key ID: F261A0E73038D89D
13 changed files with 270 additions and 84 deletions

View File

@ -1,7 +1,4 @@
use crate::{ use crate::{types::Vec3, Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION};
types::{Ray, Vec3},
Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
};
use rand::{rngs::SmallRng, Rng, SeedableRng}; use rand::{rngs::SmallRng, Rng, SeedableRng};
use rayon::prelude::*; use rayon::prelude::*;
use std::{ use std::{
@ -14,11 +11,13 @@ use std::{
mod checkered_motion_blur; mod checkered_motion_blur;
mod image_texture; mod image_texture;
mod perlin_noise_ball; mod perlin_noise_ball;
mod simple_light;
mod two_spheres; mod two_spheres;
pub use checkered_motion_blur::CheckeredMotionBlur; pub use checkered_motion_blur::CheckeredMotionBlur;
pub use image_texture::ImageTextureDemo; pub use image_texture::ImageTextureDemo;
pub use perlin_noise_ball::PerlinNoiseBall; pub use perlin_noise_ball::PerlinNoiseBall;
pub use simple_light::SimpleLight;
pub use two_spheres::TwoSpheres; pub use two_spheres::TwoSpheres;
#[derive(Debug)] #[derive(Debug)]
@ -55,7 +54,11 @@ pub trait Demo: Send + Sync {
fn camera(&self, aspect_ratio: f64) -> Camera; fn camera(&self, aspect_ratio: f64) -> Camera;
fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &Self::DemoT, samples: u8) { fn get_background(&self) -> Vec3 {
Vec3::new(0.7, 0.8, 1.0)
}
fn render_chunk(&self, chunk: &mut Chunk, camera: &Camera, world: &Self::DemoT, samples: u16) {
let &mut Chunk { let &mut Chunk {
num: _, num: _,
x, x,
@ -69,6 +72,7 @@ pub trait Demo: Send + Sync {
let mut offset = 0; let mut offset = 0;
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let mut rng = SmallRng::from_rng(&mut rng).unwrap(); let mut rng = SmallRng::from_rng(&mut rng).unwrap();
let background = self.get_background();
assert!(buffer.len() >= nx * ny * 4); assert!(buffer.len() >= nx * ny * 4);
@ -80,7 +84,7 @@ pub trait Demo: Send + Sync {
let v = (j as f64 + rng.gen::<f64>()) / y as f64; let v = (j as f64 + rng.gen::<f64>()) / y as f64;
let ray = camera.get_ray(u, v, &mut rng); let ray = camera.get_ray(u, v, &mut rng);
color += calc_color(ray, world, 0, &mut rng); color += ray.color(world, &mut rng, &background, 0);
} }
color /= samples as f64; color /= samples as f64;
@ -90,7 +94,7 @@ pub trait Demo: Send + Sync {
}); });
} }
fn render(&self, buf: &mut Vec<u8>, x: usize, y: usize, samples: u8) { fn render(&self, buf: &mut Vec<u8>, x: usize, y: usize, samples: u16) {
let world = self.world(); let world = self.world();
let delta_x = x / VERTICAL_PARTITION; let delta_x = x / VERTICAL_PARTITION;
let delta_y = y / HORIZONTAL_PARTITION; let delta_y = y / HORIZONTAL_PARTITION;
@ -163,7 +167,7 @@ pub trait Demo: Send + Sync {
} }
} }
fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize, samples: u8) { fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize, samples: u16) {
let header = format!("P3\n{} {}\n255\n", width, height); let header = format!("P3\n{} {}\n255\n", width, height);
let mut file = match File::create(&format!( let mut file = match File::create(&format!(
@ -187,22 +191,3 @@ pub trait Demo: Send + Sync {
} }
} }
} }
fn calc_color<T: Hitable>(ray: Ray, world: &T, depth: u32, rng: &mut SmallRng) -> Vec3 {
if let Some(hit_rec) = world.hit(&ray, 0.001, std::f64::MAX) {
if depth >= 50 {
Vec3::new(0.0, 0.0, 0.0)
} else {
let material = hit_rec.material;
if let (attenuation, Some(scattered_ray)) = material.scatter(&ray, &hit_rec, rng) {
calc_color(scattered_ray, world, depth + 1, rng) * attenuation
} else {
Vec3::new(0.0, 0.0, 0.0)
}
}
} else {
let unit_direction = ray.direction().unit_vector();
let t = 0.5 * (unit_direction.y() + 1.0);
Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t
}
}

81
src/demos/simple_light.rs Normal file
View File

@ -0,0 +1,81 @@
use std::sync::Arc;
use rand::{prelude::SmallRng, SeedableRng};
use crate::{
demos::{Demo, ParallelHit},
materials::{DiffuseLight, Lambertian},
shapes::{Sphere, XyRectangle},
texture::{PerlinNoise, Solid},
types::Vec3,
BvhNode, Camera,
};
pub struct SimpleLight {}
impl Demo for SimpleLight {
type DemoT = BvhNode<Arc<dyn ParallelHit>>;
fn name(&self) -> &'static str {
"simple_light"
}
fn get_background(&self) -> Vec3 {
Vec3::new(0.0, 0.0, 0.0)
}
fn world(&self) -> Self::DemoT {
let mut world: Vec<Arc<dyn ParallelHit>> = Vec::with_capacity(5);
let mut rng = rand::thread_rng();
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
world.push(Arc::new(Sphere::new(
Vec3::new(0.0, -1000.0, 0.0),
1000.0,
Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)),
)));
world.push(Arc::new(Sphere::new(
Vec3::new(0.0, 2.0, 0.0),
2.0,
Lambertian::new(PerlinNoise::with_scale(&mut rng, 4.0)),
)));
world.push(Arc::new(XyRectangle::new(
3.0,
5.0,
1.0,
3.0,
-2.0,
DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))),
)));
world.push(Arc::new(Sphere::new(
Vec3::new(0.0, 7.0, 0.0),
2.0,
DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))),
)));
world.push(Arc::new(Sphere::new(
Vec3::new(-40.0, 2.0, 5.0),
1.0,
DiffuseLight::new(Solid::new(Vec3::new(4.0, 4.0, 4.0))),
)));
BvhNode::new(&mut rng, &mut world, 0.0, 1.0)
}
fn camera(&self, aspect_ratio: f64) -> crate::Camera {
let lookfrom = Vec3::new(26.0, 3.0, 6.0);
let lookat = Vec3::new(0.0, 2.0, 0.0);
let aperture = 0.0;
let focus_distance = 10.0;
Camera::new(
lookfrom,
lookat,
Vec3::new(0.0, 1.0, 0.0),
20.0,
aspect_ratio,
aperture,
focus_distance,
0.0,
1.0,
)
}
}

View File

@ -30,6 +30,25 @@ pub struct HitRecord<'a> {
pub v: f64, pub v: f64,
} }
impl<'a> HitRecord<'a> {
pub fn new(
t: f64,
p: Vec3,
normal: Vec3,
material: &'a dyn Material,
(u, v): (f64, f64),
) -> Self {
Self {
t,
p,
normal,
material,
u,
v,
}
}
}
pub trait Hitable { pub trait Hitable {
fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord>; fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord>;

View File

@ -23,11 +23,11 @@ use demos::Demo;
use std::time::Instant; use std::time::Instant;
const NUM_SAMPLES: u8 = 100; const NUM_SAMPLES: u16 = 1000;
const VERTICAL_PARTITION: usize = 12; const VERTICAL_PARTITION: usize = 12;
const HORIZONTAL_PARTITION: usize = 12; const HORIZONTAL_PARTITION: usize = 12;
const WIDTH: usize = 1920; const WIDTH: usize = 2560;
const HEIGHT: usize = 1080; const HEIGHT: usize = 1440;
fn main() -> Result<(), String> { fn main() -> Result<(), String> {
run(WIDTH, HEIGHT) run(WIDTH, HEIGHT)
@ -67,8 +67,7 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> {
.create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32) .create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32)
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let mut active_demo: &dyn Demo<DemoT = BvhNode<Arc<dyn ParallelHit>>> = let mut active_demo: &dyn Demo<DemoT = BvhNode<Arc<dyn ParallelHit>>> = &demos::SimpleLight {};
&demos::ImageTextureDemo {};
let mut should_update = true; let mut should_update = true;
loop { loop {
@ -101,6 +100,10 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> {
active_demo = &demos::ImageTextureDemo {}; active_demo = &demos::ImageTextureDemo {};
should_update = true; should_update = true;
} }
Some(Keycode::Num5) => {
active_demo = &demos::SimpleLight {};
should_update = true;
}
None => unreachable!(), None => unreachable!(),
_ => (), _ => (),
}; };

View File

@ -0,0 +1,17 @@
use crate::{types::Vec3, Material, Texture};
pub struct DiffuseLight<T: Texture> {
emit: T,
}
impl<T: Texture> DiffuseLight<T> {
pub fn new(emit: T) -> Self {
Self { emit }
}
}
impl<T: Texture + Send + Sync> Material for DiffuseLight<T> {
fn emit(&self, u: f64, v: f64, p: Vec3) -> Vec3 {
self.emit.value(u, v, p)
}
}

View File

@ -10,7 +10,7 @@ pub struct Lambertian<T: Texture> {
albedo: T, albedo: T,
} }
impl<T: Texture + Send + Sync> Lambertian<T> { impl<T: Texture> Lambertian<T> {
pub fn new(albedo: T) -> Self { pub fn new(albedo: T) -> Self {
Self { albedo } Self { albedo }
} }

View File

@ -12,6 +12,7 @@ pub struct Metal {
} }
impl Metal { impl Metal {
#[allow(dead_code)]
pub fn new(albedo: Vec3) -> Self { pub fn new(albedo: Vec3) -> Self {
Self { albedo, fuzz: 0.0 } Self { albedo, fuzz: 0.0 }
} }

View File

@ -1,8 +1,10 @@
mod dielectric; mod dielectric;
mod diffuse_light;
mod lambertian; mod lambertian;
mod metal; mod metal;
pub use dielectric::Dielectric; pub use dielectric::Dielectric;
pub use diffuse_light::DiffuseLight;
pub use lambertian::Lambertian; pub use lambertian::Lambertian;
pub use metal::Metal; pub use metal::Metal;
use rand::{prelude::SmallRng, Rng}; use rand::{prelude::SmallRng, Rng};
@ -13,7 +15,20 @@ use crate::{
}; };
pub trait Material: Send + Sync { pub trait Material: Send + Sync {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>); // scatter returns the attenuation and the scattered ray.
// Attenuation is ignored completely if there is no scattered ray
fn scatter(
&self,
_ray: &Ray,
_hit_rec: &HitRecord,
_rng: &mut SmallRng,
) -> (Vec3, Option<Ray>) {
(Vec3::new(0.0, 0.0, 0.0), None)
}
fn emit(&self, _u: f64, _v: f64, _p: Vec3) -> Vec3 {
Vec3::new(0.0, 0.0, 0.0)
}
} }
// Christophe Schlick's Polynomial approximation to figure out reflectivity as the angle changes // Christophe Schlick's Polynomial approximation to figure out reflectivity as the angle changes

View File

@ -1,5 +1,7 @@
mod moving_sphere; mod moving_sphere;
mod sphere; mod sphere;
mod xy_rectangle;
pub use moving_sphere::MovingSphere; pub use moving_sphere::MovingSphere;
pub use sphere::Sphere; pub use sphere::Sphere;
pub use xy_rectangle::XyRectangle;

View File

@ -62,35 +62,21 @@ impl<T: Material + Sized> Hitable for MovingSphere<T> {
let discriminant_root = discriminant.sqrt(); let discriminant_root = discriminant.sqrt();
if discriminant > 0.0 { if discriminant > 0.0 {
let root = (-b - discriminant_root) / a; let mut root = (-b - discriminant_root) / a;
if root < t_max && root > t_min { if root < t_min || root > t_max {
let p = ray.point_at_parameter(root); root = (-b + discriminant_root) / a;
let normal = (p - self.center(ray.time())) / self.radius;
let (u, v) = Self::get_uv(normal);
return Some(HitRecord {
t: root,
p,
u,
v,
normal,
material: &self.material,
});
} }
let root = (-b + discriminant_root) / a; if root > t_min && root < t_max {
if root < t_max && root > t_min {
let p = ray.point_at_parameter(root); let p = ray.point_at_parameter(root);
let normal = (p - self.center(ray.time())) / self.radius; let normal = (p - self.center(ray.time())) / self.radius;
let (u, v) = Self::get_uv(normal);
return Some(HitRecord { return Some(HitRecord::new(
t: root, root,
p, p,
u, normal,
v, &self.material,
normal: (p - self.center(ray.time())) / self.radius, Self::get_uv(normal),
material: &self.material, ));
});
} }
} }
None None

View File

@ -49,36 +49,21 @@ impl<T: Material + Sized> Hitable for Sphere<T> {
let discriminant_root = discriminant.sqrt(); let discriminant_root = discriminant.sqrt();
if discriminant > 0.0 { if discriminant > 0.0 {
let root = (-b - discriminant_root) / a; let mut root = (-b - discriminant_root) / a;
if root < t_max && root > t_min { if root < t_min || root > t_max {
let p = ray.point_at_parameter(root); root = (-b + discriminant_root) / a;
let normal = (p - self.center) / self.radius;
let (u, v) = Self::get_uv(normal);
return Some(HitRecord {
t: root,
p,
u,
v,
normal,
material: &self.material,
});
} }
if root > t_min && root < t_max {
let root = (-b + discriminant_root) / a;
if root < t_max && root > t_min {
let p = ray.point_at_parameter(root); let p = ray.point_at_parameter(root);
let normal = (p - self.center) / self.radius; let normal = (p - self.center) / self.radius;
let (u, v) = Self::get_uv(normal);
return Some(HitRecord { return Some(HitRecord::new(
t: root, root,
p, p,
u, normal,
v, &self.material,
normal: (p - self.center) / self.radius, Self::get_uv(normal),
material: &self.material, ));
});
} }
} }
None None

View File

@ -0,0 +1,64 @@
use crate::{
types::{Ray, Vec3},
Aabb, HitRecord, Hitable, Material,
};
pub struct XyRectangle<T: Material> {
x0: f64,
x1: f64,
y0: f64,
y1: f64,
k: f64,
material: T,
}
impl<T: Material> XyRectangle<T> {
pub fn new(x0: f64, x1: f64, y0: f64, y1: f64, k: f64, material: T) -> Self {
Self {
x0,
x1,
y0,
y1,
k,
material,
}
}
}
impl<T: Material> Hitable for XyRectangle<T> {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let t = (self.k - ray.origin().z()) / ray.direction().z();
if t < t_min || t > t_max {
None
} else {
let x = ray.origin().x() + t * ray.direction().x();
let y = ray.origin().y() + t * ray.direction().y();
if x < self.x0 || x > self.x1 || y < self.y0 || y > self.y1 {
None
} else {
let u = (x - self.x0) / (self.x1 - self.x0);
let v = (y - self.y0) / (self.y1 - self.y0);
Some(HitRecord::new(
t,
ray.point_at_parameter(t),
Vec3::new(0.0, 0.0, 1.0),
&self.material,
(u, v),
))
}
}
}
fn bounding_box(&self, _t0: f64, _t1: f64) -> Option<Aabb> {
// Since this is a axis aligned Rectangle and we are using AABB BVH, Gap between the rectangle and
// the bounding box will be infinitely small
Some(Aabb::new(
Vec3::new(self.x0, self.y0, self.k - 0.0001),
Vec3::new(self.x1, self.y1, self.k + 0.0001),
))
}
}

View File

@ -1,4 +1,6 @@
use crate::types::Vec3; use rand::prelude::SmallRng;
use crate::{types::Vec3, Hitable};
pub struct Ray { pub struct Ray {
a: Vec3, a: Vec3,
@ -26,4 +28,30 @@ impl Ray {
pub const fn time(&self) -> f64 { pub const fn time(&self) -> f64 {
self.time self.time
} }
pub fn color<T: Hitable>(
&self,
world: &T,
rng: &mut SmallRng,
background: &Vec3,
depth: u32,
) -> Vec3 {
if let Some(hit_rec) = world.hit(self, 0.001, std::f64::MAX) {
if depth >= 50 {
Vec3::new(0.0, 0.0, 0.0)
} else {
let material = hit_rec.material;
let emitted_color = hit_rec.material.emit(hit_rec.u, hit_rec.v, hit_rec.p);
if let (attenuation, Some(scattered_ray)) = material.scatter(self, &hit_rec, rng) {
emitted_color
+ attenuation * scattered_ray.color(world, rng, background, depth + 1)
} else {
emitted_color
}
}
} else {
*background
}
}
} }