refactor: scene/raytracer/camera logic

This commit is contained in:
2026-05-08 17:16:21 +02:00
parent 549707fbb3
commit 383f739808
5 changed files with 82 additions and 20 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
/target
output.png
*.png
.env

38
scenes/bench.json Normal file
View File

@@ -0,0 +1,38 @@
{
"filename": "testing.png",
"image_width": 800,
"image_height": 600,
"max_depth": 50,
"camera": {
"anti_alias_rate": 16,
"fov": 70.0,
"look_from": [-10, 1, 15],
"look_at": [-11.0, 7.0, 0.0],
"vup": [0.0, 1.0, 0.0],
"defocus_angle": 0,
"focus_dist": 15.68
},
"materials": [
{ "type": "lambertian", "albedo": [0.2, 0.2, 0.2], "prob": 0.8 },
{ "type": "lambertian", "albedo": [0.9, 0.9, 0.0], "prob": 1.0, "fuzz": 0.1 },
{ "type": "dielectric", "refraction_index": 1.5},
{ "type": "normal"}
],
"objects": [
{ "type": "sphere", "center": [0.0, 0.0, -1.2], "radius": 0.5, "material": { "type": "metal", "albedo": [0.7, 0.4, 0.2], "prob": 1.0, "fuzz": 0.1 }},
{ "type": "sphere", "center": [-1, 0, -1], "radius": 0.4, "material": 3},
{ "type": "sphere", "center": [1, 0, -1], "radius": 0.5, "material": { "type": "metal", "albedo": [0.8, 0.6, 0.2], "prob": 1.0, "fuzz": 1.0 }},
{ "type": "triangle", "p1": [-4, 0, -4], "p2": [0, 0, -4], "p3": [-2, 2, -4], "material": 1},
{ "type": "triangle", "p1": [0, 0, -4], "p2": [4, 0, -4], "p3": [2, 2, -4], "material": 1},
{ "type": "triangle", "p1": [-2, 2, -4], "p2": [2, 2, -4], "p3": [0, 4, -4], "material": 3},
{ "type": "quad", "p1": [-20, -1, -20], "p2": [20, -1, -20], "p3": [20, 20, -20], "p4": [-20, 20, -20], "material": 0},
{ "type": "quad", "p1": [-20, -1, 20], "p2": [-20, -1, -20], "p3": [-20, 20, -20], "p4": [-20, 20, 20], "material": 0},
{ "type": "quad", "p1": [-20, -1, 20], "p2": [20, -1, 20], "p3": [20, -1, -20], "p4": [-20, -1, -20], "material": 0},
{ "type": "quad", "p1": [20, -1, 20], "p2": [20, -1, -20], "p3": [20, 20, -20], "p4": [20, 20, 20], "material": 0},
{ "type": "cube", "p1": [8, 0, 2], "p2": [12, 0, 2], "p3": [12, 4, 2], "p4": [8, 4, 2], "p5": [8, 0, -2], "p6": [12, 0, -2], "p7": [12, 4, -2], "p8": [8, 4, -2], "material": 3},
{ "type": "circle", "center": [-9, 3, 0], "radius": 3, "normal": [0, 1, 0.5], "material": {"type": "metal", "albedo": [0.9, 0.9, 0.9], "prob": 1.0, "fuzz": 0.3}}
]
}

View File

