Apply tone mapping to image, not colours
Apply tone mapping and conversion from floats to bytes only on final image. This will allow better tone mapping operators later and for now removes NormalizedAsFloat trait constraints that were creeping through everything.
This commit is contained in:
parent
f13b585bfe
commit
f58afb2ded
|
|
@ -1,7 +1,7 @@
|
|||
use nalgebra::{convert, RealField, Vector3};
|
||||
|
||||
use super::colour::{ClampingToneMapper, NamedColour, NormalizedAsByte, ToneMapper};
|
||||
use super::image::OutputImage;
|
||||
use super::colour::{ColourRgbF, NamedColour};
|
||||
use super::image::ImageRgbF;
|
||||
use super::integrators::{DirectionalLight, Integrator, PhongIntegrator};
|
||||
use super::raycasting::Ray;
|
||||
use super::scene::Scene;
|
||||
|
|
@ -61,11 +61,7 @@ impl<T: RealField> ImageSampler<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn render_scene<T: RealField + NormalizedAsByte>(
|
||||
output_image: &mut OutputImage,
|
||||
scene: &Scene<T>,
|
||||
) where
|
||||
f32: From<T>,
|
||||
pub fn render_scene<T: RealField>(output_image: &mut ImageRgbF<T>, scene: &Scene<T>)
|
||||
{
|
||||
let image_sampler = ImageSampler::new(
|
||||
output_image.get_width(),
|
||||
|
|
@ -79,7 +75,6 @@ pub fn render_scene<T: RealField + NormalizedAsByte>(
|
|||
intensity: convert(0.3),
|
||||
}],
|
||||
};
|
||||
let tone_mapper = ClampingToneMapper {};
|
||||
for column in 0..output_image.get_width() {
|
||||
for row in 0..output_image.get_height() {
|
||||
let ray = image_sampler.ray_for_pixel(row, column);
|
||||
|
|
@ -94,11 +89,10 @@ pub fn render_scene<T: RealField + NormalizedAsByte>(
|
|||
},
|
||||
);
|
||||
let colour = match hit {
|
||||
None => NamedColour::Black.as_colourrgb(),
|
||||
None => ColourRgbF::from_named(NamedColour::Black),
|
||||
Some(intersection_info) => integrator.integrate(&intersection_info),
|
||||
};
|
||||
let colour = tone_mapper.apply_tone_mapping(&colour);
|
||||
output_image.set_color(row, column, colour);
|
||||
output_image.set_colour(row, column, colour);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
228
src/colour.rs
228
src/colour.rs
|
|
@ -1,24 +1,42 @@
|
|||
use nalgebra::{clamp, convert, RealField, Vector3};
|
||||
use nalgebra::{convert, RealField, Vector3};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ColourRGB<T: RealField> {
|
||||
pub struct ColourRgbF<T: RealField> {
|
||||
values: Vector3<T>,
|
||||
}
|
||||
|
||||
pub trait NormalizedAsByte {
|
||||
fn normalized_to_byte(self) -> u8;
|
||||
fn byte_to_normalized(byte: u8) -> Self;
|
||||
}
|
||||
|
||||
impl<T: RealField + NormalizedAsByte> ColourRGB<T> {
|
||||
pub fn new(red: T, green: T, blue: T) -> ColourRGB<T> {
|
||||
ColourRGB {
|
||||
impl<T: RealField> ColourRgbF<T> {
|
||||
pub fn new(red: T, green: T, blue: T) -> ColourRgbF<T> {
|
||||
ColourRgbF {
|
||||
values: Vector3::new(red, green, blue),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_vector3(v: &Vector3<T>) -> ColourRGB<T> {
|
||||
ColourRGB { values: *v }
|
||||
pub fn from_named(name: NamedColour) -> ColourRgbF<T> {
|
||||
let zero: T = convert(0.0);
|
||||
let half: T = convert(0.5);
|
||||
let one: T = convert(1.0);
|
||||
match name {
|
||||
NamedColour::Black => ColourRgbF::new(zero, zero, zero),
|
||||
NamedColour::White => ColourRgbF::new(one, one, one),
|
||||
NamedColour::Red => ColourRgbF::new(one, zero, zero),
|
||||
NamedColour::Lime => ColourRgbF::new(zero, one, zero),
|
||||
NamedColour::Blue => ColourRgbF::new(zero, zero, one),
|
||||
NamedColour::Yellow => ColourRgbF::new(one, one, zero),
|
||||
NamedColour::Cyan => ColourRgbF::new(zero, one, one),
|
||||
NamedColour::Magenta => ColourRgbF::new(one, zero, one),
|
||||
NamedColour::Gray => ColourRgbF::new(half, half, half),
|
||||
NamedColour::Maroon => ColourRgbF::new(half, zero, zero),
|
||||
NamedColour::Olive => ColourRgbF::new(half, half, zero),
|
||||
NamedColour::Green => ColourRgbF::new(zero, half, zero),
|
||||
NamedColour::Purple => ColourRgbF::new(half, zero, half),
|
||||
NamedColour::Teal => ColourRgbF::new(zero, half, half),
|
||||
NamedColour::Navy => ColourRgbF::new(zero, zero, half),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_vector3(v: &Vector3<T>) -> ColourRgbF<T> {
|
||||
ColourRgbF { values: *v }
|
||||
}
|
||||
|
||||
pub fn red(&self) -> T {
|
||||
|
|
@ -38,38 +56,10 @@ impl<T: RealField + NormalizedAsByte> ColourRGB<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ColourRGB24 {
|
||||
pub struct ColourRgbU8 {
|
||||
pub values: [u8; 3],
|
||||
}
|
||||
|
||||
pub trait ToneMapper<T: RealField> {
|
||||
fn apply_tone_mapping(&self, colour_in: &ColourRGB<T>) -> ColourRGB24;
|
||||
}
|
||||
|
||||
pub struct ClampingToneMapper {}
|
||||
|
||||
impl ClampingToneMapper {
|
||||
pub fn new() -> ClampingToneMapper {
|
||||
ClampingToneMapper {}
|
||||
}
|
||||
|
||||
fn clamp<T: RealField + NormalizedAsByte>(v: &T) -> u8 {
|
||||
clamp(v, &T::zero(), &T::one()).normalized_to_byte()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RealField + NormalizedAsByte> ToneMapper<T> for ClampingToneMapper {
|
||||
fn apply_tone_mapping(&self, colour_in: &ColourRGB<T>) -> ColourRGB24 {
|
||||
ColourRGB24 {
|
||||
values: [
|
||||
Self::clamp(&colour_in.values[0]),
|
||||
Self::clamp(&colour_in.values[1]),
|
||||
Self::clamp(&colour_in.values[2]),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum NamedColour {
|
||||
Black,
|
||||
White,
|
||||
|
|
@ -88,51 +78,6 @@ pub enum NamedColour {
|
|||
Navy,
|
||||
}
|
||||
|
||||
impl NamedColour {
|
||||
pub fn as_colourrgb<T: RealField + NormalizedAsByte>(self) -> ColourRGB<T> {
|
||||
let zero: T = convert(0.0);
|
||||
let half: T = convert(0.5);
|
||||
let one: T = convert(1.0);
|
||||
match self {
|
||||
NamedColour::Black => ColourRGB::new(zero, zero, zero),
|
||||
NamedColour::White => ColourRGB::new(one, one, one),
|
||||
NamedColour::Red => ColourRGB::new(one, zero, zero),
|
||||
NamedColour::Lime => ColourRGB::new(zero, one, zero),
|
||||
NamedColour::Blue => ColourRGB::new(zero, zero, one),
|
||||
NamedColour::Yellow => ColourRGB::new(one, one, zero),
|
||||
NamedColour::Cyan => ColourRGB::new(zero, one, one),
|
||||
NamedColour::Magenta => ColourRGB::new(one, zero, one),
|
||||
NamedColour::Gray => ColourRGB::new(half, half, half),
|
||||
NamedColour::Maroon => ColourRGB::new(half, zero, zero),
|
||||
NamedColour::Olive => ColourRGB::new(half, half, zero),
|
||||
NamedColour::Green => ColourRGB::new(zero, half, zero),
|
||||
NamedColour::Purple => ColourRGB::new(half, zero, half),
|
||||
NamedColour::Teal => ColourRGB::new(zero, half, half),
|
||||
NamedColour::Navy => ColourRGB::new(zero, zero, half),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NormalizedAsByte for f32 {
|
||||
fn normalized_to_byte(self) -> u8 {
|
||||
(self * (std::u8::MAX as f32)) as u8
|
||||
}
|
||||
|
||||
fn byte_to_normalized(byte: u8) -> f32 {
|
||||
(byte as f32) / (std::u8::MAX as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl NormalizedAsByte for f64 {
|
||||
fn normalized_to_byte(self) -> u8 {
|
||||
(self * (std::u8::MAX as f64)) as u8
|
||||
}
|
||||
|
||||
fn byte_to_normalized(byte: u8) -> f64 {
|
||||
(byte as f64) / (std::u8::MAX as f64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -142,7 +87,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn constructor_sets_correct_red_green_and_blue() {
|
||||
let target = ColourRGB::new(1.0, 2.0, 3.0);
|
||||
let target = ColourRgbF::new(1.0, 2.0, 3.0);
|
||||
assert!(target.red() == 1.0);
|
||||
assert!(target.green() == 2.0);
|
||||
assert!(target.blue() == 3.0);
|
||||
|
|
@ -150,116 +95,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn as_vector3_returns_expected_vector() {
|
||||
let target = ColourRGB::new(1.0, 2.0, 3.0);
|
||||
let target = ColourRgbF::new(1.0, 2.0, 3.0);
|
||||
let result = target.as_vector3();
|
||||
assert!(result.x == 1.0);
|
||||
}
|
||||
}
|
||||
mod clamping_tone_mapper {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn black_colourrgb_becomes_black_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let colourf = ColourRGB::new(0.0, 0.0, 0.0);
|
||||
let colouru = target.apply_tone_mapping(&colourf);
|
||||
assert!(colouru.values == [0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_colourrgb_becomes_white_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let colourf = ColourRGB::new(1.0, 1.0, 1.0);
|
||||
let colouru = target.apply_tone_mapping(&colourf);
|
||||
assert!(colouru.values == [0xff, 0xff, 0xff]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supersaturated_white_colourrgb_becomes_white_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let colourf = ColourRGB::new(2.0, 2.0, 2.0);
|
||||
let colouru = target.apply_tone_mapping(&colourf);
|
||||
assert!(colouru.values == [0xff, 0xff, 0xff]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supersaturated_green_colourrgb_becomes_green_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let colourf = ColourRGB::new(0.0, 2.0, 0.0);
|
||||
let colouru = target.apply_tone_mapping(&colourf);
|
||||
assert!(colouru.values == [0x0, 0xff, 0x0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dark_red_colourrgb_becomes_dark_red_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let colourf = ColourRGB::new(0.5, 0.0, 0.0);
|
||||
let colouru = target.apply_tone_mapping(&colourf);
|
||||
assert!(colouru.values == [0x7f, 0x0, 0x0]);
|
||||
}
|
||||
}
|
||||
|
||||
mod normalized_as_byte {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_1_to_255_for_f32() {
|
||||
assert!((1.0f32).normalized_to_byte() == 0xff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_255_to_1_for_f32() {
|
||||
assert!(f32::byte_to_normalized(0xff) == 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_1_to_255_for_f64() {
|
||||
assert!((1.0f64).normalized_to_byte() == 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_255_to_1_for_f64() {
|
||||
assert!(f64::byte_to_normalized(0xff) == 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_0_to_0_for_f32() {
|
||||
assert!((0.0f32).normalized_to_byte() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_0_to_0_for_f32() {
|
||||
assert!(f32::byte_to_normalized(0) == 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_0_to_0_for_f64() {
|
||||
assert!((0.0f64).normalized_to_byte() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_0_to_0_for_f64() {
|
||||
assert!(f64::byte_to_normalized(0) == 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_half_to_127_for_f32() {
|
||||
assert!((0.5f32).normalized_to_byte() == 0x7f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_127_to_half_for_f32() {
|
||||
assert!((f32::byte_to_normalized(0x7f) - 0.5).abs() < 1.0 / 256.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_half_to_127_for_f64() {
|
||||
assert!((0.5f64).normalized_to_byte() == 0x7f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_127_to_half_for_f64() {
|
||||
assert!((f64::byte_to_normalized(0x7f) - 0.5).abs() < 1.0 / 256.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
284
src/image.rs
284
src/image.rs
|
|
@ -1,32 +1,44 @@
|
|||
use super::colour::ColourRGB24;
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub struct OutputImage {
|
||||
use nalgebra::{clamp, convert, RealField, Vector3};
|
||||
|
||||
use super::colour::{ColourRgbF, ColourRgbU8};
|
||||
|
||||
pub struct ImageRgbU8 {
|
||||
pixel_data: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
channels: u32,
|
||||
}
|
||||
|
||||
impl OutputImage {
|
||||
pub fn new(width: u32, height: u32) -> OutputImage {
|
||||
OutputImage {
|
||||
impl ImageRgbU8 {
|
||||
pub fn new(width: u32, height: u32) -> ImageRgbU8 {
|
||||
ImageRgbU8 {
|
||||
width: width,
|
||||
height: height,
|
||||
channels: 3,
|
||||
pixel_data: vec![0; (width * height * 3) as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) -> &mut OutputImage {
|
||||
pub fn clear(&mut self) -> &mut ImageRgbU8 {
|
||||
for byte in self.pixel_data.iter_mut() {
|
||||
*byte = 0u8;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_color(&mut self, row: u32, column: u32, colour: ColourRGB24) {
|
||||
pub fn get_colour(&self, row: u32, column: u32) -> ColourRgbU8 {
|
||||
assert!(row < self.height && column < self.width);
|
||||
let index = (((self.height - (row + 1)) * self.width + column) * self.channels) as usize;
|
||||
let index = self.calculate_index(row, column);
|
||||
ColourRgbU8 {
|
||||
values: self.pixel_data[index..index + 3]
|
||||
.try_into()
|
||||
.expect("Wrong length."),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_colour(&mut self, row: u32, column: u32, colour: ColourRgbU8) {
|
||||
assert!(row < self.height && column < self.width);
|
||||
let index = self.calculate_index(row, column);
|
||||
self.pixel_data[index..index + 3].copy_from_slice(&colour.values[..]);
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +54,255 @@ impl OutputImage {
|
|||
self.height
|
||||
}
|
||||
|
||||
pub fn get_num_channels(&self) -> u32 {
|
||||
self.channels
|
||||
pub fn num_channels() -> u32 {
|
||||
3
|
||||
}
|
||||
|
||||
fn calculate_index(&self, row: u32, column: u32) -> usize {
|
||||
assert!(row < self.height && column < self.width);
|
||||
(((self.height - (row + 1)) * self.width + column) * Self::num_channels()) as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImageRgbF<T: RealField> {
|
||||
pixel_data: Vec<T>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl<T: RealField> ImageRgbF<T> {
|
||||
pub fn new(width: u32, height: u32) -> ImageRgbF<T> {
|
||||
ImageRgbF {
|
||||
width: width,
|
||||
height: height,
|
||||
pixel_data: vec![convert(0.0); (width * height * 3) as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) -> &mut ImageRgbF<T> {
|
||||
for elem in self.pixel_data.iter_mut() {
|
||||
*elem = T::zero();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_colour(&self, row: u32, column: u32) -> ColourRgbF<T> {
|
||||
assert!(row < self.height && column < self.width);
|
||||
let index = self.calculate_index(row, column);
|
||||
ColourRgbF::from_vector3(&Vector3::from_row_slice(&self.pixel_data[index..index + 3]))
|
||||
}
|
||||
|
||||
pub fn set_colour(&mut self, row: u32, column: u32, colour: ColourRgbF<T>) {
|
||||
assert!(row < self.height && column < self.width);
|
||||
let index = self.calculate_index(row, column);
|
||||
self.pixel_data[index..index + 3].copy_from_slice(&colour.as_vector3().as_slice());
|
||||
}
|
||||
|
||||
pub fn get_pixel_data(&self) -> &Vec<T> {
|
||||
&self.pixel_data
|
||||
}
|
||||
|
||||
pub fn get_width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn get_height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn num_channels() -> u32 {
|
||||
3
|
||||
}
|
||||
|
||||
fn calculate_index(&self, row: u32, column: u32) -> usize {
|
||||
assert!(row < self.height && column < self.width);
|
||||
(((self.height - (row + 1)) * self.width + column) * Self::num_channels()) as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NormalizedAsByte {
|
||||
fn normalized_to_byte(self) -> u8;
|
||||
fn byte_to_normalized(byte: u8) -> Self;
|
||||
}
|
||||
|
||||
impl NormalizedAsByte for f32 {
|
||||
fn normalized_to_byte(self) -> u8 {
|
||||
(self * (std::u8::MAX as f32)) as u8
|
||||
}
|
||||
|
||||
fn byte_to_normalized(byte: u8) -> f32 {
|
||||
(byte as f32) / (std::u8::MAX as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl NormalizedAsByte for f64 {
|
||||
fn normalized_to_byte(self) -> u8 {
|
||||
(self * (std::u8::MAX as f64)) as u8
|
||||
}
|
||||
|
||||
fn byte_to_normalized(byte: u8) -> f64 {
|
||||
(byte as f64) / (std::u8::MAX as f64)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToneMapper<T: RealField> {
|
||||
fn apply_tone_mapping(&self, image_in: &ImageRgbF<T>, image_out: &mut ImageRgbU8);
|
||||
}
|
||||
|
||||
pub struct ClampingToneMapper {}
|
||||
|
||||
impl ClampingToneMapper {
|
||||
pub fn new() -> ClampingToneMapper {
|
||||
ClampingToneMapper {}
|
||||
}
|
||||
|
||||
fn clamp<T: RealField + NormalizedAsByte>(v: &T) -> u8 {
|
||||
clamp(v, &T::zero(), &T::one()).normalized_to_byte()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RealField + NormalizedAsByte> ToneMapper<T> for ClampingToneMapper {
|
||||
fn apply_tone_mapping(&self, image_in: &ImageRgbF<T>, image_out: &mut ImageRgbU8) {
|
||||
assert!(image_in.get_width() == image_out.get_width());
|
||||
assert!(image_in.get_height() == image_out.get_height());
|
||||
for column in 0..image_in.get_width() {
|
||||
for row in 0..image_in.get_height() {
|
||||
let colour = image_in.get_colour(row, column);
|
||||
image_out.set_colour(
|
||||
row,
|
||||
column,
|
||||
ColourRgbU8 {
|
||||
values: [
|
||||
Self::clamp(&colour.red()),
|
||||
Self::clamp(&colour.green()),
|
||||
Self::clamp(&colour.blue()),
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
mod normalized_as_byte {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_1_to_255_for_f32() {
|
||||
assert!((1.0f32).normalized_to_byte() == 0xff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_255_to_1_for_f32() {
|
||||
assert!(f32::byte_to_normalized(0xff) == 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_1_to_255_for_f64() {
|
||||
assert!((1.0f64).normalized_to_byte() == 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_255_to_1_for_f64() {
|
||||
assert!(f64::byte_to_normalized(0xff) == 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_0_to_0_for_f32() {
|
||||
assert!((0.0f32).normalized_to_byte() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_0_to_0_for_f32() {
|
||||
assert!(f32::byte_to_normalized(0) == 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_0_to_0_for_f64() {
|
||||
assert!((0.0f64).normalized_to_byte() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_0_to_0_for_f64() {
|
||||
assert!(f64::byte_to_normalized(0) == 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_half_to_127_for_f32() {
|
||||
assert!((0.5f32).normalized_to_byte() == 0x7f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_127_to_half_for_f32() {
|
||||
assert!((f32::byte_to_normalized(0x7f) - 0.5).abs() < 1.0 / 256.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_to_byte_converts_half_to_127_for_f64() {
|
||||
assert!((0.5f64).normalized_to_byte() == 0x7f);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_normalized_converts_127_to_half_for_f64() {
|
||||
assert!((f64::byte_to_normalized(0x7f) - 0.5).abs() < 1.0 / 256.0);
|
||||
}
|
||||
}
|
||||
|
||||
mod clamping_tone_mapper {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn black_colourrgb_becomes_black_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(0.0, 0.0, 0.0));
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_colourrgb_becomes_white_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(1.0, 1.0, 1.0));
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0xff, 0xff, 0xff]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supersaturated_white_colourrgb_becomes_white_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(2.0, 2.0, 2.0));
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0xff, 0xff, 0xff]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supersaturated_green_colourrgb_becomes_green_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(0.0, 2.0, 0.0));
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0x0, 0xff, 0x0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dark_red_colourrgb_becomes_dark_red_colourrgb24() {
|
||||
let target = ClampingToneMapper {};
|
||||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(0.5, 0.0, 0.0));
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0x7f, 0x0, 0x0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
use nalgebra::{RealField, Vector3};
|
||||
|
||||
use super::colour::{ColourRGB, NormalizedAsByte};
|
||||
use super::colour::ColourRgbF;
|
||||
use super::raycasting::IntersectionInfo;
|
||||
|
||||
pub trait Integrator<T: RealField + NormalizedAsByte> {
|
||||
fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRGB<T>;
|
||||
pub trait Integrator<T: RealField> {
|
||||
fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRgbF<T>;
|
||||
}
|
||||
|
||||
pub struct DirectionalLight<T: RealField + NormalizedAsByte> {
|
||||
pub struct DirectionalLight<T: RealField> {
|
||||
pub direction: Vector3<T>,
|
||||
pub intensity: T,
|
||||
}
|
||||
|
||||
pub struct PhongIntegrator<T: RealField + NormalizedAsByte> {
|
||||
pub struct PhongIntegrator<T: RealField> {
|
||||
pub ambient_light: T,
|
||||
pub lights: Vec<DirectionalLight<T>>,
|
||||
}
|
||||
|
||||
impl<T: RealField + NormalizedAsByte> Integrator<T> for PhongIntegrator<T> {
|
||||
fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRGB<T> {
|
||||
impl<T: RealField> Integrator<T> for PhongIntegrator<T> {
|
||||
fn integrate(&self, info: &IntersectionInfo<T>) -> ColourRgbF<T> {
|
||||
let intensity = self.lights
|
||||
.iter()
|
||||
.map(|light| light.intensity * light.direction.dot(&info.normal))
|
||||
.fold(self.ambient_light, |a, b| a + b);
|
||||
ColourRGB::from_vector3(&(info.material.colour.as_vector3() * intensity))
|
||||
ColourRgbF::from_vector3(&(info.material.colour.as_vector3() * intensity))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
src/main.rs
22
src/main.rs
|
|
@ -7,19 +7,19 @@ use std::time::Duration;
|
|||
|
||||
use nalgebra::Vector3;
|
||||
|
||||
use vanrijn::materials::Material;
|
||||
use vanrijn::camera::render_scene;
|
||||
use vanrijn::colour::NamedColour;
|
||||
use vanrijn::image::OutputImage;
|
||||
use vanrijn::colour::{ColourRgbF, NamedColour};
|
||||
use vanrijn::image::{ClampingToneMapper, ImageRgbF, ImageRgbU8, ToneMapper};
|
||||
use vanrijn::materials::Material;
|
||||
use vanrijn::raycasting::{Plane, Sphere};
|
||||
use vanrijn::scene::Scene;
|
||||
|
||||
fn update_texture(image: &OutputImage, texture: &mut Texture) {
|
||||
fn update_texture(image: &ImageRgbU8, texture: &mut Texture) {
|
||||
texture
|
||||
.update(
|
||||
None,
|
||||
image.get_pixel_data().as_slice(),
|
||||
(image.get_width() * image.get_num_channels()) as usize,
|
||||
(image.get_width() * ImageRgbU8::num_channels()) as usize,
|
||||
)
|
||||
.expect("Couldn't update texture.");
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
image_width as u32,
|
||||
image_height as u32,
|
||||
)?;
|
||||
let mut output_image = OutputImage::new(image_width, image_height);
|
||||
let mut output_image = ImageRgbF::<f64>::new(image_width, image_height);
|
||||
|
||||
let scene = Scene {
|
||||
camera_location: Vector3::new(0.0, 0.0, 0.0),
|
||||
|
|
@ -62,7 +62,7 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
Vector3::new(0.0, 1.0, 0.0),
|
||||
-2.0,
|
||||
Material {
|
||||
colour: NamedColour::Green.as_colourrgb(),
|
||||
colour: ColourRgbF::from_named(NamedColour::Green),
|
||||
smoothness: 0.0,
|
||||
},
|
||||
)),
|
||||
|
|
@ -70,14 +70,16 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
Vector3::new(0.0, 1.0, 5.0),
|
||||
1.0,
|
||||
Material {
|
||||
colour: NamedColour::Blue.as_colourrgb(),
|
||||
colour: ColourRgbF::from_named(NamedColour::Blue),
|
||||
smoothness: 0.7,
|
||||
},
|
||||
))
|
||||
)),
|
||||
],
|
||||
};
|
||||
render_scene(&mut output_image, &scene);
|
||||
update_texture(&output_image, &mut rendered_image_texture);
|
||||
let mut output_image_rgbu8 = ImageRgbU8::new(image_width, image_height);
|
||||
ClampingToneMapper {}.apply_tone_mapping(&output_image, &mut output_image_rgbu8);
|
||||
update_texture(&output_image_rgbu8, &mut rendered_image_texture);
|
||||
canvas.copy(&rendered_image_texture, None, None)?;
|
||||
canvas.present();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
use nalgebra::RealField;
|
||||
|
||||
use super::colour::{ColourRGB, NormalizedAsByte};
|
||||
use super::colour::ColourRgbF;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Material<T: RealField> {
|
||||
pub colour: ColourRGB<T>,
|
||||
pub colour: ColourRgbF<T>,
|
||||
pub smoothness: T,
|
||||
}
|
||||
|
||||
impl<T: RealField+NormalizedAsByte> Material<T> {
|
||||
impl<T: RealField> Material<T> {
|
||||
pub fn new_dummy() -> Material<T> {
|
||||
Material {
|
||||
colour: ColourRGB::new(T::one(), T::one(), T::one()),
|
||||
colour: ColourRgbF::new(T::one(), T::one(), T::one()),
|
||||
smoothness: T::zero(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue