pbrt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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