rich progress bar (doesn't really work..)

This commit is contained in:
David 2026-09-18 15:25:55 +02:00
parent 0ebe01704b
commit cfed733a36
4 changed files with 148 additions and 15 deletions

View file

@ -14,11 +14,27 @@ A pure Python raytracer that renders the Cornell Box scene with:
- Python 3.8+
- numpy
- Pillow
- rich (progress bars, colored tables)
- matplotlib (render metrics graphs)
```bash
pip install numpy Pillow
pip install numpy Pillow rich matplotlib
```
## Features
- **Rich Progress Bar** - animated progress with ETA, speed, and row count
- **Scene Configuration Table** - formatted parameter display
- **Region Brightness Analysis** - color-coded brightness per region (left/right wall, floor, ceiling, center)
- **Render Metrics Graph** - saved as `render_metrics.png`, shows brightness convergence and render preview
- **Live Preview** - `preview.png` updated during rendering
- **Reflections** - mirror/chrome spheres reflect surrounding geometry
- **Caustics & Refraction** - glass spheres bend light (dielectric material with Schlick approximation)
- **Multiple Bounces** - recursive path tracing up to configurable depth
- **Next Event Estimation (NEE)** - explicit light sampling for fast convergence
- **Anti-aliasing** - supersampled pixels with jittered rays
- **Multiprocessing** - parallel rendering across CPU cores
## Usage
```bash
@ -80,13 +96,23 @@ The scene is the classic **Cornell Box**:
| Dielectric | Snell refraction + Schlick fresnel + random bounce |
| Light | Emissive surface, sampled via NEE |
## Progress Indicators
The renderer uses **Rich** for terminal UI and **matplotlib** for metrics graphs:
- **Progress Bar** - animated with spinner, bar, percentage, ETA, and row count
- **Settings Table** - formatted parameter display with colored values
- **Region Brightness** - color-coded ASCII bar chart showing brightness per region
- **Metrics Graph** - saved as `render_metrics.png`, shows brightness convergence curve and live render preview
- **Preview Image** - saved as `preview.png` and updated during rendering
## Performance
Approximate render times (with multiprocessing, 4 workers):
Measured on 4 workers, single machine:
| Resolution | spp | Time |
|------------|-----|------|
| 200x150 | 20 | ~2 min |
| 200x150 | 20 | ~40s |
| 400x300 | 50 | ~10 min |
| 800x600 | 50 | ~40 min |

BIN
cornell_hq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View file

@ -427,20 +427,127 @@ def render(scene, width=800, height=600, samples_per_pixel=50, max_depth=15,
max_depth, lookfrom, lookat, vfov, s, e, w * 100000 + 42))
from concurrent.futures import ProcessPoolExecutor, as_completed
with ProcessPoolExecutor(max_workers=workers) as executor:
futures = {executor.submit(render_chunk, r): r[9] for r in ranges}
completed = 0
for f in as_completed(futures):
chunk_start, chunk_img = f.result()
end = chunk_start + chunk_img.shape[0]
image[chunk_start:end] = chunk_img
completed += 1
print(f"Chunk {completed}/{workers} done ({time.time() - start:.1f}s)")
img = (image * 255).astype(np.uint8)
Image.fromarray(img, 'RGB').save(output)
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeElapsedColumn, TimeRemainingColumn
from rich.table import Table
from rich.panel import Panel
from matplotlib import pyplot as plt
console = Console()
console.print()
console.print(Panel.fit(
"[bold magenta]Cornell Box Raytracer[/bold magenta]\n"
"[dim]Reflections | Caustics | Multiple Bounces | NEE[/dim]",
border_style="bright_blue", padding=(0, 2)
))
settings = Table(title="Scene Configuration", show_header=False)
settings.add_column("Parameter", style="cyan", no_wrap=True)
settings.add_column("Value", style="white")
settings.add_row("Resolution", f"[bold]{width}x{height}[/bold]")
settings.add_row("Samples/pixel", f"[bold yellow]{samples_per_pixel}[/bold yellow]")
settings.add_row("Max bounces", f"[bold green]{max_depth}[/bold green]")
settings.add_row("Workers", f"[bold magenta]{workers}[/bold magenta]")
settings.add_row("Output", f"[bold]{output}[/bold]")
console.print(settings)
console.print()
progress = Progress(
SpinnerColumn(finished_text="[DONE]", style="green"),
TextColumn("[progress.description]{task.description}"),
BarColumn(bar_width=50, style="bright_blue", complete_style="bright_green"),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
TextColumn("[bold]{task.completed}/{task.total}[/bold] rows"),
TimeElapsedColumn(),
TimeRemainingColumn(),
)
brightness_history = []
timestamps = []
completed_rows = 0
total_rows = height
with progress:
render_task = progress.add_task("Rendering", total=total_rows)
preview_path = "preview.png"
with ProcessPoolExecutor(max_workers=workers) as executor:
futures = {executor.submit(render_chunk, r): r[9] for r in ranges}
for f in as_completed(futures):
chunk_start, chunk_img = f.result()
chunk_end = chunk_start + chunk_img.shape[0]
image[chunk_start:chunk_end] = chunk_img
completed_rows += (chunk_end - chunk_start)
progress.update(render_task, completed=completed_rows)
img = (np.clip(image, 0.0, 1.0) * 255).astype(np.uint8)
Image.fromarray(img, 'RGB').save(preview_path)
elapsed = time.time() - start
brightness = float(image.mean())
brightness_history.append(brightness)
timestamps.append(elapsed)
elapsed = time.time() - start
print(f"Saved {output} in {elapsed:.1f}s")
img = (np.clip(image, 0.0, 1.0) * 255).astype(np.uint8)
Image.fromarray(img, 'RGB').save(output)
console.print()
console.print(Panel.fit(
f"[bold green]Rendered {output}[/bold green]\n"
f"[dim]Total time: {elapsed:.1f}s | "
f"Speed: {width * height * samples_per_pixel / elapsed / 1e6:.2f} MPix*samp/s[/dim]",
border_style="bright_green", padding=(0, 2)
))
region_table = Table(title="Region Brightness Analysis")
region_table.add_column("Region", style="cyan", no_wrap=True)
region_table.add_column("Brightness", justify="right", style="white")
region_table.add_column("Visual", justify="center")
h_start, h_end = height // 3, 2 * height // 3
regions = [
("Left Wall", image[:, :width // 4]),
("Right Wall", image[:, 3 * width // 4:]),
("Floor", image[2 * height // 3:, h_start:h_end]),
("Ceiling", image[:height // 3, h_start:h_end]),
("Center", image[h_start:h_end, h_start:h_end]),
]
for name, region in regions:
brightness = float(region.mean()) * 255
bar_len = 20
filled = int(brightness / 255 * bar_len)
bar = "#" * filled + "-" * (bar_len - filled)
color = "green" if brightness > 80 else ("yellow" if brightness > 40 else "red")
region_table.add_row(name, f"{brightness:.1f}", f"[{color}]{bar}[/{color}]")
console.print(region_table)
console.print()
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
ax1, ax2 = axes
t_arr = np.array(timestamps)
b_arr = np.array(brightness_history)
ax1.plot(t_arr, b_arr, color='#22cc66', linewidth=2)
ax1.set_xlabel("Time (s)", fontsize=11)
ax1.set_ylabel("Mean Brightness", fontsize=11)
ax1.set_title("Brightness Convergence", fontsize=13, fontweight='bold')
ax1.grid(True, alpha=0.3)
ax1.fill_between(t_arr, b_arr, alpha=0.15, color='#22cc66')
preview = (np.clip(image, 0.0, 1.0) * 255).astype(np.uint8)
ax2.imshow(preview)
ax2.set_title("Render Preview", fontsize=13, fontweight='bold')
ax2.axis('off')
plt.tight_layout()
metrics_path = "render_metrics.png"
plt.savefig(metrics_path, dpi=150, bbox_inches='tight')
plt.close()
console.print(f"[dim]Metrics graph saved to [bold]{metrics_path}[/bold][/dim]")
return image

BIN
render_metrics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB