Added upto hit table based sphere, Working on anti-aliasing module

This commit is contained in:
Ishan Jain 2020-02-13 16:12:52 +05:30
parent c4fd7ab209
commit 30c8b6054f
9 changed files with 275 additions and 4 deletions

43
ria-weekend/src/camera.rs Normal file
View File

@ -0,0 +1,43 @@
use crate::types::{Ray, Vec3};
pub struct Camera {
pub origin: Vec3,
pub horizontal: Vec3,
pub vertical: Vec3,
pub lower_left_corner: Vec3,
}
impl Camera {
pub const fn new(
origin: Vec3,
horizontal: Vec3,
vertical: Vec3,
lower_left_corner: Vec3,
) -> Self {
Self {
origin,
horizontal,
vertical,
lower_left_corner,
}
}
pub fn get_ray(&self, u: f32, v: f32) -> Ray {
Ray::new(
self.origin,
self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin,
)
}
}
impl std::default::Default for Camera {
fn default() -> Self {
Camera {
origin: Vec3::new(0.0, 0.0, 0.0),
// Because canvas is in 2:1 ratio
horizontal: Vec3::new(4.0, 0.0, 0.0),
vertical: Vec3::new(0.0, 2.0, 0.0),
lower_left_corner: Vec3::new(-2.0, -1.0, -1.0),
}
}
}

View File

@ -0,0 +1,58 @@
use crate::{
demos::Demo,
types::{Hitable, HitableList, Ray, Sphere, Vec3},
};
pub struct HitableSphere;
impl Demo for HitableSphere {
fn name(&self) -> &'static str {
"Sphere using Hit table"
}
fn render(&self, buf: &mut [u8], width: usize, height: usize, _samples: u8) {
let lower_left_corner = Vec3::new(-2.0, -1.0, -1.0);
let horizontal = Vec3::new(4.0, 0.0, 0.0);
let vertical = Vec3::new(0.0, 2.0, 0.0);
let origin = Vec3::new(0.0, 0.0, 0.0);
let world = HitableList {
list: vec![
Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)),
Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)),
],
};
let mut offset = 0;
for j in (0..height).rev() {
for i in 0..width {
let u = i as f32 / width as f32;
let v = j as f32 / height as f32;
let ray = Ray::new(origin, lower_left_corner + horizontal * u + vertical * v);
let color = calc_color(ray, &world);
buf[offset] = (255.99 * color.r()) as u8;
buf[offset + 1] = (255.99 * color.g()) as u8;
buf[offset + 2] = (255.99 * color.b()) as u8;
offset += 4;
}
}
}
}
fn calc_color(ray: Ray, world: &HitableList) -> Vec3 {
if let Some(hit_rec) = world.hit(&ray, 0.0, std::f32::MAX) {
// It's easier to visualise normals as unit vectors
// So, This trick of adding 1 to each dimension and then halving
// the resulting value shifts the normals from -1<->1 range to
// 0<->1 range
Vec3::new(
hit_rec.normal.x() + 1.0,
hit_rec.normal.y() + 1.0,
hit_rec.normal.z() + 1.0,
) * 0.5
} else {
let unit_direction = ray.direction().unit_vector();
let t = unit_direction.y() * 0.5 + 1.0;
Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t
}
}

View File

@ -1,9 +1,13 @@
mod hitable_sphere;
mod linear_gradient_rectangle;
mod simple_antialiasing;
mod simple_rectangle;
mod simple_sphere;
mod surface_normal_sphere;
pub use hitable_sphere::HitableSphere;
pub use linear_gradient_rectangle::LinearGradientRectangle;
pub use simple_antialiasing::SimpleAntialiasing;
pub use simple_rectangle::SimpleRectangle;
pub use simple_sphere::SimpleSphere;
pub use surface_normal_sphere::SurfaceNormalSphere;
@ -21,11 +25,11 @@ pub trait Demo {
Ok(file) => file,
Err(e) => panic!("couldn't create {}: {}", self.name(), e),
};
file.write(header.as_bytes())
file.write_all(header.as_bytes())
.expect("error in writing file header");
for i in buf.chunks(4) {
match file.write(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) {
match file.write_all(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) {
Ok(_) => (),
Err(e) => panic!("couldn't write to {}: {}", self.name(), e),
}

View File

@ -0,0 +1,63 @@
use {
crate::{
demos::Demo,
types::{Hitable, HitableList, Ray, Sphere, Vec3},
Camera,
},
rand::Rng,
};
pub struct SimpleAntialiasing;
impl Demo for SimpleAntialiasing {
fn name(&self) -> &'static str {
"A simple antialiasing implementation"
}
fn render(&self, buf: &mut [u8], width: usize, height: usize, samples: u8) {
let world = HitableList {
list: vec![
Box::new(Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5)),
Box::new(Sphere::new(Vec3::new(0.0, -100.5, -1.0), 100.0)),
],
};
let camera: Camera = Default::default();
let mut rng = rand::thread_rng();
let mut offset = 0;
for j in (0..height).rev() {
for i in 0..width {
let mut color = Vec3::new(0.0, 0.0, 0.0);
for _s in 0..samples {
let u = (i as f32 + rng.gen::<f32>()) / width as f32;
let v = (j as f32 + rng.gen::<f32>()) / height as f32;
let r = camera.get_ray(u, v);
color += calc_color(r, &world);
}
color /= samples as f32;
buf[offset] = (255.99 * color.r()) as u8;
buf[offset + 1] = (255.99 * color.g()) as u8;
buf[offset + 2] = (255.99 * color.b()) as u8;
offset += 4;
}
}
}
}
fn calc_color(ray: Ray, world: &HitableList) -> Vec3 {
if let Some(hit_rec) = world.hit(&ray, 0.0, std::f32::MAX) {
// It's easier to visualise normals as unit vectors
// So, This trick of adding 1 to each dimension and then halving
// the resulting value shifts the normals from -1<->1 range to
// 0<->1 range
Vec3::new(
hit_rec.normal.x() + 1.0,
hit_rec.normal.y() + 1.0,
hit_rec.normal.z() + 1.0,
) * 0.5
} else {
let unit_direction = ray.direction().unit_vector();
let t = unit_direction.y() * 0.5 + 1.0;
Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t
}
}

View File

@ -1,6 +1,11 @@
#![allow(clippy::suspicious_arithmetic_impl)]
mod camera;
mod demos;
mod types;
pub use camera::Camera;
use {
demos::Demo,
sdl2::{
@ -10,13 +15,13 @@ use {
},
};
const NUM_SAMPLES: u8 = 10;
const NUM_SAMPLES: u8 = 100;
fn main() -> Result<(), String> {
let sdl_ctx = sdl2::init()?;
let video_subsys = sdl_ctx.video()?;
let (mut width, mut height) = (1280usize, 640usize);
let (mut width, mut height) = (1280, 640);
let window = video_subsys
.window("Ray tracing in a weekend", width as u32, height as u32)
@ -65,6 +70,8 @@ fn main() -> Result<(), String> {
}
Some(Keycode::Num3) => active_demo = Box::new(demos::SimpleSphere),
Some(Keycode::Num4) => active_demo = Box::new(demos::SurfaceNormalSphere),
Some(Keycode::Num5) => active_demo = Box::new(demos::HitableSphere),
Some(Keycode::Num6) => active_demo = Box::new(demos::SimpleAntialiasing),
None => unreachable!(),
_ => (),
};

View File

@ -0,0 +1,13 @@
use crate::types::{Ray, Vec3};
pub struct HitRecord {
pub t: f32,
pub p: Vec3,
pub normal: Vec3,
}
pub trait Hitable {
fn hit(&self, _ray: &Ray, _t_min: f32, _t_max: f32) -> Option<HitRecord> {
None
}
}

View File

@ -0,0 +1,19 @@
use crate::types::{HitRecord, Hitable, Ray};
pub struct HitableList {
pub list: Vec<Box<dyn Hitable>>,
}
impl Hitable for HitableList {
fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
let mut closest_so_far = t_max;
let mut hit_rec: Option<HitRecord> = None;
for obj in &self.list {
if let Some(l_hit_rec) = obj.hit(ray, t_min, closest_so_far) {
closest_so_far = l_hit_rec.t;
hit_rec = Some(l_hit_rec);
}
}
hit_rec
}
}

View File

@ -1,5 +1,11 @@
mod hitable;
mod hitable_list;
mod ray;
mod sphere;
mod vec3;
pub use hitable::{HitRecord, Hitable};
pub use hitable_list::HitableList;
pub use ray::Ray;
pub use sphere::Sphere;
pub use vec3::Vec3;

View File

@ -0,0 +1,58 @@
use crate::types::{HitRecord, Hitable, Ray, Vec3};
pub struct Sphere {
center: Vec3,
radius: f32,
}
impl Sphere {
pub fn new(center: Vec3, radius: f32) -> Self {
Self { center, radius }
}
const fn radius(&self) -> f32 {
self.radius
}
const fn center(&self) -> Vec3 {
self.center
}
}
impl Hitable for Sphere {
fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
let oc = ray.origin() - self.center;
let a = ray.direction().dot(&ray.direction());
let b = oc.dot(&ray.direction());
let c = oc.dot(&oc) - self.radius * self.radius;
// The discriminant is calculated using b^2 - 4 * a * c
// but in this specific case, If we put the equation in the
// formula to find quadratic roots, We can get this shorter
// formula to find the discriminant.
// Check this for detailed proof
// https://vchizhov.github.io/resources/ray%20tracing/ray%20tracing%20tutorial%20series%20vchizhov/ray_casting/part1/intersecting_a_sphere.md.html#appendix
let discriminant = b * b - a * c;
if discriminant > 0.0 {
let root = (-b - discriminant.sqrt()) / a;
if root < t_max && root > t_min {
let p = ray.point_at_parameter(root);
return Some(HitRecord {
t: root,
p,
normal: (p - self.center) / self.radius,
});
}
let root = (-b + discriminant.sqrt()) / a;
if root < t_max && root > t_min {
let p = ray.point_at_parameter(root);
return Some(HitRecord {
t: root,
p,
normal: (p - self.center) / self.radius,
});
}
}
None
}
}