Created
October 29, 2020 12:34
-
-
Save bencrowder/a19033809fba888d4be28b112e8ecc4e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Rust code sample | |
// This is extracted from a path tracer I wrote for class. I can't | |
// post the whole thing per the class rules, so you get this redacted | |
// version instead. | |
extern crate nalgebra as na; | |
use super::img::Image; | |
use super::objects::traits::*; | |
use super::structs::*; | |
use rand::Rng; | |
use std::io::Write; | |
pub fn trace_image(scene: &Scene, image: &mut Image) { | |
let w = image.width(); | |
let h = image.height(); | |
let pct = (h as f32 / 100.0).floor(); | |
println!("Output: {}x{}px", w, h); | |
for y in 0..h { | |
for x in 0..w { | |
let (r, g, b) = trace_pixel(x, y, scene); | |
image.set_pixel(x, y, r, g, b); | |
} | |
// Progress bar | |
if y as f32 % pct == 0.0 { | |
print!("{} ", y); | |
std::io::stdout().flush().unwrap(); | |
} | |
} | |
} | |
pub fn trace_pixel(i: u32, j: u32, scene: &Scene) -> (f32, f32, f32) { | |
// View reference coordinates | |
let n = na::normalize(&(scene.camera_look_from - scene.camera_look_at)); | |
let u = na::normalize(&na::cross(&scene.camera_look_up, &n)); | |
let v = na::normalize(&na::cross(&n, &u)); | |
// Window settings | |
let imin = 0 as f32; | |
let jmin = 0 as f32; | |
let imax = 512 as f32; | |
let jmax = 512 as f32; | |
// Calculate umin, umax, vmin, vmax | |
let angle = (scene.fov as f32).to_radians(); | |
let umax = angle.tan() * 2f32.sqrt(); | |
let umin = -umax; | |
let vmin = umax; | |
let vmax = umin; | |
// Do the inverse transform | |
let u_s = (i as f32 - imin) * ((umax - umin) / (imax - imin)) + umin; | |
let v_s = (j as f32 - jmin) * ((vmax - vmin) / (jmax - jmin)) + vmin; | |
let w_s = 0.0f32; | |
// Get world space point | |
let world_pt = scene.camera_look_at + u_s * u + v_s * v + w_s * n; | |
// Ray | |
let origin = &scene.camera_look_from; | |
let direction = na::normalize(&(world_pt - scene.camera_look_from)); | |
// Initial parameters | |
let depth = 0; | |
let ior = 1.0; | |
let debug = match (i, j) { | |
(253, 280) => true, | |
(_, _) => false, | |
}; | |
// Antialiasing | |
let mut c: (f32, f32, f32) = (0.0, 0.0, 0.0); | |
for _ in 0..scene.settings.antialiasing_num_rays { | |
let aa_dir = jitter_ray(&direction, scene.settings.antialiasing_jitter_angle, debug); | |
// Send paths and average the result | |
let mut pc: (f32, f32, f32) = (0.0, 0.0, 0.0); | |
for _ in 0..scene.settings.num_paths { | |
let rays = trace_ray(origin, &aa_dir, scene, depth, ior, false, debug); | |
// Average ray | |
let num_rays = rays.len(); | |
let mut ray_avg: (f32, f32, f32) = (0.0, 0.0, 0.0); | |
for r in rays { | |
ray_avg.0 += r.0 / num_rays as f32; | |
ray_avg.1 += r.1 / num_rays as f32; | |
ray_avg.2 += r.2 / num_rays as f32; | |
} | |
// And now average it in across paths | |
pc.0 += ray_avg.0 / scene.settings.num_paths as f32; | |
pc.1 += ray_avg.1 / scene.settings.num_paths as f32; | |
pc.2 += ray_avg.2 / scene.settings.num_paths as f32; | |
} | |
c.0 += pc.0 / scene.settings.antialiasing_num_rays as f32; | |
c.1 += pc.1 / scene.settings.antialiasing_num_rays as f32; | |
c.2 += pc.2 / scene.settings.antialiasing_num_rays as f32; | |
} | |
c | |
} | |
// ... | |
// Checks if the point is in shadow | |
pub fn check_shadow(p: &V3, light: &Light, scene: &Scene, debug: bool) -> f32 { | |
let mut intensity = 1.0; | |
for _ in 0..scene.settings.shadow_num_rays { | |
let light_point = if light.radius > 0.0 { | |
// Area light, so jitter to somewhere on the light sphere | |
random_point_on_area_light(light) | |
} else { | |
// Point light | |
light.position | |
}; | |
let light_vec = light_point - p; | |
let direction = na::normalize(&light_vec); | |
let (intersects, _, t) = get_intersects(&p, &direction, &scene, debug); | |
if intersects { | |
// Check to see if there's line of sight to the light | |
let dist_to_light = v3_magnitude(light_vec); | |
let dist_to_intersect = v3_magnitude(p + direction * t); | |
if dist_to_intersect < dist_to_light { | |
// We don't have line of sight, so take out a sliver | |
intensity -= 1.0 / scene.settings.shadow_num_rays as f32; | |
} | |
} | |
} | |
intensity | |
} | |
// ... | |
pub fn trace_light( | |
light: &Light, | |
obj: &Box<dyn SceneObject>, | |
p: &V3, | |
scene: &Scene, | |
debug: bool, | |
) -> (f32, f32, f32) { | |
let normal = obj.get_normal(&p); | |
let material = obj.get_material(); | |
// Direction to the light source | |
let light_direction = na::normalize(&(light.position - p)); | |
// Direction to the viewer/camera, used for specular highlights | |
let view_direction = na::normalize(&(scene.camera_look_from - p)); | |
// Reflected light | |
let reflected = 2.0 * normal * na::dot(&normal, &light_direction) - light_direction; | |
// Get the diffuse color (either innate or texture mapped) | |
let diffuse = obj.get_diffuse(&p, &normal); | |
// Ambient contribution | |
let c_a = diffuse * scene.ambient_light; | |
// Diffuse contribution | |
let c_d = diffuse * light.color * na::dot(&normal, &light_direction).max(0.0); | |
// Specular contribution | |
let c_s = material.specular | |
* na::dot(&view_direction, &reflected) | |
.max(0.0) | |
.powf(material.phong as f32); | |
// Shadow ray (offset a little so it doesn't collide with the surface) | |
let test_p = p + (normal * 1e-6); | |
let intensity = check_shadow(&test_p, &light, &scene, debug); | |
// Total diffuse/specular contribution (depending on how much we're in shadow) | |
let c = (c_d + c_s) * intensity + c_a; | |
(c.x, c.y, c.z) | |
} | |
// ... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment