170 lines
5.9 KiB
Rust
170 lines
5.9 KiB
Rust
use nalgebra::{convert, Point3, Vector3};
|
|
|
|
use super::colour::{ColourRgbF, NamedColour};
|
|
use super::image::ImageRgbF;
|
|
use super::integrators::{DirectionalLight, Integrator, WhittedIntegrator};
|
|
use super::raycasting::Ray;
|
|
use super::sampler::Sampler;
|
|
use super::scene::Scene;
|
|
use super::util::Tile;
|
|
|
|
use crate::Real;
|
|
|
|
struct ImageSampler<T: Real> {
|
|
image_height_pixels: usize,
|
|
image_width_pixels: usize,
|
|
|
|
film_width: T,
|
|
film_height: T,
|
|
|
|
camera_location: Point3<T>,
|
|
film_distance: T,
|
|
}
|
|
|
|
impl<T: Real> ImageSampler<T> {
|
|
pub fn new(width: usize, height: usize, camera_location: Point3<T>) -> ImageSampler<T> {
|
|
let (film_width, film_height) = {
|
|
let width: T = convert(width as f64);
|
|
let height: T = convert(height as f64);
|
|
let film_size: T = convert(1.0);
|
|
if width > height {
|
|
(width / height, film_size)
|
|
} else {
|
|
(film_size, width / height)
|
|
}
|
|
};
|
|
ImageSampler {
|
|
image_height_pixels: height,
|
|
image_width_pixels: width,
|
|
film_distance: convert(1.0),
|
|
film_width,
|
|
film_height,
|
|
camera_location,
|
|
}
|
|
}
|
|
|
|
fn scale(i: usize, n: usize, l: T) -> T {
|
|
let one: T = convert(1.0);
|
|
let n: T = convert(n as f64);
|
|
let i: T = convert(i as f64);
|
|
let pixel_size: T = l * (one / n);
|
|
(i + convert(0.5)) * pixel_size
|
|
}
|
|
|
|
fn ray_for_pixel(&self, row: usize, column: usize) -> Ray<T> {
|
|
Ray::new(
|
|
self.camera_location,
|
|
Vector3::new(
|
|
Self::scale(column, self.image_width_pixels, self.film_width)
|
|
- self.film_width * convert(0.5),
|
|
Self::scale(row, self.image_height_pixels, self.film_height)
|
|
- self.film_height * convert(0.5),
|
|
self.film_distance,
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
const RECURSION_LIMIT: u16 = 32;
|
|
|
|
pub fn partial_render_scene<T: Real>(
|
|
scene: &Scene<T>,
|
|
tile: Tile,
|
|
height: usize,
|
|
width: usize,
|
|
) -> (Tile, ImageRgbF<T>) {
|
|
let mut output_image_tile = ImageRgbF::new(tile.width(), tile.height());
|
|
let image_sampler = ImageSampler::new(width, height, scene.camera_location);
|
|
let ambient_intensity: T = convert(0.0);
|
|
let directional_intensity1: T = convert(7.0);
|
|
let directional_intensity2: T = convert(3.0);
|
|
let directional_intensity3: T = convert(2.0);
|
|
let integrator = WhittedIntegrator::<T> {
|
|
ambient_light: ColourRgbF::from_named(NamedColour::White) * ambient_intensity,
|
|
lights: vec![
|
|
DirectionalLight {
|
|
direction: Vector3::new(convert(1.0), convert(1.0), convert(-1.0)).normalize(),
|
|
colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity1,
|
|
},
|
|
DirectionalLight {
|
|
direction: Vector3::new(convert(-0.5), convert(2.0), convert(-0.5)).normalize(),
|
|
colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity2,
|
|
},
|
|
DirectionalLight {
|
|
direction: Vector3::new(convert(-3.0), convert(0.1), convert(-0.5)).normalize(),
|
|
colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity3,
|
|
},
|
|
],
|
|
};
|
|
let sampler = Sampler { scene: &scene };
|
|
for column in 0..tile.width() {
|
|
for row in 0..tile.height() {
|
|
let ray = image_sampler.ray_for_pixel(tile.start_row + row, tile.end_column + column);
|
|
let hit = sampler.sample(&ray);
|
|
let colour = match hit {
|
|
None => ColourRgbF::from_named(NamedColour::Black),
|
|
Some(intersection_info) => {
|
|
integrator.integrate(&sampler, &intersection_info, RECURSION_LIMIT)
|
|
}
|
|
};
|
|
output_image_tile.set_colour(row, column, colour);
|
|
}
|
|
}
|
|
(tile, output_image_tile)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::materials::LambertianMaterial;
|
|
use crate::raycasting::{Intersect, IntersectionInfo, Plane};
|
|
use std::sync::Arc;
|
|
|
|
#[cfg(test)]
|
|
mod imagesampler {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn scale_returns_correct_value_for_zero() {
|
|
let correct_value = (3.0 / 10.0) / 2.0;
|
|
assert!((ImageSampler::scale(0, 10, 3.0f64) - correct_value).abs() < 0.0000000001)
|
|
}
|
|
|
|
#[test]
|
|
fn scale_returns_correct_value_for_last_pixel() {
|
|
let correct_value = 3.0 - (3.0 / 10.0) / 2.0;
|
|
assert!((ImageSampler::scale(9, 10, 3.0f64) - correct_value).abs() < 0.0000000001)
|
|
}
|
|
|
|
#[test]
|
|
fn ray_for_pixel_returns_value_that_intersects_film_plane_at_expected_location() {
|
|
let target = ImageSampler::new(800, 600, Point3::new(0.0, 0.0, 0.0));
|
|
let ray = target.ray_for_pixel(100, 200);
|
|
let film_plane = Plane::new(
|
|
Vector3::new(0.0, 0.0, 1.0),
|
|
target.film_distance,
|
|
Arc::new(LambertianMaterial::<f64>::new_dummy()),
|
|
);
|
|
let point_on_film_plane = match film_plane.intersect(&ray) {
|
|
Some(IntersectionInfo {
|
|
location,
|
|
distance: _,
|
|
normal: _,
|
|
tangent: _,
|
|
cotangent: _,
|
|
retro: _,
|
|
material: _,
|
|
}) => location,
|
|
None => panic!(),
|
|
};
|
|
let expected_x: f64 =
|
|
ImageSampler::scale(200, 800, target.film_width) - target.film_width * 0.5;
|
|
print!("{}, {}", expected_x, point_on_film_plane);
|
|
assert!((point_on_film_plane.x - expected_x).abs() < 0.0000000001);
|
|
let expected_y =
|
|
ImageSampler::scale(100, 600, target.film_height) - target.film_height * 0.5;
|
|
assert!((point_on_film_plane.y - expected_y).abs() < 0.0000000001);
|
|
}
|
|
}
|
|
}
|