caustics 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07a93d859b9558e27661a91aab00238fa15788a739c3c9abba0c45694e65551c
4
- data.tar.gz: 90105dc64c843e76cbe7cfd6a7cfe73d56313aad8652f93e04fc881cd542f1bc
3
+ metadata.gz: 10c88b54baa715ecbb00dc89d92a450a11376482b271b62830ecb3bdcaf07e8b
4
+ data.tar.gz: f1784ca8a4e044c45d632f467dc30109e57b6ee9b2f02b635c45062941cf661a
5
5
  SHA512:
6
- metadata.gz: 4502f7ff407720bf3d647607a0b5d6984ef7efebbd44a13840c6b29869e1b20dd0cb2b9d5307f5f63bea2dac493024bb898021a48999578477e81c14d10b2a15
7
- data.tar.gz: c012eb9814e5089bb52dd94e102960d170baa4a771072f4912adc69e4f5ef6e3a4d5aedef3ab6ce54381212d9759d4ca4c7379e9eb935ccb6ab08a019400804d
6
+ metadata.gz: 8f6205eef7c6d43866cacefaf43a95163c0d8b067567f659faf622e297fc1a43a2b93993f4764973b247cc391acb66eeceda5e1199ffedf767edfbcd9c8c410f
7
+ data.tar.gz: 0dcea5536f7219d8a1161faeda31f86bcb2b4b8bdb61ca8a598b5ab3925b74601df16f02315caf127ef514da5a7ecc50f685544eb9cfb55ed0074d26a11b1205
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.0] - 2026-09-25
6
+
7
+ - Add cumulative GIF output to `caustics render` through optional Flipbook.
8
+
5
9
  ## [0.1.0] - 2026-09-23
6
10
 
7
- - Initial release.
11
+ - Initial release.
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2026 ydah
3
+ Copyright (c) 2026 Yudai Takada
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,19 +1,19 @@
1
- # Caustics
1
+ <h1 align="center">Caustics</h1>
2
2
 
3
- [![Gem version](https://badge.fury.io/rb/caustics.svg)](https://rubygems.org/gems/caustics)
4
- [![Downloads](https://img.shields.io/gem/dt/caustics?label=downloads)](https://rubygems.org/gems/caustics)
5
- [![CI](https://github.com/rbgfx/caustics/actions/workflows/ci.yml/badge.svg)](https://github.com/rbgfx/caustics/actions/workflows/ci.yml)
6
- [![Ruby](https://img.shields.io/badge/ruby-%3E%3D3.1-CC342D?logo=ruby&logoColor=white)](https://www.ruby-lang.org/)
7
- [![License](https://img.shields.io/badge/license-MIT-750014.svg)](LICENSE.txt)
3
+ <p align="center">A deterministic pure Ruby path tracer and teaching example.</p>
8
4
 
9
- > A deterministic pure Ruby path tracer and teaching example.
5
+ <p align="center">
6
+ <a href="https://rubygems.org/gems/caustics"><img src="https://badge.fury.io/rb/caustics.svg" alt="Gem Version"></a>
7
+ <a href="https://rubygems.org/gems/caustics"><img src="https://img.shields.io/gem/dt/caustics?label=downloads" alt="Downloads"></a>
8
+ <a href="https://www.ruby-lang.org/"><img src="https://img.shields.io/badge/ruby-%3E%3D3.1-CC342D?logo=ruby&amp;logoColor=white" alt="Ruby Version"></a>
9
+ <a href="LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-750014.svg" alt="License"></a>
10
+ </p>
10
11
 
11
- Caustics is a small ray tracer for experimenting with cameras, materials,
12
- textures, acceleration, and renderer backends. The Ruby engine is the reference
13
- implementation; optional rlsl and Metal engines cover a deliberately smaller
14
- shader subset.
12
+ [Features](#features) · [Installation](#installation) · [Quick Start](#quick-start) · [Engines](#engines)
15
13
 
16
- **[Features](#features) · [Installation](#installation) · [Quick start](#quick-start) · [Engines](#engines) · [Development](#development)**
14
+ ***
15
+
16
+ Caustics is a small ray tracer for experimenting with cameras, materials, textures, acceleration, and renderer backends. The Ruby engine is the reference implementation; optional RLSL and Metal engines cover a deliberately smaller shader subset.
17
17
 
18
18
  ## Features
19
19
 
@@ -44,15 +44,23 @@ Or install the released gem:
44
44
  gem install caustics
45
45
  ~~~
46
46
 
47
- ## Quick start
47
+ ### Requirements
48
+
49
+ - Ruby 3.1 or newer.
50
+ - The Metal engine requires macOS with a Metal-capable device.
51
+
52
+ ## Quick Start
48
53
 
49
54
  Render the included scene:
50
55
 
51
56
  ~~~sh
52
57
  caustics render scenes/weekend.rb --size 400x225 --spp 16 --workers 4 -o out.png
58
+ caustics render scenes/weekend.rb --size 400x225 --spp 16 --progress-gif -o progress.gif
53
59
  caustics preview scenes/weekend.rb --size 400x225 --spp 16 -o preview.png
54
60
  ~~~
55
61
 
62
+ `--progress-gif` writes a cumulative frame after every sample. Install the optional [Flipbook](https://github.com/rbgfx/flipbook) gem to use it.
63
+
56
64
  Use the Ruby API directly:
57
65
 
58
66
  ~~~ruby
@@ -85,6 +93,10 @@ bundle install
85
93
  bundle exec rake verify
86
94
  ~~~
87
95
 
96
+ ## Contributing
97
+
98
+ Bug reports and pull requests are welcome at [rbgfx/caustics](https://github.com/rbgfx/caustics).
99
+
88
100
  ## License
89
101
 
90
102
  [MIT](LICENSE.txt)
data/Rakefile CHANGED
@@ -3,7 +3,7 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
5
 
6
- RSpec::Core::RakeTask.new(:spec) { |task| task.ruby_opts = ["-I../tessel/lib", "-I../rlsl/lib"] }
6
+ RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  task default: :spec
9
9
  task verify: :spec
data/docs/index.html ADDED
@@ -0,0 +1,33 @@
1
+ <!doctype html>
2
+ <html lang="en"><head>
3
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
4
+ <meta name="theme-color" content="#020617"><meta name="description" content="A pure Ruby path tracer and teaching example for cameras, materials, textures, and acceleration.">
5
+ <meta property="og:title" content="Caustics — Follow the light."><meta property="og:description" content="A pure Ruby path tracer and teaching example for cameras, materials, textures, and acceleration."><meta property="og:type" content="website"><meta property="og:url" content="https://rbgfx.github.io/caustics/">
6
+ <meta name="twitter:card" content="summary"><link rel="canonical" href="https://rbgfx.github.io/caustics/"><title>Caustics — Follow the light.</title>
7
+ <style>
8
+ :root{color-scheme:dark;--bg:#020617;--panel:#0f172a;--line:#334155;--ink:#e2e8f0;--muted:#94a3b8;--a:#38bdf8;--mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--sans:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;--w:min(100% - 2.5rem,76rem)}
9
+ *{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.65 var(--sans)}a{color:inherit;text-underline-offset:.22em}a:hover{color:var(--a)}a:focus-visible{outline:3px solid var(--a);outline-offset:4px}h1,h2,h3,p,figure,pre{margin:0}h1,h2,h3{line-height:1.08;text-wrap:balance}p{text-wrap:pretty}code,pre,.mono{font-family:var(--mono);font-variant-numeric:tabular-nums}.wrap{width:var(--w);margin:auto}.skip{position:fixed;left:1rem;top:1rem;z-index:1;transform:translateY(-160%);padding:.7rem 1rem;background:var(--ink);color:var(--bg)}.skip:focus{transform:none}
10
+ header{height:4.75rem;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between}.brand{display:flex;align-items:center;gap:.7rem;font-weight:750;text-decoration:none}.mark{width:2rem;height:2rem;display:grid;place-items:center;border:1px solid var(--a);color:var(--a);font:.85rem var(--mono)}nav{display:flex;gap:1.5rem;font-size:.9rem}nav a{text-decoration:none;color:var(--muted)}nav a:last-child{color:var(--ink)}
11
+ .hero{display:grid;grid-template-columns:.88fr 1.12fr;align-items:center;gap:clamp(2rem,6vw,6rem);padding-block:clamp(4rem,9vw,8rem)}.kicker,.visual figcaption,.num{font:.76rem/1.4 var(--mono);color:var(--a)}h1{max-width:11ch;margin-top:1rem;font-size:clamp(3rem,7.5vw,6.5rem);letter-spacing:-.065em}.lede{max-width:40rem;margin-top:1.5rem;color:var(--muted);font-size:1.15rem}.actions{display:flex;flex-wrap:wrap;gap:.8rem;margin-top:2rem}.button{min-height:2.8rem;padding:.65rem 1rem;border:1px solid var(--line);display:inline-flex;align-items:center;text-decoration:none;font-weight:650}.button.primary{background:var(--a);border-color:var(--a);color:var(--bg)}.install{margin-top:1.4rem;color:var(--muted);font:.86rem var(--mono)}.install code{color:var(--ink)}
12
+ .visual{border:1px solid var(--line);background:var(--panel)}.art{min-height:18rem;padding:1.25rem;display:grid;place-items:center;overflow:hidden}.art svg{width:100%;max-height:23rem}.visual figcaption{display:flex;justify-content:space-between;gap:1rem;padding:.75rem 1rem;border-top:1px solid var(--line);color:var(--muted)}.visual figcaption span:last-child{color:var(--a)}
13
+ section.info{padding-block:4.5rem;border-top:1px solid var(--line)}.section-head{display:grid;grid-template-columns:.72fr 1.28fr;gap:2rem;align-items:end;margin-bottom:2rem}h2{font-size:clamp(2rem,4vw,3.25rem);letter-spacing:-.045em}.section-head p,.feature p,.try-copy p{color:var(--muted)}.features{display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--line)}.feature{padding:1.25rem 1.2rem 1.4rem 0;border-bottom:1px solid var(--line)}.feature+.feature{padding-left:1.2rem;border-left:1px solid var(--line)}.feature h3{margin-top:.8rem;font-size:1.1rem}.feature p{margin-top:.5rem;font-size:.94rem}.try{display:grid;grid-template-columns:.75fr 1.25fr;align-items:center;gap:clamp(2rem,7vw,7rem)}.try-copy p{margin-top:1rem}.try-copy a{color:var(--ink)}pre{overflow-x:auto;padding:1.25rem;border:1px solid var(--line);background:var(--panel);font-size:.82rem;line-height:1.8;tab-size:2}
14
+ footer{display:flex;justify-content:space-between;gap:1rem;padding-block:1.6rem;border-top:1px solid var(--line);color:var(--muted);font-size:.85rem}footer div{display:flex;gap:1.25rem}
15
+ @media(max-width:800px){.hero{grid-template-columns:1fr;gap:2.5rem}.hero-copy{max-width:42rem}.section-head,.try{grid-template-columns:1fr;gap:1rem}}@media(max-width:560px){:root{--w:min(100% - 1.5rem,76rem)}header{height:4.2rem}nav{gap:.8rem;font-size:.8rem}nav a:first-child{display:none}h1{font-size:clamp(3rem,15vw,5rem)}.features{grid-template-columns:1fr}.feature,.feature+.feature{padding:1.1rem 0;border-left:0}.num{display:inline-block}footer{flex-direction:column}.art{min-height:14rem}section.info{padding-block:3.5rem}pre{font-size:.75rem;padding:1rem}}
16
+ @media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
17
+
18
+ </style>
19
+ </head><body style="--a:#f59e0b"><a class="skip" href="#main">Skip to content</a><div class="wrap">
20
+ <header><a class="brand" href="#top" aria-label="Caustics home"><span class="mark" aria-hidden="true">C</span><span>Caustics</span></a><nav aria-label="Primary navigation"><a href="#features">Overview</a><a href="#quick-start">Quick start</a><a href="https://github.com/rbgfx/caustics">Source ↗</a></nav></header>
21
+ <main id="main"><section class="hero" id="top" aria-labelledby="title"><div class="hero-copy"><p class="kicker">Deterministic path tracing · Ruby</p><h1 id="title">Follow the light.</h1><p class="lede">A pure Ruby path tracer and teaching example for cameras, materials, textures, and acceleration.</p><div class="actions"><a class="button primary" href="#quick-start">Get started</a><a class="button" href="https://rubygems.org/gems/caustics">RubyGems ↗</a></div><p class="install"><span aria-hidden="true">$</span> <code>gem install caustics</code></p></div>
22
+ <figure class="visual" aria-labelledby="cap-caustics"><div class="art"><svg viewBox="0 0 640 360" aria-hidden="true"><rect width="640" height="360" fill="#0b1224"/><path d="M0 264H640" stroke="#334155"/><g stroke="#f59e0b" fill="none"><path d="M42 44L244 224M42 44L320 236M42 44L432 215M42 44L548 252M42 44L256 130M42 44L356 115"/></g><circle cx="240" cy="218" r="50" fill="#7c2d12" stroke="#fbbf24" stroke-width="3"/><circle cx="404" cy="239" r="75" fill="#1e293b" stroke="#94a3b8" stroke-width="2"/></svg></div><figcaption id="cap-caustics"><span>A scene, in rays</span><span>Ruby reference engine</span></figcaption></figure></section>
23
+ <section class="info" id="features" aria-labelledby="feat"><div class="section-head"><h2 id="feat">A renderer you can inspect.</h2><p>Build scenes from small parts, render repeatable samples, and compare backends.</p></div><div class="features"><article class="feature"><span class="num">01</span><h3>Compose geometry</h3><p>Trace spheres, planes, triangles, and quads through a BVH.</p></article><article class="feature"><span class="num">02</span><h3>Work with materials</h3><p>Use Lambertian, metal, dielectric, emission, and image textures.</p></article><article class="feature"><span class="num">03</span><h3>Render repeatably</h3><p>Deterministic sampling, depth of field, and process workers where fork exists.</p></article></div></section>
24
+ <section class="info try" id="quick-start" aria-labelledby="quick"><div class="try-copy"><p class="kicker">A small first step</p><h2 id="quick">Start with one example.</h2><p>Install the gem, then follow the project guide for platform requirements and the full API.</p><p><a href="https://github.com/rbgfx/caustics#installation">Read the installation guide ↗</a></p></div><pre><code>require &quot;caustics&quot;
25
+
26
+ scene = Caustics::Scene.build do
27
+ sphere center: [0, 0, 0], radius: 0.5,
28
+ material: lambertian([0.8, 0.2, 0.2])
29
+ end
30
+
31
+ Caustics.render(scene, width: 320, height: 180,
32
+ spp: 16, workers: 4).write(&quot;out.png&quot;)</code></pre></section></main>
33
+ <footer><span>Caustics · Ruby 3.1+ · MIT</span><div><a href="https://github.com/rbgfx/caustics">GitHub</a><a href="https://github.com/rbgfx/caustics/blob/main/LICENSE.txt">License</a></div></footer></div></body></html>
data/exe/caustics CHANGED
@@ -6,29 +6,34 @@ require "caustics"
6
6
 
7
7
  command = ARGV.shift
8
8
  unless %w[render bench codegen preview].include?(command)
9
- warn "usage: caustics render|bench|codegen|preview SCENE.rb [--size WxH] [--spp N] [--workers N] [--engine NAME,...] [-o FILE]"
9
+ warn "usage: caustics render|bench|codegen|preview SCENE.rb [--size WxH] [--spp N] [--workers N] [--engine NAME,...] [--progress-gif] [-o FILE]"
10
10
  exit 1
11
11
  end
12
12
 
13
13
  path = ARGV.shift
14
14
  raise ArgumentError, "scene file is required" unless path
15
15
  output = "out.png"
16
+ output_explicit = false
16
17
  size = [400, 225]
17
18
  spp = 4
18
19
  workers = 1
19
20
  seed = 42
20
21
  max_depth = 8
21
22
  engines = [:ruby]
23
+ progress_gif = false
22
24
  OptionParser.new do |options|
23
- options.on("-o FILE") { |value| output = value }
25
+ options.on("-o FILE") { |value| output = value; output_explicit = true }
24
26
  options.on("--size SIZE") { |value| size = value.split("x").map(&:to_i) }
25
27
  options.on("--spp N", Integer) { |value| spp = value }
26
28
  options.on("--workers N", Integer) { |value| workers = value }
27
29
  options.on("--seed N", Integer) { |value| seed = value }
28
30
  options.on("--max-depth N", Integer) { |value| max_depth = value }
29
31
  options.on("--engine NAME") { |value| engines = value.split(",").map { |name| name.to_sym } }
32
+ options.on("--progress-gif") { progress_gif = true }
30
33
  end.parse!(ARGV)
31
34
  raise ArgumentError, "size must be WIDTHxHEIGHT" unless size.length == 2 && size.all?(&:positive?)
35
+ raise ArgumentError, "spp, workers and max-depth must be positive" unless [spp, workers, max_depth].all?(&:positive?)
36
+ raise ArgumentError, "--progress-gif is only valid with render" if progress_gif && command != "render"
32
37
  load path
33
38
  scene = defined?(SCENE) ? SCENE : $scene
34
39
  raise ArgumentError, "scene file must assign SCENE or $scene" unless scene
@@ -36,7 +41,26 @@ raise ArgumentError, "scene file must assign SCENE or $scene" unless scene
36
41
  case command
37
42
  when "render"
38
43
  raise ArgumentError, "render accepts one engine" unless engines.one?
39
- Caustics.render(scene, width: size[0], height: size[1], spp: spp, max_depth: max_depth, workers: workers, seed: seed, engine: engines.first).write(output)
44
+ if progress_gif
45
+ output = "out.gif" unless output_explicit
46
+ raise ArgumentError, "--progress-gif output must end in .gif" unless File.extname(output).downcase == ".gif"
47
+ begin
48
+ require "flipbook"
49
+ rescue LoadError => error
50
+ raise LoadError, "caustics --progress-gif requires the optional flipbook gem", cause: error
51
+ end
52
+ accumulator = Caustics::Accumulator.new(size[0], size[1])
53
+ Flipbook::GIF::Writer.open(output, width: size[0], height: size[1], loop: true, palette: :per_frame) do |writer|
54
+ spp.times do
55
+ sample = Caustics.render(scene, width: size[0], height: size[1], spp: 1, max_depth: max_depth,
56
+ workers: workers, seed: seed + accumulator.samples, engine: engines.first)
57
+ writer.add(accumulator.add(sample), delay: Rational(1, 10))
58
+ end
59
+ end
60
+ else
61
+ Caustics.render(scene, width: size[0], height: size[1], spp: spp, max_depth: max_depth,
62
+ workers: workers, seed: seed, engine: engines.first).write(output)
63
+ end
40
64
  puts output
41
65
  when "bench"
42
66
  results = Caustics.benchmark(scene, engines: engines, width: size[0], height: size[1], spp: spp, max_depth: max_depth)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caustics
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caustics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
- - ydah
7
+ - Yudai Takada
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-09-25 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: tessel
@@ -16,6 +17,9 @@ dependencies:
16
17
  - - ">="
17
18
  - !ruby/object:Gem::Version
18
19
  version: 0.1.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '1'
19
23
  type: :runtime
20
24
  prerelease: false
21
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -23,6 +27,9 @@ dependencies:
23
27
  - - ">="
24
28
  - !ruby/object:Gem::Version
25
29
  version: 0.1.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '1'
26
33
  description: A deterministic pure Ruby ray tracer used as an rbgfx teaching example.
27
34
  email:
28
35
  - t.yudai92@gmail.com
@@ -35,6 +42,7 @@ files:
35
42
  - LICENSE.txt
36
43
  - README.md
37
44
  - Rakefile
45
+ - docs/index.html
38
46
  - docs/spikes.md
39
47
  - exe/caustics
40
48
  - lib/caustics.rb
@@ -48,6 +56,7 @@ licenses:
48
56
  metadata:
49
57
  homepage_uri: https://github.com/rbgfx/caustics
50
58
  source_code_uri: https://github.com/rbgfx/caustics/tree/main
59
+ post_install_message:
51
60
  rdoc_options: []
52
61
  require_paths:
53
62
  - lib
@@ -62,7 +71,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
71
  - !ruby/object:Gem::Version
63
72
  version: '0'
64
73
  requirements: []
65
- rubygems_version: 4.0.16
74
+ rubygems_version: 3.4.19
75
+ signing_key:
66
76
  specification_version: 4
67
77
  summary: A small Ruby path tracer
68
78
  test_files: []