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"] }
|
||||
cgmath = "0.18.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 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() }
|
||||
}
|
||||
29
src/main.rs
29
src/main.rs
|
|
@ -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!") }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue