algorithm to cast rays and build an image based on result

This commit is contained in:
CDaut 2023-11-18 01:44:04 +01:00
parent 11ceb6ebc7
commit 6cbd93aad0
7 changed files with 159 additions and 30 deletions

View file

@ -1,10 +1,10 @@
use cgmath::Vector3;
use easy_gltf::Camera;
use cgmath::{Angle, InnerSpace, Matrix4, SquareMatrix, Vector3, Vector4};
use easy_gltf::{Camera, Projection};
use easy_gltf::model::Triangle;
pub struct Ray {
source: Vector3<f64>,
direction: Vector3<f64>,
source: Vector3<f32>,
direction: Vector3<f32>,
}
pub trait Intersectable {
@ -18,10 +18,70 @@ pub trait Intersectable {
impl Intersectable for Triangle {
//perform muller trumbore intersection
fn test_isec(&self, ray: &Ray) -> Option<Vector3<f32>> {
todo!()
//TODO: implement correct intersection here
if ray.direction.x > 0.5 &&
ray.direction.x < 0.6 &&
ray.direction.y > 0.1 &&
ray.direction.y < 0.6 {
return Some(Vector3::new(1.0, 1.0, 1.0));
}
return None;
}
}
pub fn construct_rays(camera: &Camera) -> Vec<Ray> {
todo!()
pub fn construct_primary_rays(camera: &Camera,
(width, height): (usize, usize),
(pixel_x_coord, pixel_y_coord): (usize, usize),
) -> Vec<Ray> {
//only allow perspective rendering
//TODO: ignoring aspect ratio here
let fovy = match camera.projection {
Projection::Perspective { yfov, aspect_ratio: _aspect_ratio } => { yfov }
Projection::Orthographic { .. } => { panic!("Orthographic rendering not supported.") }
};
//ray origin in world space
let origin_world_space = camera.position();
//seems to be the distance from the camera origin to the view plane
let z: f32 = height as f32 / fovy.tan();
//obtain the inverse transformation Matrix
let inverse_transform = camera.transform.invert().unwrap_or_else(||
panic!("Non invertible transform Matrix. giving up.")
);
//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
//TODO: use blue noise here to generate multiple rays per pixel
rays.push(generate_ray(
width,
height,
&inverse_transform,
z,
pixel_x_coord,
pixel_y_coord,
origin_world_space));
rays
}
fn generate_ray(image_width: usize,
image_height: usize,
inverse_transform: &Matrix4<f32>,
focal_length: f32,
u: usize,
v: usize,
ray_origin: Vector3<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),
-focal_length,
0.0);
let direction_world_space = inverse_transform * direction_view_space;
Ray { source: ray_origin, direction: direction_world_space.normalize().truncate() }
}

View file

@ -2,31 +2,50 @@ mod renderer;
mod geometry;
use std::string::String;
use std::sync::{Arc, Mutex};
use clap::{Parser};
use easy_gltf::Scene;
use image::{DynamicImage};
use crate::renderer::render;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
pub struct Args {
gltf_file_path: String,
/// which scene to use in the gltf file
#[arg(short, long)]
#[arg(long)]
scene_index: usize,
/// which camera to render from
#[arg(short, long)]
#[arg(long)]
camera_index: usize,
/// image width
#[arg(long)]
width: usize,
/// image height
#[arg(long)]
height: usize,
}
fn main() {
//parse clargs
let args = Args::parse();
let args: Args = Args::parse();
//load gltf scene
let scenes: &Vec<Scene> = &easy_gltf::load(
&args.gltf_file_path)
.expect(&*format!("Failed to load glTF file {}", &args.gltf_file_path));
render(scenes, args.scene_index, args.camera_index)
//build an image
let output_image: Arc<Mutex<DynamicImage>> = Arc::new(Mutex::new(DynamicImage::new_rgba8(
args.width as u32, args.height as u32,
)));
render(scenes, &args, &output_image);
match output_image.lock() {
Ok(image) => {
image.save("result_image.png").expect("Unable to save image!");
}
Err(_) => { panic!("Error aquiring lock on image while saving!") }
};
}

View file

@ -1,26 +1,75 @@
use std::sync::{Arc, Mutex};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use easy_gltf::model::{Mode};
use easy_gltf::Scene;
use crate::geometry::{construct_rays, Intersectable, Ray};
use easy_gltf::{Camera, Scene};
use image::{DynamicImage, GenericImage, Rgba};
use crate::Args;
use crate::geometry::{construct_primary_rays, Intersectable, Ray};
pub fn render(scenes: &Vec<Scene>, scene_idx: usize, camera_idx: usize) {
//construct all rays and cast them all
let rays: Vec<Ray> = construct_rays(&scenes[scene_idx].cameras[camera_idx]);
rays.into_par_iter().for_each(|ray| {
//test intersection with all models in the scene
//TODO: Improve, to avoid iterating all models
scenes[scene_idx].models.iter().for_each(|model| {
match model.mode() {
Mode::Triangles => {
//in triangle mode there will always be a triangle vector
let triangles = model.triangles().unwrap();
triangles.iter().for_each(|triangle| {
triangle.test_isec(&ray);
});
pub fn render(scenes: &Vec<Scene>,
cl_args: &Args,
output_image: &Arc<Mutex<DynamicImage>>) {
let render_scene: &Scene = &scenes[cl_args.scene_index];
let render_camera: &Camera = &scenes[cl_args.scene_index].cameras[cl_args.camera_index];
//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),
);
//let the initial pixel color be white and opaque
let mut pixel_color: Rgba<u8> = Rgba([0, 0, 0, 255]);
//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);
});
//save pixel to output image
match output_image.lock() {
Ok(mut image) => {
image.put_pixel(px as u32, py as u32, pixel_color);
}
_ => { panic!("Unable to render model in mode {:?}", model.mode()) }
Err(_) => { panic!("Unable to obtain lock on image!") }
}
});
});
}
fn raytrace(ray: &Ray, scene: &Scene) -> Rgba<u8> {
let mut pixel_color: Rgba<u8> = Rgba([0, 0, 0, 255]);
//test intersection with all models in the scene
//TODO: Improve, to avoid iterating all models
scene.models.iter().for_each(|model| {
//get all triangles in the model
let triangles = match model.mode() {
Mode::Triangles => {
//in triangle mode there will always be a triangle vector
model.triangles().unwrap()
}
_ => { panic!("Unable to render model in mode {:?}", model.mode()) }
};
//iterate all triangles in a model
triangles.iter().for_each(|triangle| {
match triangle.test_isec(&ray) {
//boilerplate implementation to set pixel to red if any intersection happened
None => {}
Some(point) => {
pixel_color.0 = [255, 255, 255, 255];
return
}
};
});
});
pixel_color
}