Anti aliasing by multisampling

This commit is contained in:
CDaut 2023-11-24 19:56:04 +01:00
parent ab590a4844
commit 9fa51dd71f
7 changed files with 107 additions and 57 deletions

View file

@ -10,4 +10,5 @@ easy-gltf = "1.1.1"
clap = { version = "4.4.8", features = ["derive"] }
cgmath = "0.18.0"
rayon = "1.8.0"
image = "0.24.7"
image = "0.24.7"
rand = "0.8.5"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

Binary file not shown.

BIN
scenes/cube_on_plane.blend1 Normal file

Binary file not shown.

View file

@ -1,3 +1,5 @@
#![feature(array_zip)]
mod renderer;
mod geometry;
mod ray;
@ -25,6 +27,9 @@ pub struct Args {
/// image height
#[arg(long)]
height: usize,
/// rays per pixel
#[arg(long)]
multiplier: usize,
}
fn main() {

View file

@ -1,52 +1,36 @@
use std::ops::Mul;
use cgmath::{Angle, InnerSpace, Matrix3, Matrix4, Point3, SquareMatrix, Vector3, Vector4};
use easy_gltf::{Camera, Projection};
use cgmath::{InnerSpace, Matrix4, Vector3, Vector4};
use rand::{Rng};
pub struct Ray {
pub(crate) source: Vector3<f32>,
pub(crate) direction: Vector3<f32>,
}
pub fn construct_primary_rays(camera: &Camera,
(width, height): (usize, usize),
pub fn construct_primary_rays((width, height): (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> {
//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 origin_world_space = camera.position();
let mut rays: Vec<Ray> = Vec::with_capacity(rays_per_pixel);
//use a custom transform matrix because the one from easy_gltf is fucked
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);
let mut rng = rand::thread_rng();
//generate all rays for this pixel and add them to the rays vector
//TODO: use blue noise here to generate multiple rays per pixel
rays.push(generate_single_primary_ray(
width,
height,
&transform_matrix,
z,
pixel_x_coord,
pixel_y_coord,
origin_world_space));
for _ in 0..rays_per_pixel {
rays.push(generate_single_primary_ray(
width,
height,
cam_to_world_matrix,
focal_length,
pixel_x_coord,
pixel_y_coord,
rng.gen(),
rng.gen()
));
}
rays
}
@ -57,15 +41,20 @@ fn generate_single_primary_ray(image_width: usize,
focal_length: f32,
u: 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
let direction_view_space: Vector4<f32> =
Vector4::new(u as f32 - (image_width as f32 / 2.0),
v as f32 - (image_height as f32 / 2.0),
let direction_camera_space: Vector4<f32> =
Vector4::new(u as f32 - (image_width as f32 / 2.0) + u_offset,
v as f32 - (image_height as f32 / 2.0) + v_offset,
focal_length,
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(),
}
}

View file

@ -1,9 +1,9 @@
use std::cmp::max;
use std::ops::Mul;
use std::sync::{Arc, Mutex};
use cgmath::{Vector2, Vector3, Vector4, Zero};
use cgmath::{Angle, Matrix4, Vector2, Vector3};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use easy_gltf::model::{Mode};
use easy_gltf::{Camera, Scene};
use easy_gltf::{Camera, Projection, Scene};
use image::{DynamicImage, GenericImage, Rgba};
use crate::Args;
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_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
(0..cl_args.width).into_par_iter().for_each(|px| {
(0..cl_args.height).into_par_iter().for_each(|py| {
//construct all rays
let rays: Vec<Ray> = construct_primary_rays(
render_camera,
(cl_args.width, cl_args.height),
(px, py),
&transform_matrix,
z,
cl_args.multiplier,
);
//let the initial pixel color be black and transparent
let mut pixel_color: Rgba<u8> = Rgba::from([0, 0, 0, 255]);
//let the initial pixel color be black and opaque
let mut pixel_luminosity: [f32; 4] = [0.0, 0.0, 0.0, 255.0];
//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| {
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
match output_image.lock() {
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!") }
}
@ -46,13 +78,13 @@ pub fn render(scenes: &Vec<Scene>,
}
fn raytrace(ray: &Ray, scene: &Scene) -> Rgba<u8> {
let mut pixel_color: Rgba<u8> = Rgba::from([0, 0, 0, 255]);
fn raytrace(ray: &Ray, scene: &Scene, recursion_depth: u32) -> [u8; 4] {
let mut pixel_color: [u8; 4] = [0, 0, 0, 255];
let mut smallest_t: f32 = f32::MAX;
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
//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)
).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
match clostest_intersection_point {
Some(_) => {
pixel_color = Rgba::from(color_at_isec);
pixel_color = color_at_isec;
}
None {} => {}
}
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)
}