pbrt 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2c1d00561cc281ba73d41b7eec84aa1b810ab11faa5f9b81e0b327f70cfb766b
4
+ data.tar.gz: 03c1f5222c7920c6d55629ba9ca36190f1a3b9c41548ad2ba37f570e4bef7f30
5
+ SHA512:
6
+ metadata.gz: 7e971e81039dae5d17d12e051cdff6d3c276c17c5c7364ca2f9cb0f78466f326de9ef86359c74b721da285e34506bb3ea3da1ec631064b67455d867ff99c2816
7
+ data.tar.gz: f2818a5c28c5c158e17a56856fd643dd0df422770e8e5c646e78948c60ea0dd76a40c23af465e9503e3b8c7b004df3578891558218751e78b31de2688829b6cb
@@ -0,0 +1,267 @@
1
+ ## PBRT
2
+
3
+ A Ruby gem to generate scene description files for the third edition of [Physically Based Rendering](http://www.pbr-book.org/).
4
+
5
+ This gem implements its [file format specification](https://pbrt.org/fileformat-v3.html) and wraps it in a friendly DSL.
6
+
7
+ ## Overview
8
+
9
+ This gem makes it easier to generate scene description files, because:
10
+ - It provides methods that only accept valid parameter names
11
+ - It knows the types of parameters and automatically adds them for you
12
+ - It closely resembles the structure in the documentation
13
+ - It's easy to script with bits of Ruby
14
+ - It has no dependencies and streams to a plain IO object
15
+
16
+ ## Example
17
+
18
+ This generates [the example](https://pbrt.org/fileformat-v3.html#example) from the documentation:
19
+
20
+ ```ruby
21
+ require "pbrt"
22
+
23
+ builder = PBRT::Builder.new do
24
+ look_at(3, 4, 1.5, 0.5, 0.5, 0, 0, 0, 1)
25
+ camera.perspective(fov: 45)
26
+
27
+ sampler.halton(pixelsamples: 128)
28
+ integrator.path
29
+ film.image(filename: "simple.png", xresolution: 400, yresolution: 400)
30
+
31
+ world_begin do
32
+ comment "uniform blue-ish illumination from all directions"
33
+ light_source.infinite(L: rgb(0.4, 0.45, 0.5))
34
+
35
+ comment "approximate the sun"
36
+ light_source.distant(from: [-30, 40, 100], L: blackbody(3000, 1.5))
37
+
38
+ attribute_begin do
39
+ material.glass
40
+ shape.sphere(radius: 1)
41
+ end
42
+
43
+ attribute_begin do
44
+ texture("checks").spectrum.checkerboard(
45
+ uscale: [8],
46
+ vscale: [8],
47
+ tex1: rgb(0.1, 0.1, 0.1),
48
+ tex2: rgb(0.8, 0.8, 0.8),
49
+ )
50
+
51
+ material.matte(Kd: texture("checks"))
52
+ translate(0, 0, -1)
53
+
54
+ shape.trianglemesh(
55
+ indices: [0, 1, 2, 0, 2, 3],
56
+ P: [-20, -20, 0, 20, -20, 0, 20, 20, 0, -20, 20, 0],
57
+ st: [0, 0, 1, 0, 1, 1, 0, 1],
58
+ )
59
+ end
60
+ end
61
+ end
62
+
63
+ puts builder.to_s
64
+ ```
65
+
66
+ [This](https://pbrt.org/simple.png) is what it looks like when rendered.
67
+
68
+ ## How do I use the gem?
69
+
70
+ As you can see in the example above, the gem has a `PBRT::Builder` that takes a block
71
+ where you can call methods to generate 'directives'. The methods you can call directly
72
+ correspond to the documentation.
73
+
74
+ For example, in the [Transformations section](https://pbrt.org/fileformat-v3.html#transformations)
75
+ there is an 'Identity' directive which you can generate with the `identity` method.
76
+
77
+ There are two kinds of directives:
78
+ - Those that take plain arguments
79
+ - Those that take arguments as named parameter lists
80
+
81
+ For example:
82
+
83
+ ```ruby
84
+ # plain arguments
85
+ translate(1, 2, 3)
86
+
87
+ # parameter list
88
+ shape.sphere(radius: 2, zmin: 0.2, zmax: 0.7)
89
+ ```
90
+
91
+ The majority of directives have 'implementations' such as the 'perspective' implementation
92
+ for the 'Camera' directive or the 'sphere' implementation for the 'Shape' directive. To
93
+ specify this, call the method on the directive:
94
+
95
+ ```ruby
96
+ camera.perspective
97
+ sampler.halton(pixelsamples: 16)
98
+ shape.sphere(radius: 1)
99
+ light_source.spotlight(from: [0, 1, 0], to: [0, 0, 0])
100
+ ```
101
+
102
+ All directives and implementations are specified in the documentation as well as the names
103
+ of parameters and their types. For parameters that have type `point2`, `point3`, `vector2` etc,
104
+ you can pass an array and if the parameter takes several points e.g. `point[4]` you can either
105
+ pass a flat array or group the individual points into sub-arrays:
106
+
107
+ ```ruby
108
+ # flat array
109
+ shape.curve(P: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
110
+
111
+ # sub-arrays
112
+ shape.curve(P: [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]])
113
+ ```
114
+
115
+ There is no difference (other than readability). They will generate the same code. In fact, the
116
+ same is true for any parameter that takes multiple values. For example, the
117
+ [`LookAt` directive](https://pbrt.org/fileformat-v3.html#transformations) takes 9 floats that may
118
+ be grouped into sub-arrays for clarity:
119
+
120
+ ```ruby
121
+ look_at(
122
+ [3, 4, 1.5], # eye
123
+ [0.5, 0.5, 0], # look at point
124
+ [0, 0, 1], # up vector
125
+ )
126
+ ```
127
+
128
+ Some directives come in pairs like `WorldBegin` / `WorldEnd` and `AttributeBegin` / `AttributeEnd`.
129
+ The gem only provides a method for 'begin' and lets you pass a block to specify what goes inside it:
130
+
131
+ ```ruby
132
+ world_begin do
133
+ transform(1, 2, 3)
134
+ sphere.shape
135
+
136
+ attribute_begin do
137
+ # ...
138
+ end
139
+ end
140
+ ```
141
+
142
+ Finally, there are some directives that take a name such as [`Texture`](https://pbrt.org/fileformat-v3.html#textures)
143
+ and [`MakeNamedMaterial`](https://pbrt.org/fileformat-v3.html#materials). In these cases, pass the name in to the
144
+ top-level method before calling others on it.
145
+
146
+ Textures also take a 'class' which is either 'spectrum' or 'float' which can be specified with a chained method call:
147
+
148
+ ```ruby
149
+ texture("mytexture").spectrum.checkerboard(dimension: 2)
150
+ make_named_material("mymaterial").plastic(roughness: 0.1)
151
+ ```
152
+
153
+ If you can't figure out how to call the directive / implementation you want, this gem has an example of every single
154
+ one being called in it's [`builder_spec.rb`](https://github.com/tuzz/pbrt/blob/master/spec/pbrt/builder_spec.rb).
155
+
156
+ ## Spectrums and Textures
157
+
158
+ Some parameters take types that are `spectrum`, `spectrum texture`, `float texture`, `spectrum / float texture`.
159
+
160
+ A spectrum type is used to specify a color spectrum which can be represented in different ways.
161
+ [Parameter Lists](https://pbrt.org/fileformat-v3.html#parameter-lists) section for more details. When a parameter
162
+ has the spectrum type, wrap its arguments with one of the representations:
163
+
164
+ ```ruby
165
+ # rgb
166
+ light_source.point(scale: rgb(0.8, 0.1, 0.1))
167
+
168
+ # xyz
169
+ light_source.point(scale: xyz(0.8, 0.1, 0.1))
170
+
171
+ # sampled
172
+ light_source.point(scale: sampled([300, 0.3, 400, 0.6])
173
+ light_source.point(scale: sampled("filename"))
174
+
175
+ # blackbody
176
+ light_source.point(scale: blackbody(6500, 1))
177
+ ```
178
+
179
+ Occasionally this can be omitted if it can be inferred:
180
+
181
+ ```ruby
182
+ light_source.point(scale: "filename")
183
+ ```
184
+
185
+ But it's usually better to include it to make your intent clearer.
186
+
187
+ A type like `spectrum texture` actually means the parameter accepts either a spectrum or the name of a texture
188
+ that you have created with the 'Texture' directive. In this case, the string "filename" is ambiguous because it could
189
+ be a file containing spectrum sample data, or it could be the name of one of your textures.
190
+
191
+ If things are ambiguous, PBRT will raise an error:
192
+
193
+ ```ruby
194
+ AmbiguousArgumentError:
195
+ Please specify whether "filename" is a spectrum or texture.
196
+ If it's a texture, wrap it with: texture("filename")
197
+ If it's a spectrum, wrap it with its representation: sampled("filename")
198
+ Valid representations are: rgb, xyz, sampled and blackbody
199
+ ```
200
+
201
+ For the `float texture` type, it can always be decided what you meant because a `float` is always a number and a
202
+ `texture` is always a string.
203
+
204
+ There's only one case where the type can be ambiguous and PBRT will not raise an error. If you enter a number for the
205
+ `spectrum / float texture`, PBRT will assume you meant a float rather than a spectrum as floats are much more common, but
206
+ if you really want a spectrum then wrap the argument in one of its representations (e.g. rgb).
207
+
208
+ ## IO / Builder pattern
209
+
210
+ In the example above, a block is being passed to the `PBRT::Builder`, but you can also use this as a more traditional
211
+ builder by calling methods on it:
212
+
213
+ ```ruby
214
+ builder = PBRT::Builder.new
215
+ builder.world_begin do
216
+ builder.translate(1, 2, 3)
217
+ builder.shape.sphere
218
+ end
219
+ ```
220
+
221
+ Builder methods return `self` so you can chain methods if you'd like:
222
+
223
+ ```ruby
224
+ translate(1, 2, 3).shape.sphere(radius: 1)
225
+ ```
226
+
227
+ When a builder is constructed, it takes an IO object that it streams its directives to. You can pass one of your own,
228
+ for example, if you want to stream your directives straight into a file to save on memory usage:
229
+
230
+ ```ruby
231
+ File.open("myscene.pbrt", "w") do |file|
232
+ PBRT::Builder.new(io: file) do
233
+ translate(1, 2, 3)
234
+ shape.sphere(radius: 1)
235
+ end
236
+ end
237
+ ```
238
+
239
+ ## Things this gem won't do
240
+
241
+ This gem does some basic checking of parameter and can infer types for you, but it won't do much beyond that. Specifically:
242
+
243
+ - It won't error if you pass values of the wrong type
244
+ - It won't error if you pass arrays with too many / few values
245
+ - It won't error if you use directives inappropriately, e.g. specifying `LookAt` inside the `WorldBegin` section
246
+ - It won't explain what any of the directives mean or how to use them
247
+
248
+ It would be great if it did some of those things, and I'd happily welcome pull requests to add them.
249
+
250
+ ## Quirks
251
+
252
+ - The 02sequence sampler is called o2sequence because Ruby methods can't begin with a number
253
+ - The texture method can be used for adding a directive as well as disambiguating values
254
+ - Every single material parameter can be used in shape directives because the specification allows it
255
+ - You can actually generate multiple renders for the same scene by adding more `WorldBegin` directives
256
+
257
+ ## Contribution
258
+
259
+ [MIT License](LICENSE)
260
+
261
+ I am in no way affiliated with PBRT or any of its authors.
262
+
263
+ If you find this gem useful, I'd love to hear how you're using it on [Twitter](https://twitter.com/chrispatuzzo) and will happily
264
+ welcome pull requests or suggestions for improvement.
265
+
266
+ I intend to use this on some other projects of my own, so [a search of my GitHub repositories](https://github.com/search?q=user%3Atuzz+pbrt)
267
+ might be useful if you're planning to use it. Good luck.
@@ -0,0 +1,26 @@
1
+ module PBRT
2
+ end
3
+
4
+ require "pbrt/parameter"
5
+ require "pbrt/values"
6
+ require "pbrt/parameter_list"
7
+ require "pbrt/statement"
8
+ require "pbrt/statement/fixed_size"
9
+ require "pbrt/statement/variadic"
10
+ require "pbrt/signature"
11
+ require "pbrt/spectrum"
12
+ require "pbrt/texture"
13
+ require "pbrt/builder"
14
+ require "pbrt/builder/camera"
15
+ require "pbrt/builder/sampler"
16
+ require "pbrt/builder/film"
17
+ require "pbrt/builder/pixel_filter"
18
+ require "pbrt/builder/integrator"
19
+ require "pbrt/builder/accelerator"
20
+ require "pbrt/builder/shape"
21
+ require "pbrt/builder/light_source"
22
+ require "pbrt/builder/area_light_source"
23
+ require "pbrt/builder/material"
24
+ require "pbrt/builder/named_material"
25
+ require "pbrt/builder/texture"
26
+ require "pbrt/builder/named_medium"
@@ -0,0 +1,184 @@
1
+ module PBRT
2
+ class Builder
3
+ attr_accessor :io
4
+
5
+ def initialize(io: StringIO.new, &block)
6
+ self.io = io
7
+
8
+ instance_eval &block if block_given?
9
+ end
10
+
11
+ def to_s
12
+ io.string
13
+ end
14
+
15
+ def camera
16
+ Camera.new(self)
17
+ end
18
+
19
+ def sampler
20
+ Sampler.new(self)
21
+ end
22
+
23
+ def film
24
+ Film.new(self)
25
+ end
26
+
27
+ def pixel_filter
28
+ PixelFilter.new(self)
29
+ end
30
+
31
+ def integrator
32
+ Integrator.new(self)
33
+ end
34
+
35
+ def accelerator
36
+ Accelerator.new(self)
37
+ end
38
+
39
+ def shape
40
+ Shape.new(self)
41
+ end
42
+
43
+ def light_source
44
+ LightSource.new(self)
45
+ end
46
+
47
+ def area_light_source
48
+ AreaLightSource.new(self)
49
+ end
50
+
51
+ def material
52
+ Material.new(self)
53
+ end
54
+
55
+ def make_named_material(name)
56
+ NamedMaterial.new(self, name)
57
+ end
58
+
59
+ def make_named_medium(name)
60
+ NamedMedium.new(self, name)
61
+ end
62
+
63
+ def medium_interface(*args)
64
+ write Statement.fixed_size("MediumInterface", 2, args)
65
+ end
66
+
67
+ def identity
68
+ write Statement.fixed_size("Identity", 0)
69
+ end
70
+
71
+ def translate(*args)
72
+ write Statement.fixed_size("Translate", 3, args)
73
+ end
74
+
75
+ def scale(*args)
76
+ write Statement.fixed_size("Scale", 3, args)
77
+ end
78
+
79
+ def rotate(*args)
80
+ write Statement.fixed_size("Rotate", 4, args)
81
+ end
82
+
83
+ def look_at(*args)
84
+ write Statement.fixed_size("LookAt", 9, args)
85
+ end
86
+
87
+ def coordinate_system(*args)
88
+ write Statement.fixed_size("CoordinateSystem", 1, args)
89
+ end
90
+
91
+ def coord_sys_transform(*args)
92
+ write Statement.fixed_size("CoordSysTransform", 1, args)
93
+ end
94
+
95
+ def transform(*args)
96
+ write Statement.fixed_size("Transform", 16, args)
97
+ end
98
+
99
+ def concat_transform(*args)
100
+ write Statement.fixed_size("ConcatTransform", 16, args)
101
+ end
102
+
103
+ def transform_times(*args)
104
+ write Statement.fixed_size("TransformTimes", 2, args)
105
+ end
106
+
107
+ def active_transform(*args)
108
+ write Statement.fixed_size("ActiveTransform", 1, args)
109
+ end
110
+
111
+ def reverse_orientation(*args)
112
+ write Statement.fixed_size("ReverseOrientation", 0)
113
+ end
114
+
115
+ def world_begin(&block)
116
+ write Statement.fixed_size("WorldBegin", 0)
117
+ instance_eval &block
118
+ write Statement.fixed_size("WorldEnd", 0)
119
+ end
120
+
121
+ def attribute_begin(&block)
122
+ write Statement.fixed_size("AttributeBegin", 0)
123
+ instance_eval &block
124
+ write Statement.fixed_size("AttributeEnd", 0)
125
+ end
126
+
127
+ def transform_begin(&block)
128
+ write Statement.fixed_size("TransformBegin", 0)
129
+ instance_eval &block
130
+ write Statement.fixed_size("TransformEnd", 0)
131
+ end
132
+
133
+ def object_begin(*args, &block)
134
+ write Statement.fixed_size("ObjectBegin", 1, *args)
135
+ instance_eval &block
136
+ write Statement.fixed_size("ObjectEnd", 0)
137
+ end
138
+
139
+ def object_instance(*args)
140
+ write Statement.fixed_size("ObjectInstance", 1, *args)
141
+ end
142
+
143
+ def comment(string)
144
+ write string.split("\n").map { |s| "# #{s}\n" }.join
145
+ end
146
+
147
+ def include(args)
148
+ write Statement.fixed_size("Include", 1, args)
149
+ end
150
+
151
+ def named_material(args)
152
+ write Statement.fixed_size("NamedMaterial", 1, args)
153
+ end
154
+
155
+ def rgb(*args)
156
+ Spectrum.new(:rgb, *args)
157
+ end
158
+
159
+ def color(*args)
160
+ Spectrum.new(:color, *args)
161
+ end
162
+
163
+ def xyz(*args)
164
+ Spectrum.new(:xyz, *args)
165
+ end
166
+
167
+ def sampled(*args)
168
+ Spectrum.new(:spectrum, *args)
169
+ end
170
+
171
+ def blackbody(*args)
172
+ Spectrum.new(:blackbody, *args)
173
+ end
174
+
175
+ def texture(*args)
176
+ PBRT::Texture.new(self, *args)
177
+ end
178
+
179
+ def write(statement)
180
+ io.puts statement
181
+ self
182
+ end
183
+ end
184
+ end