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"
|
||||
rayon = "1.8.0"
|
||||
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 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() {
|
||||
|
|
|
|||
73
src/ray.rs
73
src/ray.rs
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue