Skip to content

Instantly share code, notes, and snippets.

@bencrowder
Created October 29, 2020 12:34
Show Gist options
  • Save bencrowder/a19033809fba888d4be28b112e8ecc4e to your computer and use it in GitHub Desktop.
Save bencrowder/a19033809fba888d4be28b112e8ecc4e to your computer and use it in GitHub Desktop.
// 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