Compare commits
2 commits
main
...
selective-
Author | SHA1 | Date | |
---|---|---|---|
|
5f33c86566 | ||
|
69cedf3af2 |
6
.gitignore
vendored
|
@ -1,6 +1,6 @@
|
|||
/target
|
||||
/*.ppm
|
||||
/*.jpg
|
||||
/*.png
|
||||
*.ppm
|
||||
*.jpg
|
||||
*.png
|
||||
flamegraph.svg
|
||||
perf.data*
|
||||
|
|
98
Cargo.lock
generated
|
@ -8,6 +8,12 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
|
@ -33,9 +39,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.12.1"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da"
|
||||
checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
|
@ -57,15 +63,14 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
|||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.1"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
|
||||
checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"terminal_size",
|
||||
"unicode-width",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -124,10 +129,19 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
name = "deflate"
|
||||
version = "1.0.0"
|
||||
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]]
|
||||
name = "encode_unicode"
|
||||
|
@ -135,16 +149,6 @@ version = "0.3.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "getrandom"
|
||||
version = "0.2.7"
|
||||
|
@ -182,13 +186,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.1"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfddc9561e8baf264e0e45e197fd7696320026eb10a8180340debc27b18f535b"
|
||||
checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b"
|
||||
dependencies = [
|
||||
"console",
|
||||
"lazy_static",
|
||||
"number_prefix",
|
||||
"unicode-width",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -198,10 +203,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.133"
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
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]]
|
||||
name = "memoffset"
|
||||
|
@ -214,9 +225,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.5.4"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
|
||||
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
@ -269,19 +280,19 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.14.0"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.6"
|
||||
version = "0.17.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c"
|
||||
checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"deflate",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
|
@ -314,9 +325,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
@ -345,6 +356,21 @@ dependencies = [
|
|||
"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]]
|
||||
name = "renderer"
|
||||
version = "0.1.0"
|
||||
|
@ -381,12 +407,6 @@ dependencies = [
|
|||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
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"] }
|
||||
rayon = "1.5.3"
|
||||
tobj = "3.2.3"
|
||||
|
|
15
README.md
|
@ -1,20 +1,5 @@
|
|||
# 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
|
||||
https://github.com/flamegraph-rs/flamegraph
|
||||
|
|
Before Width: | Height: | Size: 738 KiB |
Before Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 4 MiB |
Before Width: | Height: | Size: 2.1 MiB |
Before Width: | Height: | Size: 578 KiB |
BIN
images/cat.png
Before Width: | Height: | Size: 674 KiB |
Before Width: | Height: | Size: 454 KiB |
Before Width: | Height: | Size: 520 KiB |
|
@ -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.151382 -0.000154
|
||||
v -0.573651 0.164474 0.619081
|
||||
|
|
12
src/color.rs
|
@ -1,22 +1,22 @@
|
|||
use super::{utility, Color, Rgb, RgbImage};
|
||||
|
||||
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 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();
|
||||
g = (scale * g).sqrt();
|
||||
b = (scale * b).sqrt();
|
||||
b = (scale * b).sqrt();*/
|
||||
|
||||
img.put_pixel(
|
||||
x,
|
||||
y,
|
||||
Rgb([
|
||||
(256.0 * utility::clamp(r, 0.0, 0.999)) as u8,
|
||||
(256.0 * utility::clamp(g, 0.0, 0.999)) as u8,
|
||||
(256.0 * utility::clamp(b, 0.0, 0.999)) as u8,
|
||||
(256.0 * utility::clamp(pixel_color.x(), 0.0, 0.999)) as u8,
|
||||
(256.0 * utility::clamp(pixel_color.y(), 0.0, 0.999)) as u8,
|
||||
(256.0 * utility::clamp(pixel_color.z(), 0.0, 0.999)) as u8,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,8 +29,7 @@ impl HittableList {
|
|||
|
||||
for obj in &self.objects {
|
||||
let mut temp_rec = HitRecord::empty();
|
||||
if obj.hit(&r, t_min, closest_so_far, &mut temp_rec)
|
||||
&& closest_so_far > temp_rec.t {
|
||||
if obj.hit(&r, t_min, closest_so_far, &mut temp_rec) {
|
||||
hit_anything = true;
|
||||
closest_so_far = temp_rec.t;
|
||||
*rec = temp_rec;
|
||||
|
|
653
src/main.rs
|
@ -14,7 +14,7 @@ use hittable::{HitRecord, Hittable, Sphere, Triangle};
|
|||
use hittable_list::HittableList;
|
||||
use image::{Rgb, RgbImage};
|
||||
use indicatif::ProgressBar;
|
||||
use material::{Dielectric, Lambertian, Material, Metal, Rainbow};
|
||||
use material::{Dielectric, Lambertian, Material, Metal};
|
||||
use ray::Ray;
|
||||
use rayon::prelude::*;
|
||||
use std::env;
|
||||
|
@ -22,7 +22,6 @@ use std::sync::Arc;
|
|||
use tobj;
|
||||
use vec3::{Color, Point3, Vec3};
|
||||
|
||||
/* Gets the pixel color for the passed ray */
|
||||
fn ray_color(r: &Ray, world: &HittableList, depth: u32) -> Color {
|
||||
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 Color::new(255.0, 0.0, 0.0);//Color::null();
|
||||
return Color::null();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
let mut world = HittableList::new();
|
||||
|
||||
|
@ -514,3 +166,304 @@ fn random_world() -> HittableList {
|
|||
|
||||
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!");
|
||||
}
|
||||
|
|
|
@ -17,8 +17,6 @@ pub struct Dielectric {
|
|||
ir: f64,
|
||||
}
|
||||
|
||||
pub struct Rainbow {}
|
||||
|
||||
pub trait Material: Sync + Send {
|
||||
fn scatter(
|
||||
&self,
|
||||
|
@ -77,13 +75,7 @@ impl Material for Metal {
|
|||
*scattered = Ray::new(rec.p, reflected + self.fuzz * Vec3::random_in_unit_sphere());
|
||||
*attenuation = self.albedo.clone();
|
||||
|
||||
let reflect = Vec3::dot(r_in.direction(), rec.normal);
|
||||
//dbg!(reflect);
|
||||
|
||||
/*if reflect > 0.0 {
|
||||
*attenuation = Color::new(100.0, 0.0, 0.0);
|
||||
}*/
|
||||
return true;//reflect > 0.0;
|
||||
return Vec3::dot(scattered.direction(), rec.normal) > 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +95,7 @@ impl Material for Mirror {
|
|||
) -> bool {
|
||||
if utility::random_f64() > 0.8 {
|
||||
// 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);
|
||||
*attenuation = self.albedo.clone();
|
||||
|
||||
|
@ -165,35 +157,3 @@ impl Material for Dielectric {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,10 +30,6 @@ impl Vec3 {
|
|||
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 {
|
||||
Vec3::new(
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
Vec3::new(
|
||||
utility::random_f64(),
|
||||
|
|