Compare commits

...

No commits in common. "cfb6438c9b506c4aa51c00fe40b3be136bff7b6e" and "e7e258cb41a237337ff5cd93154fc32c5ea51c03" have entirely different histories.

25 changed files with 48 additions and 5 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Full-size renders stay local; commit downscaled JPEG previews to renders/ instead.
*.png

View file

@ -83,7 +83,9 @@ cargo build --release
./target/release/pathtracer --list-scenes ./target/release/pathtracer --list-scenes
``` ```
It uses all cores via rayon, shows a progress bar and (for the `cornell` scene) the region brightness table, but does not write `preview.png` or `render_metrics.png`. Without an output name it writes `<scene>.png`. It uses all cores via rayon, shows a progress bar and (for the `cornell` scene) the region brightness table, but does not write `preview.png` or `render_metrics.png`. Without an output name it writes `<scene>.png`; an output name ending in `.jpg` or `.jpeg` writes a JPEG (quality 90) instead, which for noisy renders is several times smaller.
PNG files are ignored by git (see `.gitignore`), so full-size renders stay local. To add a render to the repo, commit a downscaled JPEG preview to [`renders/`](renders/) (the gallery below uses 1280x720, quality 85, about 100 KB each).
### Scenes ### Scenes
@ -100,6 +102,16 @@ It uses all cores via rayon, shows a progress bar and (for the `cornell` scene)
| `haze` | The `city` under a layer of atmospheric haze that fades the horizon and glows around the sun | | `haze` | The `city` under a layer of atmospheric haze that fades the horizon and glows around the sun |
| `city` | Massive scale: a procedural 400x400-lot city of ~124,000 objects (buildings with stepped tiers, mirror-glass towers, parks with trees) under a low sun. Renders 800x600 at 64 spp in ~1 s; without the BVH it is ~1000x slower | | `city` | Massive scale: a procedural 400x400-lot city of ~124,000 objects (buildings with stepped tiers, mirror-glass towers, parks with trees) under a low sun. Renders 800x600 at 64 spp in ~1 s; without the BVH it is ~1000x slower |
### Gallery
| | |
|---|---|
| ![beam](renders/beam.jpg) `beam` | ![fog](renders/fog.jpg) `fog` |
| ![caustics](renders/caustics.jpg) `caustics` | ![media](renders/media.jpg) `media` |
| ![city](renders/city.jpg) `city` | ![haze](renders/haze.jpg) `haze` |
| ![spheres](renders/spheres.jpg) `spheres` | ![night](renders/night.jpg) `night` |
| ![classic](renders/classic.jpg) `classic` | ![cornell (Python)](renders/cornell_python.jpg) `cornell`, rendered by `raytracer.py` |
Scenes are defined in [`rust/src/scenes.rs`](rust/src/scenes.rs) with a small builder (`plane`, `sphere`, `cuboid`, `box_light`, `sphere_light`, `rect_light`, `spot_light`, `fog`, `caustic_photons`, `media_photons`). Every emitter must be added with a `*_light` method so light sampling knows about it. Scenes are defined in [`rust/src/scenes.rs`](rust/src/scenes.rs) with a small builder (`plane`, `sphere`, `cuboid`, `box_light`, `sphere_light`, `rect_light`, `spot_light`, `fog`, `caustic_photons`, `media_photons`). Every emitter must be added with a `*_light` method so light sampling knows about it.
### Volumetrics ### Volumetrics

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

BIN
renders/beam.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
renders/caustics.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
renders/city.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
renders/classic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
renders/cornell_python.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

BIN
renders/fog.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
renders/haze.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
renders/media.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
renders/night.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
renders/spheres.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

17
rust/Cargo.lock generated
View file

@ -169,6 +169,8 @@ dependencies = [
"moxcms", "moxcms",
"num-traits", "num-traits",
"png", "png",
"zune-core",
"zune-jpeg",
] ]
[[package]] [[package]]
@ -581,3 +583,18 @@ name = "zlib-rs"
version = "0.6.8" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
[[package]]
name = "zune-core"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b"
[[package]]
name = "zune-jpeg"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core",
]

View file

@ -8,7 +8,7 @@ description = "Cornell Box path tracer (Rust port of raytracer.py)"
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
rayon = "1" rayon = "1"
indicatif = "0.17" indicatif = "0.17"
image = { version = "0.25", default-features = false, features = ["png"] } image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
[profile.release] [profile.release]
opt-level = 3 opt-level = 3

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 MiB

View file

@ -419,13 +419,25 @@ fn render(scene: &Scene, s: &Settings, caustics: Option<&CausticMaps>) -> (Vec<V
(image, total_samples) (image, total_samples)
} }
fn save_png(image: &[Vec3], width: usize, height: usize, path: &str) -> image::ImageResult<()> { /// JPEG quality for .jpg/.jpeg output: much smaller files than PNG for
/// noisy renders, with no visible loss at this setting.
const JPEG_QUALITY: u8 = 90;
/// Saves as JPEG if `path` ends in .jpg/.jpeg, otherwise as PNG.
fn save_image(image: &[Vec3], width: usize, height: usize, path: &str) -> image::ImageResult<()> {
let bytes: Vec<u8> = image let bytes: Vec<u8> = image
.iter() .iter()
.flat_map(|c| [c.x, c.y, c.z]) .flat_map(|c| [c.x, c.y, c.z])
.map(|c| (c.clamp(0.0, 1.0) * 255.0) as u8) .map(|c| (c.clamp(0.0, 1.0) * 255.0) as u8)
.collect(); .collect();
image::save_buffer(path, &bytes, width as u32, height as u32, image::ColorType::Rgb8) let lower = path.to_ascii_lowercase();
if lower.ends_with(".jpg") || lower.ends_with(".jpeg") {
let file = std::io::BufWriter::new(std::fs::File::create(path)?);
let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(file, JPEG_QUALITY);
encoder.encode(&bytes, width as u32, height as u32, image::ExtendedColorType::Rgb8)
} else {
image::save_buffer(path, &bytes, width as u32, height as u32, image::ColorType::Rgb8)
}
} }
fn region_mean(image: &[Vec3], width: usize, rows: std::ops::Range<usize>, cols: std::ops::Range<usize>) -> f64 { fn region_mean(image: &[Vec3], width: usize, rows: std::ops::Range<usize>, cols: std::ops::Range<usize>) -> f64 {
@ -582,7 +594,7 @@ fn main() {
let elapsed = start.elapsed().as_secs_f64(); let elapsed = start.elapsed().as_secs_f64();
let pixels = (settings.width * settings.height) as f64; let pixels = (settings.width * settings.height) as f64;
if let Err(e) = save_png(&image, settings.width, settings.height, &settings.output) { if let Err(e) = save_image(&image, settings.width, settings.height, &settings.output) {
eprintln!("failed to write {}: {e}", settings.output); eprintln!("failed to write {}: {e}", settings.output);
std::process::exit(1); std::process::exit(1);
} }