Replace committed PNG renders with small JPEG previews
Full-size PNG renders are now ignored; downscaled 1280x720 JPEG previews live in renders/ and are shown in a README gallery. The renderer writes JPEG when the output name ends in .jpg/.jpeg. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Full-size renders stay local; commit downscaled JPEG previews to renders/ instead.
|
||||
*.png
|
||||
14
README.md
|
|
@ -83,7 +83,9 @@ cargo build --release
|
|||
./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
|
||||
|
||||
|
|
@ -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 |
|
||||
| `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` |  `fog` |
|
||||
|  `caustics` |  `media` |
|
||||
|  `city` |  `haze` |
|
||||
|  `spheres` |  `night` |
|
||||
|  `classic` |  `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.
|
||||
|
||||
### Volumetrics
|
||||
|
|
|
|||
BIN
renders/beam.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
renders/caustics.jpg
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
renders/city.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
renders/classic.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
renders/cornell_python.jpg
Normal file
|
After Width: | Height: | Size: 283 KiB |
BIN
renders/fog.jpg
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
renders/haze.jpg
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
renders/media.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
renders/night.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
renders/spheres.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
17
rust/Cargo.lock
generated
|
|
@ -169,6 +169,8 @@ dependencies = [
|
|||
"moxcms",
|
||||
"num-traits",
|
||||
"png",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -581,3 +583,18 @@ name = "zlib-rs"
|
|||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ description = "Cornell Box path tracer (Rust port of raytracer.py)"
|
|||
rand = { version = "0.8", features = ["small_rng"] }
|
||||
rayon = "1"
|
||||
indicatif = "0.17"
|
||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
|
|
|
|||
|
|
@ -419,13 +419,25 @@ fn render(scene: &Scene, s: &Settings, caustics: Option<&CausticMaps>) -> (Vec<V
|
|||
(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
|
||||
.iter()
|
||||
.flat_map(|c| [c.x, c.y, c.z])
|
||||
.map(|c| (c.clamp(0.0, 1.0) * 255.0) as u8)
|
||||
.collect();
|
||||
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 {
|
||||
|
|
@ -582,7 +594,7 @@ fn main() {
|
|||
let elapsed = start.elapsed().as_secs_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);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
|
|
|||