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+
|
- Python 3.8+
|
||||||
- numpy
|
- numpy
|
||||||
- Pillow
|
- Pillow
|
||||||
|
- rich (progress bars, colored tables)
|
||||||
|
- matplotlib (render metrics graphs)
|
||||||
|
|
||||||
```bash
|
```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
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -80,13 +96,23 @@ The scene is the classic **Cornell Box**:
|
||||||
| Dielectric | Snell refraction + Schlick fresnel + random bounce |
|
| Dielectric | Snell refraction + Schlick fresnel + random bounce |
|
||||||
| Light | Emissive surface, sampled via NEE |
|
| 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
|
## Performance
|
||||||
|
|
||||||
Approximate render times (with multiprocessing, 4 workers):
|
Measured on 4 workers, single machine:
|
||||||
|
|
||||||
| Resolution | spp | Time |
|
| Resolution | spp | Time |
|
||||||
|------------|-----|------|
|
|------------|-----|------|
|
||||||
| 200x150 | 20 | ~2 min |
|
| 200x150 | 20 | ~40s |
|
||||||
| 400x300 | 50 | ~10 min |
|
| 400x300 | 50 | ~10 min |
|
||||||
| 800x600 | 50 | ~40 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 |
123
raytracer.py
123
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))
|
max_depth, lookfrom, lookat, vfov, s, e, w * 100000 + 42))
|
||||||
|
|
||||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||||
|
|
||||||
|
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:
|
with ProcessPoolExecutor(max_workers=workers) as executor:
|
||||||
futures = {executor.submit(render_chunk, r): r[9] for r in ranges}
|
futures = {executor.submit(render_chunk, r): r[9] for r in ranges}
|
||||||
completed = 0
|
|
||||||
for f in as_completed(futures):
|
for f in as_completed(futures):
|
||||||
chunk_start, chunk_img = f.result()
|
chunk_start, chunk_img = f.result()
|
||||||
end = chunk_start + chunk_img.shape[0]
|
chunk_end = chunk_start + chunk_img.shape[0]
|
||||||
image[chunk_start:end] = chunk_img
|
image[chunk_start:chunk_end] = chunk_img
|
||||||
completed += 1
|
completed_rows += (chunk_end - chunk_start)
|
||||||
print(f"Chunk {completed}/{workers} done ({time.time() - start:.1f}s)")
|
|
||||||
|
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)
|
||||||
|
|
||||||
img = (image * 255).astype(np.uint8)
|
|
||||||
Image.fromarray(img, 'RGB').save(output)
|
|
||||||
elapsed = time.time() - start
|
elapsed = time.time() - start
|
||||||
print(f"Saved {output} in {elapsed:.1f}s")
|
brightness = float(image.mean())
|
||||||
|
brightness_history.append(brightness)
|
||||||
|
timestamps.append(elapsed)
|
||||||
|
|
||||||
|
elapsed = time.time() - start
|
||||||
|
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
|
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