Compare commits

..

2 commits

Author SHA1 Message Date
Jonathan Flueren
5f33c86566 format 2022-07-27 00:55:07 +02:00
Jonathan Flueren
69cedf3af2 First implementation try + progress bars 2022-07-27 00:49:34 +02:00
18 changed files with 385 additions and 465 deletions

6
.gitignore vendored
View file

@ -1,6 +1,6 @@
/target /target
/*.ppm *.ppm
/*.jpg *.jpg
/*.png *.png
flamegraph.svg flamegraph.svg
perf.data* perf.data*

98
Cargo.lock generated
View file

@ -8,6 +8,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.7.6" version = "0.7.6"
@ -33,9 +39,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.12.1" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -57,15 +63,14 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.1" version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"libc", "libc",
"once_cell", "once_cell",
"terminal_size", "terminal_size",
"unicode-width",
"winapi", "winapi",
] ]
@ -124,10 +129,19 @@ dependencies = [
] ]
[[package]] [[package]]
name = "either" name = "deflate"
version = "1.8.0" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f"
dependencies = [
"adler32",
]
[[package]]
name = "either"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
@ -135,16 +149,6 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "flate2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.7" version = "0.2.7"
@ -182,13 +186,14 @@ dependencies = [
[[package]] [[package]]
name = "indicatif" name = "indicatif"
version = "0.17.1" version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfddc9561e8baf264e0e45e197fd7696320026eb10a8180340debc27b18f535b" checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b"
dependencies = [ dependencies = [
"console", "console",
"lazy_static",
"number_prefix", "number_prefix",
"unicode-width", "regex",
] ]
[[package]] [[package]]
@ -198,10 +203,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b"
[[package]] [[package]]
name = "libc" name = "lazy_static"
version = "0.2.133" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
@ -214,9 +225,9 @@ dependencies = [
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.5.4" version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [ dependencies = [
"adler", "adler",
] ]
@ -269,19 +280,19 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.14.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]] [[package]]
name = "png" name = "png"
version = "0.17.6" version = "0.17.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"crc32fast", "crc32fast",
"flate2", "deflate",
"miniz_oxide", "miniz_oxide",
] ]
@ -314,9 +325,9 @@ dependencies = [
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.4" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [ dependencies = [
"getrandom", "getrandom",
] ]
@ -345,6 +356,21 @@ dependencies = [
"num_cpus", "num_cpus",
] ]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]] [[package]]
name = "renderer" name = "renderer"
version = "0.1.0" version = "0.1.0"
@ -381,12 +407,6 @@ dependencies = [
"ahash", "ahash",
] ]
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"

View file

@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
image = { version = "0.24.2", default-features = false, features = ["jpeg", "png", "pnm"] } image = { version = "0.24.2", default-features = false, features = ["jpeg", "png", "pnm"] }
indicatif = "0.17.0" indicatif = "0.16.2"
rand = { version = "0.8.5", features = ["small_rng"] } rand = { version = "0.8.5", features = ["small_rng"] }
rayon = "1.5.3" rayon = "1.5.3"
tobj = "3.2.3" tobj = "3.2.3"

View file

@ -1,20 +1,5 @@
# Renderer - Grafik-AG # Renderer - Grafik-AG
Programmed in the [Computergrafik-AG](https://fsinfo.cs.tu-dortmund.de/ags/cg-ag/start).
Following [Raytracing In One Weekend](https://raytracing.github.io/books/RayTracingInOneWeekend.html).
Some rendered images are in `images/`.
### Image after adding metal material for spheres
![RaytracingInOneWeekend](images/RayTracingInOneWeekend1.jpg)
### One of the final images from the guide
![RaytracingInOneWeekend](images/RayTracingInOneWeekend2.png)
### Upgraded with triangles and mesh importing
![Suzanne Rainbow](images/suzanne_rainbow.png)
*** ***
### Performance analysis with Flamegraph ### Performance analysis with Flamegraph
https://github.com/flamegraph-rs/flamegraph https://github.com/flamegraph-rs/flamegraph

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 KiB

View file

@ -1,4 +1,7 @@
# Blender v2.82 (sub 7) OBJ File: ''
# www.blender.org
mtllib viking_room.mtl
o mesh_all1_Texture1_0
v -0.573651 0.001530 0.713748 v -0.573651 0.001530 0.713748
v -0.573651 0.151382 -0.000154 v -0.573651 0.151382 -0.000154
v -0.573651 0.164474 0.619081 v -0.573651 0.164474 0.619081

View file

@ -1,22 +1,22 @@
use super::{utility, Color, Rgb, RgbImage}; use super::{utility, Color, Rgb, RgbImage};
pub fn put_color(img: &mut RgbImage, pixel_color: &Color, x: u32, y: u32, samples_per_pixel: u32) { pub fn put_color(img: &mut RgbImage, pixel_color: &Color, x: u32, y: u32, samples_per_pixel: u32) {
let mut r = pixel_color.x(); /*let mut r = pixel_color.x();
let mut g = pixel_color.y(); let mut g = pixel_color.y();
let mut b = pixel_color.z(); let mut b = pixel_color.z();
let scale = 1.0;// / samples_per_pixel as f64; let scale = 1.0 / samples_per_pixel as f64;
r = (scale * r).sqrt(); r = (scale * r).sqrt();
g = (scale * g).sqrt(); g = (scale * g).sqrt();
b = (scale * b).sqrt(); b = (scale * b).sqrt();*/
img.put_pixel( img.put_pixel(
x, x,
y, y,
Rgb([ Rgb([
(256.0 * utility::clamp(r, 0.0, 0.999)) as u8, (256.0 * utility::clamp(pixel_color.x(), 0.0, 0.999)) as u8,
(256.0 * utility::clamp(g, 0.0, 0.999)) as u8, (256.0 * utility::clamp(pixel_color.y(), 0.0, 0.999)) as u8,
(256.0 * utility::clamp(b, 0.0, 0.999)) as u8, (256.0 * utility::clamp(pixel_color.z(), 0.0, 0.999)) as u8,
]), ]),
); );
} }

