You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
11 KiB
323 lines
11 KiB
use crate::structs::{Vec3, ColorVec, COLOR_ZERO, Ray, Y, X};
|
|
use crate::object::Obj;
|
|
use crate::material::{Material, SurfaceType};
|
|
use crate::consts::*;
|
|
use crate::light::Light;
|
|
use std::f64::consts::PI;
|
|
use rand::prelude::*;
|
|
use crossbeam_channel::unbounded;
|
|
use std::ops::{Mul, Add, Sub};
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
pub struct Cam {
|
|
pos: Vec3,
|
|
dir: Vec3,
|
|
up: Vec3,
|
|
right: Vec3
|
|
}
|
|
|
|
impl Cam {
|
|
pub fn new(pos: Vec3, dir: Vec3, fov: f64) -> Cam {
|
|
let right = (dir ^ UP).unit() * fov;
|
|
let up = (right ^ dir).unit() * fov;
|
|
let dir = dir.unit();
|
|
Cam { pos, dir, up, right }
|
|
}
|
|
|
|
pub fn new_pointing(pos: Vec3, pointing: Vec3, fov: f64) -> Cam {
|
|
Self::new(pos, pointing - pos, fov)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
struct RayData {
|
|
pub pos: Vec3,
|
|
pub dir: Vec3,
|
|
pub normal: Vec3,
|
|
pub material: Material,
|
|
pub dist: f64,
|
|
pub steps: u32
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
struct LightRayData<'a> {
|
|
pub light: &'a Light,
|
|
pub dir: Vec3,
|
|
pub dist: f64
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
struct Slice {
|
|
pub x: usize,
|
|
pub y: usize,
|
|
pub w: usize,
|
|
pub h: usize
|
|
}
|
|
|
|
type CastData = [Option<RayData>; MAX_BOUNCES as usize];
|
|
const NONE_CAST_DATA: CastData = [None; MAX_BOUNCES as usize];
|
|
pub type Image = [u8; IMG_BYTE_SIZE];
|
|
|
|
fn l2s(l: f64) -> u8 {
|
|
(f64::powf(f64::clamp(l, 0., 1.), 1./2.2) * 255. + 0.5) as u8
|
|
}
|
|
|
|
fn randab(a: f64, b: f64) -> f64 {
|
|
random::<f64>() * (b-a) + a
|
|
}
|
|
|
|
fn shoot_ray<T: Obj>(scene: &T, pos: Vec3, dir: Vec3) -> Option<RayData> {
|
|
let dir = dir.unit();
|
|
let mut steps = 0;
|
|
let mut dist = 0.;
|
|
let ray = Ray::new(pos, dir);
|
|
while steps < MAX_STEPS && dist < MAX_DIST {
|
|
let pos = ray.point(dist);
|
|
let d = scene.distance_to(pos);
|
|
if d < EPSILON {
|
|
return Some(RayData {
|
|
pos,
|
|
dir,
|
|
normal: scene.normal_at(pos),
|
|
material: scene.material_at(pos),
|
|
dist,
|
|
steps
|
|
})
|
|
}
|
|
dist += d;
|
|
steps += 1;
|
|
}
|
|
None
|
|
}
|
|
|
|
fn shoot_ray_at<T: Obj>(scene: &T, pos: Vec3, dest: Vec3) -> bool {
|
|
let dir = (dest - pos).unit();
|
|
let mut steps = 0;
|
|
let mut dist = EPSILON; //XXX 0.
|
|
let ray = Ray::new(pos, dir);
|
|
while steps < MAX_STEPS && dist < MAX_DIST {
|
|
let point = ray.point(dist);
|
|
let sd = scene.distance_to(point);
|
|
let dd = dest.distance_to(point);
|
|
if sd < 0. {
|
|
return false;
|
|
}
|
|
if dd < sd {
|
|
return true;
|
|
}
|
|
dist += if sd < EPSILON { EPSILON } else { sd };
|
|
steps += 1;
|
|
}
|
|
false
|
|
}
|
|
|
|
impl Cam {
|
|
pub fn render<T: Obj + Clone>(&self, scene: &T) -> Image {
|
|
if THREAD_COUNT > 1 {
|
|
self.render_multithreaded(scene)
|
|
} else {
|
|
self.render_singlethreaded(scene)
|
|
}
|
|
}
|
|
|
|
pub fn render_singlethreaded<T: Obj>(&self, scene: &T) -> Image {
|
|
let lights = scene.get_lights();
|
|
let mut pixels = [0; IMG_BYTE_SIZE];
|
|
for y in 0..IMG_HEIGHT {
|
|
for x in 0..IMG_WIDTH {
|
|
let field_pos = (x + y*IMG_WIDTH) * 3;
|
|
self.render_single(scene, &lights, x, y, &mut pixels[field_pos..(field_pos+3)]);
|
|
}
|
|
}
|
|
pixels
|
|
}
|
|
|
|
pub fn render_multithreaded<T: Obj + Clone>(&self, scene: &T) -> Image {
|
|
let mut pixels = [0; IMG_BYTE_SIZE];
|
|
|
|
crossbeam::scope(|scope| {
|
|
// initialize everything for render
|
|
let lights = scene.get_lights();
|
|
let slice_width = IMG_WIDTH / THREAD_COUNT;
|
|
let slice_height = IMG_HEIGHT / SLICES_PER_THREAD;
|
|
let slice_byte_size = slice_width * slice_height * 3;
|
|
|
|
let (slice_tx, slice_rx) = unbounded::<Slice>();
|
|
let (data_tx, data_rx) = unbounded();
|
|
|
|
// spawn a bunch of threads
|
|
for _i in 0..THREAD_COUNT {
|
|
let cam = self.clone();
|
|
let scene = scene.clone();
|
|
let lights = lights.clone();
|
|
let slice_rx = slice_rx.clone();
|
|
let data_tx = data_tx.clone();
|
|
|
|
scope.spawn(move |_| {
|
|
for slice in slice_rx.iter() {
|
|
let mut pixels = Vec::with_capacity(slice_byte_size);
|
|
pixels.resize(slice_byte_size, 0 as u8);
|
|
let data = pixels.as_mut_slice();
|
|
|
|
for x in 0..slice.w {
|
|
for y in 0..slice.h {
|
|
let i = (x + y*slice_width) * 3;
|
|
cam.render_single(&scene, &lights, slice.x+x, slice.y+y, &mut data[i..(i+3)]);
|
|
}
|
|
}
|
|
|
|
data_tx.send((slice, pixels)).unwrap();
|
|
}
|
|
});
|
|
}
|
|
|
|
// don't forget to close the ends of the channel we don't use
|
|
drop(slice_rx);
|
|
drop(data_tx);
|
|
|
|
// send the slice data
|
|
let mut y = 0;
|
|
while y < IMG_HEIGHT {
|
|
let mut x = 0;
|
|
while x < IMG_WIDTH {
|
|
let w = usize::min(slice_width, IMG_WIDTH - x);
|
|
let h = usize::min(slice_height, IMG_HEIGHT - y);
|
|
slice_tx.send(Slice { x, y, w, h }).unwrap();
|
|
x += slice_width;
|
|
}
|
|
y += slice_height;
|
|
}
|
|
drop(slice_tx);
|
|
|
|
// merge stuff as we get it
|
|
for (slice, data) in data_rx {
|
|
let data = data.as_slice();
|
|
for sy in 0..slice.h {
|
|
let py = sy + slice.y;
|
|
for sx in 0..slice.w {
|
|
let px = sx + slice.x;
|
|
|
|
let pi = (px + py * IMG_WIDTH) * 3;
|
|
let si = (sx + sy * slice_width) * 3;
|
|
pixels[pi + 0] = data[si + 0];
|
|
pixels[pi + 1] = data[si + 1];
|
|
pixels[pi + 2] = data[si + 2];
|
|
}
|
|
}
|
|
}
|
|
}).unwrap();
|
|
|
|
pixels
|
|
}
|
|
|
|
fn render_single<T: Obj>(&self, scene: &T, lights: &Vec<Light>, x: usize, y: usize, field: &mut [u8]) -> () {
|
|
let mut color = COLOR_ZERO;
|
|
for xs in 0..SUPERSAMPLING {
|
|
for ys in 0..SUPERSAMPLING {
|
|
for _i in 0..RAYS_PER_PIXEL {
|
|
let cast = self.cast_single(scene, x * SUPERSAMPLING + xs, y * SUPERSAMPLING + ys);
|
|
color = color + self.color_single(scene, &lights, cast);
|
|
}
|
|
}
|
|
}
|
|
color = color / (SUPERSAMPLING * SUPERSAMPLING * RAYS_PER_PIXEL) as f64;
|
|
|
|
field[0] = l2s(color.v(0));
|
|
field[1] = l2s(color.v(1));
|
|
field[2] = l2s(color.v(2));
|
|
}
|
|
|
|
fn color_single<'a, T: Obj>(&self, scene: &'a T, lights: &'a Vec<Light>, cast: CastData) -> ColorVec {
|
|
let mut color = COLOR_ZERO;
|
|
let mut dist: f64 = 1.;
|
|
|
|
for i in (0..MAX_BOUNCES).rev() {
|
|
if let Some(ray) = cast[i as usize] {
|
|
match ray.material.surface() {
|
|
SurfaceType::Diffuse => {
|
|
color = color / dist.powf(DIST_POWER);
|
|
|
|
color = ray.material.reflection() * color + ray.material.emission();
|
|
for lightray in Self::get_lights(scene, lights, ray.pos + (scene.normal_at(ray.pos)- ray.dir) * LIGHT_EPSILON) {
|
|
let angle = f64::max(0., ray.dir.angle_to(lightray.dir).abs() / PI / ANGLE_CORRECTION + 1. - ANGLE_FIX_CORRECTION);
|
|
let dist = f64::max(1., lightray.dist / DIST_CORRECTION - DIST_FIX_CORRECTION);
|
|
color = color + ray.material.reflection() * lightray.light.color() / angle.powf(ANGLE_POWER) / dist.powf(DIST_POWER);
|
|
}
|
|
},
|
|
SurfaceType::Reflective => {
|
|
color = ray.material.reflection() * color + ray.material.emission();
|
|
},
|
|
SurfaceType::Stop => {
|
|
color = ray.material.emission();
|
|
}
|
|
}
|
|
dist = f64::max(1., ray.dist / DIST_CORRECTION - DIST_FIX_CORRECTION);
|
|
}
|
|
}
|
|
|
|
color / dist.powf(DIST_POWER)
|
|
}
|
|
|
|
fn cast_single<T: Obj>(&self, scene: &T, x: usize, y: usize) -> CastData {
|
|
let mut cast = NONE_CAST_DATA;
|
|
let mut pos = self.pos;
|
|
let mut dir = self.dir_for(x, y);
|
|
let mut i = 0;
|
|
while i < MAX_BOUNCES {
|
|
let hit = shoot_ray(scene, pos, dir);
|
|
cast[i as usize] = hit;
|
|
match hit {
|
|
Some(hit) => {
|
|
i += 1;
|
|
pos = hit.pos - dir * EPSILON;
|
|
match hit.material.surface() {
|
|
SurfaceType::Stop => break,
|
|
SurfaceType::Diffuse => {
|
|
let r1 = randab(0., 2.*PI);
|
|
let r2 = randab(0., 1.);
|
|
let w = hit.normal;
|
|
let u = ((if w.x().abs() > 0.5 { Y } else { X }) ^ w).unit();
|
|
let v = w ^ u;
|
|
let x = r1.cos()*r2.sqrt();
|
|
let y = r1.sin()*r2.sqrt();
|
|
let z = (1.-r2).sqrt();
|
|
if w.x().abs() < 0.5 {
|
|
dir = (u*x + v*y + w*z).unit();
|
|
} else {
|
|
dir = (u*x + v*y + w*z).unit();
|
|
}
|
|
},
|
|
SurfaceType::Reflective => {
|
|
dir = dir - hit.normal * (hit.normal * dir) * 2.;
|
|
}
|
|
}
|
|
},
|
|
None => break,
|
|
}
|
|
}
|
|
cast
|
|
}
|
|
|
|
fn get_lights<'a, T: Obj>(scene: &'a T, lights: &'a Vec<Light>, pos: Vec3) -> Vec<LightRayData<'a>> {
|
|
lights
|
|
.into_iter()
|
|
.filter_map(|light| {
|
|
if shoot_ray_at(scene, pos, light.pos()) {
|
|
Some(LightRayData {
|
|
light,
|
|
dir: (pos - light.pos()),
|
|
dist: pos.distance_to(light.pos())
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn dir_for(&self, x: usize, y: usize) -> Vec3 {
|
|
let x = (((x + (IMG_DIM - IMG_WIDTH) * SUPERSAMPLING / 2) as f64) / ((IMG_DIM * SUPERSAMPLING) as f64) - 0.5) * 2.;
|
|
let y = (0.5 - ((y + (IMG_DIM - IMG_HEIGHT) * SUPERSAMPLING / 2) as f64) / ((IMG_DIM * SUPERSAMPLING) as f64)) * 2.;
|
|
(self.dir + self.right * x + self.up * y).unit()
|
|
}
|
|
} |