186 lines
6.5 KiB
Rust
186 lines
6.5 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;
|
|
|
|
struct ImageSampler {
|
|
image_height_pixels: usize,
|
|
image_width_pixels: usize,
|
|
|
|
film_width: f64,
|
|
film_height: f64,
|
|
|
|
camera_location: Point3<f64>,
|
|
film_distance: f64,
|
|
}
|
|
|
|
impl ImageSampler {
|
|
pub fn new(width: usize, height: usize, camera_location: Point3<f64>) -> ImageSampler {
|
|
let (film_width, film_height) = {
|
|
let width = width as f64;
|
|
let height = height as f64;
|
|
let film_size = 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: f64) -> f64 {
|
|
let n = n as f64;
|
|
let i = i as f64;
|
|
let pixel_size = l * (1.0 / n);
|
|
(i + 0.5) * pixel_size
|
|
}
|
|
|
|
fn ray_for_pixel(&self, row: usize, column: usize) -> Ray {
|
|
Ray::new(
|
|
self.camera_location,
|
|
Vector3::new(
|
|
Self::scale(column, self.image_width_pixels, self.film_width)
|
|
- self.film_width * 0.5,
|
|
Self::scale(row, self.image_height_pixels, self.film_height)
|
|
- self.film_height * 0.5,
|
|
self.film_distance,
|
|
),
|
|
)
|
|
}
|
|
}
|
|
|
|
const RECURSION_LIMIT: u16 = 32;
|
|
|
|
/// Render a rectangular section of the image.
|
|
///
|
|
/// The contents and the image, along with the camera, are defined by `scene`.
|
|
///
|
|
/// Assuming an overall image size given by `width` and `height`, the part of the image
|
|
/// defined by `tile` is rendered and returned. Rendering a tile at a time allows a partially-
|
|
/// rendered image to be displayed to the user.
|
|
///
|
|
/// # Examples
|
|
//
|
|
/// ```
|
|
/// # use nalgebra::Point3;
|
|
/// # use vanrijn::scene::Scene;
|
|
/// # use vanrijn::util::TileIterator;
|
|
/// # use vanrijn::partial_render_scene;
|
|
/// # let scene = Scene { camera_location: Point3::new(0.0, 0.0, 0.0), objects: vec![] };
|
|
/// let image_width = 640;
|
|
/// let image_height = 480;
|
|
/// let time_size = 32;
|
|
/// for tile in TileIterator::new(640, 480, 32) {
|
|
/// let tile_image = partial_render_scene( &scene, tile, image_height, image_width );
|
|
/// // display and/or save tile_image
|
|
/// }
|
|
/// ```
|
|
pub fn partial_render_scene(scene: &Scene, tile: Tile, height: usize, width: usize) -> ImageRgbF {
|
|
let mut output_image_tile = ImageRgbF::new(tile.width(), tile.height());
|
|
let image_sampler = ImageSampler::new(width, height, scene.camera_location);
|
|
let ambient_intensity = 0.0;
|
|
let directional_intensity1 = 7.0;
|
|
let directional_intensity2 = 3.0;
|
|
let directional_intensity3 = 2.0;
|
|
let integrator = WhittedIntegrator {
|
|
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.start_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);
|
|
}
|
|
}
|
|
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::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);
|
|
}
|
|
}
|
|
}
|