View file

@ -29,8 +29,7 @@ impl HittableList {
for obj in &self.objects { for obj in &self.objects {
let mut temp_rec = HitRecord::empty(); let mut temp_rec = HitRecord::empty();
if obj.hit(&r, t_min, closest_so_far, &mut temp_rec) if obj.hit(&r, t_min, closest_so_far, &mut temp_rec) {
&& closest_so_far > temp_rec.t {
hit_anything = true; hit_anything = true;
closest_so_far = temp_rec.t; closest_so_far = temp_rec.t;
*rec = temp_rec; *rec = temp_rec;

View file

@ -14,7 +14,7 @@ use hittable::{HitRecord, Hittable, Sphere, Triangle};
use hittable_list::HittableList; use hittable_list::HittableList;
use image::{Rgb, RgbImage}; use image::{Rgb, RgbImage};
use indicatif::ProgressBar; use indicatif::ProgressBar;
use material::{Dielectric, Lambertian, Material, Metal, Rainbow}; use material::{Dielectric, Lambertian, Material, Metal};
use ray::Ray; use ray::Ray;
use rayon::prelude::*; use rayon::prelude::*;
use std::env; use std::env;
@ -22,7 +22,6 @@ use std::sync::Arc;
use tobj; use tobj;
use vec3::{Color, Point3, Vec3}; use vec3::{Color, Point3, Vec3};
/* Gets the pixel color for the passed ray */
fn ray_color(r: &Ray, world: &HittableList, depth: u32) -> Color { fn ray_color(r: &Ray, world: &HittableList, depth: u32) -> Color {
let mut rec = HitRecord::empty(); let mut rec = HitRecord::empty();
@ -39,7 +38,7 @@ fn ray_color(r: &Ray, world: &HittableList, depth: u32) -> Color {
{ {
return attenuation * ray_color(&scattered, world, depth - 1); return attenuation * ray_color(&scattered, world, depth - 1);
} }
return Color::new(255.0, 0.0, 0.0);//Color::null(); return Color::null();
} }
let unit_direction = r.direction(); let unit_direction = r.direction();
@ -47,353 +46,6 @@ fn ray_color(r: &Ray, world: &HittableList, depth: u32) -> Color {
return (1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0); return (1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0);
} }
// Current world view:
//
// I y
// I
// I
// I
// / \
// / \
// / \
// / z \ x
/*
* Main function that builds everything and runs the raytracing
*/
fn main() {
// File
let mut default_file = "image.ppm";
// Image
let aspect_ratio = 10.0 / 7.5; //16.0 / 9.0;
let image_width = 1200;
let image_height = (image_width as f64 / aspect_ratio) as u32;
let samples_per_pixel = 1_u32;
let max_depth = 50;
let antialiasing_threshold = 0.2; // at what diff between two colors will a pixel be antialiased
let vfov = 43.0;
let lookfrom = Point3::new(2.0, 1.0, 1.0);
let lookat = Point3::new(0.0, 0.2, 0.0);
let vup = Vec3::new(0.0, 1.0, 0.0);
let dist_to_focus = 1.0;
let aperture = 0.0; // disable depth of field
// limit rayon multithreading thread count
let thread_count = 0; // if 0, for each logical cpu core a thread wil be created
if thread_count > 0 {
env::set_var("RAYON_NUM_THREADS", thread_count.to_string());
}
// World
eprintln!("[1/4] Loading meshes from file...");
let world = from_obj("obj/viking_room.obj");
// let world = random_world();
// Camera
let cam = Camera::new(
lookfrom,
lookat,
vup,
vfov,
aspect_ratio,
aperture,
dist_to_focus,
);
// Render
let args: Vec<String> = env::args().collect();
if args.len() > 1 && args[1] != "" {
default_file = &args[1];
}
eprintln!("[2/4] Generating image...");
let bar = ProgressBar::new(image_height as u64);
let mut image = RgbImage::new(image_width, image_height);
let color_lines: Vec<_> = (0..image_height)
.into_par_iter() // threadded/parallel variant
//.into_iter() // iterative variant
.rev()
.map(|j| {
bar.inc(1);
let mut colors = Vec::new();
for i in 0..image_width {
let pixel_color = ray_color(
&cam.get_ray(
i as f64 / (image_width - 1) as f64,
j as f64 / (image_height - 1) as f64,
),
&world,
max_depth,
);
colors.push(pixel_color);
}
return colors;
})
.collect();
bar.finish_and_clear();
// no antialiasing
if samples_per_pixel == 1_u32 {
eprintln!("[4/4] Exporting image to disk...");
(0..image_height).into_iter().rev().for_each(|j| {
(0..image_width).into_iter().for_each(|i| {
color::put_color(
&mut image,
&color_lines[(image_height - j - 1) as usize][i as usize],
i,
image_height - j - 1,
1,
);
})
});
image.save(default_file).unwrap();
return;
}
eprintln!("[3/4] Antialiasing image...");
let mut antialiasing: Vec<Vec<bool>> = Vec::new();
let mut antialiasing_counter = 0;
let mut antialiasing_col_counter = 0;
let mut antialiases_per_line: Vec<u64> = Vec::new();
(0..image_height).into_iter().for_each(|j| {
antialiasing.push(Vec::new());
(0..image_width).into_iter().for_each(|i| {
if j != 0
&& Color::diff(
&color_lines[(j - 1) as usize][i as usize],
&color_lines[j as usize][i as usize],
) >= antialiasing_threshold
{
antialiasing[j as usize].push(true);
antialiasing_col_counter += 1;
} else if j != image_height - 1
&& Color::diff(
&color_lines[(j + 1) as usize][i as usize],
&color_lines[j as usize][i as usize],
) >= antialiasing_threshold
{
antialiasing[j as usize].push(true);
antialiasing_col_counter += 1;
} else if i != 0
&& Color::diff(
&color_lines[j as usize][(i - 1) as usize],
&color_lines[j as usize][i as usize],
) >= antialiasing_threshold
{
antialiasing[j as usize].push(true);
antialiasing_col_counter += 1;
} else if i != image_width - 1
&& Color::diff(
&color_lines[j as usize][(i + 1) as usize],
&color_lines[j as usize][i as usize],
) >= antialiasing_threshold
{
antialiasing[j as usize].push(true);
antialiasing_col_counter += 1;
}
if antialiasing[j as usize].len() < (i + 1) as usize {
antialiasing[j as usize].push(false);
}
});
antialiases_per_line.push(antialiasing_col_counter);
antialiasing_counter += antialiasing_col_counter;
antialiasing_col_counter = 0;
});
let bar = ProgressBar::new(antialiasing_counter as u64);
let color_lines_antialiased: Vec<_> = (0..image_height)
.into_par_iter() // threadded/parallel variant
//.into_iter() // iterative variant
.map(|j| {
let mut colors = Vec::new();
for i in 0..image_width {
if samples_per_pixel > 0 && antialiasing[j as usize][i as usize] {
let mut pixel_color = Color::null();
for _ in 0..samples_per_pixel {
let u = (i as f64 + utility::random_f64()) / (image_width - 1) as f64;
let v = (image_height as f64- 1.0 -(j as f64 + utility::random_f64())) / (image_height - 1) as f64;
let new_pixel_color = ray_color(&cam.get_ray(u, v), &world, max_depth);
pixel_color += new_pixel_color;
}
// Correct antialiasing gamma
let fin_color = (1.0/samples_per_pixel as f64) * pixel_color;
//println!("x: {}, y: {}, z: {}", pixel_color.x(), pixel_color.y(), pixel_color.z());
colors.push(fin_color);
} else {
colors.push(color_lines[j as usize][i as usize]);
}
}
bar.inc(antialiases_per_line[j as usize]);
return colors;
})
.collect();
bar.finish_and_clear();
eprintln!("[4/4] Exporting image to disk...");
(0..image_height).into_iter().rev().for_each(|j| {
(0..image_width).into_iter().for_each(|i| {
color::put_color(
&mut image,
&color_lines_antialiased[(image_height - j - 1) as usize][i as usize],
i,
image_height - j - 1,
1,
);
})
});
image.save(default_file).unwrap();
}
/********************
* WORLD GENERATION *
********************/
/*
* Generates world based on .obj mesh file passed by path.
*
* Currently only works for .obj files which contain face normals.
*/
fn from_obj(path: &str) -> HittableList {
let mut world = HittableList::new();
/*
let material_ground = Arc::new(Lambertian::new(&Color::new(
29.0 / 255.0,
71.0 / 255.0,
14.0 / 255.0,
)));
world.add(Box::<Sphere>::new(Sphere::new(
Point3::new(-500.0, -5005.0, -500.0),
5000.0,
material_ground.clone(),
)));
*/
//let material = Arc::new(Lambertian::new(&Color::new(
// 77.0 / 255.0,
// 77.0 / 255.0,
// 118.0 / 255.0,
//)));
//let material = Arc::new(Dielectric::new(2.0));
let material = Arc::new(Metal::new(&Color::new(0.5, 0.55, 0.7), 0.0));
//let material = Arc::new(Rainbow::new());
let rotate_obj = 1; // rotate clockwise, 0: 0, 1: 90, 2: 180, 3: 270
let cornell_box = tobj::load_obj(path, &tobj::OFFLINE_RENDERING_LOAD_OPTIONS);
let (models, materials) = cornell_box.expect("Failed to load OBJ file");
let materials = materials.expect("Failed to load MTL file");
let mut new_x = 0;
let mut new_y = 1;
let mut new_z = 2;
match rotate_obj {
1 => {
new_x = 0;
new_y = 2;
new_z = 1;
},
2 => {
new_x = 0;
new_y = 2;
new_z = 1;
},
3 => {
new_x = 0;
new_y = 2;
new_z = 1;
},
_ => {}
}
for (i, m) in models.iter().enumerate() {
let mesh = &m.mesh;
let mut next_face = 0;
for f in 0..mesh.face_arities.len() {
let end = next_face + mesh.face_arities[f] as usize;
let face_indices: Vec<_> = mesh.indices[next_face..end].iter().collect();
println!(" face[{}] = {:?}", f, face_indices);
next_face = end;
}
for v in 0..mesh.indices.len() / 3 {
let index_a = mesh.indices[3 * v] as usize;
let index_b = mesh.indices[3 * v + 1] as usize;
let index_c = mesh.indices[3 * v + 2] as usize;
let index_normal_a = mesh.normal_indices[3 * v] as usize;
let index_normal_b = mesh.normal_indices[3 * v + 1] as usize;
let index_normal_c = mesh.normal_indices[3 * v + 2] as usize;
let normal_avg = Vec3::unit_vector(
Vec3::new(
mesh.positions[3 * index_normal_a + new_x] as f64,
mesh.positions[3 * index_normal_a + new_y] as f64,
mesh.positions[3 * index_normal_a + new_z] as f64,
) + Vec3::new(
mesh.positions[3 * index_normal_b + new_x] as f64,
mesh.positions[3 * index_normal_b + new_y] as f64,
mesh.positions[3 * index_normal_b + new_z] as f64,
) + Vec3::new(
mesh.positions[3 * index_normal_c + new_x] as f64,
mesh.positions[3 * index_normal_c + new_y] as f64,
mesh.positions[3 * index_normal_c + new_z] as f64,
),
);
/*
println!("a:{},{},{}; b:{},{},{}; c:{},{},{}",
mesh.normals[3*index_normal_a],
mesh.normals[3*index_normal_a+1],
mesh.normals[3*index_normal_a+2],
mesh.normals[3*index_normal_b],
mesh.normals[3*index_normal_b+1],
mesh.normals[3*index_normal_b+2],
mesh.normals[3*index_normal_c],
mesh.normals[3*index_normal_c+1],
mesh.normals[3*index_normal_c+2]);
*/
world.add(Box::<Triangle>::new(Triangle::new(
Point3::new(
mesh.positions[3 * index_a + new_x] as f64,
mesh.positions[3 * index_a + new_y] as f64,
mesh.positions[3 * index_a + new_z] as f64,
),
Point3::new(
mesh.positions[3 * index_b + new_x] as f64,
mesh.positions[3 * index_b + new_y] as f64,
mesh.positions[3 * index_b + new_z] as f64,
),
Point3::new(
mesh.positions[3 * index_c + new_x] as f64,
mesh.positions[3 * index_c + new_y] as f64,
mesh.positions[3 * index_c + new_z] as f64,
),
normal_avg,
material.clone(),
)));
}
}
return world;
}
/*
* Generates a world with a bunch of spheres
*/
fn random_world() -> HittableList { fn random_world() -> HittableList {
let mut world = HittableList::new(); let mut world = HittableList::new();
@ -514,3 +166,304 @@ fn random_world() -> HittableList {
return world; return world;
} }
fn from_obj(path: &str) -> HittableList {
let mut world = HittableList::new();
let material_ground = Arc::new(Lambertian::new(&Color::new(
29.0 / 255.0,
71.0 / 255.0,
14.0 / 255.0,
)));
world.add(Box::<Sphere>::new(Sphere::new(
Point3::new(0.0, -5005.0, 0.0),
5000.0,
material_ground.clone(),
)));
let material = Arc::new(Lambertian::new(&Color::new(
26.0 / 255.0,
82.0 / 255.0,
118.0 / 255.0,
)));
//let material = Arc::new(Dielectric::new(2.0));
//let material = Arc::new(Metal::new(&Color::new(0.9, 0.9, 0.7), 1.0));
let cornell_box = tobj::load_obj(path, &tobj::OFFLINE_RENDERING_LOAD_OPTIONS);
let (models, materials) = cornell_box.expect("Failed to load OBJ file");
let materials = materials.expect("Failed to load MTL file");
for (i, m) in models.iter().enumerate() {
let mesh = &m.mesh;
/* Mesh vector lengths
println!("positions: {}", mesh.positions.len());
println!("vertex_color: {}", mesh.vertex_color.len());
println!("normals: {}", mesh.normals.len());
println!("texcoords: {}", mesh.texcoords.len());
println!("indices: {}", mesh.indices.len());
println!("face_arities: {}", mesh.face_arities.len());
println!("texcoord_indices: {}", mesh.texcoord_indices.len());
println!("normal_indices: {}", mesh.normal_indices.len());
*/
let mut next_face = 0;
for f in 0..mesh.face_arities.len() {
let end = next_face + mesh.face_arities[f] as usize;
let face_indices: Vec<_> = mesh.indices[next_face..end].iter().collect();
println!(" face[{}] = {:?}", f, face_indices);
next_face = end;
}
for v in 0..mesh.indices.len() / 3 {
let index_a = mesh.indices[3 * v] as usize;
let index_b = mesh.indices[3 * v + 1] as usize;
let index_c = mesh.indices[3 * v + 2] as usize;
let index_normal_a = mesh.normal_indices[3 * v] as usize;
let index_normal_b = mesh.normal_indices[3 * v + 1] as usize;
let index_normal_c = mesh.normal_indices[3 * v + 2] as usize;
let normal_avg = Vec3::unit_vector(
Vec3::new(
mesh.positions[3 * index_normal_a] as f64,
mesh.positions[3 * index_normal_a + 1] as f64,
mesh.positions[3 * index_normal_a + 2] as f64,
) + Vec3::new(
mesh.positions[3 * index_normal_b] as f64,
mesh.positions[3 * index_normal_b + 1] as f64,
mesh.positions[3 * index_normal_b + 2] as f64,
) + Vec3::new(
mesh.positions[3 * index_normal_c] as f64,
mesh.positions[3 * index_normal_c + 1] as f64,
mesh.positions[3 * index_normal_c + 2] as f64,
),
);
/*
println!("a:{},{},{}; b:{},{},{}; c:{},{},{}",
mesh.normals[3*index_normal_a],
mesh.normals[3*index_normal_a+1],
mesh.normals[3*index_normal_a+2],
mesh.normals[3*index_normal_b],
mesh.normals[3*index_normal_b+1],
mesh.normals[3*index_normal_b+2],
mesh.normals[3*index_normal_c],
mesh.normals[3*index_normal_c+1],
mesh.normals[3*index_normal_c+2]);
*/
world.add(Box::<Triangle>::new(Triangle::new(
Point3::new(
mesh.positions[3 * index_a] as f64,
mesh.positions[3 * index_a + 1] as f64,
mesh.positions[3 * index_a + 2] as f64,
),
Point3::new(
mesh.positions[3 * index_b] as f64,
mesh.positions[3 * index_b + 1] as f64,
mesh.positions[3 * index_b + 2] as f64,
),
Point3::new(
mesh.positions[3 * index_c] as f64,
mesh.positions[3 * index_c + 1] as f64,
mesh.positions[3 * index_c + 2] as f64,
),
normal_avg,
material.clone(),
)));
}
}
return world;
}
/*
Current world view:
I y
I
I
I
/ \
/ \
/ \
/ z \ x
*/
fn main() {
// File
let mut default_file = "image.ppm";
// Image
let aspect_ratio = 16.0 / 9.0;
let image_width = 800;
let image_height = (image_width as f64 / aspect_ratio) as u32;
let samples_per_pixel = 50 as u32;
let max_depth = 50;
let antialiasing_threshold = 0.3; // at what diff between two colors will a pixel be antialiased
let vfov = 40.0;
let lookfrom = Point3::new(2.0, 0.8, 3.0);
let lookat = Point3::new(0.0, 0.0, 0.0);
let vup = Vec3::new(0.0, 1.0, 0.0);
let dist_to_focus = 3.0;
let aperture = 0.1;
// limit rayon multithreading thread count
let thread_count = 6; // if 0, for each logical cpu core a thread wil be created
if thread_count > 0 {
env::set_var("RAYON_NUM_THREADS", thread_count.to_string());
}
// World
eprintln!("[1/4] Loading meshes from file...");
let world = from_obj("obj/suzanne.obj");
// let world = random_world();
// Camera
let cam = Camera::new(
lookfrom,
lookat,
vup,
vfov,
aspect_ratio,
aperture,
dist_to_focus,
);
// Render
let args: Vec<String> = env::args().collect();
if args.len() > 1 && args[1] != "" {
default_file = &args[1];
}
eprintln!("[2/4] Gerenating image without antialiasing...");
let bar = ProgressBar::new(image_height as u64);
let mut image = RgbImage::new(image_width, image_height);
let color_lines: Vec<_> = (0..image_height)
.into_par_iter() // threadded/parallel variant
//.into_iter() // iterative variant
.rev()
.map(|j| {
bar.inc(1);
let mut colors = Vec::new();
for i in 0..image_width {
let pixel_color = ray_color(
&cam.get_ray(
i as f64 / (image_width - 1) as f64,
j as f64 / (image_height - 1) as f64,
),
&world,
max_depth,
);
colors.push(pixel_color);
}
return colors;
})
.collect();
bar.finish_and_clear();
eprintln!("[3/4] Antialiasing image...");
let mut antialiasing: Vec<Vec<bool>> = Vec::new();
let mut antialiasing_counter = 0;
let mut antialiasing_col_counter = 0;
let mut antialiases_per_line: Vec<u64> = Vec::new();
(0..image_height).into_iter().for_each(|j| {
antialiasing.push(Vec::new());
(0..image_width).into_iter().for_each(|i| {
if j != 0
&& Color::diff(
&color_lines[(j - 1) as usize][i as usize],
&color_lines[j as usize][i as usize],
) >= antialiasing_threshold
{
antialiasing[j as usize].push(true);
antialiasing_col_counter += 1;
} else if j != image_height - 1
&& Color::diff(
&color_lines[(j + 1) as usize][i as usize],
&color_lines[j as usize][i as usize],
) >= antialiasing_threshold
{
antialiasing[j as usize].push(true);
antialiasing_col_counter += 1;
} else if i != 0
&& Color::diff(
&color_lines[j as usize][(i - 1) as usize],
&color_lines[j as usize][i as usize],
) >= antialiasing_threshold
{
antialiasing[j as usize].push(true);
antialiasing_col_counter += 1;
} else if i != image_width - 1
&& Color::diff(
&color_lines[j as usize][(i + 1) as usize],
&color_lines[j as usize][i as usize],
) >= antialiasing_threshold
{
antialiasing[j as usize].push(true);
antialiasing_col_counter += 1;
}
if antialiasing[j as usize].len() < (i + 1) as usize {
antialiasing[j as usize].push(false);
}
});
antialiases_per_line.push(antialiasing_col_counter);
antialiasing_counter += antialiasing_col_counter;
antialiasing_col_counter = 0;
});
let bar = ProgressBar::new(antialiasing_counter as u64);
let color_lines_antialiased: Vec<_> = (0..image_height)
.into_par_iter() // threadded/parallel variant
//.into_iter() // iterative variant
.map(|j| {
let mut colors = Vec::new();
for i in 0..image_width {
if samples_per_pixel > 0 && antialiasing[j as usize][i as usize] {
let mut pixel_color = Color::null();
for _ in 0..samples_per_pixel {
let u = (i as f64 + utility::random_f64()) / (image_width - 1) as f64;
let v = (j as f64 + utility::random_f64()) / (image_height - 1) as f64;
let r = cam.get_ray(u, v);
pixel_color += ray_color(&r, &world, max_depth);
}
// Correct antialiasing gamma
pixel_color = Color::new(
((1.0 / samples_per_pixel as f64) * pixel_color.x()),
((1.0 / samples_per_pixel as f64) * pixel_color.y()),
((1.0 / samples_per_pixel as f64) * pixel_color.z()),
);
colors.push(pixel_color);
} else {
colors.push(color_lines[j as usize][i as usize]);
}
}
bar.inc(antialiases_per_line[j as usize]);
return colors;
})
.collect();
bar.finish_and_clear();
eprintln!("[4/4] Exporting image to disk...");
(0..image_height).into_iter().rev().for_each(|j| {
(0..image_width).into_iter().for_each(|i| {
color::put_color(
&mut image,
&color_lines_antialiased[(image_height - j - 1) as usize][i as usize],
i,
image_height - j - 1,
1,
);
})
});
image.save(default_file).unwrap();
eprintln!("\nDone!");
}

View file

@ -17,8 +17,6 @@ pub struct Dielectric {
ir: f64, ir: f64,
} }
pub struct Rainbow {}
pub trait Material: Sync + Send { pub trait Material: Sync + Send {
fn scatter( fn scatter(
&self, &self,
@ -77,13 +75,7 @@ impl Material for Metal {
*scattered = Ray::new(rec.p, reflected + self.fuzz * Vec3::random_in_unit_sphere()); *scattered = Ray::new(rec.p, reflected + self.fuzz * Vec3::random_in_unit_sphere());
*attenuation = self.albedo.clone(); *attenuation = self.albedo.clone();
let reflect = Vec3::dot(r_in.direction(), rec.normal); return Vec3::dot(scattered.direction(), rec.normal) > 0.0;
//dbg!(reflect);
/*if reflect > 0.0 {
*attenuation = Color::new(100.0, 0.0, 0.0);
}*/
return true;//reflect > 0.0;
} }
} }
@ -103,7 +95,7 @@ impl Material for Mirror {
) -> bool { ) -> bool {
if utility::random_f64() > 0.8 { if utility::random_f64() > 0.8 {
// Reflektiert // Reflektiert
let reflected = Vec3::reflect(&r_in.direction(), &rec.normal); let reflected = Vec3::reflect(&Vec3::unit_vector(r_in.direction()), &rec.normal);
*scattered = Ray::new(rec.p, reflected); *scattered = Ray::new(rec.p, reflected);
*attenuation = self.albedo.clone(); *attenuation = self.albedo.clone();
@ -165,35 +157,3 @@ impl Material for Dielectric {
return true; return true;
} }
} }
impl Rainbow {
pub fn new() -> Self {
Rainbow {}
}
}
impl Material for Rainbow {
fn scatter(
&self,
r_in: &Ray,
rec: &HitRecord,
attenuation: &mut Color,
scattered: &mut Ray,
) -> bool {
let mut scatter_direction = rec.normal + Vec3::random_unit_vector();
if scatter_direction.near_zero() {
scatter_direction = rec.normal;
}
*scattered = Ray::new(rec.p, scatter_direction);
let color = 0.5
* Color::new(
rec.normal.x() + 1.0,
rec.normal.y() + 1.0,
rec.normal.z() + 1.0,
);
*attenuation = color;
return true;
}
}

View file

@ -30,10 +30,6 @@ impl Vec3 {
self.e[0] * self.e[0] + self.e[1] * self.e[1] + self.e[2] * self.e[2] self.e[0] * self.e[0] + self.e[1] * self.e[1] + self.e[2] * self.e[2]
} }
pub fn diff(a: &Self, b: &Self) -> f64 {
(a.x() - b.x()).abs() + (a.y() - b.y()).abs() + (a.z() - b.z()).abs()
}
pub fn cross(a: Vec3, b: Vec3) -> Vec3 { pub fn cross(a: Vec3, b: Vec3) -> Vec3 {
Vec3::new( Vec3::new(
a.y() * b.z() - a.z() * b.y(), a.y() * b.z() - a.z() * b.y(),
@ -66,6 +62,10 @@ impl Vec3 {
return (self.e[0].abs() < s) && (self.e[1].abs() < s) && (self.e[2].abs() < s); return (self.e[0].abs() < s) && (self.e[1].abs() < s) && (self.e[2].abs() < s);
} }
pub fn diff(a: &Self, b: &Self) -> f64 {
(a.x() - b.x()).abs() + (a.y() - b.y()).abs() + (a.z() - b.z()).abs()
}
pub fn random_f64() -> Vec3 { pub fn random_f64() -> Vec3 {
Vec3::new( Vec3::new(
utility::random_f64(), utility::random_f64(),