algorithm to cast rays and build an image based on result
This commit is contained in:
parent
11ceb6ebc7
commit
6cbd93aad0
7 changed files with 159 additions and 30 deletions
|
|
@ -10,3 +10,4 @@ easy-gltf = "1.1.1"
|
||||||
clap = { version = "4.4.8", features = ["derive"] }
|
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"
|
||||||
BIN
result_image.png
Normal file
BIN
result_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
Binary file not shown.
Binary file not shown.
|
|
@ -1,10 +1,10 @@
|
||||||
use cgmath::Vector3;
|
use cgmath::{Angle, InnerSpace, Matrix4, SquareMatrix, Vector3, Vector4};
|
||||||
use easy_gltf::Camera;
|
use easy_gltf::{Camera, Projection};
|
||||||
use easy_gltf::model::Triangle;
|
use easy_gltf::model::Triangle;
|
||||||
|
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
source: Vector3<f64>,
|
source: Vector3<f32>,
|
||||||
direction: Vector3<f64>,
|
direction: Vector3<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Intersectable {
|
pub trait Intersectable {
|
||||||
|
|
@ -18,10 +18,70 @@ pub trait Intersectable {
|
||||||
impl Intersectable for Triangle {
|
impl Intersectable for Triangle {
|
||||||
//perform muller trumbore intersection
|
//perform muller trumbore intersection
|
||||||
fn test_isec(&self, ray: &Ray) -> Option<Vector3<f32>> {
|
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> {
|
pub fn construct_primary_rays(camera: &Camera,
|
||||||
todo!()
|
(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() }
|
||||||
}
|
}
|
||||||
29
src/main.rs
29
src/main.rs
|
|
@ -2,31 +2,50 @@ mod renderer;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
|
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use clap::{Parser};
|
use clap::{Parser};
|
||||||
use easy_gltf::Scene;
|
use easy_gltf::Scene;
|
||||||
|
use image::{DynamicImage};
|
||||||
use crate::renderer::render;
|
use crate::renderer::render;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Args {
|
pub struct Args {
|
||||||
gltf_file_path: String,
|
gltf_file_path: String,
|
||||||
/// which scene to use in the gltf file
|
/// which scene to use in the gltf file
|
||||||
#[arg(short, long)]
|
#[arg(long)]
|
||||||
scene_index: usize,
|
scene_index: usize,
|
||||||
/// which camera to render from
|
/// which camera to render from
|
||||||
#[arg(short, long)]
|
#[arg(long)]
|
||||||
camera_index: usize,
|
camera_index: usize,
|
||||||
|
/// image width
|
||||||
|
#[arg(long)]
|
||||||
|
width: usize,
|
||||||
|
/// image height
|
||||||
|
#[arg(long)]
|
||||||
|
height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
//parse clargs
|
//parse clargs
|
||||||
let args = Args::parse();
|
let args: Args = Args::parse();
|
||||||
|
|
||||||
//load gltf scene
|
//load gltf scene
|
||||||
let scenes: &Vec<Scene> = &easy_gltf::load(
|
let scenes: &Vec<Scene> = &easy_gltf::load(
|
||||||
&args.gltf_file_path)
|
&args.gltf_file_path)
|
||||||
.expect(&*format!("Failed to load glTF file {}", &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!") }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,75 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||||
use easy_gltf::model::{Mode};
|
use easy_gltf::model::{Mode};
|
||||||
use easy_gltf::Scene;
|
use easy_gltf::{Camera, Scene};
|
||||||
use crate::geometry::{construct_rays, Intersectable, Ray};
|
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) {
|
pub fn render(scenes: &Vec<Scene>,
|
||||||
//construct all rays and cast them all
|
cl_args: &Args,
|
||||||
let rays: Vec<Ray> = construct_rays(&scenes[scene_idx].cameras[camera_idx]);
|
output_image: &Arc<Mutex<DynamicImage>>) {
|
||||||
rays.into_par_iter().for_each(|ray| {
|
let render_scene: &Scene = &scenes[cl_args.scene_index];
|
||||||
//test intersection with all models in the scene
|
let render_camera: &Camera = &scenes[cl_args.scene_index].cameras[cl_args.camera_index];
|
||||||
//TODO: Improve, to avoid iterating all models
|
|
||||||
scenes[scene_idx].models.iter().for_each(|model| {
|
//iterate over all pixels in the image
|
||||||
match model.mode() {
|
(0..cl_args.width).into_par_iter().for_each(|px| {
|
||||||
Mode::Triangles => {
|
(0..cl_args.height).into_par_iter().for_each(|py| {
|
||||||
//in triangle mode there will always be a triangle vector
|
//construct all rays
|
||||||
let triangles = model.triangles().unwrap();
|
let rays: Vec<Ray> = construct_primary_rays(
|
||||||
triangles.iter().for_each(|triangle| {
|
render_camera,
|
||||||
triangle.test_isec(&ray);
|
(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
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue