Anti aliasing by multisampling
This commit is contained in:
parent
ab590a4844
commit
9fa51dd71f
7 changed files with 107 additions and 57 deletions
|
|
@ -11,3 +11,4 @@ clap = { version = "4.4.8", features = ["derive"] }
|
||||||
cgmath = "0.18.0"
|
cgmath = "0.18.0"
|
||||||
rayon = "1.8.0"
|
rayon = "1.8.0"
|
||||||
image = "0.24.7"
|
image = "0.24.7"
|
||||||
|
rand = "0.8.5"
|
||||||
BIN
result_image.png
BIN
result_image.png
Binary file not shown.
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 11 KiB |
Binary file not shown.
BIN
scenes/cube_on_plane.blend1
Normal file
BIN
scenes/cube_on_plane.blend1
Normal file
Binary file not shown.
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![feature(array_zip)]
|
||||||
|
|
||||||
mod renderer;
|
mod renderer;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
mod ray;
|
mod ray;
|
||||||
|
|
@ -25,6 +27,9 @@ pub struct Args {
|
||||||
/// image height
|
/// image height
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
height: usize,
|
height: usize,
|
||||||
|
/// rays per pixel
|
||||||
|
#[arg(long)]
|
||||||
|
multiplier: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
||||||
63
src/ray.rs
63
src/ray.rs
|
|
@ -1,52 +1,36 @@
|
||||||
use std::ops::Mul;
|
use cgmath::{InnerSpace, Matrix4, Vector3, Vector4};
|
||||||
use cgmath::{Angle, InnerSpace, Matrix3, Matrix4, Point3, SquareMatrix, Vector3, Vector4};
|
use rand::{Rng};
|
||||||
use easy_gltf::{Camera, Projection};
|
|
||||||
|
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
pub(crate) source: Vector3<f32>,
|
pub(crate) source: Vector3<f32>,
|
||||||
pub(crate) direction: Vector3<f32>,
|
pub(crate) direction: Vector3<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn construct_primary_rays(camera: &Camera,
|
pub fn construct_primary_rays((width, height): (usize, usize),
|
||||||
(width, height): (usize, usize),
|
|
||||||
(pixel_x_coord, pixel_y_coord): (usize, usize),
|
(pixel_x_coord, pixel_y_coord): (usize, usize),
|
||||||
|
cam_to_world_matrix: &Matrix4<f32>,
|
||||||
|
focal_length: f32,
|
||||||
|
rays_per_pixel: usize,
|
||||||
) -> Vec<Ray> {
|
) -> Vec<Ray> {
|
||||||
//only allow perspective rendering
|
|
||||||
//TODO: ignoring aspect ratio here
|
|
||||||
let (fovy, aspect_ratio) = match camera.projection {
|
|
||||||
Projection::Perspective { yfov, aspect_ratio } => {
|
|
||||||
(yfov, aspect_ratio)
|
|
||||||
}
|
|
||||||
Projection::Orthographic { .. } => { panic!("Orthographic rendering not supported.") }
|
|
||||||
};
|
|
||||||
|
|
||||||
//ray origin in world space
|
let mut rays: Vec<Ray> = Vec::with_capacity(rays_per_pixel);
|
||||||
let origin_world_space = camera.position();
|
|
||||||
|
|
||||||
//use a custom transform matrix because the one from easy_gltf is fucked
|
let mut rng = rand::thread_rng();
|
||||||
let transform_matrix = Matrix4::from_cols(
|
|
||||||
camera.right().extend(0.0),
|
|
||||||
-camera.up().extend(0.0),
|
|
||||||
-camera.forward().extend(0.0),
|
|
||||||
origin_world_space.extend(1.0),
|
|
||||||
);
|
|
||||||
|
|
||||||
// the distance from the camera origin to the view plane
|
|
||||||
let z: f32 = height as f32 / (2.0 * fovy.mul(0.5).tan());
|
|
||||||
|
|
||||||
//TODO: take ray multiplier per pixel into account here
|
|
||||||
let mut rays: Vec<Ray> = Vec::with_capacity(height * width);
|
|
||||||
|
|
||||||
//generate all rays for this pixel and add them to the rays vector
|
//generate all rays for this pixel and add them to the rays vector
|
||||||
//TODO: use blue noise here to generate multiple rays per pixel
|
//TODO: use blue noise here to generate multiple rays per pixel
|
||||||
|
for _ in 0..rays_per_pixel {
|
||||||
rays.push(generate_single_primary_ray(
|
rays.push(generate_single_primary_ray(
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
&transform_matrix,
|
cam_to_world_matrix,
|
||||||
z,
|
focal_length,
|
||||||
pixel_x_coord,
|
pixel_x_coord,
|
||||||
pixel_y_coord,
|
pixel_y_coord,
|
||||||
origin_world_space));
|
rng.gen(),
|
||||||
|
rng.gen()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
rays
|
rays
|
||||||
}
|
}
|
||||||
|
|
@ -57,15 +41,20 @@ fn generate_single_primary_ray(image_width: usize,
|
||||||
focal_length: f32,
|
focal_length: f32,
|
||||||
u: usize,
|
u: usize,
|
||||||
v: usize,
|
v: usize,
|
||||||
ray_origin: Vector3<f32>) -> Ray {
|
u_offset: f32,
|
||||||
|
v_offset: f32) -> Ray {
|
||||||
//calculate the ray direction and translate it to world space
|
//calculate the ray direction and translate it to world space
|
||||||
let direction_view_space: Vector4<f32> =
|
let direction_camera_space: Vector4<f32> =
|
||||||
Vector4::new(u as f32 - (image_width as f32 / 2.0),
|
Vector4::new(u as f32 - (image_width as f32 / 2.0) + u_offset,
|
||||||
v as f32 - (image_height as f32 / 2.0),
|
v as f32 - (image_height as f32 / 2.0) + v_offset,
|
||||||
focal_length,
|
focal_length,
|
||||||
0.0);
|
0.0);
|
||||||
|
|
||||||
let direction_world_space = cam_to_world_transform * direction_view_space.normalize();
|
let direction_world_space =
|
||||||
|
cam_to_world_transform * direction_camera_space.normalize();
|
||||||
|
|
||||||
Ray { source: ray_origin, direction: direction_world_space.truncate().normalize() }
|
Ray {
|
||||||
|
source: cam_to_world_transform.w.truncate(),
|
||||||
|
direction: direction_world_space.truncate().normalize(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use std::cmp::max;
|
use std::ops::Mul;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use cgmath::{Vector2, Vector3, Vector4, Zero};
|
use cgmath::{Angle, Matrix4, Vector2, Vector3};
|
||||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
use easy_gltf::model::{Mode};
|
use easy_gltf::model::{Mode};
|
||||||
use easy_gltf::{Camera, Scene};
|
use easy_gltf::{Camera, Projection, Scene};
|
||||||
use image::{DynamicImage, GenericImage, Rgba};
|
use image::{DynamicImage, GenericImage, Rgba};
|
||||||
use crate::Args;
|
use crate::Args;
|
||||||
use crate::geometry::{Intersectable};
|
use crate::geometry::{Intersectable};
|
||||||
|
|
@ -15,29 +15,61 @@ pub fn render(scenes: &Vec<Scene>,
|
||||||
let render_scene: &Scene = &scenes[cl_args.scene_index];
|
let render_scene: &Scene = &scenes[cl_args.scene_index];
|
||||||
let render_camera: &Camera = &scenes[cl_args.scene_index].cameras[cl_args.camera_index];
|
let render_camera: &Camera = &scenes[cl_args.scene_index].cameras[cl_args.camera_index];
|
||||||
|
|
||||||
|
//use a custom transform matrix because the one from easy_gltf is fucked
|
||||||
|
let transform_matrix = Matrix4::from_cols(
|
||||||
|
render_camera.right().extend(0.0),
|
||||||
|
-render_camera.up().extend(0.0),
|
||||||
|
-render_camera.forward().extend(0.0),
|
||||||
|
render_camera.position().extend(1.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
//only allow perspective rendering
|
||||||
|
//TODO: ignoring aspect ratio here. Maybe implement an assertion?
|
||||||
|
let fovy = match render_camera.projection {
|
||||||
|
Projection::Perspective { yfov, aspect_ratio: _aspect_ratio } => {
|
||||||
|
yfov
|
||||||
|
}
|
||||||
|
Projection::Orthographic { .. } => { panic!("Orthographic rendering not supported.") }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// the distance from the camera origin to the view plane
|
||||||
|
let z: f32 = cl_args.height as f32 / (2.0 * fovy.mul(0.5).tan());
|
||||||
|
|
||||||
//iterate over all pixels in the image
|
//iterate over all pixels in the image
|
||||||
(0..cl_args.width).into_par_iter().for_each(|px| {
|
(0..cl_args.width).into_par_iter().for_each(|px| {
|
||||||
(0..cl_args.height).into_par_iter().for_each(|py| {
|
(0..cl_args.height).into_par_iter().for_each(|py| {
|
||||||
//construct all rays
|
//construct all rays
|
||||||
let rays: Vec<Ray> = construct_primary_rays(
|
let rays: Vec<Ray> = construct_primary_rays(
|
||||||
render_camera,
|
|
||||||
(cl_args.width, cl_args.height),
|
(cl_args.width, cl_args.height),
|
||||||
(px, py),
|
(px, py),
|
||||||
|
&transform_matrix,
|
||||||
|
z,
|
||||||
|
cl_args.multiplier,
|
||||||
);
|
);
|
||||||
|
|
||||||
//let the initial pixel color be black and transparent
|
//let the initial pixel color be black and opaque
|
||||||
let mut pixel_color: Rgba<u8> = Rgba::from([0, 0, 0, 255]);
|
let mut pixel_luminosity: [f32; 4] = [0.0, 0.0, 0.0, 255.0];
|
||||||
|
|
||||||
//cast each ray and get the output color
|
//cast each ray and get the output color
|
||||||
//TODO: in the end we will want to average the colors out here
|
|
||||||
rays.iter().for_each(|ray| {
|
rays.iter().for_each(|ray| {
|
||||||
pixel_color = raytrace(ray, render_scene);
|
pixel_luminosity = raytrace(ray, render_scene, 4)
|
||||||
|
.zip(pixel_luminosity)
|
||||||
|
.map(|(a, b)| a as f32 + b);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
//save pixel to output image
|
//save pixel to output image
|
||||||
match output_image.lock() {
|
match output_image.lock() {
|
||||||
Ok(mut image) => {
|
Ok(mut image) => {
|
||||||
image.put_pixel(px as u32, py as u32, pixel_color);
|
//save pixel
|
||||||
|
image.put_pixel(
|
||||||
|
px as u32,
|
||||||
|
py as u32,
|
||||||
|
colormap(
|
||||||
|
pixel_luminosity,
|
||||||
|
cl_args.multiplier),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(_) => { panic!("Unable to obtain lock on image!") }
|
Err(_) => { panic!("Unable to obtain lock on image!") }
|
||||||
}
|
}
|
||||||
|
|
@ -46,13 +78,13 @@ pub fn render(scenes: &Vec<Scene>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn raytrace(ray: &Ray, scene: &Scene) -> Rgba<u8> {
|
fn raytrace(ray: &Ray, scene: &Scene, recursion_depth: u32) -> [u8; 4] {
|
||||||
let mut pixel_color: Rgba<u8> = Rgba::from([0, 0, 0, 255]);
|
let mut pixel_color: [u8; 4] = [0, 0, 0, 255];
|
||||||
|
|
||||||
|
|
||||||
let mut smallest_t: f32 = f32::MAX;
|
let mut smallest_t: f32 = f32::MAX;
|
||||||
let mut clostest_intersection_point: Option<Vector3<f32>> = None;
|
let mut clostest_intersection_point: Option<Vector3<f32>> = None;
|
||||||
let mut color_at_isec = [0, 0, 0, 0];
|
let mut color_at_isec: [u8; 4] = [0, 0, 0, 255];
|
||||||
|
|
||||||
//test intersection with all models in the scene
|
//test intersection with all models in the scene
|
||||||
//TODO: Improve, to avoid iterating all models
|
//TODO: Improve, to avoid iterating all models
|
||||||
|
|
@ -81,7 +113,12 @@ fn raytrace(ray: &Ray, scene: &Scene) -> Rgba<u8> {
|
||||||
Vector2::new(0.0, 0.0)
|
Vector2::new(0.0, 0.0)
|
||||||
).map(|comp| comp * 255.0);
|
).map(|comp| comp * 255.0);
|
||||||
|
|
||||||
color_at_isec = [color_vec[0] as u8, color_vec[1] as u8, color_vec[2] as u8, color_vec[3] as u8]
|
color_at_isec = [
|
||||||
|
color_vec[0] as u8,
|
||||||
|
color_vec[1] as u8,
|
||||||
|
color_vec[2] as u8,
|
||||||
|
color_vec[3] as u8
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -91,10 +128,28 @@ fn raytrace(ray: &Ray, scene: &Scene) -> Rgba<u8> {
|
||||||
//make pixel opaque if intersection is found
|
//make pixel opaque if intersection is found
|
||||||
match clostest_intersection_point {
|
match clostest_intersection_point {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
pixel_color = Rgba::from(color_at_isec);
|
pixel_color = color_at_isec;
|
||||||
}
|
}
|
||||||
None {} => {}
|
None {} => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pixel_color
|
pixel_color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn colormap(luminosity_array: [f32; 4], rays_per_pixel: usize) -> Rgba<u8> {
|
||||||
|
luminosity_array.map(|component|
|
||||||
|
f32::ceil(component / rays_per_pixel as f32) as u8
|
||||||
|
);
|
||||||
|
|
||||||
|
//linearly map luminosity values to [0..255]
|
||||||
|
let range: f32 =
|
||||||
|
luminosity_array.iter().fold(f32::NEG_INFINITY, |a, b| a.max(*b)) -
|
||||||
|
luminosity_array.iter().fold(f32::INFINITY, |a, b| a.min(*b));
|
||||||
|
|
||||||
|
let color_value: [u8; 4] =
|
||||||
|
luminosity_array.map(|lum_val|
|
||||||
|
f32::ceil((lum_val / range) * 255.0) as u8
|
||||||
|
);
|
||||||
|
|
||||||
|
Rgba::from(color_value)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue