Complete simple light demo
This commit is contained in:
parent
2545765e2a
commit
298e0a2301
|
@ -1,7 +1,4 @@
|
|||
use crate::{
|
||||
types::{Ray, Vec3},
|
||||
Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
|
||||
};
|
||||
use crate::{types::Vec3, Camera, Hitable, HORIZONTAL_PARTITION, VERTICAL_PARTITION};
|
||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||
use rayon::prelude::*;
|
||||
use std::{
|
||||
|
@ -14,11 +11,13 @@ use std::{
|
|||
mod checkered_motion_blur;
|
||||
mod image_texture;
|
||||
mod perlin_noise_ball;
|
||||
mod simple_light;
|
||||
mod two_spheres;
|
||||
|
||||
pub use checkered_motion_blur::CheckeredMotionBlur;
|
||||
pub use image_texture::ImageTextureDemo;
|
||||
pub use perlin_noise_ball::PerlinNoiseBall;
|
||||
pub use simple_light::SimpleLight;
|
||||
pub use two_spheres::TwoSpheres;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -55,7 +54,11 @@ pub trait Demo: Send + Sync {
|
|||
|
||||
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 {
|
||||
num: _,
|
||||
x,
|
||||
|
@ -69,6 +72,7 @@ pub trait Demo: Send + Sync {
|
|||
let mut offset = 0;
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
|
||||
let background = self.get_background();
|
||||
|
||||
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 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;
|
||||
|
@ -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 delta_x = x / VERTICAL_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 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
81
src/demos/simple_light.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -30,6 +30,25 @@ pub struct HitRecord<'a> {
|
|||
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 {
|
||||
fn hit(&self, _ray: &Ray, _t_min: f64, _t_max: f64) -> Option<HitRecord>;
|
||||
|
||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -23,11 +23,11 @@ use demos::Demo;
|
|||
|
||||
use std::time::Instant;
|
||||
|
||||
const NUM_SAMPLES: u8 = 100;
|
||||
const NUM_SAMPLES: u16 = 1000;
|
||||
const VERTICAL_PARTITION: usize = 12;
|
||||
const HORIZONTAL_PARTITION: usize = 12;
|
||||
const WIDTH: usize = 1920;
|
||||
const HEIGHT: usize = 1080;
|
||||
const WIDTH: usize = 2560;
|
||||
const HEIGHT: usize = 1440;
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
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)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut active_demo: &dyn Demo<DemoT = BvhNode<Arc<dyn ParallelHit>>> =
|
||||
&demos::ImageTextureDemo {};
|
||||
let mut active_demo: &dyn Demo<DemoT = BvhNode<Arc<dyn ParallelHit>>> = &demos::SimpleLight {};
|
||||
let mut should_update = true;
|
||||
|
||||
loop {
|
||||
|
@ -101,6 +100,10 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> {
|
|||
active_demo = &demos::ImageTextureDemo {};
|
||||
should_update = true;
|
||||
}
|
||||
Some(Keycode::Num5) => {
|
||||
active_demo = &demos::SimpleLight {};
|
||||
should_update = true;
|
||||
}
|
||||
None => unreachable!(),
|
||||
_ => (),
|
||||
};
|
||||
|
|
17
src/materials/diffuse_light.rs
Normal file
17
src/materials/diffuse_light.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ pub struct Lambertian<T: Texture> {
|
|||
albedo: T,
|
||||
}
|
||||
|
||||
impl<T: Texture + Send + Sync> Lambertian<T> {
|
||||
impl<T: Texture> Lambertian<T> {
|
||||
pub fn new(albedo: T) -> Self {
|
||||
Self { albedo }
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ pub struct Metal {
|
|||
}
|
||||
|
||||
impl Metal {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(albedo: Vec3) -> Self {
|
||||
Self { albedo, fuzz: 0.0 }
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
mod dielectric;
|
||||
mod diffuse_light;
|
||||
mod lambertian;
|
||||
mod metal;
|
||||
|
||||
pub use dielectric::Dielectric;
|
||||
pub use diffuse_light::DiffuseLight;
|
||||
pub use lambertian::Lambertian;
|
||||
pub use metal::Metal;
|
||||
use rand::{prelude::SmallRng, Rng};
|
||||
|
@ -13,7 +15,20 @@ use crate::{
|
|||
};
|
||||
|
||||
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
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
mod moving_sphere;
|
||||
mod sphere;
|
||||
mod xy_rectangle;
|
||||
|
||||
pub use moving_sphere::MovingSphere;
|
||||
pub use sphere::Sphere;
|
||||
pub use xy_rectangle::XyRectangle;
|
||||
|
|
|
@ -62,35 +62,21 @@ impl<T: Material + Sized> Hitable for MovingSphere<T> {
|
|||
let discriminant_root = discriminant.sqrt();
|
||||
|
||||
if discriminant > 0.0 {
|
||||
let root = (-b - discriminant_root) / a;
|
||||
if root < t_max && root > t_min {
|
||||
let p = ray.point_at_parameter(root);
|
||||
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 mut root = (-b - discriminant_root) / a;
|
||||
if root < t_min || root > t_max {
|
||||
root = (-b + discriminant_root) / a;
|
||||
}
|
||||
let 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);
|
||||
let normal = (p - self.center(ray.time())) / self.radius;
|
||||
let (u, v) = Self::get_uv(normal);
|
||||
|
||||
return Some(HitRecord {
|
||||
t: root,
|
||||
return Some(HitRecord::new(
|
||||
root,
|
||||
p,
|
||||
u,
|
||||
v,
|
||||
normal: (p - self.center(ray.time())) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
normal,
|
||||
&self.material,
|
||||
Self::get_uv(normal),
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
|
|
@ -49,36 +49,21 @@ impl<T: Material + Sized> Hitable for Sphere<T> {
|
|||
let discriminant_root = discriminant.sqrt();
|
||||
|
||||
if discriminant > 0.0 {
|
||||
let root = (-b - discriminant_root) / a;
|
||||
if root < t_max && root > t_min {
|
||||
let p = ray.point_at_parameter(root);
|
||||
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,
|
||||
});
|
||||
let mut root = (-b - discriminant_root) / a;
|
||||
if root < t_min || root > t_max {
|
||||
root = (-b + discriminant_root) / a;
|
||||
}
|
||||
|
||||
let 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);
|
||||
let normal = (p - self.center) / self.radius;
|
||||
let (u, v) = Self::get_uv(normal);
|
||||
|
||||
return Some(HitRecord {
|
||||
t: root,
|
||||
return Some(HitRecord::new(
|
||||
root,
|
||||
p,
|
||||
u,
|
||||
v,
|
||||
normal: (p - self.center) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
normal,
|
||||
&self.material,
|
||||
Self::get_uv(normal),
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
|
64
src/shapes/xy_rectangle.rs
Normal file
64
src/shapes/xy_rectangle.rs
Normal 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),
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
use crate::types::Vec3;
|
||||
use rand::prelude::SmallRng;
|
||||
|
||||
use crate::{types::Vec3, Hitable};
|
||||
|
||||
pub struct Ray {
|
||||
a: Vec3,
|
||||
|
@ -26,4 +28,30 @@ impl Ray {
|
|||
pub const fn time(&self) -> f64 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user