@@ -1,4 +1,4 @@
use std::f32::consts::PI;
use std::{f32::consts::PI};
use crate::{ray::Ray, vec3::Vec3};
@@ -6,18 +6,18 @@ use crate::{ray::Ray, vec3::Vec3};
pub struct Camera {
// raytracing
pub anti_alias_rate: u32,
pub pixel00_loc: Vec3,
pub pixel_delta_u: Vec3,
pub pixel_delta_v: Vec3,
pixel00_loc: Vec3,
pixel_delta_u: Vec3,
pixel_delta_v: Vec3,
// camera
dirty: bool,
pub fov: f32,
pub look_from: Vec3,
pub look_at: Vec3,
fov: f32,
look_from: Vec3,
look_at: Vec3,
vup: Vec3,
pub defocus_angle: f32,
pub focus_dist: f32,
defocus_angle: f32,
focus_dist: f32,
// camera helpers
u: Vec3,
@@ -115,6 +115,14 @@ impl Camera {
}
}
pub fn get_anti_alias_rate(&self) -> u32 {
self.anti_alias_rate
}
pub fn get_pixel_tl(&self, x: u32, y: u32) -> Vec3 {
return self.pixel00_loc + (x * self.pixel_delta_u) + (y * self.pixel_delta_v);
}
pub fn get_ray(&self, pixel_tl: Vec3, x: u32, y: u32) -> Ray {
let pixel_loc = pixel_tl
+ (x * self.pixel_delta_u / (self.anti_alias_rate + 1) as f32)

View File

@@ -2,7 +2,7 @@ use image::{ExtendedColorType, ImageEncoder, codecs::png::PngEncoder};
use log::{error, info};
use rayon::iter::IntoParallelIterator;
use rayon::prelude::*;
use std::{fs::File, sync::Arc};
use std::{fs::File, sync::Arc, time::Instant};
use crate::{
objects::{hit::Hit, traits::Hittable},
@@ -24,8 +24,7 @@ pub fn write_image(filename: &str, pixels: &[u8], width: u32, height: u32) {
}
pub fn render(scene: &mut Scene) {
let camera = &mut scene.camera;
camera.init(scene.image_width as f32, scene.image_height as f32);
scene.init();
// TODO: currently splits per vertical line, but could be more granular (per chunk)
let mut pixels = vec![0 as u8; (scene.image_width * scene.image_height * 3) as usize];
@@ -34,9 +33,11 @@ pub fn render(scene: &mut Scene) {
.enumerate()
.collect();
let now = Instant::now();
scanlines.into_par_iter().for_each(|(i, chunk)| {
render_chunk(chunk, scene, i as u32);
});
info!("rendering took {}.{} seconds.", now.elapsed().as_secs(), now.elapsed().subsec_nanos());
info!("Writing image file...");
write_image(
@@ -48,23 +49,22 @@ pub fn render(scene: &mut Scene) {
}
pub fn render_chunk(chunk: &mut [u8], scene: &Scene, y: u32) {
let camera = &scene.camera;
let camera = &scene.get_camera();
let aa_rate = &scene.get_camera().get_anti_alias_rate();
for i in 0..scene.image_width {
let pixel_tl = camera.pixel00_loc + (i * camera.pixel_delta_u) + (y * camera.pixel_delta_v);
// THOUGHT: the above seems more efficient (esp for larger aa rates) but can test for a
// get_ray_no_tl() function and compare performance.
let pixel_tl = camera.get_pixel_tl(i, y);
let mut pixel_colour = Colour::default();
for y in 1..(camera.anti_alias_rate + 1) {
for x in 1..(camera.anti_alias_rate + 1) {
for y in 1..(aa_rate + 1) {
for x in 1..(aa_rate + 1) {
let r = camera.get_ray(pixel_tl, x, y);
pixel_colour += ray_colour(&scene.objects, &r, scene.max_depth);
}
}
let (r, g, b) =
(pixel_colour / (camera.anti_alias_rate * camera.anti_alias_rate) as f32).output();
(pixel_colour / (aa_rate * aa_rate) as f32).output();
chunk[(i * 3) as usize] = r;
chunk[(i * 3) as usize + 1] = g;
chunk[(i * 3) as usize + 2] = b;

View File

@@ -38,6 +38,22 @@ impl Scene {
max_depth,
}
}
pub fn get_camera(&self) -> &Camera {
&self.camera
}
pub fn get_image_width(&self) -> f32 {
self.image_width as f32
}
pub fn get_image_height(&self) -> f32 {
self.image_height as f32
}
pub fn init(&mut self) {
self.camera.init(self.get_image_width(), self.get_image_height());
}
}
#[derive(Deserialize)]