rich progress bar (doesn't really work..)
This commit is contained in:
parent
0ebe01704b
commit
cfed733a36
4 changed files with 148 additions and 15 deletions
32
README.md
32
README.md
|
|
@ -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
BIN
cornell_hq.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
131
raytracer.py
131
raytracer.py
|
|
@ -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
BIN
render_metrics.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Loading…
Add table
Reference in a new issue