1
0

Added new scene, added pdf in lambertian material

This commit is contained in:
Ishan Jain 2023-05-31 18:34:04 +05:30
parent 96e103ee61
commit f645eb4f00
Signed by: ishan
GPG Key ID: 0506DB2A1CC75C27
9 changed files with 148 additions and 126 deletions

View File

@ -1,19 +1,16 @@
use std::sync::Arc;
use rand::{prelude::SmallRng, Rng, SeedableRng};
use crate::{
demos::Demo,
hitable::{
hitable_list::HitableList,
shapes::{Cuboid, MovingSphere, RectBuilder, Sphere},
volume::ConstantMedium,
shapes::{Cuboid, RectBuilder, Sphere},
Hitable,
},
materials::{Dielectric, DiffuseLight, Isotropic, Lambertian, MaterialBuilder, Metal},
texture::{ImageTexture, PerlinNoise, Solid},
materials::{DiffuseLight, Lambertian, MaterialBuilder, Metal},
texture::Solid,
types::Vec3,
BvhNode, Camera,
Camera,
};
pub struct CornellBox {}
@ -26,125 +23,79 @@ impl Demo for CornellBox {
}
fn world(&self) -> Self::DemoT {
let mut rng = rand::thread_rng();
let mut rng = SmallRng::from_rng(&mut rng).unwrap();
let mut ground_boxes = HitableList { list: Vec::new() };
let ground = Lambertian::new(Solid::new(Vec3::new(0.48, 0.83, 0.53)));
for i in 0..20 {
let i = i as f64;
for j in 0..20 {
let j = j as f64;
let w = 100.0;
let x0 = -1000.0 + i * w;
let z0 = -1000.0 + j * w;
let y0 = 0.0;
let x1 = x0 + w;
let y1 = rng.gen_range(1.0..=101.0);
let z1 = z0 + w;
ground_boxes.push(Arc::new(Cuboid::new(
Vec3::new(x0, y0, z0),
Vec3::new(x1, y1, z1),
ground.clone(),
)));
}
}
let red = Lambertian::new(Solid::new(Vec3::new(0.65, 0.05, 0.05)));
let white = Lambertian::new(Solid::new(Vec3::new(0.73, 0.73, 0.73)));
let green = Lambertian::new(Solid::new(Vec3::new(0.12, 0.45, 0.15)));
let light = DiffuseLight::new(Solid::new(Vec3::splat(15)));
let mut objects = HitableList { list: Vec::new() };
objects.push(Arc::new(BvhNode::new(
&mut rng,
&mut ground_boxes.list,
0.0,
1.0,
)));
let light = DiffuseLight::new(Solid::new(Vec3::splat(7.0)));
objects.push(Arc::new(
RectBuilder
.x(123.0..=423.0)
.z(147.0..=412.0)
.y(0.0..=555.0)
.z(0.0..=555.0)
.x(555.0)
.material(green),
));
objects.push(Arc::new(
RectBuilder
.y(0.0..=555.0)
.z(0.0..=555.0)
.x(0.0)
.material(red),
));
objects.push(Arc::new(
RectBuilder
.x(213.0..=343.0)
.z(227.0..=332.0)
.y(554.0)
.material(light),
));
let center1 = Vec3::new(400.0, 400.0, 200.0);
let center2 = center1 + Vec3::new(30.0, 0.0, 0.0);
objects.push(Arc::new(MovingSphere::new(
center1,
center2,
0.0,
1.0,
50.0,
Lambertian::new(Solid::new(Vec3::new(0.7, 0.3, 0.1))),
)));
objects.push(Arc::new(Sphere::new(
Vec3::new(260.0, 150.0, 45.0),
50.0,
Dielectric::new(1.5),
)));
objects.push(Arc::new(Sphere::new(
Vec3::new(0.0, 150.0, 145.0),
50.0,
Metal::with_fuzz(Vec3::new(0.8, 0.8, 0.9), 1.0),
)));
let boundary = Sphere::new(Vec3::new(360.0, 150.0, 145.0), 70.0, Dielectric::new(1.5));
objects.push(Arc::new(boundary.clone()));
objects.push(Arc::new(ConstantMedium::new(
boundary,
Isotropic::new(Solid::new(Vec3::new(0.2, 0.4, 0.9))),
0.2,
)));
objects.push(Arc::new(ConstantMedium::new(
Sphere::new(Vec3::splat(0.0), 5000.0, Dielectric::new(1.5)),
Isotropic::new(Solid::new(Vec3::splat(1.0))),
0.0001,
)));
let earthmap = ImageTexture::from_filename("assets/earthmap.jpg")
.expect("error in reading assets/earthmap.jpg");
objects.push(Arc::new(Sphere::new(
Vec3::new(400.0, 200.0, 400.0),
100.0,
Lambertian::new(earthmap),
)));
objects.push(Arc::new(Sphere::new(
Vec3::new(220.0, 280.0, 300.0),
80.0,
Lambertian::new(PerlinNoise::with_scale(&mut rng, 0.1)),
)));
let mut boxes2 = HitableList { list: Vec::new() };
let white = Lambertian::new(Solid::new(Vec3::splat(0.73)));
for _ in 0..1000 {
boxes2.push(Arc::new(Sphere::new(
Vec3::random_in_range(&mut rng, 0.0..=165.0),
10.0,
white.clone(),
)));
}
objects.push(Arc::new(
RectBuilder
.x(0.0..=555.0)
.z(0.0..=555.0)
.y(555.0)
.material(white.clone()),
));
objects.push(Arc::new(
RectBuilder
.x(0.0..=555.0)
.z(0.0..=555.0)
.y(0.0)
.material(white.clone()),
));
objects.push(Arc::new(
RectBuilder
.x(0.0..=555.0)
.y(0.0..=555.0)
.z(555.0)
.material(white.clone()),
));
objects.push(Arc::new(
BvhNode::new(&mut rng, &mut boxes2.list, 0.0, 1.0)
.rotate_y(15.0)
.translate(Vec3::new(-100.0, 270.0, 395.0)),
Cuboid::new(
Vec3::splat(0.0),
Vec3::new(165.0, 330.0, 165.0),
white.clone(),
)
.rotate_y(15.0)
.translate(Vec3::new(265.0, 0.0, 295.0)),
));
objects.push(Arc::new(
Cuboid::new(Vec3::splat(0.0), Vec3::splat(165.0), white)
.rotate_y(-18.0)
.translate(Vec3::new(130.0, 0.0, 65.0)),
));
objects
}
fn camera(&self, aspect_ratio: f64) -> Camera {
let lookfrom = Vec3::new(478.0, 278.0, -600.0);
let lookfrom = Vec3::new(278.0, 278.0, -800.0);
let lookat = Vec3::new(278.0, 278.0, 0.0);
let aperture = 0.1;
let aperture = 0.0;
let focus_distance = 40.0;
Camera::new(
lookfrom,

View File

@ -137,6 +137,8 @@ fn run(mut width: usize, mut height: usize) -> Result<(), String> {
}
if should_update {
let now = Instant::now();
// TODO(ishan): Update it to only re-render if height/width has changed
// this block should run if the app was sent to background
active_demo.render(&mut buffer, width, height, NUM_SAMPLES);
println!(
"Demo {} Time Taken(s) = {}",

View File

@ -24,7 +24,7 @@ impl Material for Dielectric {
ray_in: &Ray,
hit_rec: &HitRecord,
rng: &mut SmallRng,
) -> (Vec3, Option<Ray>) {
) -> (Vec3, f64, Option<Ray>) {
// Glass absorbs nothing! So, Attenuation is always going to be 1.0 for this
let attenuation = Vec3::splat(1.0);
@ -44,17 +44,20 @@ impl Material for Dielectric {
let direction = reflect(unit_direction, hit_rec.normal);
(
attenuation,
0.0,
Some(Ray::new(hit_rec.p, direction, ray_in.time())),
)
} else if let Some(direction) = refract(unit_direction, hit_rec.normal, refraction_ratio) {
(
attenuation,
0.0,
Some(Ray::new(hit_rec.p, direction, ray_in.time())),
)
} else {
let direction = reflect(unit_direction, hit_rec.normal);
(
attenuation,
0.0,
Some(Ray::new(hit_rec.p, direction, ray_in.time())),
)
}

View File

@ -18,9 +18,15 @@ impl<T: Texture> Isotropic<T> {
}
impl<T: Texture + Send + Sync> Material for Isotropic<T> {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>) {
fn scatter(
&self,
ray: &Ray,
hit_rec: &HitRecord,
rng: &mut SmallRng,
) -> (Vec3, f64, Option<Ray>) {
(
self.texture.value(hit_rec.u, hit_rec.v, hit_rec.p),
0.0,
Some(Ray::new(
hit_rec.p,
random_point_in_unit_sphere(rng),

View File

@ -2,7 +2,7 @@ use rand::prelude::SmallRng;
use crate::{
hitable::HitRecord,
materials::random_point_in_unit_sphere,
materials::{random_point_in_unit_hemisphere, random_point_in_unit_sphere},
types::{Ray, Vec3},
Material, Texture,
};
@ -19,13 +19,29 @@ impl<T: Texture> Lambertian<T> {
}
impl<T: Texture + Send + Sync> Material for Lambertian<T> {
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord, rng: &mut SmallRng) -> (Vec3, Option<Ray>) {
let scatter_direction = hit_rec.normal + random_point_in_unit_sphere(rng);
let scattered_ray = Ray::new(hit_rec.p, scatter_direction, ray.time());
fn scatter(
&self,
ray: &Ray,
hit_rec: &HitRecord,
rng: &mut SmallRng,
) -> (Vec3, f64, Option<Ray>) {
let direction = random_point_in_unit_hemisphere(rng, &hit_rec.normal);
let scattered_ray = Ray::new(hit_rec.p, direction.unit_vector(), ray.time());
(
self.albedo.value(hit_rec.u, hit_rec.v, hit_rec.p),
0.5 / std::f64::consts::PI,
Some(scattered_ray),
)
}
fn scatter_pdf(&self, _ray: &Ray, hit_rec: &HitRecord, scatterd: &Ray) -> f64 {
let cosine: f64 = hit_rec.normal.dot(&scatterd.direction.unit_vector());
if cosine < 0.0 {
0.0
} else {
cosine / std::f64::consts::PI
}
}
}

View File

@ -29,7 +29,7 @@ impl Material for Metal {
ray_in: &Ray,
hit_rec: &HitRecord,
rng: &mut SmallRng,
) -> (Vec3, Option<Ray>) {
) -> (Vec3, f64, Option<Ray>) {
let reflected_ray = reflect(ray_in.direction.unit_vector(), hit_rec.normal);
let scattered_ray = Ray::new(
hit_rec.p,
@ -38,9 +38,9 @@ impl Material for Metal {
);
if scattered_ray.direction.dot(&hit_rec.normal) > 0.0 {
(self.albedo, Some(scattered_ray))
(self.albedo, 0.0, Some(scattered_ray))
} else {
(self.albedo, None)
(self.albedo, 0.0, None)
}
}
}

View File

@ -24,8 +24,12 @@ pub trait Material: Send + Sync {
_ray: &Ray,
_hit_rec: &HitRecord,
_rng: &mut SmallRng,
) -> (Vec3, Option<Ray>) {
(Vec3::splat(0.0), None)
) -> (Vec3, f64, Option<Ray>) {
(Vec3::splat(0.0), 0.0, None)
}
fn scatter_pdf(&self, _ray: &Ray, _hit_rec: &HitRecord, _scattered: &Ray) -> f64 {
0.0
}
fn emit(&self, _u: f64, _v: f64, _p: Vec3) -> Vec3 {
@ -58,11 +62,41 @@ fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f64) -> Option<Vec3> {
}
fn random_point_in_unit_sphere<R: Rng + ?Sized>(rng: &mut R) -> Vec3 {
let mut point = Vec3::random(rng) * 2.0 - Vec3::splat(1.0);
while point.sq_len() >= 1.0 {
point = Vec3::random(rng) * 2.0 - Vec3::splat(1.0);
let u: f64 = rng.gen();
let v: f64 = rng.gen();
let theta = u * 2.0 * std::f64::consts::PI;
let phi = (2.0 * v - 1.0).acos();
let radius = rng.gen::<f64>().cbrt();
let x = radius * phi.sin() * theta.cos();
let y = radius * phi.sin() * theta.sin();
let z = radius * phi.cos();
Vec3::new(x, y, z)
}
fn random_point_in_unit_hemisphere<R: Rng + ?Sized>(rng: &mut R, normal: &Vec3) -> Vec3 {
let u: f64 = rng.gen();
let v: f64 = rng.gen();
let theta = u * 2.0 * std::f64::consts::PI;
let phi = (v.sqrt()).acos();
let radius = rng.gen::<f64>().cbrt();
let x = radius * phi.sin() * theta.cos();
let y = radius * phi.sin() * theta.sin();
let z = radius * phi.cos();
let point = Vec3::new(x, y, z);
if point.dot(normal) >= 0.0 {
point
} else {
-point
}
point
}
pub trait MaterialBuilder<T> {

View File

@ -40,9 +40,14 @@ impl Ray {
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) {
if let (attenuation, pdf, Some(scattered_ray)) =
material.scatter(self, &hit_rec, rng)
{
emitted_color
+ attenuation * scattered_ray.color(world, rng, background, depth + 1)
+ attenuation
* material.scatter_pdf(self, &hit_rec, &scattered_ray)
* scattered_ray.color(world, rng, background, depth + 1)
/ pdf
} else {
emitted_color
}

View File

@ -47,6 +47,11 @@ impl Vec3 {
self.get::<Z>()
}
#[inline]
pub fn abs(&self) -> Self {
Vec3::new(self.x().abs(), self.y().abs(), self.z().abs())
}
pub fn get<D: Dimension>(&self) -> f64 {
unsafe { self.0.extract_unchecked(D::INDEX) }
}