├── .gitignore ├── CMakeLists.txt ├── FILEFORMAT.md ├── LICENSE.md ├── NOTES.md ├── PERFORMANCE.md ├── README.md ├── ROADMAP.md ├── TODO.md ├── main.cpp ├── minipbrt.cpp └── minipbrt.h /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(minipbrt LANGUAGES CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | add_executable(minipbrt 9 | main.cpp 10 | minipbrt.cpp 11 | minipbrt.h 12 | ) 13 | -------------------------------------------------------------------------------- /FILEFORMAT.md: -------------------------------------------------------------------------------- 1 | The PBRT v3 File Format 2 | ======================= 3 | 4 | The canonical reference for the file format is its implementation in 5 | [PBRT v3](https://github.com/mmp/pbrt-v3). 6 | 7 | There is a page documenting the file format here: 8 | 9 | https://www.pbrt.org/fileformat-v3.html 10 | 11 | However there are cases where that document doesn't match the PBRT 12 | implementation. Some details are incorrect, some are missing. Details of those 13 | are below. 14 | 15 | 16 | General parsing 17 | --------------- 18 | 19 | * Brackets are allowed around arg lists, e.g. for Transform the 16 floats can 20 | be enclosed in '[' and ']' characters. 21 | 22 | * PBRT itself doesn't seem to allow forward references to named items, but 23 | some of the scenes in the pbrt-v3-scenes collection do this, specifically 24 | for materials. 25 | 26 | * Errors get logged but usually don't prevent the file from loading. 27 | 28 | 29 | Curve shapes 30 | ------------ 31 | 32 | * The `P` array does not hold 4 points, as the doc suggests. It holds a number 33 | determined by the number of curve segments, with different rules for bezier 34 | and bspline curves. 35 | 36 | * The `N` array holds `degree + 1` normals, not 2 as per the doc. 37 | 38 | 39 | Textures 40 | -------- 41 | 42 | There is an additional texture type, "ptex", which isn't mentioned in the 43 | docs. It has two parameters: 44 | * `"string filename"` - the name of a PTex file on the local disk 45 | * `"float gamma"` - a gamma value to apply to colours from the PTex file. 46 | 47 | Float textures exist in a different namespace to spectrum textures. You can 48 | have a float texture and a spectrum texture both called "foo". PBRT hardcodes 49 | which namespace to do the lookup in for any given parameter. 50 | 51 | 52 | Integrators 53 | ----------- 54 | 55 | * Parameters for the Whitted integrator are not documented. 56 | * PBRT supports two additional integrator types which aren't mentioned in the doc: 57 | * `"volpath"` 58 | * `"ao"` 59 | 60 | 61 | Coordinate Systems 62 | ------------------ 63 | 64 | * The Camera statement defines a coordinate system called "camera" as a side 65 | effect. 66 | 67 | 68 | Objects 69 | ------- 70 | 71 | * ObjectBegin does an implicit AttributeBegin. Likewise ObjectEnd does an 72 | implicit AttributeEnd. 73 | 74 | * ObjectBegin calls cannot be nested. 75 | 76 | * No nested instancing: it's an error to use ObjectInstance inside an 77 | ObjectBegin/End block. 78 | 79 | 80 | Named items 81 | ----------- 82 | 83 | These are all the different things that can be referenced by name: 84 | * Material 85 | * Medium 86 | * Object 87 | * Texture 88 | * Coordinate system 89 | 90 | Material and texture names are scoped to the current AttributeBegin/End block 91 | (ObjectBegin/End counts for this purpose as well). Medium and Object names are 92 | global, as are Coordinate Systems. 93 | 94 | There are no forward references/late binding of any names. Whenever we 95 | encounter a reference, it's resolved to the object it refers to at that time. 96 | If the name is later redefined, this has no effect on any references which 97 | occur before that point. 98 | 99 | Behaviour with undefined names: 100 | * If a NamedMaterial directive refers to a material name that hasn't been 101 | defined yet, PBRT reports an error and leaves the current material unchanged. 102 | * Where a MixMaterial refers to a material name that hasn't been defined yet, 103 | it uses a default-initialised MatteMaterial instead. 104 | * If a material parameter uses a texture name that hasn't been defined yet, it 105 | simply creates an unnamed ConstantTexture with the default value for that 106 | parameter instead. 107 | * If an ObjectInstance directive refers to an object name that hasn't been 108 | defined yet, PBRT reports an error and doesn't add anything to the scene. 109 | * If a CoordSysTransform directive uses a name that hasn't been defined yet, a 110 | warning (not an error) is reported and the current transform is left 111 | unchanged. 112 | * If a MediumInterface refers to an undefined medium name, an error is logged 113 | but parsing continues and the medium is left undefined. 114 | 115 | Redefining names: 116 | * Redefining a texture or material name causes a warning in PBRT, not an error. 117 | * Redefining a medium, object or coordinate system name is silently accepted. 118 | 119 | 120 | Other 121 | ----- 122 | 123 | * There is one case where a `scale` parameter has type `spectrum`, where the 124 | docs say it should be `float`, but I can't remember where that was just now. 125 | 126 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Vilya Harvey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | Notes 2 | ===== 3 | 4 | Post-multiplying by a translation matrix 5 | ---------------------------------------- 6 | 7 | this = 8 | a b c d 9 | e f g h 10 | i j k l 11 | m n o p 12 | 13 | T = 14 | 1 0 0 x 15 | 0 1 0 y 16 | 0 0 1 z 17 | 0 0 0 1 18 | 19 | this * T = 20 | a b c (ax + by + cz + d) 21 | e f g (ex + fy + gz + h) 22 | i j k (ix + jy + kz + l) 23 | m n o (mx + ny + oz + p) 24 | 25 | 26 | Post-multiplying by a scale matrix 27 | ---------------------------------- 28 | 29 | this = 30 | a b c d 31 | e f g h 32 | i j k l 33 | m n o p 34 | 35 | S = 36 | x 0 0 0 37 | 0 y 0 0 38 | 0 0 z 0 39 | 0 0 0 1 40 | 41 | this * S = 42 | ax by cz d 43 | ex fy gz h 44 | ix jy kz l 45 | mx ny oz p 46 | 47 | 48 | Post-multiplying by a rotation matrix 49 | ------------------------------------- 50 | 51 | this = 52 | a b c d 53 | e f g h 54 | i j k l 55 | m n o p 56 | 57 | R = 58 | ra rb rc 0 59 | rd re rf 0 60 | rg rh ri 0 61 | 0 0 0 1 62 | 63 | this * R = 64 | (a.ra + b.rd + c.rg) (a.rb + b.re + c.rh) (a.rc + b.rf + c.ri) d 65 | (e.ra + f.rd + g.rg) (e.rb + f.re + g.rh) (e.rc + f.rf + g.ri) h 66 | (i.ra + j.rd + k.rg) (i.rb + j.re + k.rh) (i.rc + j.rf + k.ri) l 67 | (m.ra + n.rd + o.rg) (m.rb + n.re + o.rh) (m.rc + n.rf + o.ri) p 68 | 69 | ra = ux * ux * (1 - cosTheta) + cosTheta 70 | rb = ux * uy * (1 - cosTheta) - uz * sinTheta 71 | rc = ux * uz * (1 - cosTheta) + uy * sinTheta 72 | 73 | rd = uy * ux * (1 - cosTheta) + uz * sinTheta 74 | re = uy * uy * (1 - cosTheta) + cosTheta 75 | rf = uy * uz * (1 - cosTheta) - ux * sinTheta 76 | 77 | rg = uz * ux * (1 - cosTheta) - uy * sinTheta 78 | rh = uz * uy * (1 - cosTheta) + ux * sinTheta 79 | ri = uz * uz * (1 - cosTheta) + cosTheta 80 | 81 | 82 | Computing a 4x4 Matrix Inverse 83 | ------------------------------ 84 | 85 | The minor of a matrix for row i and column j is what you get when you delete 86 | row i and column j from the matrix. 87 | 88 | Determinant of a 2x2 matrix 89 | 90 | a b 91 | c d 92 | 93 | = `ad - bc` 94 | 95 | 96 | Determinant of a 3x3 matrix 97 | 98 | a00 a01 a02 99 | a10 a11 a12 100 | a20 a21 a22 101 | 102 | = 103 | 104 | a00 * det(minor(a, 0, 0)) - a01 * det(minor(a, 0, 1)) + a02 * det(minor(a, 0, 2)) 105 | 106 | = 107 | 108 | a00 * det(a11 a12 - a01 * det(a10 a12 + a02 * det(a10 a11 109 | a21 a22) a20 a22) a20 a21) 110 | 111 | = 112 | 113 | a00 * (a11 * a22 - a12 * a21) 114 | - a01 * (a10 * a22 - a12 * a20) 115 | + a02 * (a10 * a21 - a11 * a20) 116 | 117 | 118 | 119 | Determinant of a 4x4 matrix a 120 | 121 | a00 a01 a02 a03 122 | a10 a11 a12 a13 123 | a20 a21 a22 a23 124 | a30 a31 a32 a33 125 | 126 | = 127 | 128 | a00 * det(minor(a, 0, 0)) - a01 * det(minor(a, 0, 1)) + a02 * det(minor(a, 0, 2)) - a03 * det(minor(a, 0, 3)) 129 | 130 | = 131 | 132 | a00 * det(a11 a12 a13 - a01 * det(a10 a12 a13 + a02 * det(a10 a11 a13 - a03 * det(a10 a11 a12 133 | a21 a22 a23 a20 a22 a23 a20 a21 a23 a20 a21 a22 134 | a31 a32 a33) a30 a32 a33) a30 a31 a33) a30 a31 a32) 135 | 136 | = 137 | 138 | a00 * (a11 * (a22 * a33 - a23 * a32) - a12 * (a21 * a33 - a23 * a31) + a13 * (a21 * a32 - a22 * a31)) 139 | - a01 * (a10 * (a22 * a33 - a23 * a32) - a12 * (a20 * a33 - a23 * a30) + a13 * (a20 * a32 - a22 * a30)) 140 | + a02 * (a10 * (a21 * a33 - a23 * a31) - a11 * (a20 * a33 - a23 * a30) + a13 * (a20 * a31 - a21 * a30)) 141 | - a03 * (a10 * (a21 * a32 - a22 * a31) - a11 * (a20 * a32 - a22 * a30) + a12 * (a20 * a31 - a21 * a30)) 142 | 143 | 144 | The cofactor matrix for a 4x4 matrix a is 145 | 146 | +det(minor(a, 0, 0)) -det(minor(a, 0, 1)) +det(minor(a, 0, 2)) -det(minor(a, 0, 3)) 147 | -det(minor(a, 1, 0)) +det(minor(a, 1, 1)) -det(minor(a, 1, 2)) +det(minor(a, 1, 3)) 148 | +det(minor(a, 2, 0)) -det(minor(a, 2, 1)) +det(minor(a, 2, 2)) -det(minor(a, 2, 3)) 149 | -det(minor(a, 3, 0)) +det(minor(a, 3, 1)) -det(minor(a, 3, 2)) +det(minor(a, 3, 3)) 150 | 151 | Notice that the determinant of a is simply the sum of the elements in the top row. 152 | 153 | 154 | The "classical adjoint" or "adjugate" is simply the transpose of the cofactor 155 | matrix. The inverse of a 4x4 matrix is the adjugate with every element divided 156 | by the determinant. 157 | 158 | 159 | So inv(a) = 160 | 161 | (1 / det(a)) * 162 | 163 | +det(minor(a, 0, 0)) -det(minor(a, 1, 0)) +det(minor(a, 2, 0)) -det(minor(a, 2, 0)) 164 | -det(minor(a, 0, 1)) +det(minor(a, 1, 1)) -det(minor(a, 2, 1)) +det(minor(a, 2, 1)) 165 | +det(minor(a, 0, 2)) -det(minor(a, 1, 2)) +det(minor(a, 2, 2)) -det(minor(a, 2, 2)) 166 | -det(minor(a, 0, 3)) +det(minor(a, 1, 3)) -det(minor(a, 2, 3)) +det(minor(a, 2, 3)) 167 | 168 | 169 | Denote the minor of a minor of a 4x4 matrix as the 2x2 matrix mab,cd where a 170 | and b are the indices of the rows that remain & c and d are the indices of the 171 | columns that remain. 172 | 173 | For the cofactor matrix c 174 | ``` 175 | c00 = det(minor(a, 0, 0)) 176 | c00 = det(a11 a12 a13 177 | a21 a22 a23 178 | a31 a32 a33) 179 | c00 = a11 * det(m23,23) - a12 * det(m23,13) + a13 * det(m23,12) 180 | ``` 181 | 182 | ``` 183 | c01 = det(minor(a, 0, 1)) 184 | c01 = det(a10 a12 a13 185 | a20 a22 a23 186 | a30 a32 a33) 187 | c01 = a10 * det(m23,23) - a12 * det(m23,03) + a13 * det(m23,02) 188 | ``` 189 | 190 | ``` 191 | c02 = det(minor(a, 0, 2)) 192 | c02 = det(a10 a11 a13 193 | a20 a21 a23 194 | a30 a31 a33) 195 | c02 = a10 * det(m23,13) - a11 * det(m23,03) + a13 * det(m23,01) 196 | ``` 197 | 198 | ``` 199 | c03 = det(minor(a, 0, 3)) 200 | c03 = det(a10 a11 a12 201 | a20 a21 a22 202 | a30 a31 a32) 203 | c03 = a10 * det(m23,12) - a11 * det(m23,02) + a12 * det(m23,01) 204 | ``` 205 | 206 | ``` 207 | c10 = det(minor(a, 1, 0)) 208 | c10 = det(a01 a02 a03 209 | a21 a22 a23 210 | a31 a32 a33) 211 | c10 = a01 * det(m23,23) - a02 * det(m23,13) + a03 * det(m23,12) 212 | ``` 213 | 214 | ``` 215 | c11 = det(minor(a, 1, 1)) 216 | c11 = det(a00 a02 a03 217 | a20 a22 a23 218 | a30 a32 a33) 219 | c11 = a00 * det(m23,23) - a02 * det(m23,03) + a03 * det(m23,02) 220 | ``` 221 | 222 | ``` 223 | c12 = det(minor(a, 1, 2)) 224 | c12 = det(a00 a01 a03 225 | a20 a21 a23 226 | a30 a31 a33) 227 | c12 = a00 * det(m23,13) - a01 * det(m23,03) + a03 * det(m23,01) 228 | ``` 229 | 230 | ``` 231 | c13 = det(minor(a, 1, 3)) 232 | c13 = det(a00 a01 a02 233 | a20 a21 a22 234 | a30 a31 a32) 235 | c13 = a00 * det(m23,12) - a01 * det(m23,02) + a02 * det(m23,01) 236 | ``` 237 | 238 | ``` 239 | c20 = det(minor(a, 2, 0)) 240 | c20 = det(a01 a02 a03 241 | a11 a12 a13 242 | a31 a32 a33) 243 | c20 = a01 * det(m13,23) - a02 * det(m13,13) + a03 * det(m13,12) 244 | ``` 245 | 246 | ``` 247 | c21 = det(minor(a, 2, 1)) 248 | c21 = det(a00 a02 a03 249 | a10 a12 a13 250 | a30 a32 a33) 251 | c21 = a00 * det(m13,23) - a02 * det(m13,03) + a03 * det(m13,02) 252 | ``` 253 | 254 | ``` 255 | c22 = det(minor(a, 2, 2)) 256 | c22 = det(a00 a01 a03 257 | a10 a11 a13 258 | a30 a31 a33) 259 | c22 = a00 * det(m13,13) - a01 * det(m13,03) + a03 * det(m13,01) 260 | ``` 261 | 262 | ``` 263 | c23 = det(minor(a, 2, 3)) 264 | c23 = det(a00 a01 a02 265 | a10 a11 a12 266 | a30 a31 a32) 267 | c23 = a00 * det(m13,12) - a01 * det(m13,02) + a02 * det(m13,01) 268 | ``` 269 | 270 | ``` 271 | c30 = det(minor(a, 3, 0)) 272 | c30 = det(a01 a02 a03 273 | a11 a12 a13 274 | a21 a22 a23) 275 | c30 = a01 * det(m12,23) - a02 * det(m12,13) + a03 * det(m12,12) 276 | ``` 277 | 278 | ``` 279 | c31 = det(minor(a, 3, 1)) 280 | c31 = det(a00 a02 a03 281 | a10 a12 a13 282 | a20 a22 a23) 283 | c31 = a00 * det(m12,23) - a02 * det(m12,03) + a03 * det(m12,02) 284 | ``` 285 | 286 | ``` 287 | c32 = det(minor(a, 3, 2)) 288 | c32 = det(a00 a01 a03 289 | a10 a11 a13 290 | a20 a21 a23) 291 | c32 = a00 * det(m12,13) - a01 * det(m12,03) + a03 * det(m12,01) 292 | ``` 293 | 294 | ``` 295 | c33 = det(minor(a, 3, 3)) 296 | c33 = det(a00 a01 a02 297 | a10 a11 a12 298 | a20 a21 a22) 299 | c33 = a00 * det(m12,12) - a01 * det(m12,02) + a02 * det(m12,01) 300 | ``` 301 | 302 | 303 | So the cofactor matrix entries are: 304 | 305 | c00 = a11 * det(m23,23) - a12 * det(m23,13) + a13 * det(m23,12) 306 | c01 = a10 * det(m23,23) - a12 * det(m23,03) + a13 * det(m23,02) 307 | c02 = a10 * det(m23,13) - a11 * det(m23,03) + a13 * det(m23,01) 308 | c03 = a10 * det(m23,12) - a11 * det(m23,02) + a12 * det(m23,01) 309 | 310 | c10 = a01 * det(m23,23) - a02 * det(m23,13) + a03 * det(m23,12) 311 | c11 = a00 * det(m23,23) - a02 * det(m23,03) + a03 * det(m23,02) 312 | c12 = a00 * det(m23,13) - a01 * det(m23,03) + a03 * det(m23,01) 313 | c13 = a00 * det(m23,12) - a01 * det(m23,02) + a02 * det(m23,01) 314 | 315 | c20 = a01 * det(m13,23) - a02 * det(m13,13) + a03 * det(m13,12) 316 | c21 = a00 * det(m13,23) - a02 * det(m13,03) + a03 * det(m13,02) 317 | c22 = a00 * det(m13,13) - a01 * det(m13,03) + a03 * det(m13,01) 318 | c23 = a00 * det(m13,12) - a01 * det(m13,02) + a02 * det(m13,01) 319 | 320 | c30 = a01 * det(m12,23) - a02 * det(m12,13) + a03 * det(m12,12) 321 | c31 = a00 * det(m12,23) - a02 * det(m12,03) + a03 * det(m12,02) 322 | c32 = a00 * det(m12,13) - a01 * det(m12,03) + a03 * det(m12,01) 323 | c33 = a00 * det(m12,12) - a01 * det(m12,02) + a02 * det(m12,01) 324 | 325 | 326 | If we cache the determinants of the 2x2 matrices: 327 | 328 | A = det(m23,23) 329 | B = det(m23,13) 330 | C = det(m23,12) 331 | D = det(m23,03) 332 | E = det(m23,02) 333 | F = det(m23,01) 334 | G = det(m13,23) 335 | H = det(m13,13) 336 | I = det(m13,12) 337 | J = det(m13,03) 338 | K = det(m13,02) 339 | L = det(m13,01) 340 | M = det(m12,23) 341 | N = det(m12,13) 342 | O = det(m12,12) 343 | P = det(m12,03) 344 | Q = det(m12,02) 345 | R = det(m12,01) 346 | 347 | Then we get: 348 | 349 | c00 = +(a11 * A - a12 * B + a13 * C) 350 | c01 = -(a10 * A - a12 * D + a13 * E) 351 | c02 = +(a10 * B - a11 * D + a13 * F) 352 | c03 = -(a10 * C - a11 * E + a12 * F) 353 | 354 | c10 = -(a01 * A - a02 * B + a03 * C) 355 | c11 = +(a00 * A - a02 * D + a03 * E) 356 | c12 = -(a00 * B - a01 * D + a03 * F) 357 | c13 = +(a00 * C - a01 * E + a02 * F) 358 | 359 | c20 = +(a01 * G - a02 * H + a03 * I) 360 | c21 = -(a00 * G - a02 * J + a03 * K) 361 | c22 = +(a00 * H - a01 * J + a03 * L) 362 | c23 = -(a00 * I - a01 * K + a02 * L) 363 | 364 | c30 = -(a01 * M - a02 * N + a03 * O) 365 | c31 = +(a00 * M - a02 * P + a03 * Q) 366 | c32 = -(a00 * N - a01 * P + a03 * R) 367 | c33 = +(a00 * O - a01 * Q + a02 * R) 368 | 369 | The determinant of the matrix is the dot product of the first row of the 370 | cofactor matrix, which is the first column of the adjugate, with the first row 371 | of the input matrix. 372 | 373 | So to calculate the inverse: 374 | 375 | inv00 = +(a11 * A - a12 * B + a13 * C) 376 | inv01 = -(a01 * A - a02 * B + a03 * C) 377 | inv02 = +(a01 * G - a02 * H + a03 * I) 378 | inv03 = -(a01 * M - a02 * N + a03 * O) 379 | 380 | inv10 = -(a10 * A - a12 * D + a13 * E) 381 | inv11 = +(a00 * A - a02 * D + a03 * E) 382 | inv12 = -(a00 * G - a02 * J + a03 * K) 383 | inv13 = +(a00 * M - a02 * P + a03 * Q) 384 | 385 | inv20 = +(a10 * C - a11 * D + a13 * F) 386 | inv21 = -(a00 * B - a01 * D + a03 * F) 387 | inv22 = +(a00 * H - a01 * J + a03 * L) 388 | inv23 = -(a00 * N - a01 * P + a03 * R) 389 | 390 | inv30 = -(a10 * C - a11 * E + a12 * F) 391 | inv31 = +(a00 * C - a01 * E + a02 * F) 392 | inv32 = -(a00 * I - a01 * K + a02 * L) 393 | inv33 = +(a00 * O - a01 * Q + a02 * R) 394 | 395 | detA = a00 * inv00 + a01 * inv10 + a02 * inv20 + inv30 396 | 397 | inv /= detA 398 | -------------------------------------------------------------------------------- /PERFORMANCE.md: -------------------------------------------------------------------------------- 1 | Performance 2 | =========== 3 | 4 | Test hardware used 5 | ------------------ 6 | 7 | The laptop used for the results below was: 8 | 9 | - Windows 10, v1903 10 | - Intel Core i7-6700HQ CPU, 2.6 GHz 11 | - 16 GB Dual-channel DDR4 RAM 12 | - Samsung MZVPV512 SSD, theoretical peak read speed of 1165 MB/s 13 | 14 | All times reported below are in seconds. 15 | 16 | The times include parsing the PBRT files, constructing the scene 17 | representation and loading any PLY files referenced by it. They do not include 18 | the time taken to destroy the scene representation. 19 | 20 | I used a minipbrt binary compiled with CMake's default Release Build 21 | settings using Visual Studio 2019. 22 | 23 | 24 | Results for all scenes in pbrt-v3-scenes 25 | ---------------------------------------- 26 | 27 | Source: https://www.pbrt.org/scenes-v3.html 28 | 29 | | Scene | Parsed OK? | Time | 30 | | :----------------------------------------------- | :--------: | ---------: | 31 | | barcelona-pavilion/pavilion-day.pbrt | passed | 0.617 secs | 32 | | barcelona-pavilion/pavilion-night.pbrt | passed | 0.608 secs | 33 | | bathroom/bathroom.pbrt | passed | 0.055 secs | 34 | | bmw-m6/bmw-m6.pbrt | passed | 0.070 secs | 35 | | breakfast/breakfast.pbrt | passed | 0.030 secs | 36 | | breakfast/breakfast-lamps.pbrt | passed | 0.028 secs | 37 | | breakfast/f16-8a.pbrt | passed | 0.028 secs | 38 | | breakfast/f16-8b.pbrt | passed | 0.030 secs | 39 | | buddha-fractal/buddha-fractal.pbrt | passed | 0.028 secs | 40 | | bunny-fur/f3-15.pbrt | passed | 1.976 secs | 41 | | caustic-glass/f16-11a.pbrt | passed | 0.026 secs | 42 | | caustic-glass/f16-11b.pbrt | passed | 0.011 secs | 43 | | caustic-glass/f16-9a.pbrt | passed | 0.011 secs | 44 | | caustic-glass/f16-9b.pbrt | passed | 0.011 secs | 45 | | caustic-glass/f16-9c.pbrt | passed | 0.012 secs | 46 | | caustic-glass/glass.pbrt | passed | 0.012 secs | 47 | | chopper-titan/chopper-titan.pbrt | passed | 2.244 secs | 48 | | cloud/cloud.pbrt | passed | 0.028 secs | 49 | | cloud/f15-4a.pbrt | passed | 0.016 secs | 50 | | cloud/f15-4b.pbrt | passed | 0.017 secs | 51 | | cloud/f15-4c.pbrt | passed | 0.017 secs | 52 | | cloud/smoke.pbrt | passed | 0.016 secs | 53 | | coffee-splash/f15-5.pbrt | passed | 0.103 secs | 54 | | coffee-splash/splash.pbrt | passed | 0.023 secs | 55 | | contemporary-bathroom/contemporary-bathroom.pbrt | passed | 0.092 secs | 56 | | crown/crown.pbrt | passed | 4.443 secs | 57 | | dambreak/dambreak0.pbrt | passed | 0.196 secs | 58 | | dambreak/dambreak1.pbrt | passed | 0.255 secs | 59 | | dragon/f11-13.pbrt | passed | 0.391 secs | 60 | | dragon/f11-14.pbrt | passed | 0.364 secs | 61 | | dragon/f14-3.pbrt | passed | 0.190 secs | 62 | | dragon/f14-5.pbrt | passed | 0.363 secs | 63 | | dragon/f15-13.pbrt | passed | 0.364 secs | 64 | | dragon/f8-10.pbrt | passed | 0.187 secs | 65 | | dragon/f8-14a.pbrt | passed | 0.191 secs | 66 | | dragon/f8-14b.pbrt | passed | 0.186 secs | 67 | | dragon/f8-21a.pbrt | passed | 0.187 secs | 68 | | dragon/f8-21b.pbrt | passed | 0.188 secs | 69 | | dragon/f8-24.pbrt | passed | 0.363 secs | 70 | | dragon/f8-4a.pbrt | passed | 0.187 secs | 71 | | dragon/f8-4b.pbrt | passed | 0.185 secs | 72 | | dragon/f9-3.pbrt | passed | 0.186 secs | 73 | | dragon/f9-4.pbrt | passed | 0.188 secs | 74 | | ecosys/ecosys.pbrt | passed | 0.173 secs | 75 | | figures/f10-1ac.pbrt | passed | 0.006 secs | 76 | | figures/f10-1b.pbrt | passed | 0.005 secs | 77 | | figures/f11-15.pbrt | passed | 0.004 secs | 78 | | figures/f3-18.pbrt | passed | 0.005 secs | 79 | | figures/f7-19a.pbrt | passed | 0.005 secs | 80 | | figures/f7-19b.pbrt | passed | 0.005 secs | 81 | | figures/f7-19c.pbrt | passed | 0.005 secs | 82 | | figures/f7-30a.pbrt | passed | 0.005 secs | 83 | | figures/f7-30b.pbrt | passed | 0.005 secs | 84 | | figures/f7-30c.pbrt | passed | 0.006 secs | 85 | | figures/f7-34a.pbrt | passed | 0.005 secs | 86 | | figures/f7-34b.pbrt | passed | 0.004 secs | 87 | | figures/f7-34c.pbrt | passed | 0.005 secs | 88 | | figures/f8-22.pbrt | passed | 0.009 secs | 89 | | ganesha/f3-11.pbrt | passed | 0.515 secs | 90 | | ganesha/ganesha.pbrt | passed | 0.442 secs | 91 | | hair/curly-hair.pbrt | passed | 4.712 secs | 92 | | hair/sphere-hairblock.pbrt | passed | 0.025 secs | 93 | | hair/straight-hair.pbrt | passed | 1.718 secs | 94 | | head/f9-5.pbrt | passed | 0.017 secs | 95 | | head/head.pbrt | passed | 0.008 secs | 96 | | killeroos/killeroo-gold.pbrt | passed | 0.061 secs | 97 | | killeroos/killeroo-moving.pbrt | passed | 0.051 secs | 98 | | killeroos/killeroo-simple.pbrt | passed | 0.009 secs | 99 | | landscape/f4-1.pbrt | passed | 6.439 secs | 100 | | landscape/f6-13.pbrt | passed | 3.561 secs | 101 | | landscape/f6-14.pbrt | passed | 3.546 secs | 102 | | landscape/view-0.pbrt | passed | 3.563 secs | 103 | | landscape/view-1.pbrt | passed | 3.586 secs | 104 | | landscape/view-2.pbrt | passed | 3.519 secs | 105 | | landscape/view-3.pbrt | passed | 3.545 secs | 106 | | landscape/view-4.pbrt | passed | 3.511 secs | 107 | | lte-orb/lte-orb-roughglass.pbrt | passed | 0.052 secs | 108 | | lte-orb/lte-orb-silver.pbrt | passed | 0.046 secs | 109 | | measure-one/frame120.pbrt | passed | 8.163 secs | 110 | | measure-one/frame180.pbrt | passed | 1.156 secs | 111 | | measure-one/frame210.pbrt | passed | 1.099 secs | 112 | | measure-one/frame25.pbrt | passed | 1.047 secs | 113 | | measure-one/frame300.pbrt | passed | 1.129 secs | 114 | | measure-one/frame35.pbrt | passed | 1.127 secs | 115 | | measure-one/frame380.pbrt | passed | 1.028 secs | 116 | | measure-one/frame52.pbrt | passed | 1.239 secs | 117 | | measure-one/frame85.pbrt | passed | 1.018 secs | 118 | | pbrt-book/book.pbrt | passed | 0.027 secs | 119 | | sanmiguel/f10-8.pbrt | passed | 6.190 secs | 120 | | sanmiguel/f16-21a.pbrt | passed | 0.795 secs | 121 | | sanmiguel/f16-21b.pbrt | passed | 0.791 secs | 122 | | sanmiguel/f16-21c.pbrt | passed | 0.801 secs | 123 | | sanmiguel/f6-17.pbrt | passed | 0.902 secs | 124 | | sanmiguel/f6-25.pbrt | passed | 0.813 secs | 125 | | sanmiguel/sanmiguel.pbrt | passed | 0.813 secs | 126 | | sanmiguel/sanmiguel_cam1.pbrt | passed | 0.790 secs | 127 | | sanmiguel/sanmiguel_cam14.pbrt | passed | 0.712 secs | 128 | | sanmiguel/sanmiguel_cam15.pbrt | passed | 0.674 secs | 129 | | sanmiguel/sanmiguel_cam18.pbrt | passed | 0.894 secs | 130 | | sanmiguel/sanmiguel_cam20.pbrt | passed | 0.812 secs | 131 | | sanmiguel/sanmiguel_cam25.pbrt | passed | 0.805 secs | 132 | | sanmiguel/sanmiguel_cam3.pbrt | passed | 0.803 secs | 133 | | sanmiguel/sanmiguel_cam4.pbrt | passed | 0.812 secs | 134 | | simple/anim-bluespheres.pbrt | passed | 0.007 secs | 135 | | simple/buddha.pbrt | passed | 0.117 secs | 136 | | simple/bump-sphere.pbrt | passed | 0.005 secs | 137 | | simple/caustic-proj.pbrt | passed | 0.004 secs | 138 | | simple/dof-dragons.pbrt | passed | 0.334 secs | 139 | | simple/miscquads.pbrt | passed | 0.005 secs | 140 | | simple/room-mlt.pbrt | passed | 0.028 secs | 141 | | simple/room-path.pbrt | passed | 0.016 secs | 142 | | simple/room-sppm.pbrt | passed | 0.017 secs | 143 | | simple/spheres-differentials-texfilt.pbrt | passed | 0.006 secs | 144 | | simple/spotfog.pbrt | passed | 0.005 secs | 145 | | simple/teapot-area-light.pbrt | passed | 0.011 secs | 146 | | simple/teapot-metal.pbrt | passed | 0.013 secs | 147 | | smoke-plume/plume-084.pbrt | passed | 0.290 secs | 148 | | smoke-plume/plume-184.pbrt | passed | 0.329 secs | 149 | | smoke-plume/plume-284.pbrt | passed | 0.347 secs | 150 | | sportscar/f12-19a.pbrt | passed | 0.917 secs | 151 | | sportscar/f12-19b.pbrt | passed | 0.628 secs | 152 | | sportscar/f12-20a.pbrt | passed | 0.624 secs | 153 | | sportscar/f12-20b.pbrt | passed | 0.622 secs | 154 | | sportscar/f7-37a.pbrt | passed | 0.627 secs | 155 | | sportscar/f7-37b.pbrt | passed | 0.621 secs | 156 | | sportscar/sportscar.pbrt | passed | 0.619 secs | 157 | | sssdragon/dragon_10.pbrt | passed | 1.112 secs | 158 | | sssdragon/dragon_250.pbrt | passed | 0.982 secs | 159 | | sssdragon/dragon_50.pbrt | passed | 0.990 secs | 160 | | sssdragon/f15-7.pbrt | passed | 0.998 secs | 161 | | structuresynth/arcsphere.pbrt | passed | 0.024 secs | 162 | | structuresynth/ballpile.pbrt | passed | 0.014 secs | 163 | | structuresynth/metal.pbrt | passed | 0.025 secs | 164 | | structuresynth/microcity.pbrt | passed | 0.032 secs | 165 | | transparent-machines/frame1266.pbrt | passed | 5.404 secs | 166 | | transparent-machines/frame542.pbrt | passed | 1.844 secs | 167 | | transparent-machines/frame675.pbrt | passed | 2.507 secs | 168 | | transparent-machines/frame812.pbrt | passed | 3.108 secs | 169 | | transparent-machines/frame888.pbrt | passed | 3.962 secs | 170 | | tt/tt.pbrt | passed | 1.888 secs | 171 | | veach-bidir/bidir.pbrt | passed | 0.084 secs | 172 | | veach-mis/mis.pbrt | passed | 0.027 secs | 173 | | villa/f16-20a.pbrt | passed | 2.231 secs | 174 | | villa/f16-20b.pbrt | passed | 0.393 secs | 175 | | villa/f16-20c.pbrt | passed | 0.380 secs | 176 | | villa/villa-daylight.pbrt | passed | 0.387 secs | 177 | | villa/villa-lights-on.pbrt | passed | 0.395 secs | 178 | | villa/villa-photons.pbrt | passed | 0.382 secs | 179 | | volume-caustic/caustic.pbrt | passed | 0.007 secs | 180 | | volume-caustic/f16-22a.pbrt | passed | 0.005 secs | 181 | | volume-caustic/f16-22b.pbrt | passed | 0.005 secs | 182 | | vw-van/vw-van.pbrt | passed | 1.036 secs | 183 | | white-room/whiteroom-daytime.pbrt | passed | 1.160 secs | 184 | | white-room/whiteroom-night.pbrt | passed | 0.106 secs | 185 | | yeahright/yeahright.pbrt | passed | 0.034 secs | 186 | 187 | * 132.442 secs total 188 | * 155 passed 189 | * 0 failed 190 | 191 | 192 | Results for all scenes in Benedikt Bitterli's collection 193 | -------------------------------------------------------- 194 | 195 | Source: https://benedikt-bitterli.me/resources/ 196 | 197 | | Scene | Parsed OK? | Time | 198 | | :------------------------------ | :--------: | ---------: | 199 | | ./bathroom/scene.pbrt | passed | 4.837 secs | 200 | | ./bathroom2/scene.pbrt | passed | 0.731 secs | 201 | | ./bedroom/scene.pbrt | passed | 0.994 secs | 202 | | ./car/scene.pbrt | passed | 0.069 secs | 203 | | ./car2/scene.pbrt | passed | 0.082 secs | 204 | | ./classroom/scene.pbrt | passed | 0.402 secs | 205 | | ./coffee/scene.pbrt | passed | 0.020 secs | 206 | | ./cornell-box/scene.pbrt | passed | 0.006 secs | 207 | | ./curly-hair/scene.pbrt | passed | 6.029 secs | 208 | | ./dining-room/scene.pbrt | passed | 0.351 secs | 209 | | ./dragon/scene.pbrt | passed | 0.279 secs | 210 | | ./furball/scene.pbrt | passed | 6.456 secs | 211 | | ./glass-of-water/scene.pbrt | passed | 0.239 secs | 212 | | ./hair-curl/scene.pbrt | passed | 5.942 secs | 213 | | ./house/scene.pbrt | passed | 2.584 secs | 214 | | ./kitchen/scene.pbrt | passed | 2.523 secs | 215 | | ./lamp/scene.pbrt | passed | 0.187 secs | 216 | | ./living-room/scene.pbrt | passed | 0.596 secs | 217 | | ./living-room-2/scene.pbrt | passed | 1.551 secs | 218 | | ./living-room-3/scene.pbrt | passed | 1.610 secs | 219 | | ./material-testball/scene.pbrt | passed | 0.082 secs | 220 | | ./spaceship/scene.pbrt | passed | 0.215 secs | 221 | | ./staircase/scene.pbrt | passed | 4.470 secs | 222 | | ./staircase2/scene.pbrt | passed | 0.163 secs | 223 | | ./straight-hair/scene.pbrt | passed | 3.114 secs | 224 | | ./teapot/scene.pbrt | passed | 0.079 secs | 225 | | ./teapot-full/scene.pbrt | passed | 0.095 secs | 226 | | ./veach-ajar/scene.pbrt | passed | 0.449 secs | 227 | | ./veach-bidir/scene.pbrt | passed | 0.085 secs | 228 | | ./veach-mis/scene.pbrt | passed | 0.006 secs | 229 | | ./volumetric-caustic/scene.pbrt | passed | 0.005 secs | 230 | | ./water-caustic/scene.pbrt | passed | 0.066 secs | 231 | 232 | * 47.744 secs total 233 | * 32 passed 234 | * 0 failed 235 | 236 | 237 | Results for Disney's Moana island scene 238 | --------------------------------------- 239 | 240 | Source: https://www.technology.disneyanimation.com/islandscene 241 | 242 | | Scene | Parsed OK? | Time | 243 | | :------------------------------ | :--------: | -----------: | 244 | | ./pbrt/island.pbrt | passed | 137.763 secs | 245 | | ./pbrt/islandX.pbrt | passed | 140.141 secs | 246 | 247 | * 322.364 secs total 248 | * 2 passed 249 | * 0 failed 250 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | minipbrt - a simple and fast parser for PBRT v3 files 2 | ===================================================== 3 | 4 | minipbrt is a fast and easy-to-use parser for [PBRT v3](https://www.pbrt.org/fileformat-v3.html) 5 | files, written in c++ 11. The entire parser is a single header and cpp file 6 | which you can copy into your own project. The parser creates an in-memory 7 | representation of the scene. 8 | 9 | 10 | Features 11 | -------- 12 | 13 | - *Small*: just a single .h and .cpp file which you can copy into your project. 14 | - *Fast*: parses Disney's enormous Moana island scene (36 GB of data across 15 | 491 files) in just 138 seconds. Many sample scenes take just a few tens of 16 | milliseconds. 17 | - *Complete*: supports the full PBRTv3 file format and can load triangle mesh 18 | data from any valid PLY file (ascii, little endian or big endian). 19 | - *Thread-friendly*: designed so that PLY files can be loaded, or shapes 20 | turned into triangle meshes, on multiple threads; and can be integrated 21 | easily with your own threading system of choice. 22 | - *Cross platform*: works on Windows, macOS and Linux. 23 | - Converts spectrum values to RGB at load time using the CIE XYZ response 24 | curves. 25 | - Utility functions for converting shapes into triangle meshes. 26 | - PLY loading automatically triangulates polygons with more than 3 vertices. 27 | - Gives useful error info if a file fails to parse, including the line and 28 | column number where it failed. 29 | - MIT Licensed. 30 | 31 | 32 | Getting started 33 | --------------- 34 | 35 | - Copy `minipbrt.h` and `minipbrt.cpp` into your project. 36 | - Add `#include ` wherever necessary. 37 | 38 | The CMake file in this directory is just for building the examples. 39 | 40 | 41 | Loading a file 42 | -------------- 43 | 44 | Simply create a `minipbrt::Loader` object and call it's `load()` method, 45 | passing in the name of the file you want to parse. The method will return 46 | `true` if parsing succeeded or `false` if there was an error. 47 | 48 | If parsing succeeded, call `loader.take_scene()` or `loader.borrow_scene()` to 49 | get a pointer to a `minipbrt::Scene` object describing the scene. The 50 | `take_scene` method transfers ownership of the scene object to the caller, 51 | meaning you must delete it yourself when you're finished with it; whereas 52 | `borrow_scene` lets you use the scene object temporarily, but it will be 53 | deleted automatically by the loader's destructor. 54 | 55 | If loading failed, call `loader.error()` to get a `minipbrt::Error` object 56 | describing what went wrong. The object includes the filename, line number and 57 | column number where the error occurred. This will be exact for syntactic 58 | errors, but may give the location of the token immediately *after* the error 59 | for semantic errors. The error object remains owned by the loader, so you 60 | never have to delete it yourself. 61 | 62 | Example code: 63 | ```cpp 64 | minipbrt::Loader loader; 65 | if (loader.load(filename)) { 66 | minipbrt::Scene* scene = loader.take_scene(); 67 | // ... process the scene, then delete it ... 68 | delete scene; 69 | } 70 | else { 71 | // If parsing failed, the parser will have an error object. 72 | const minipbrt::Error* err = loader.error(); 73 | fprintf(stderr, "[%s, line %lld, column %lld] %s\n", 74 | err->filename(), err->line(), err->column(), err->message()); 75 | // Don't delete err, it's still owned by the parser. 76 | } 77 | ``` 78 | 79 | 80 | Loading triangle meshes from external PLY files, single threaded 81 | ---------------------------------------------------------------- 82 | 83 | If you're happy with loading all the PLY files on a single thread, it's a 84 | single method call: 85 | 86 | ```cpp 87 | // Assuming we've already loaded the scene successfully 88 | minipbrt::Scene* scene = /* ... */; 89 | scene->load_all_ply_meshes(); 90 | ``` 91 | 92 | The `load_all_ply_meshes()` method will replace all PLYMesh shapes in the 93 | scenes shape list with corresponding TriangleMesh shapes. This function is 94 | provided as a convenience, but note that it's single-threaded. Some scenes 95 | reference a lot of PLY files and loading them in parallel can give a big speed 96 | up. See below for more info on that. 97 | 98 | 99 | Loading triangle meshes from external PLY files, multi-threaded 100 | --------------------------------------------------------------- 101 | 102 | minipbrt does not provide any built-in multithreaded code, however the 103 | `to_triangle_mesh` method can be safely called from multiple threads so it's 104 | easy to integrate into your own threading system. 105 | 106 | Here's a simple example using `std::thread`: 107 | 108 | ```cpp 109 | // Assuming we've already loaded the scene successfully 110 | minipbrt::Scene* scene = /* ... */; 111 | 112 | std::atomic_uint nextShape(0); 113 | const uint32_t endShape = uint32_t(scene->shapes.size()); 114 | const uint32_t numThreads = std::thread::hardware_concurrency(); 115 | std::vector loaderThreads; 116 | loaderThreads.reserve(numThreads); 117 | for (uint32_t i = 0; i < numThreads; i++) { 118 | loaderThreads.push_back(std::thread([scene, &nextMesh, endMesh]() { 119 | uint32_t shape = nextShape++; 120 | while (shape < endShape) { 121 | if (scene->shapes[shape]->type() == minipbrt::ShapeType::PLYMesh) { 122 | scene->to_triangle_mesh(shape); 123 | } 124 | shape = nextShape++; 125 | } 126 | })); 127 | } 128 | for (std::thread& th : loaderThreads) { 129 | th.join(); 130 | } 131 | ``` 132 | 133 | Note that this example doesn't ensure that all PLY files loaded successfully - 134 | some may have failed to load. A more robust implementation should check for 135 | this, either by checking the return value of `scene->to_triangle_mesh` or by 136 | scanning `scene-shapes` a second time to see whether it still contains any 137 | PLYMesh shapes. 138 | 139 | 140 | Implementation notes 141 | -------------------- 142 | 143 | * The code is C++11. 144 | 145 | * Spectra are always converted to RGB at load time. (This may change in 146 | future; for now it simplifies things to convert them straight away). 147 | 148 | * PLY files are not automatically loaded. Call `Scene::load_all_ply_meshes` to 149 | load all of them (single threaded), or `Scene::to_triangle_mesh` to load an 150 | individual plymesh. You can safely call `Scene::to_triangle_mesh` from 151 | multiple threads, as long as each thread is calling it with a different shape 152 | index. 153 | 154 | * Most material properties can be either a texture or a value. These 155 | properties are represented as structs with type `ColorTex` or `FloatTex`. In 156 | both structs, if the `texture` member is anything other than 157 | `kInvalidTexture`, the texture should be used *instead* of the `value`. 158 | 159 | * Parsing will fail if there's a token which is longer than the input buffer, 160 | like a long string or filename. You can work around this by increasing the 161 | size of the input buffer, although the default (1 MB) should be fine in all 162 | but the most extreme cases. 163 | 164 | 165 | Performance 166 | ----------- 167 | 168 | See PERFORMANCE.md for parsing times on a wide range of input files. 169 | 170 | The [pbrt-parsing-perf](https://github.com/vilya/pbrt-parsing-perf) project 171 | has a detailed performance comparison against pbrt-parser, the only other 172 | open-source PBRT parsing library for C++ that I know of. 173 | 174 | 175 | Feedback, suggestions and bug reports 176 | ------------------------------------- 177 | 178 | GitHub Issues: https://github.com/vilya/minipbrt/issues 179 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | Roadmap 2 | ======= 3 | 4 | v1.0 5 | ---- 6 | 7 | Goal: correct, complete and fast parsing of PBRT v3 files into an in-memory 8 | data structure. 9 | 10 | This includes loading of mesh data from external .ply files and sampled 11 | spectrum data from external .spd files. It also includes converting the 12 | "special" shape types (height field, loop subdiv and nurbs) into triangle 13 | meshes. 14 | 15 | 16 | v2.0 17 | ---- 18 | 19 | Goal: make the in-memory scene representation optional. 20 | 21 | Most programs will - I believe - have their own scene representation and will 22 | only be using ours as an intermediate step to populating theirs from. If we can 23 | provide a way to cut out that intermediate step it will save a lot of time and 24 | memory. 25 | 26 | Refactor the API to separate parsing from scene building: 27 | * Parser invokes callbacks on a user-provided object. 28 | * The scene builder implements the callback interface and builds the scene from 29 | it. 30 | * Expose the PLY loading code so that users can extract arbitrary data from 31 | the file. 32 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | TO DO 2 | ===== 3 | 4 | Features 5 | -------- 6 | 7 | Expose the API for extracting data from PLY files to end users. 8 | 9 | Add functions for converting other shapes into a TriangleMesh: 10 | * [ ] Cone 11 | * [ ] Curve 12 | * [ ] Cylinder 13 | * [ ] Disk 14 | * [ ] Hyperboloid 15 | * [ ] Paraboloid 16 | * [ ] Sphere 17 | 18 | Strict mode vs. permissive mode 19 | * Strict mode errors on unknown statements/parameters, permissive mode ignores 20 | them. 21 | * Both still error on malformed tokens, missing args & required params, etc. 22 | 23 | Callback-based parsing interface 24 | * For use if you have your own scene data model that you want to populate and 25 | don't want to create our data model as an intermediate step. 26 | * Separate the existing scene construction code into a SceneBuilder class, 27 | Parser should only invoke callbacks. SceneBuilder should be the default 28 | callback handler. 29 | 30 | 31 | Performance 32 | ----------- 33 | 34 | Reduce memory usage while parsing large attribute values, e.g. the vertex and 35 | index arrays for a large triangle mesh. 36 | 37 | Reduce memory usage for the in-memory scene representation: 38 | - Transforms are represented by a pair of matrices, but we only need one 39 | matrix if the scene isn't animated. 40 | - Many transform matrices will have a bottom row of 0,0,0,1 - perhaps can we use a 4x3 matrix instead, for those cases? 41 | - Try using a packed (compressed?) representation, with decoding done in 42 | accessor methods? 43 | - Use a disk cache. 44 | 45 | Reduce overall number of memory allocations. 46 | - Parsing itself doesn't do very many, but constructing the scene representation does a lot. 47 | 48 | Improve IO performance: 49 | - Use a background thread and/or async I/O calls to load the next buffer while 50 | we're parsing the current buffer. 51 | 52 | 53 | Code structure 54 | -------------- 55 | 56 | Factor out checks for required params into a function. 57 | 58 | `pbrtinfo` example 59 | * Rename main.cpp to pbrtinfo.cpp & move into an `examples` directory. 60 | 61 | Code clean-up: 62 | * Merge the tokenizer class into the parser class. 63 | * Move as much as possible out of the header and into the cpp file. 64 | * Make `set_error()` return bool so it can be used as part of a return statement. 65 | * Remove any unused code. 66 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Vilya Harvey 2 | #include "minipbrt.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace minipbrt { 13 | 14 | // 15 | // Forward declarations 16 | // 17 | 18 | static void print_error(const Error* err); 19 | static void print_scene_info(const Scene* scene); 20 | 21 | static void print_accelerator(const Accelerator* accel); 22 | static void print_camera(const Camera* camera); 23 | static void print_film(const Film* film); 24 | static void print_filter(const Filter* filter); 25 | static void print_integrator(const Integrator* integrator); 26 | static void print_sampler(const Sampler* sampler); 27 | 28 | static void print_world_summary(const Scene* scene); 29 | static void print_shapes_summary(const Scene* scene); 30 | static void print_lights_summary(const Scene* scene); 31 | static void print_area_lights_summary(const Scene* scene); 32 | static void print_materials_summary(const Scene* scene); 33 | static void print_textures_summary(const Scene* scene); 34 | static void print_mediums_summary(const Scene* scene); 35 | 36 | static void print_triangle_mesh_summary(const Scene* scene); 37 | 38 | static uint64_t triangle_mesh_bytes(const TriangleMesh* trimesh); 39 | 40 | 41 | // 42 | // Timer class 43 | // 44 | 45 | class Timer { 46 | public: 47 | Timer(bool autostart=false); 48 | 49 | void start(); 50 | void stop(); 51 | 52 | double elapsedSecs() const; 53 | 54 | private: 55 | std::chrono::high_resolution_clock::time_point _start; 56 | std::chrono::high_resolution_clock::time_point _stop; 57 | bool _running = false; 58 | }; 59 | 60 | 61 | Timer::Timer(bool autostart) 62 | { 63 | if (autostart) { 64 | start(); 65 | } 66 | } 67 | 68 | 69 | void Timer::start() 70 | { 71 | _start = _stop = std::chrono::high_resolution_clock::now(); 72 | _running = true; 73 | } 74 | 75 | 76 | void Timer::stop() 77 | { 78 | if (_running) { 79 | _stop = std::chrono::high_resolution_clock::now(); 80 | _running = false; 81 | } 82 | } 83 | 84 | 85 | double Timer::elapsedSecs() const 86 | { 87 | std::chrono::duration secs = 88 | (_running ? std::chrono::high_resolution_clock::now() : _stop) - _start; 89 | return secs.count(); 90 | } 91 | 92 | 93 | // 94 | // Helper functions 95 | // 96 | 97 | template 98 | const char* lookup(const char* values[], T enumVal) 99 | { 100 | return values[static_cast(enumVal)]; 101 | } 102 | 103 | 104 | static uint32_t num_digits(uint32_t n) 105 | { 106 | uint32_t numDigits = 0; 107 | do { 108 | n /= 10; 109 | ++numDigits; 110 | } while (n > 0); 111 | return numDigits; 112 | } 113 | 114 | 115 | template 116 | static void histogram_by_type(const std::vector& values, uint32_t n, uint32_t histogram[]) 117 | { 118 | for (uint32_t i = 0; i < n; i++) { 119 | histogram[i] = 0; 120 | } 121 | for (const T* value : values) { 122 | histogram[static_cast(value->type())]++; 123 | } 124 | } 125 | 126 | 127 | static void print_histogram(uint32_t n, const uint32_t histogram[], const char* names[]) 128 | { 129 | uint32_t maxDigits = 1; 130 | for (uint32_t i = 0; i < n; i++) { 131 | uint32_t numDigits = num_digits(histogram[i]); 132 | if (numDigits > maxDigits) { 133 | maxDigits = numDigits; 134 | } 135 | } 136 | 137 | for (uint32_t i = 0; i < n; i++) { 138 | if (histogram[i] > 0) { 139 | printf("%*u %s\n", maxDigits, histogram[i], names[i]); 140 | } 141 | } 142 | 143 | printf("\n"); 144 | } 145 | 146 | 147 | template 148 | static void print_histogram_by_type(const std::vector& values, const char* names[]) 149 | { 150 | uint32_t histogram[N]; 151 | histogram_by_type(values, N, histogram); 152 | print_histogram(N, histogram, names); 153 | } 154 | 155 | 156 | // 157 | // Functions 158 | // 159 | 160 | static void print_error(const Error* err) 161 | { 162 | if (err == nullptr) { 163 | fprintf(stderr, "Parsing failed but the Error object was null.\n"); 164 | return; 165 | } 166 | 167 | fprintf(stderr, "[%s, line %u, column %u] %s\n", 168 | err->filename(), 169 | uint32_t(err->line()), 170 | uint32_t(err->column()), 171 | err->message()); 172 | } 173 | 174 | 175 | static void print_scene_info(const Scene* scene) 176 | { 177 | if (scene->accelerator) { 178 | print_accelerator(scene->accelerator); 179 | } 180 | if (scene->camera) { 181 | print_camera(scene->camera); 182 | } 183 | if (scene->film) { 184 | print_film(scene->film); 185 | } 186 | if (scene->filter) { 187 | print_filter(scene->filter); 188 | } 189 | if (scene->integrator) { 190 | print_integrator(scene->integrator); 191 | } 192 | if (scene->sampler) { 193 | print_sampler(scene->sampler); 194 | } 195 | if (scene->outsideMedium) { 196 | printf("Outside medium is \"%s\"\n", scene->outsideMedium->mediumName); 197 | } 198 | 199 | print_world_summary(scene); 200 | 201 | print_shapes_summary(scene); 202 | print_lights_summary(scene); 203 | print_area_lights_summary(scene); 204 | print_materials_summary(scene); 205 | print_textures_summary(scene); 206 | print_mediums_summary(scene); 207 | print_triangle_mesh_summary(scene); 208 | } 209 | 210 | 211 | static void print_accelerator(const Accelerator* accel) 212 | { 213 | const char* accelTypes[] = { "bvh", "kdtree" }; 214 | printf("==== Accelerator [%s] ====\n", lookup(accelTypes, accel->type())); 215 | switch (accel->type()) { 216 | case AcceleratorType::BVH: 217 | { 218 | const char* splitMethods[] = { "sah", "middle", "equal", "hlbvh" }; 219 | const BVHAccelerator* bvh = dynamic_cast(accel); 220 | printf("maxnodeprims = %d\n", bvh->maxnodeprims); 221 | printf("splitmethod = \"%s\"\n", lookup(splitMethods, bvh->splitmethod)); 222 | } 223 | break; 224 | case AcceleratorType::KdTree: 225 | { 226 | const KdTreeAccelerator* kdtree = dynamic_cast(accel); 227 | printf("intersectcost = %d\n", kdtree->intersectcost); 228 | printf("traversalcost = %d\n", kdtree->traversalcost); 229 | printf("emptybonus = %f\n", kdtree->emptybonus); 230 | printf("maxprims = %d\n", kdtree->maxprims); 231 | printf("maxdepth = %d\n", kdtree->maxdepth); 232 | } 233 | break; 234 | } 235 | printf("\n"); 236 | } 237 | 238 | 239 | static void print_camera(const Camera* camera) 240 | { 241 | const char* cameraTypes[] = { "perspective", "orthographic", "environment", "realistic" }; 242 | printf("==== Camera [%s] ====\n", lookup(cameraTypes, camera->type())); 243 | printf("shutteropen = %f\n", camera->shutteropen); 244 | printf("shutterclose = %f\n", camera->shutterclose); 245 | switch (camera->type()) { 246 | case CameraType::Perspective: 247 | { 248 | const PerspectiveCamera* typedCam = dynamic_cast(camera); 249 | printf("frameaspectratio = %f\n", typedCam->frameaspectratio); 250 | printf("screenwindow = [ %f, %f, %f, %f ]\n", typedCam->screenwindow[0], typedCam->screenwindow[1], typedCam->screenwindow[2], typedCam->screenwindow[3]); 251 | printf("lensradius = %f\n", typedCam->lensradius); 252 | printf("focaldistance = %f\n", typedCam->focaldistance); 253 | printf("fov = %f\n", typedCam->fov); 254 | printf("halffov = %f\n", typedCam->halffov); 255 | } 256 | break; 257 | case CameraType::Orthographic: 258 | { 259 | const OrthographicCamera* typedCam = dynamic_cast(camera); 260 | printf("frameaspectratio = %f\n", typedCam->frameaspectratio); 261 | printf("screenwindow = [ %f, %f, %f, %f ]\n", typedCam->screenwindow[0], typedCam->screenwindow[1], typedCam->screenwindow[2], typedCam->screenwindow[3]); 262 | printf("lensradius = %f\n", typedCam->lensradius); 263 | printf("focaldistance = %f\n", typedCam->focaldistance); 264 | } 265 | break; 266 | case CameraType::Environment: 267 | { 268 | const EnvironmentCamera* typedCam = dynamic_cast(camera); 269 | printf("frameaspectratio = %f\n", typedCam->frameaspectratio); 270 | printf("screenwindow = [ %f, %f, %f, %f ]\n", typedCam->screenwindow[0], typedCam->screenwindow[1], typedCam->screenwindow[2], typedCam->screenwindow[3]); 271 | } 272 | break; 273 | case CameraType::Realistic: 274 | { 275 | const RealisticCamera* typedCam = dynamic_cast(camera); 276 | printf("lensfile = \"%s\"\n", typedCam->lensfile); 277 | printf("aperturediameter = %f\n", typedCam->aperturediameter); 278 | printf("focusdistance = %f\n", typedCam->focusdistance); 279 | printf("simpleweighting = %s\n", typedCam->simpleweighting ? "true" : "false"); 280 | } 281 | break; 282 | } 283 | printf("\n"); 284 | } 285 | 286 | 287 | static void print_film(const Film* film) 288 | { 289 | const char* filmTypes[] = { "image" }; 290 | printf("==== Film [%s] ====\n", lookup(filmTypes, film->type())); 291 | switch(film->type()) { 292 | case FilmType::Image: 293 | { 294 | const ImageFilm* imagefilm = dynamic_cast(film); 295 | printf("xresolution = %d\n", imagefilm->xresolution); 296 | printf("yresolution = %d\n", imagefilm->yresolution); 297 | printf("cropwindow = [ %f, %f, %f, %f ]\n", imagefilm->cropwwindow[0], imagefilm->cropwwindow[1], imagefilm->cropwwindow[2], imagefilm->cropwwindow[3]); 298 | printf("scale = %f\n", imagefilm->scale); 299 | printf("maxsampleluminance = %f\n", imagefilm->maxsampleluminance); 300 | printf("diagonal = %f mm\n", imagefilm->diagonal); 301 | printf("filename = %s\n", imagefilm->filename); 302 | } 303 | break; 304 | } 305 | 306 | printf("\n"); 307 | } 308 | 309 | 310 | static void print_filter(const Filter* filter) 311 | { 312 | const char* filterTypes[] = { "box", "gaussian", "mitchell", "sinc", "triangle" }; 313 | printf("==== Filter [%s] ====\n", lookup(filterTypes, filter->type())); 314 | printf("xwidth = %f\n", filter->xwidth); 315 | printf("ywidth = %f\n", filter->ywidth); 316 | switch (filter->type()) { 317 | case FilterType::Box: 318 | break; 319 | case FilterType::Gaussian: 320 | { 321 | const GaussianFilter* gaussian = dynamic_cast(filter); 322 | printf("alpha = %f\n", gaussian->alpha); 323 | } 324 | break; 325 | case FilterType::Mitchell: 326 | { 327 | const MitchellFilter* mitchell = dynamic_cast(filter); 328 | printf("B = %f\n", mitchell->B); 329 | printf("C = %f\n", mitchell->C); 330 | } 331 | break; 332 | case FilterType::Sinc: 333 | { 334 | const SincFilter* sinc = dynamic_cast(filter); 335 | printf("tau = %f\n", sinc->tau); 336 | } 337 | break; 338 | case FilterType::Triangle: 339 | break; 340 | } 341 | printf("\n"); 342 | } 343 | 344 | 345 | static void print_integrator(const Integrator* integrator) 346 | { 347 | static const char* integratorTypes[] = { "bdpt", "directlighting", "mlt", "path", "sppm", "whitted", "volpath", "ambientocclusion", nullptr }; 348 | static const char* lightSampleStrategies[] = { "uniform", "power", "spatial", nullptr }; 349 | static const char* directLightSampleStrategies[] = { "uniform", "power", "spatial", nullptr }; 350 | 351 | printf("==== Integrator [%s] ====\n", lookup(integratorTypes, integrator->type())); 352 | switch (integrator->type()) { 353 | case IntegratorType::BDPT: 354 | { 355 | const BDPTIntegrator* typed = dynamic_cast(integrator); 356 | printf("maxdepth = %d\n", typed->maxdepth); 357 | printf("pixelbounds = [ %d, %d, %d, %d ]\n", typed->pixelbounds[0], typed->pixelbounds[1], typed->pixelbounds[2], typed->pixelbounds[3]); 358 | printf("lightsamplestrategy = %s\n", lookup(lightSampleStrategies, typed->lightsamplestrategy)); 359 | printf("visualizestrategies = %s\n", typed->visualizestrategies ? "true" : "false"); 360 | printf("visualizeweights = %s\n", typed->visualizeweights ? "true" : "false"); 361 | } 362 | break; 363 | case IntegratorType::DirectLighting: 364 | { 365 | const DirectLightingIntegrator* typed = dynamic_cast(integrator); 366 | printf("strategy = %s\n", lookup(directLightSampleStrategies, typed->strategy)); 367 | printf("maxdepth = %d\n", typed->maxdepth); 368 | printf("pixelbounds = [ %d, %d, %d, %d ]\n", typed->pixelbounds[0], typed->pixelbounds[1], typed->pixelbounds[2], typed->pixelbounds[3]); 369 | } 370 | break; 371 | case IntegratorType::MLT: 372 | { 373 | const MLTIntegrator* typed = dynamic_cast(integrator); 374 | printf("maxdepth = %d\n", typed->maxdepth); 375 | printf("bootstrapsamples = %d\n", typed->bootstrapsamples); 376 | printf("chains = %d\n", typed->chains); 377 | printf("mutationsperpixel = %d\n", typed->mutationsperpixel); 378 | printf("largestprobability = %f\n", typed->largestprobability); 379 | printf("sigma = %f\n", typed->sigma); 380 | } 381 | break; 382 | case IntegratorType::Path: 383 | { 384 | const PathIntegrator* typed = dynamic_cast(integrator); 385 | printf("maxdepth = %d\n", typed->maxdepth); 386 | printf("pixelbounds = [ %d, %d, %d, %d ]\n", typed->pixelbounds[0], typed->pixelbounds[1], typed->pixelbounds[2], typed->pixelbounds[3]); 387 | printf("rrthreshold = %f\n", typed->rrthreshold); 388 | printf("lightsamplestrategy = %s\n", lookup(lightSampleStrategies, typed->lightsamplestrategy)); 389 | } 390 | break; 391 | case IntegratorType::SPPM: 392 | { 393 | const SPPMIntegrator* typed = dynamic_cast(integrator); 394 | printf("maxdepth = %d\n", typed->maxdepth); 395 | printf("maxiterations = %d\n", typed->maxiterations); 396 | printf("photonsperiteration = %d\n", typed->photonsperiteration); 397 | printf("imagewritefrequency = %d\n", typed->imagewritefrequency); 398 | printf("radius = %f\n", typed->radius); 399 | } 400 | break; 401 | case IntegratorType::Whitted: 402 | { 403 | const WhittedIntegrator* typed = dynamic_cast(integrator); 404 | printf("maxdepth = %d\n", typed->maxdepth); 405 | printf("pixelbounds = [ %d, %d, %d, %d ]\n", typed->pixelbounds[0], typed->pixelbounds[1], typed->pixelbounds[2], typed->pixelbounds[3]); 406 | } 407 | break; 408 | case IntegratorType::VolPath: 409 | { 410 | const VolPathIntegrator* typed = dynamic_cast(integrator); 411 | printf("maxdepth = %d\n", typed->maxdepth); 412 | printf("pixelbounds = [ %d, %d, %d, %d ]\n", typed->pixelbounds[0], typed->pixelbounds[1], typed->pixelbounds[2], typed->pixelbounds[3]); 413 | printf("rrthreshold = %f\n", typed->rrthreshold); 414 | printf("lightsamplestrategy = %s\n", lookup(lightSampleStrategies, typed->lightsamplestrategy)); 415 | } 416 | break; 417 | case IntegratorType::AO: 418 | { 419 | const AOIntegrator* typed = dynamic_cast(integrator); 420 | printf("pixelbounds = [ %d, %d, %d, %d ]\n", typed->pixelbounds[0], typed->pixelbounds[1], typed->pixelbounds[2], typed->pixelbounds[3]); 421 | printf("cossample = %s\n", typed->cossample ? "true" : "false"); 422 | printf("nsamples = %d\n", typed->nsamples); 423 | } 424 | break; 425 | } 426 | 427 | printf("\n"); 428 | } 429 | 430 | 431 | static void print_sampler(const Sampler* sampler) 432 | { 433 | static const char* samplerTypes[] = { "02sequence", "lowdiscrepancy", "halton", "maxmindist", "random", "sobol", "stratified", nullptr }; 434 | 435 | printf("==== Sampler [%s] ====\n", lookup(samplerTypes, sampler->type())); 436 | switch (sampler->type()) { 437 | case SamplerType::ZeroTwoSequence: 438 | case SamplerType::LowDiscrepancy: 439 | { 440 | const ZeroTwoSequenceSampler* typed = dynamic_cast(sampler); 441 | printf("pixelsamples = %d\n", typed->pixelsamples); 442 | } 443 | break; 444 | case SamplerType::Halton: 445 | { 446 | const HaltonSampler* typed = dynamic_cast(sampler); 447 | printf("pixelsamples = %d\n", typed->pixelsamples); 448 | } 449 | break; 450 | case SamplerType::MaxMinDist: 451 | { 452 | const MaxMinDistSampler* typed = dynamic_cast(sampler); 453 | printf("pixelsamples = %d\n", typed->pixelsamples); 454 | } 455 | break; 456 | case SamplerType::Random: 457 | { 458 | const RandomSampler* typed = dynamic_cast(sampler); 459 | printf("pixelsamples = %d\n", typed->pixelsamples); 460 | } 461 | break; 462 | case SamplerType::Sobol: 463 | { 464 | const SobolSampler* typed = dynamic_cast(sampler); 465 | printf("pixelsamples = %d\n", typed->pixelsamples); 466 | } 467 | break; 468 | case SamplerType::Stratified: 469 | { 470 | const StratifiedSampler* typed = dynamic_cast(sampler); 471 | printf("jitter = %s\n", typed->jitter ? "true" : "false"); 472 | printf("xsamples = %d\n", typed->xsamples); 473 | printf("ysamples = %d\n", typed->ysamples); 474 | } 475 | break; 476 | } 477 | 478 | printf("\n"); 479 | } 480 | 481 | 482 | static void print_world_summary(const Scene* scene) 483 | { 484 | constexpr uint32_t N = 8; 485 | uint32_t counts[N] = { 486 | static_cast(scene->shapes.size()), 487 | static_cast(scene->objects.size()), 488 | static_cast(scene->instances.size()), 489 | static_cast(scene->lights.size()), 490 | static_cast(scene->areaLights.size()), 491 | static_cast(scene->materials.size()), 492 | static_cast(scene->textures.size()), 493 | static_cast(scene->mediums.size()), 494 | }; 495 | const char* names[N] = { 496 | "shapes", 497 | "objects", 498 | "instances", 499 | "lights", 500 | "area lights", 501 | "materials", 502 | "textures", 503 | "mediums", 504 | }; 505 | printf("==== World Summary ====\n"); 506 | print_histogram(N, counts, names); 507 | } 508 | 509 | 510 | static void print_shapes_summary(const Scene* scene) 511 | { 512 | if (scene->shapes.empty()) { 513 | return; 514 | } 515 | constexpr uint32_t N = static_cast(ShapeType::PLYMesh) + 1; 516 | const char* names[] = { "cones", "curves", "cylinders", "disks", "hyperboloids", "paraboloids", "spheres", "trianglemeshes", "heightfields", "loopsubdivs", "nurbses", "plymeshes", nullptr }; 517 | printf("==== Shape Types ====\n"); 518 | print_histogram_by_type(scene->shapes, names); 519 | } 520 | 521 | 522 | static void print_lights_summary(const Scene* scene) 523 | { 524 | 525 | if (scene->lights.empty()) { 526 | return; 527 | } 528 | constexpr uint32_t N = static_cast(LightType::Spot) + 1; 529 | const char* names[] = { "distant", "goniometric", "infinite", "point", "projection", "spot", nullptr }; 530 | printf("==== Light Types ====\n"); 531 | print_histogram_by_type(scene->lights, names); 532 | } 533 | 534 | 535 | static void print_area_lights_summary(const Scene* scene) 536 | { 537 | if (scene->areaLights.empty()) { 538 | return; 539 | } 540 | constexpr uint32_t N = static_cast(AreaLightType::Diffuse) + 1; 541 | const char* names[] = { "diffuse", nullptr }; 542 | printf("==== Area Light Types ====\n"); 543 | print_histogram_by_type(scene->areaLights, names); 544 | } 545 | 546 | 547 | static void print_materials_summary(const Scene* scene) 548 | { 549 | if (scene->materials.empty()) { 550 | return; 551 | } 552 | constexpr uint32_t N = static_cast(MaterialType::Uber) + 1; 553 | const char* names[] = { "disney", "fourier", "glass", "hair", "kdsubsurface", "matte", "metal", "mirror", "mix", "none", "plastic", "substrate", "subsurface", "translucent", "uber", nullptr }; 554 | printf("==== Material Types ====\n"); 555 | print_histogram_by_type(scene->materials, names); 556 | } 557 | 558 | 559 | static void print_textures_summary(const Scene* scene) 560 | { 561 | if (scene->textures.empty()) { 562 | return; 563 | } 564 | constexpr uint32_t N = static_cast(TextureType::PTex) + 1; 565 | const char* names[] = { "bilerp", "checkerboard", "constant", "dots", "fbm", "imagemap", "marble", "mix", "scale", "uv", "windy", "wrinkled", "ptex", nullptr }; 566 | printf("==== Texture Types ====\n"); 567 | print_histogram_by_type(scene->textures, names); 568 | } 569 | 570 | 571 | static void print_mediums_summary(const Scene* scene) 572 | { 573 | if (scene->mediums.empty()) { 574 | return; 575 | } 576 | constexpr uint32_t N = static_cast(MediumType::Heterogeneous) + 1; 577 | const char* names[] = { "homogeneous", "heterogeneous", nullptr }; 578 | printf("==== Medium Types ====\n"); 579 | print_histogram_by_type(scene->mediums, names); 580 | } 581 | 582 | 583 | static void print_triangle_mesh_summary(const Scene* scene) 584 | { 585 | uint32_t numMeshes = 0; 586 | 587 | uint64_t totalTris = 0; 588 | uint64_t totalVerts = 0; 589 | uint64_t totalBytes = 0; 590 | 591 | std::vector vertCounts; 592 | std::vector triCounts; 593 | std::vector byteCounts; 594 | 595 | vertCounts.reserve(scene->shapes.size()); 596 | triCounts.reserve(scene->shapes.size()); 597 | byteCounts.reserve(scene->shapes.size()); 598 | 599 | for (size_t i = 0, endI = scene->shapes.size(); i < endI; i++) { 600 | if (scene->shapes[i]->type() != ShapeType::TriangleMesh) { 601 | continue; 602 | } 603 | 604 | ++numMeshes; 605 | 606 | const TriangleMesh* trimesh = dynamic_cast(scene->shapes[i]); 607 | uint32_t meshTris = trimesh->num_indices / 3; 608 | uint64_t meshBytes = triangle_mesh_bytes(trimesh); 609 | 610 | totalTris += meshTris; 611 | totalVerts += trimesh->num_vertices; 612 | totalBytes += meshBytes; 613 | 614 | vertCounts.push_back(trimesh->num_vertices); 615 | triCounts.push_back(meshTris); 616 | byteCounts.push_back(meshBytes); 617 | } 618 | 619 | if (numMeshes == 0) { 620 | return; 621 | } 622 | 623 | if (numMeshes > 1) { 624 | std::sort(triCounts.begin(), triCounts.end()); 625 | std::sort(vertCounts.begin(), vertCounts.end()); 626 | std::sort(byteCounts.begin(), byteCounts.end()); 627 | } 628 | 629 | const uint32_t kPrefixCounts = 5; 630 | const uint32_t kSuffixCounts = kPrefixCounts; 631 | bool abbreviate = (numMeshes > (kPrefixCounts + kSuffixCounts + 1)); 632 | 633 | printf("==== Triangle Mesh Info ====\n"); 634 | printf("\n"); 635 | 636 | uint32_t countDigits = uint32_t(ceil(log10(double(triCounts.back())))); 637 | printf("Triangle counts:\n"); 638 | printf("- Min: %*u\n", countDigits, triCounts.front()); 639 | printf("- Max: %*u\n", countDigits, triCounts.back()); 640 | printf("- Median: %*u\n", countDigits, triCounts[numMeshes / 2]); 641 | printf("- Mean: %*.1lf\n", countDigits + 2, double(totalTris) / double(numMeshes)); 642 | printf("- Counts:\n"); 643 | if (abbreviate) { 644 | for (uint32_t i = 0; i < kPrefixCounts; i++) { 645 | printf(" %*u\n", countDigits, triCounts[i]); 646 | } 647 | printf(" %*s\n", countDigits, "..."); 648 | for (uint32_t i = numMeshes - kSuffixCounts; i < numMeshes; i++) { 649 | printf(" %*u\n", countDigits, triCounts[i]); 650 | } 651 | } 652 | else { 653 | for (uint32_t count : triCounts) { 654 | printf(" %*u\n", countDigits, count); 655 | } 656 | } 657 | printf("\n"); 658 | 659 | countDigits = uint32_t(ceil(log10(double(vertCounts.back())))); 660 | printf("Vertex counts:\n"); 661 | printf("- Min: %*u\n", countDigits, vertCounts.front()); 662 | printf("- Max: %*u\n", countDigits, vertCounts.back()); 663 | printf("- Median: %*u\n", countDigits, vertCounts[numMeshes / 2]); 664 | printf("- Mean: %*.1lf\n", countDigits + 2, double(totalVerts) / double(numMeshes)); 665 | printf("- Counts:\n"); 666 | if (abbreviate) { 667 | for (uint32_t i = 0; i < kPrefixCounts; i++) { 668 | printf(" %*u\n", countDigits, vertCounts[i]); 669 | } 670 | printf(" %*s\n", countDigits, "..."); 671 | for (uint32_t i = numMeshes - kSuffixCounts; i < numMeshes; i++) { 672 | printf(" %*u\n", countDigits, vertCounts[i]); 673 | } 674 | } 675 | else { 676 | for (uint32_t count : vertCounts) { 677 | printf(" %*u\n", countDigits, count); 678 | } 679 | } 680 | printf("\n"); 681 | } 682 | 683 | 684 | static uint64_t triangle_mesh_bytes(const TriangleMesh* trimesh) 685 | { 686 | uint64_t meshBytes = trimesh->num_indices * sizeof(int) + 687 | trimesh->num_vertices * sizeof(int) * 3; 688 | if (trimesh->N != nullptr) { 689 | meshBytes += trimesh->num_vertices * sizeof(int) * 3; 690 | } 691 | if (trimesh->S != nullptr) { 692 | meshBytes += trimesh->num_vertices * sizeof(int) * 3; 693 | } 694 | if (trimesh->uv != nullptr) { 695 | meshBytes += trimesh->num_vertices * sizeof(int) * 2; 696 | } 697 | return meshBytes; 698 | } 699 | 700 | 701 | 702 | } // namespace minipbrt 703 | 704 | 705 | static bool has_extension(const char* filename, const char* ext) 706 | { 707 | int j = int(strlen(ext)); 708 | int i = int(strlen(filename)) - j; 709 | if (i <= 0 || filename[i - 1] != '.') { 710 | return false; 711 | } 712 | return strcmp(filename + i, ext) == 0; 713 | } 714 | 715 | 716 | int main(int argc, char** argv) 717 | { 718 | const int kFilenameBufferLen = 16 * 1024 - 1; 719 | char* filenameBuffer = new char[kFilenameBufferLen + 1]; 720 | filenameBuffer[kFilenameBufferLen] = '\0'; 721 | 722 | std::vector filenames; 723 | for (int i = 1; i < argc; i++) { 724 | if (has_extension(argv[i], "txt")) { 725 | FILE* f = fopen(argv[i], "r"); 726 | if (f != nullptr) { 727 | while (fgets(filenameBuffer, kFilenameBufferLen, f)) { 728 | filenames.push_back(filenameBuffer); 729 | while (filenames.back().back() == '\n') { 730 | filenames.back().pop_back(); 731 | } 732 | } 733 | fclose(f); 734 | } 735 | else { 736 | fprintf(stderr, "Failed to open %s\n", argv[i]); 737 | } 738 | } 739 | else { 740 | filenames.push_back(argv[i]); 741 | } 742 | } 743 | 744 | if (filenames.empty()) { 745 | fprintf(stderr, "No input files provided.\n"); 746 | return EXIT_SUCCESS; 747 | } 748 | else if (filenames.size() == 1) { 749 | minipbrt::Loader loader; 750 | bool ok = loader.load(filenames.front().c_str()); 751 | bool plyOK = ok ? loader.borrow_scene()->load_all_ply_meshes() : false; 752 | 753 | if (!ok) { 754 | minipbrt::print_error(loader.error()); 755 | return EXIT_FAILURE; 756 | } 757 | else if (!plyOK) { 758 | fprintf(stderr, "[%s] Failed to load ply meshes.\n", filenames.front().c_str()); 759 | return EXIT_FAILURE; 760 | } 761 | else { 762 | minipbrt::print_scene_info(loader.borrow_scene()); 763 | return EXIT_SUCCESS; 764 | } 765 | } 766 | else { 767 | int width = 0; 768 | for (const std::string& filename : filenames) { 769 | int newWidth = int(filename.size()); 770 | if (newWidth > width) { 771 | width = newWidth; 772 | } 773 | } 774 | 775 | minipbrt::Timer overallTimer(true); // true ==> autostart the timer. 776 | int numPassed = 0; 777 | int numFailed = 0; 778 | for (const std::string& filename : filenames) { 779 | minipbrt::Timer timer(true); // true ==> autostart the timer. 780 | 781 | minipbrt::Loader loader; 782 | bool ok = loader.load(filename.c_str()); 783 | bool plyOK = ok ? loader.borrow_scene()->load_all_ply_meshes() : false; 784 | 785 | timer.stop(); 786 | printf("%-*s %s %8.3lf secs", 787 | width, filename.c_str(), 788 | (ok && plyOK) ? "passed" : "FAILED", 789 | timer.elapsedSecs()); 790 | if (!ok) { 791 | const minipbrt::Error* err = loader.error(); 792 | printf(" ---> [%s, line %u, column %u] %s\n", err->filename(), uint32_t(err->line()), uint32_t(err->column()), err->message()); 793 | ++numFailed; 794 | } 795 | else if (!plyOK) { 796 | printf(" ---> Failed to load ply meshes\n"); 797 | ++numFailed; 798 | } 799 | else { 800 | printf("\n"); 801 | ++numPassed; 802 | } 803 | fflush(stdout); 804 | } 805 | 806 | overallTimer.stop(); 807 | printf("----\n"); 808 | printf("%.3lf secs total\n", overallTimer.elapsedSecs()); 809 | printf("%d passed\n", numPassed); 810 | printf("%d failed\n", numFailed); 811 | return (numFailed > 0) ? EXIT_FAILURE : EXIT_SUCCESS; 812 | } 813 | } 814 | -------------------------------------------------------------------------------- /minipbrt.h: -------------------------------------------------------------------------------- 1 | #ifndef MINIPBRT_H 2 | #define MINIPBRT_H 3 | 4 | /* 5 | MIT License 6 | 7 | Copyright (c) 2019 Vilya Harvey 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | 36 | /// minipbrt - A simple and fast parser for PBRT v3 files 37 | /// ===================================================== 38 | /// 39 | /// For info about the PBRT file format, see: 40 | /// https://www.pbrt.org/fileformat-v3.html 41 | /// 42 | /// Getting started 43 | /// --------------- 44 | /// 45 | /// - Add minipbrt.h and minipbrt.cpp into your project. 46 | /// - Include minipbrt.h wherever you need it. 47 | /// 48 | /// Loading a file 49 | /// -------------- 50 | /// ``` 51 | /// minipbrt::Loader loader; 52 | /// if (loader.load(filename)) { 53 | /// minipbrt::Scene* scene = loader.take_scene(); 54 | /// // ... process the scene, then delete it ... 55 | /// delete scene; 56 | /// } 57 | /// else { 58 | /// // If parsing failed, the parser will have an error object. 59 | /// const minipbrt::Error* err = loader.error(); 60 | /// fprintf(stderr, "[%s, line %lld, column %lld] %s\n", 61 | /// err->filename(), err->line(), err->column(), err->message()); 62 | /// // Don't delete err, it's still owned by the loader. 63 | /// } 64 | /// ``` 65 | /// 66 | /// Implementation notes 67 | /// -------------------- 68 | /// 69 | /// * The code is C++11. 70 | /// 71 | /// * Spectra are always converted to RGB at load time. (This may change in 72 | /// future; for now it simplifies things to convert them straight away). 73 | /// 74 | /// * PLY files are not automatically loaded. Call `load_ply_mesh` to load one 75 | /// of them. You can call this for each plymesh shape in parallel, or you can 76 | /// call `load_ply_meshes` to load them all on a single thread. 77 | /// (This is not implemented yet) 78 | /// 79 | /// * Likewise, we provide helper functions for triangulating shapes but it's 80 | /// up to you to call them. 81 | /// 82 | /// * Most material properties can be either a texture or a value. These 83 | /// properties are represented as structs with type `ColorTex` or `FloatTex`. In 84 | /// both structs, if the `texture` member is anything other than 85 | /// `kInvalidTexture`, the texture should be used *instead* of the `value`. 86 | 87 | namespace minipbrt { 88 | 89 | // 90 | // Constants 91 | // 92 | 93 | static constexpr uint32_t kInvalidIndex = 0xFFFFFFFFu; 94 | 95 | 96 | // 97 | // Forward declarations 98 | // 99 | 100 | // Interfaces 101 | struct Accelerator; 102 | struct Camera; 103 | struct Film; 104 | struct Filter; 105 | struct Integrator; 106 | struct Material; 107 | struct Medium; 108 | struct Sampler; 109 | struct Shape; 110 | struct Texture; 111 | 112 | // Instancing structs 113 | struct Object; 114 | struct Instance; 115 | 116 | class Parser; 117 | 118 | 119 | // 120 | // Helper types 121 | // 122 | 123 | enum class ParamType : uint32_t { 124 | Bool, //!< A boolean value. 125 | Int, //!< 1 int: a single integer value. 126 | Float, //!< 1 float: a single floating point value. 127 | Point2, //!< 2 floats: a 2D point. 128 | Point3, //!< 3 floats: a 3D point. 129 | Vector2, //!< 2 floats: a 2D direction vector. 130 | Vector3, //!< 3 floats: a 3D direction vector. 131 | Normal3, //!< 3 floats: a 3D normal vector. 132 | RGB, //!< 3 floats: an RGB color. 133 | XYZ, //!< 3 floats: a CIE XYZ color. 134 | Blackbody, //!< 2 floats: temperature (in Kelvin) and scale. 135 | Samples, //!< 2n floats: n pairs of (wavelength, value) samples, sorted by wavelength. 136 | String, //!< A char* pointer and a length. 137 | Texture, //!< A texture reference, stored as a (name, index) pair. The index will be 0xFFFFFFFF if not resolved yet. 138 | }; 139 | 140 | 141 | struct FloatTex { 142 | uint32_t texture; 143 | float value; 144 | }; 145 | 146 | 147 | struct ColorTex { 148 | uint32_t texture; 149 | float value[3]; 150 | }; 151 | 152 | 153 | /// A bit set for elements of an enum. 154 | template 155 | struct Bits { 156 | uint32_t val; 157 | 158 | Bits() : val(0) {} 159 | Bits(T ival) : val(1u << static_cast(ival)) {} 160 | Bits(uint32_t ival) : val(ival) {} 161 | Bits(const Bits& other) : val(other.val) {} 162 | 163 | Bits& operator = (const Bits& other) { val = other.val; return *this; } 164 | 165 | void set(T ival) { val |= (1u << static_cast(ival)); } 166 | void clear(T ival) { val &= ~(1u << static_cast(ival)); } 167 | void toggle(T ival) { val ^= (1u << static_cast(ival)); } 168 | 169 | void setAll() { val = 0xFFFFFFFFu; } 170 | void clearAll() { val = 0u;} 171 | void toggleAll() { val = ~val; } 172 | 173 | bool contains(T ival) const { return (val & (1u << static_cast(ival))) != 0u; } 174 | }; 175 | 176 | template Bits operator | (Bits lhs, Bits rhs) { return Bits(lhs.val | rhs.val); } 177 | template Bits operator & (Bits lhs, Bits rhs) { return Bits(lhs.val & rhs.val); } 178 | template Bits operator ^ (Bits lhs, Bits rhs) { return Bits(lhs.val ^ rhs.val); } 179 | template Bits operator ~ (Bits rhs) { return Bits(~rhs.val); } 180 | 181 | template Bits operator | (Bits lhs, T rhs) { return lhs | Bits(rhs); } 182 | template Bits operator & (Bits lhs, T rhs) { return lhs & Bits(rhs); } 183 | template Bits operator ^ (Bits lhs, T rhs) { return lhs ^ Bits(rhs); } 184 | 185 | template Bits operator | (T lhs, T rhs) { return Bits(lhs) | Bits(rhs); } 186 | 187 | 188 | struct Transform { 189 | float start[4][4]; // row major matrix for when time = start time. 190 | float end[4][4]; // row major for when time = end time. 191 | }; 192 | 193 | 194 | // 195 | // Accelerator types 196 | // 197 | 198 | enum class AcceleratorType { 199 | BVH, 200 | KdTree, 201 | }; 202 | 203 | 204 | enum class BVHSplit { 205 | SAH, 206 | Middle, 207 | Equal, 208 | HLBVH, 209 | }; 210 | 211 | 212 | struct Accelerator { 213 | virtual ~Accelerator() {} 214 | virtual AcceleratorType type() const = 0; 215 | }; 216 | 217 | 218 | struct BVHAccelerator : public Accelerator { 219 | int maxnodeprims = 4; 220 | BVHSplit splitmethod = BVHSplit::SAH; 221 | 222 | virtual ~BVHAccelerator() override {} 223 | virtual AcceleratorType type() const override { return AcceleratorType::BVH; } 224 | }; 225 | 226 | 227 | struct KdTreeAccelerator : public Accelerator { 228 | int intersectcost = 80; 229 | int traversalcost = 1; 230 | float emptybonus = 0.2f; 231 | int maxprims = 1; 232 | int maxdepth = -1; 233 | 234 | virtual ~KdTreeAccelerator() override {} 235 | virtual AcceleratorType type() const override { return AcceleratorType::KdTree; } 236 | }; 237 | 238 | 239 | // 240 | // Area Light types 241 | // 242 | 243 | enum class AreaLightType { 244 | Diffuse, 245 | }; 246 | 247 | 248 | struct AreaLight { 249 | float scale[3] = { 1.0f, 1.0f, 1.0f }; 250 | 251 | virtual ~AreaLight() {} 252 | virtual AreaLightType type() const = 0; 253 | }; 254 | 255 | 256 | struct DiffuseAreaLight : public AreaLight { 257 | float L[3] = { 1.0f, 1.0f, 1.0f }; 258 | bool twosided = false; 259 | int samples = 1; 260 | 261 | virtual ~DiffuseAreaLight() override {} 262 | virtual AreaLightType type() const override { return AreaLightType::Diffuse; } 263 | }; 264 | 265 | 266 | // 267 | // Camera types 268 | // 269 | 270 | enum class CameraType { 271 | Perspective, 272 | Orthographic, 273 | Environment, 274 | Realistic, 275 | }; 276 | 277 | 278 | struct Camera { 279 | Transform cameraToWorld; 280 | float shutteropen = 0.0f; 281 | float shutterclose = 1.0f; 282 | 283 | virtual ~Camera() {} 284 | virtual CameraType type() const = 0; 285 | virtual void compute_defaults(const Film* film) {} 286 | }; 287 | 288 | 289 | struct PerspectiveCamera : public Camera { 290 | float frameaspectratio = 0.0f; // 0 or less means "compute this from the film resolution" 291 | float screenwindow[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; // endx <= startx or endy <= starty means "compute this from the film resolution" 292 | float lensradius = 0.0f; 293 | float focaldistance = 1e30f; 294 | float fov = 90.0f; 295 | float halffov = 45.0f; 296 | 297 | virtual ~PerspectiveCamera() override {} 298 | virtual CameraType type() const override { return CameraType::Perspective; } 299 | virtual void compute_defaults(const Film* film) override; 300 | }; 301 | 302 | 303 | struct OrthographicCamera : public Camera { 304 | float frameaspectratio = 1.0f; 305 | float screenwindow[4] = { -1.0f, 1.0f, -1.0f, 1.0f }; 306 | float lensradius = 0.0f; 307 | float focaldistance = 1e30f; 308 | 309 | virtual ~OrthographicCamera() override {} 310 | virtual CameraType type() const override { return CameraType::Orthographic; } 311 | virtual void compute_defaults(const Film* film) override; 312 | }; 313 | 314 | 315 | struct EnvironmentCamera : public Camera { 316 | float frameaspectratio = 1.0f; 317 | float screenwindow[4] = { -1.0f, 1.0f, -1.0f, 1.0f }; 318 | 319 | virtual ~EnvironmentCamera() override {} 320 | virtual CameraType type() const override { return CameraType::Environment; } 321 | virtual void compute_defaults(const Film* film) override; 322 | }; 323 | 324 | 325 | struct RealisticCamera : public Camera { 326 | char* lensfile = nullptr; 327 | float aperturediameter = 1.0f; 328 | float focusdistance = 10.0f; 329 | bool simpleweighting = true; 330 | 331 | virtual ~RealisticCamera() override { delete[] lensfile; } 332 | virtual CameraType type() const override { return CameraType::Realistic; } 333 | }; 334 | 335 | 336 | // 337 | // Film types 338 | // 339 | 340 | enum class FilmType { 341 | Image, 342 | }; 343 | 344 | 345 | struct Film { 346 | virtual ~Film() {} 347 | virtual FilmType type() const = 0; 348 | virtual float get_aspect_ratio() const = 0; 349 | virtual void get_resolution(int& w, int& h) const = 0; 350 | }; 351 | 352 | 353 | struct ImageFilm : public Film { 354 | int xresolution = 640; 355 | int yresolution = 480; 356 | float cropwindow[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; 357 | float scale = 1.0f; 358 | float maxsampleluminance = std::numeric_limits::infinity(); 359 | float diagonal = 35.0f; // in millimetres 360 | char* filename = nullptr; // name of the output image. 361 | 362 | virtual ~ImageFilm() override { delete[] filename; } 363 | virtual FilmType type() const override { return FilmType::Image; } 364 | virtual float get_aspect_ratio() const override { return float(xresolution) / float(yresolution); } 365 | virtual void get_resolution(int& w, int& h) const override { w = xresolution; h = yresolution; } 366 | }; 367 | 368 | 369 | // 370 | // Filter types 371 | // 372 | 373 | enum class FilterType { 374 | Box, 375 | Gaussian, 376 | Mitchell, 377 | Sinc, 378 | Triangle, 379 | }; 380 | 381 | 382 | struct Filter { 383 | float xwidth = 2.0f; 384 | float ywidth = 2.0f; 385 | 386 | virtual ~Filter() {} 387 | virtual FilterType type() const = 0; 388 | }; 389 | 390 | 391 | struct BoxFilter : public Filter { 392 | BoxFilter() { xwidth = 0.5f; ywidth = 0.5f; } 393 | virtual ~BoxFilter() override {} 394 | virtual FilterType type() const override { return FilterType::Box; } 395 | }; 396 | 397 | 398 | struct GaussianFilter : public Filter { 399 | float alpha = 2.0f; 400 | 401 | virtual ~GaussianFilter() override {} 402 | virtual FilterType type() const override { return FilterType::Gaussian; } 403 | }; 404 | 405 | 406 | struct MitchellFilter : public Filter { 407 | float B = 1.0f / 3.0f; 408 | float C = 1.0f / 3.0f; 409 | 410 | virtual ~MitchellFilter() override {} 411 | virtual FilterType type() const override { return FilterType::Mitchell; } 412 | }; 413 | 414 | 415 | struct SincFilter : public Filter { 416 | float tau = 3.0f; 417 | 418 | SincFilter() { xwidth = 4.0f; ywidth = 4.0f; } 419 | virtual ~SincFilter() override {} 420 | virtual FilterType type() const override { return FilterType::Sinc; } 421 | }; 422 | 423 | 424 | struct TriangleFilter : public Filter { 425 | virtual ~TriangleFilter() override {} 426 | virtual FilterType type() const override { return FilterType::Triangle; } 427 | }; 428 | 429 | 430 | // 431 | // Integrator types 432 | // 433 | 434 | enum class IntegratorType { 435 | BDPT, 436 | DirectLighting, 437 | MLT, 438 | Path, 439 | SPPM, 440 | Whitted, 441 | VolPath, 442 | AO, 443 | }; 444 | 445 | 446 | enum class LightSampleStrategy { 447 | Uniform, 448 | Power, 449 | Spatial, 450 | }; 451 | 452 | 453 | enum class DirectLightSampleStrategy { 454 | All, 455 | One, 456 | }; 457 | 458 | 459 | struct Integrator { 460 | virtual ~Integrator() {} 461 | virtual IntegratorType type() const = 0; 462 | virtual void compute_defaults(const Film* /*film*/) {} 463 | }; 464 | 465 | 466 | struct BDPTIntegrator : public Integrator { 467 | int maxdepth = 5; 468 | int pixelbounds[4] = { 0, -1, 0, -1 }; // endx <= startx or endy <= starty means "whole image". 469 | LightSampleStrategy lightsamplestrategy = LightSampleStrategy::Power; 470 | bool visualizestrategies = false; 471 | bool visualizeweights = false; 472 | 473 | virtual ~BDPTIntegrator() override {} 474 | virtual IntegratorType type() const override { return IntegratorType::BDPT; } 475 | virtual void compute_defaults(const Film* film) override; 476 | }; 477 | 478 | 479 | struct DirectLightingIntegrator : public Integrator { 480 | DirectLightSampleStrategy strategy = DirectLightSampleStrategy::All; 481 | int maxdepth = 5; 482 | int pixelbounds[4] = { 0, -1, 0, -1 }; 483 | 484 | virtual ~DirectLightingIntegrator() override {} 485 | virtual IntegratorType type() const override { return IntegratorType::DirectLighting; } 486 | virtual void compute_defaults(const Film* film) override; 487 | }; 488 | 489 | 490 | struct MLTIntegrator : public Integrator { 491 | int maxdepth = 5; 492 | int bootstrapsamples = 100000; 493 | int chains = 1000; 494 | int mutationsperpixel = 100; 495 | float largestprobability = 0.3f; 496 | float sigma = 0.01f; 497 | 498 | virtual ~MLTIntegrator() override {} 499 | virtual IntegratorType type() const override { return IntegratorType::MLT; } 500 | }; 501 | 502 | 503 | struct PathIntegrator : public Integrator { 504 | int maxdepth = 5; 505 | int pixelbounds[4] = { 0, -1, 0, -1 }; // endx <= startx or endy <= starty means "whole image". 506 | float rrthreshold = 1.0f; 507 | LightSampleStrategy lightsamplestrategy = LightSampleStrategy::Spatial; 508 | 509 | virtual ~PathIntegrator() override {} 510 | virtual IntegratorType type() const override { return IntegratorType::Path; } 511 | virtual void compute_defaults(const Film* film) override; 512 | }; 513 | 514 | 515 | struct SPPMIntegrator : public Integrator { 516 | int maxdepth = 5; 517 | int maxiterations = 64; 518 | int photonsperiteration = -1; 519 | int imagewritefrequency = 1 << 30; 520 | float radius = 1.0f; 521 | 522 | virtual ~SPPMIntegrator() override {} 523 | virtual IntegratorType type() const override { return IntegratorType::SPPM; } 524 | }; 525 | 526 | 527 | struct WhittedIntegrator : public Integrator { 528 | int maxdepth = 5; 529 | int pixelbounds[4] = { 0, -1, 0, -1 }; 530 | 531 | virtual ~WhittedIntegrator() override {} 532 | virtual IntegratorType type() const override { return IntegratorType::Whitted; } 533 | virtual void compute_defaults(const Film* film) override; 534 | }; 535 | 536 | 537 | struct VolPathIntegrator : public Integrator { 538 | int maxdepth = 5; 539 | int pixelbounds[4] = { 0, -1, 0, -1 }; // endx < startx or endy < starty means "whole image". 540 | float rrthreshold = 1.0f; 541 | LightSampleStrategy lightsamplestrategy = LightSampleStrategy::Spatial; 542 | 543 | virtual ~VolPathIntegrator() override {} 544 | virtual IntegratorType type() const override { return IntegratorType::VolPath; } 545 | virtual void compute_defaults(const Film* film) override; 546 | }; 547 | 548 | 549 | struct AOIntegrator : public Integrator { 550 | int pixelbounds[4] = { 0, -1, 0, -1 }; // endx < startx or endy < starty means "whole image". 551 | bool cossample = true; 552 | int nsamples = 64; 553 | 554 | virtual ~AOIntegrator() override {} 555 | virtual IntegratorType type() const override { return IntegratorType::AO; } 556 | virtual void compute_defaults(const Film* film) override; 557 | }; 558 | 559 | 560 | // 561 | // Light types 562 | // 563 | 564 | enum class LightType { 565 | Distant, 566 | Goniometric, 567 | Infinite, 568 | Point, 569 | Projection, 570 | Spot, 571 | }; 572 | 573 | 574 | struct Light { 575 | Transform lightToWorld; // row major. 576 | float scale[3] = { 1.0f, 1.0f, 1.0f }; 577 | 578 | virtual ~Light() {} 579 | virtual LightType type() const = 0; 580 | }; 581 | 582 | 583 | struct DistantLight : public Light { 584 | float L[3] = { 1.0f, 1.0f, 1.0f }; 585 | float from[3] = { 0.0f, 0.0f, 0.0f }; 586 | float to[3] = { 0.0f, 0.0f, 1.0f }; 587 | 588 | virtual ~DistantLight() override {} 589 | virtual LightType type() const override { return LightType::Distant; } 590 | }; 591 | 592 | 593 | struct GoniometricLight : public Light { 594 | float I[3] = { 1.0f, 1.0f, 1.0f }; 595 | char* mapname = nullptr; 596 | 597 | virtual ~GoniometricLight() override { delete[] mapname; } 598 | virtual LightType type() const override { return LightType::Goniometric; } 599 | }; 600 | 601 | 602 | struct InfiniteLight : public Light { 603 | float L[3] = { 1.0f, 1.0f, 1.0f }; 604 | int samples = 1; 605 | char* mapname = nullptr; 606 | 607 | virtual ~InfiniteLight() override { delete[] mapname; } 608 | virtual LightType type() const override { return LightType::Infinite; } 609 | }; 610 | 611 | 612 | struct PointLight : public Light { 613 | float I[3] = { 1.0f, 1.0f, 1.0f }; 614 | float from[3] = { 0.0f, 0.0f, 0.0f }; 615 | 616 | virtual ~PointLight() override {} 617 | virtual LightType type() const override { return LightType::Point; } 618 | }; 619 | 620 | 621 | struct ProjectionLight : public Light { 622 | float I[3] = { 1.0f, 1.0f, 1.0f }; 623 | float fov = 45.0f; 624 | char* mapname = nullptr; 625 | 626 | virtual ~ProjectionLight() override { delete[] mapname; } 627 | virtual LightType type() const override { return LightType::Projection; } 628 | }; 629 | 630 | 631 | struct SpotLight : public Light { 632 | float I[3] = { 1.0f, 1.0f, 1.0f }; 633 | float from[3] = { 0.0f, 0.0f, 0.0f }; 634 | float to[3] = { 0.0f, 0.0f, 1.0f }; 635 | float coneangle = 30.0f; 636 | float conedeltaangle = 5.0f; 637 | 638 | virtual ~SpotLight() override {} 639 | virtual LightType type() const override { return LightType::Spot; } 640 | }; 641 | 642 | 643 | // 644 | // Material types 645 | // 646 | 647 | enum class MaterialType { 648 | Disney, 649 | Fourier, 650 | Glass, 651 | Hair, 652 | KdSubsurface, 653 | Matte, 654 | Metal, 655 | Mirror, 656 | Mix, 657 | None, 658 | Plastic, 659 | Substrate, 660 | Subsurface, 661 | Translucent, 662 | Uber, 663 | }; 664 | 665 | 666 | struct Material { 667 | const char* name = nullptr; 668 | uint32_t bumpmap = kInvalidIndex; 669 | 670 | virtual ~Material() { delete[] name; } 671 | virtual MaterialType type() const = 0; 672 | }; 673 | 674 | 675 | struct DisneyMaterial : public Material { 676 | ColorTex color = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; 677 | FloatTex anisotropic = { kInvalidIndex, 0.0f }; 678 | FloatTex clearcoat = { kInvalidIndex, 0.0f }; 679 | FloatTex clearcoatgloss = { kInvalidIndex, 1.0f }; 680 | FloatTex eta = { kInvalidIndex, 1.5f }; 681 | FloatTex metallic = { kInvalidIndex, 0.0f }; 682 | FloatTex roughness = { kInvalidIndex, 0.5f }; 683 | ColorTex scatterdistance = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 684 | FloatTex sheen = { kInvalidIndex, 0.0f }; 685 | FloatTex sheentint = { kInvalidIndex, 0.5f }; 686 | FloatTex spectrans = { kInvalidIndex, 0.0f }; 687 | FloatTex speculartint = { kInvalidIndex, 0.0f }; 688 | bool thin = false; 689 | ColorTex difftrans = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; // only used if `thin == true` 690 | ColorTex flatness = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; // only used if `thin == true` 691 | 692 | virtual ~DisneyMaterial() override {} 693 | virtual MaterialType type() const override { return MaterialType::Disney; } 694 | }; 695 | 696 | 697 | struct FourierMaterial : public Material { 698 | char* bsdffile = nullptr; 699 | 700 | virtual ~FourierMaterial() override { delete[] bsdffile; } 701 | virtual MaterialType type() const override { return MaterialType::Fourier; } 702 | }; 703 | 704 | 705 | struct GlassMaterial : public Material { 706 | ColorTex Kr = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 707 | ColorTex Kt = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 708 | FloatTex eta = { kInvalidIndex, 1.5f }; 709 | FloatTex uroughness = { kInvalidIndex, 0.0f }; 710 | FloatTex vroughness = { kInvalidIndex, 0.0f }; 711 | bool remaproughness = true; 712 | 713 | virtual ~GlassMaterial() override {} 714 | virtual MaterialType type() const override { return MaterialType::Glass; } 715 | }; 716 | 717 | 718 | struct HairMaterial : public Material { 719 | ColorTex sigma_a = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 720 | ColorTex color = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 721 | FloatTex eumelanin = { kInvalidIndex, 1.3f }; 722 | FloatTex pheomelanin = { kInvalidIndex, 0.0f }; 723 | FloatTex eta = { kInvalidIndex, 1.55f }; 724 | FloatTex beta_m = { kInvalidIndex, 0.3f }; 725 | FloatTex beta_n = { kInvalidIndex, 0.3f }; 726 | FloatTex alpha = { kInvalidIndex, 2.0f }; 727 | bool has_sigma_a = false; 728 | bool has_color = false; 729 | 730 | virtual ~HairMaterial() override {} 731 | virtual MaterialType type() const override { return MaterialType::Hair; } 732 | }; 733 | 734 | 735 | struct KdSubsurfaceMaterial : public Material { 736 | ColorTex Kd = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; 737 | ColorTex mfp = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; 738 | FloatTex eta = { kInvalidIndex, 1.3f }; 739 | ColorTex Kr = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 740 | ColorTex Kt = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 741 | FloatTex uroughness = { kInvalidIndex, 0.0f }; 742 | FloatTex vroughness = { kInvalidIndex, 0.0f }; 743 | bool remaproughness = true; 744 | 745 | virtual ~KdSubsurfaceMaterial() override {} 746 | virtual MaterialType type() const override { return MaterialType::KdSubsurface; } 747 | }; 748 | 749 | 750 | struct MatteMaterial : public Material { 751 | ColorTex Kd = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; 752 | FloatTex sigma = { kInvalidIndex, 0.0f }; 753 | 754 | virtual ~MatteMaterial() override {} 755 | virtual MaterialType type() const override { return MaterialType::Matte; } 756 | }; 757 | 758 | 759 | struct MetalMaterial : public Material { 760 | ColorTex eta = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; // TODO: indices of refraction for copper 761 | ColorTex k = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; // TODO: absorption coefficients for copper 762 | FloatTex uroughness = { kInvalidIndex, 0.01f }; 763 | FloatTex vroughness = { kInvalidIndex, 0.01f }; 764 | bool remaproughness = true; 765 | 766 | virtual ~MetalMaterial() override {} 767 | virtual MaterialType type() const override { return MaterialType::Metal; } 768 | }; 769 | 770 | 771 | struct MirrorMaterial : public Material { 772 | ColorTex Kr = { kInvalidIndex, {0.9f, 0.9f, 0.9f} }; 773 | 774 | virtual ~MirrorMaterial() override {} 775 | virtual MaterialType type() const override { return MaterialType::Mirror; } 776 | }; 777 | 778 | 779 | struct MixMaterial : public Material { 780 | ColorTex amount = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; 781 | uint32_t namedmaterial1 = kInvalidIndex; 782 | uint32_t namedmaterial2 = kInvalidIndex; 783 | 784 | virtual ~MixMaterial() override {} 785 | virtual MaterialType type() const override { return MaterialType::Mix; } 786 | }; 787 | 788 | 789 | struct NoneMaterial : public Material { 790 | virtual ~NoneMaterial() override {} 791 | virtual MaterialType type() const override { return MaterialType::None; } 792 | }; 793 | 794 | 795 | struct PlasticMaterial : public Material { 796 | ColorTex Kd = { kInvalidIndex, {0.25f, 0.25f, 0.25f} }; 797 | ColorTex Ks = { kInvalidIndex, {0.25f, 0.25f, 0.25f} }; 798 | FloatTex roughness = { kInvalidIndex, 0.1f }; 799 | bool remaproughness = true; 800 | 801 | virtual ~PlasticMaterial() override {} 802 | virtual MaterialType type() const override { return MaterialType::Plastic; } 803 | }; 804 | 805 | 806 | struct SubstrateMaterial : public Material { 807 | ColorTex Kd = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; 808 | ColorTex Ks = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; 809 | FloatTex uroughness = { kInvalidIndex, 0.1f }; 810 | FloatTex vroughness = { kInvalidIndex, 0.1f }; 811 | bool remaproughness = true; 812 | 813 | virtual ~SubstrateMaterial() override {} 814 | virtual MaterialType type() const override { return MaterialType::Substrate; } 815 | }; 816 | 817 | 818 | struct SubsurfaceMaterial : public Material { 819 | char* coefficients = nullptr; // name of the measured subsurface scattering coefficients 820 | ColorTex sigma_a = { kInvalidIndex, {0.0011f, 0.0024f, 0.014f} }; 821 | ColorTex sigma_prime_s = { kInvalidIndex, {2.55f, 3.12f, 3.77f} }; 822 | float scale = 1.0f; 823 | FloatTex eta = { kInvalidIndex, 1.33f }; 824 | ColorTex Kr = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 825 | ColorTex Kt = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 826 | FloatTex uroughness = { kInvalidIndex, 0.0f }; 827 | FloatTex vroughness = { kInvalidIndex, 0.0f }; 828 | bool remaproughness = true; 829 | 830 | virtual ~SubsurfaceMaterial() override { delete[] coefficients; } 831 | virtual MaterialType type() const override { return MaterialType::Subsurface; } 832 | }; 833 | 834 | 835 | struct TranslucentMaterial : public Material { 836 | ColorTex Kd = { kInvalidIndex, {0.25f, 0.25f, 0.25f} }; 837 | ColorTex Ks = { kInvalidIndex, {0.25f, 0.25f, 0.25f} }; 838 | ColorTex reflect = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; 839 | ColorTex transmit = { kInvalidIndex, {0.5f, 0.5f, 0.5f} }; 840 | FloatTex roughness = { kInvalidIndex, 0.1f }; 841 | bool remaproughness = true; 842 | 843 | virtual ~TranslucentMaterial() override {} 844 | virtual MaterialType type() const override { return MaterialType::Translucent; } 845 | }; 846 | 847 | 848 | struct UberMaterial : public Material { 849 | ColorTex Kd = { kInvalidIndex, {0.25f, 0.25f, 0.25f} }; 850 | ColorTex Ks = { kInvalidIndex, {0.25f, 0.25f, 0.25f} }; 851 | ColorTex Kr = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 852 | ColorTex Kt = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 853 | FloatTex eta = { kInvalidIndex, 1.5f }; 854 | ColorTex opacity = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 855 | FloatTex uroughness = { kInvalidIndex, 0.1f }; 856 | FloatTex vroughness = { kInvalidIndex, 0.1f }; 857 | bool remaproughness = true; 858 | 859 | virtual ~UberMaterial() override {} 860 | virtual MaterialType type() const override { return MaterialType::Uber; } 861 | }; 862 | 863 | 864 | // 865 | // Medium types 866 | // 867 | 868 | enum class MediumType { 869 | Homogeneous, 870 | Heterogeneous, 871 | }; 872 | 873 | 874 | struct Medium { 875 | const char* mediumName = nullptr; 876 | 877 | float sigma_a[3] = { 0.0011f, 0.0024f, 0.0014f }; 878 | float sigma_s[3] = { 2.55f, 3.21f, 3.77f}; 879 | char* preset = nullptr; 880 | float g = 0.0f; 881 | float scale = 1.0f; 882 | 883 | virtual ~Medium() { 884 | delete[] mediumName; 885 | delete[] preset; 886 | } 887 | virtual MediumType type() const = 0; 888 | }; 889 | 890 | 891 | struct HomogeneousMedium : public Medium { 892 | virtual ~HomogeneousMedium() override {} 893 | virtual MediumType type() const override { return MediumType::Homogeneous; } 894 | }; 895 | 896 | 897 | struct HeterogeneousMedium : public Medium { 898 | float p0[3] = { 0.0f, 0.0f, 0.0f }; 899 | float p1[3] = { 1.0f, 1.0f, 1.0f }; 900 | int nx = 1; 901 | int ny = 1; 902 | int nz = 1; 903 | float* density = nullptr; 904 | 905 | virtual ~HeterogeneousMedium() override { delete[] density; } 906 | virtual MediumType type() const override { return MediumType::Heterogeneous; } 907 | }; 908 | 909 | 910 | // 911 | // Sampler types 912 | // 913 | 914 | enum class SamplerType { 915 | ZeroTwoSequence, 916 | LowDiscrepancy, // An alias for ZeroTwoSequence, kept for backwards compatibility 917 | Halton, 918 | MaxMinDist, 919 | Random, 920 | Sobol, 921 | Stratified, 922 | }; 923 | 924 | 925 | struct Sampler { 926 | virtual ~Sampler() {} 927 | virtual SamplerType type() const = 0; 928 | }; 929 | 930 | 931 | struct ZeroTwoSequenceSampler : public Sampler { 932 | int pixelsamples = 16; 933 | 934 | virtual ~ZeroTwoSequenceSampler() override {} 935 | virtual SamplerType type() const override { return SamplerType::ZeroTwoSequence; } 936 | }; 937 | 938 | 939 | struct HaltonSampler : public Sampler { 940 | int pixelsamples = 16; 941 | 942 | virtual ~HaltonSampler() override {} 943 | virtual SamplerType type() const override { return SamplerType::Halton; } 944 | }; 945 | 946 | 947 | struct MaxMinDistSampler : public Sampler { 948 | int pixelsamples = 16; 949 | 950 | virtual ~MaxMinDistSampler() override {} 951 | virtual SamplerType type() const override { return SamplerType::MaxMinDist; } 952 | }; 953 | 954 | 955 | struct RandomSampler : public Sampler { 956 | int pixelsamples = 16; 957 | 958 | virtual ~RandomSampler() override {} 959 | virtual SamplerType type() const override { return SamplerType::Random; } 960 | }; 961 | 962 | 963 | struct SobolSampler : public Sampler { 964 | int pixelsamples = 16; 965 | 966 | virtual ~SobolSampler() override {} 967 | virtual SamplerType type() const override { return SamplerType::Sobol; } 968 | }; 969 | 970 | 971 | struct StratifiedSampler : public Sampler { 972 | bool jitter = true; 973 | int xsamples = 2; 974 | int ysamples = 2; 975 | 976 | virtual ~StratifiedSampler() override {} 977 | virtual SamplerType type() const override { return SamplerType::Stratified; } 978 | }; 979 | 980 | 981 | // 982 | // Shape types 983 | // 984 | 985 | enum class ShapeType { 986 | Cone, 987 | Curve, 988 | Cylinder, 989 | Disk, 990 | Hyperboloid, 991 | Paraboloid, 992 | Sphere, 993 | TriangleMesh, 994 | HeightField, 995 | LoopSubdiv, 996 | Nurbs, 997 | PLYMesh, 998 | }; 999 | 1000 | 1001 | enum class CurveBasis { 1002 | Bezier, 1003 | BSpline, 1004 | }; 1005 | 1006 | 1007 | enum class CurveType { 1008 | Flat, 1009 | Ribbon, 1010 | Cylinder, 1011 | }; 1012 | 1013 | struct TriangleMesh; 1014 | 1015 | struct Shape { 1016 | Transform shapeToWorld; // If shape is part of an object, this is the shapeToObject transform. 1017 | uint32_t material = kInvalidIndex; 1018 | uint32_t areaLight = kInvalidIndex; 1019 | uint32_t insideMedium = kInvalidIndex; 1020 | uint32_t outsideMedium = kInvalidIndex; 1021 | uint32_t object = kInvalidIndex; // The object that this shape is part of, or kInvalidIndex if it's not part of one. 1022 | bool reverseOrientation = false; 1023 | 1024 | virtual ~Shape() {} 1025 | virtual ShapeType type() const = 0; 1026 | virtual bool can_convert_to_triangle_mesh() const { return false; } 1027 | virtual TriangleMesh* triangle_mesh() const { return nullptr; } 1028 | 1029 | void copy_common_properties(const Shape* other); 1030 | }; 1031 | 1032 | 1033 | struct Cone : public Shape { 1034 | float radius = 1.0f; 1035 | float height = 1.0f; 1036 | float phimax = 360.0f; 1037 | 1038 | virtual ~Cone() override {} 1039 | virtual ShapeType type() const override { return ShapeType::Cone; } 1040 | }; 1041 | 1042 | 1043 | struct Curve : public Shape { 1044 | CurveBasis basis = CurveBasis::Bezier; 1045 | unsigned int degree = 3; 1046 | CurveType curvetype = CurveType::Flat; // This is the `type` param in the file, but that name would clash with our `type()` method. 1047 | float* P = nullptr; // Elements 3i, 3i+1 and 3i+2 are the xyz coords for control point i. 1048 | unsigned int num_P = 0; // Number of control points in P. 1049 | unsigned int num_segments = 0; // Number of curve segments. 1050 | float* N = nullptr; // Normals at each segment start and end point. Only used when curve type = "ribbon". When used, there must be exactly `num_segments + 1` values. 1051 | float width0 = 1.0f; 1052 | float width1 = 1.0f; 1053 | int splitdepth = 3; 1054 | 1055 | virtual ~Curve() override { 1056 | delete[] P; 1057 | delete[] N; 1058 | } 1059 | virtual ShapeType type() const override { return ShapeType::Curve; } 1060 | }; 1061 | 1062 | 1063 | struct Cylinder : public Shape { 1064 | float radius = 1.0f; 1065 | float zmin = -1.0f; 1066 | float zmax = 1.0f; 1067 | float phimax = 360.0f; 1068 | 1069 | virtual ~Cylinder() override {} 1070 | virtual ShapeType type() const override { return ShapeType::Cylinder; } 1071 | }; 1072 | 1073 | 1074 | struct Disk : public Shape { 1075 | float height = 0.0f; 1076 | float radius = 1.0f; 1077 | float innerradius = 0.0f; 1078 | float phimax = 360.0f; 1079 | 1080 | virtual ~Disk() override {} 1081 | virtual ShapeType type() const override { return ShapeType::Disk; } 1082 | }; 1083 | 1084 | 1085 | struct HeightField : public Shape { 1086 | int nu = 0; 1087 | int nv = 0; 1088 | float* Pz = nullptr; 1089 | 1090 | virtual ~HeightField() override { delete Pz; } 1091 | virtual ShapeType type() const override { return ShapeType::HeightField; } 1092 | virtual bool can_convert_to_triangle_mesh() const override { return true; } 1093 | virtual TriangleMesh* triangle_mesh() const override; 1094 | }; 1095 | 1096 | 1097 | struct Hyperboloid : public Shape { 1098 | float p1[3] = { 0.0f, 0.0f, 0.0f }; 1099 | float p2[3] = { 1.0f, 1.0f, 1.0f }; 1100 | float phimax = 360.0f; 1101 | 1102 | virtual ~Hyperboloid() override {} 1103 | virtual ShapeType type() const override { return ShapeType::Hyperboloid; } 1104 | }; 1105 | 1106 | 1107 | struct LoopSubdiv : public Shape { 1108 | int levels = 3; 1109 | int* indices = nullptr; 1110 | unsigned int num_indices = 0; 1111 | float* P = nullptr; // Elements 3i, 3i+1 and 3i+2 are the xyz coords for point i. 1112 | unsigned int num_points = 0; // Number of points. Multiply by 3 to get the number of floats in P. 1113 | 1114 | virtual ~LoopSubdiv() override { 1115 | delete[] indices; 1116 | delete[] P; 1117 | } 1118 | virtual ShapeType type() const override { return ShapeType::LoopSubdiv; } 1119 | virtual bool can_convert_to_triangle_mesh() const override { return true; } 1120 | virtual TriangleMesh* triangle_mesh() const override; 1121 | }; 1122 | 1123 | 1124 | struct Nurbs : public Shape { 1125 | int nu = 0; 1126 | int nv = 0; 1127 | int uorder = 0; 1128 | int vorder = 0; 1129 | float* uknots = nullptr; // There are `nu + uorder` knots in the u direction. 1130 | float* vknots = nullptr; // There are `nv + vorder` knots in the v direction. 1131 | float u0 = 0.0f; 1132 | float v0 = 0.0f; 1133 | float u1 = 1.0f; 1134 | float v1 = 1.0f; 1135 | float* P = nullptr; // Elements 3i, 3i+1 and 3i+2 are the xyz coords for control point i. There are `nu*nv` control points. 1136 | float* Pw = nullptr; // Elements 4i, 4i+1, 4i+2 aand 4i+3 are xyzw coords for control point i; w is a weight value. The are `nu*nv` control points. 1137 | 1138 | virtual ~Nurbs() override { 1139 | delete[] uknots; 1140 | delete[] vknots; 1141 | delete[] P; 1142 | delete[] Pw; 1143 | } 1144 | virtual ShapeType type() const override { return ShapeType::Nurbs; } 1145 | virtual bool can_convert_to_triangle_mesh() const override { return true; } 1146 | virtual TriangleMesh* triangle_mesh() const override; 1147 | }; 1148 | 1149 | 1150 | struct PLYMesh : public Shape { 1151 | char* filename = nullptr; 1152 | uint32_t alpha = kInvalidIndex; 1153 | uint32_t shadowalpha = kInvalidIndex; 1154 | 1155 | virtual ~PLYMesh() override { 1156 | delete[] filename; 1157 | } 1158 | virtual ShapeType type() const override { return ShapeType::PLYMesh; } 1159 | virtual bool can_convert_to_triangle_mesh() const override { return true; } 1160 | virtual TriangleMesh* triangle_mesh() const override; 1161 | }; 1162 | 1163 | 1164 | struct Paraboloid : public Shape { 1165 | float radius = 1.0f; 1166 | float zmin = 0.0f; 1167 | float zmax = 1.0f; 1168 | float phimax = 360.0f; 1169 | 1170 | virtual ~Paraboloid() override {} 1171 | virtual ShapeType type() const override { return ShapeType::Paraboloid; } 1172 | }; 1173 | 1174 | 1175 | struct Sphere : public Shape { 1176 | float radius = 1.0f; 1177 | float zmin = 0.0f; // Will be set to -radius 1178 | float zmax = 0.0f; // Will be set to +radius 1179 | float phimax = 360.0f; 1180 | 1181 | virtual ~Sphere() override {} 1182 | virtual ShapeType type() const override { return ShapeType::Sphere; } 1183 | }; 1184 | 1185 | 1186 | struct TriangleMesh : public Shape { 1187 | int* indices = nullptr; 1188 | unsigned int num_indices = 0; 1189 | float* P = nullptr; // Elements 3i, 3i+1 and 3i+2 are the xyz position components for vertex i. 1190 | float* N = nullptr; // Elements 3i, 3i+1 and 3i+2 are the xyz normal components for veretx i. 1191 | float* S = nullptr; // Elements 3i, 3i+1 and 3i+2 are the xyz tangent components for vertex i. 1192 | float* uv = nullptr; // Elements 2i and 2i+1 are the uv coords for vertex i. 1193 | unsigned int num_vertices = 0; // Number of vertices. Multiply by 3 to get the number of floats in P, N or S. Multiply by 2 to get the number of floats in uv. 1194 | uint32_t alpha = kInvalidIndex; 1195 | uint32_t shadowalpha = kInvalidIndex; 1196 | 1197 | virtual ~TriangleMesh() override { 1198 | delete[] indices; 1199 | delete[] P; 1200 | delete[] N; 1201 | delete[] S; 1202 | delete[] uv; 1203 | } 1204 | virtual ShapeType type() const override { return ShapeType::TriangleMesh; } 1205 | }; 1206 | 1207 | 1208 | // 1209 | // Texture types 1210 | // 1211 | 1212 | enum class TextureType { 1213 | Bilerp, 1214 | Checkerboard2D, 1215 | Checkerboard3D, 1216 | Constant, 1217 | Dots, 1218 | FBM, 1219 | ImageMap, 1220 | Marble, 1221 | Mix, 1222 | Scale, 1223 | UV, 1224 | Windy, 1225 | Wrinkled, 1226 | PTex, 1227 | }; 1228 | 1229 | 1230 | enum class TextureData { 1231 | Float, 1232 | Spectrum, 1233 | }; 1234 | 1235 | 1236 | enum class TexCoordMapping { 1237 | UV, 1238 | Spherical, 1239 | Cylindrical, 1240 | Planar, 1241 | }; 1242 | 1243 | 1244 | enum class WrapMode { 1245 | Repeat, 1246 | Black, 1247 | Clamp, 1248 | }; 1249 | 1250 | 1251 | enum class CheckerboardAAMode { 1252 | ClosedForm, 1253 | None, 1254 | }; 1255 | 1256 | 1257 | struct Texture { 1258 | const char* name; 1259 | TextureData dataType; 1260 | 1261 | virtual ~Texture() { delete[] name; } 1262 | virtual TextureType type() const = 0; 1263 | }; 1264 | 1265 | 1266 | struct TextureAnyD : public Texture { 1267 | virtual ~TextureAnyD() override {} 1268 | }; 1269 | 1270 | 1271 | struct Texture2D : public Texture { 1272 | TexCoordMapping mapping = TexCoordMapping::UV; 1273 | float uscale = 1.0f; 1274 | float vscale = 1.0f; 1275 | float udelta = 0.0f; 1276 | float vdelta = 0.0f; 1277 | float v1[3] = { 1.0f, 0.0f, 0.0f }; 1278 | float v2[3] = { 0.0f, 1.0f, 0.0f }; 1279 | 1280 | virtual ~Texture2D() override {} 1281 | }; 1282 | 1283 | 1284 | struct Texture3D : public Texture { 1285 | Transform objectToTexture; // row major 1286 | 1287 | virtual ~Texture3D() override {} 1288 | }; 1289 | 1290 | 1291 | struct BilerpTexture : public Texture2D { 1292 | ColorTex v00 = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 1293 | ColorTex v01 = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 1294 | ColorTex v10 = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 1295 | ColorTex v11 = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 1296 | 1297 | virtual ~BilerpTexture() override {} 1298 | virtual TextureType type() const override { return TextureType::Bilerp; } 1299 | }; 1300 | 1301 | 1302 | struct Checkerboard2DTexture : public Texture2D { 1303 | ColorTex tex1 = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 1304 | ColorTex tex2 = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 1305 | CheckerboardAAMode aamode = CheckerboardAAMode::ClosedForm; 1306 | 1307 | virtual ~Checkerboard2DTexture() override {} 1308 | virtual TextureType type() const override { return TextureType::Checkerboard2D; } 1309 | }; 1310 | 1311 | 1312 | struct Checkerboard3DTexture : public Texture3D { 1313 | ColorTex tex1 = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 1314 | ColorTex tex2 = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 1315 | 1316 | virtual ~Checkerboard3DTexture() override {} 1317 | virtual TextureType type() const override { return TextureType::Checkerboard3D; } 1318 | }; 1319 | 1320 | 1321 | struct ConstantTexture : public TextureAnyD { 1322 | float value[3]; 1323 | 1324 | virtual ~ConstantTexture() override {} 1325 | virtual TextureType type() const override { return TextureType::Constant; } 1326 | }; 1327 | 1328 | 1329 | struct DotsTexture : public Texture2D { 1330 | ColorTex inside = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 1331 | ColorTex outside = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 1332 | 1333 | virtual ~DotsTexture() override {} 1334 | virtual TextureType type() const override { return TextureType::Dots; } 1335 | }; 1336 | 1337 | 1338 | struct FBMTexture : public Texture3D { 1339 | int octaves = 8; 1340 | float roughness = 0.5f; 1341 | 1342 | virtual ~FBMTexture() override {} 1343 | virtual TextureType type() const override { return TextureType::FBM; } 1344 | }; 1345 | 1346 | 1347 | struct ImageMapTexture : public Texture2D { 1348 | char* filename = nullptr; 1349 | WrapMode wrap = WrapMode::Repeat; 1350 | float maxanisotropy = 8.0f; 1351 | bool trilinear = false; 1352 | float scale = 1.0f; 1353 | bool gamma = false; 1354 | 1355 | virtual ~ImageMapTexture() override { delete[] filename; } 1356 | virtual TextureType type() const override { return TextureType::ImageMap; } 1357 | }; 1358 | 1359 | 1360 | struct MarbleTexture : public Texture3D { 1361 | int octaves = 8; 1362 | float roughness = 0.5f; 1363 | float scale = 1.0f; 1364 | float variation = 0.2f; 1365 | 1366 | virtual ~MarbleTexture() override {} 1367 | virtual TextureType type() const override { return TextureType::Marble; } 1368 | }; 1369 | 1370 | 1371 | struct MixTexture : public TextureAnyD { 1372 | ColorTex tex1 = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 1373 | ColorTex tex2 = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 1374 | FloatTex amount = { kInvalidIndex, 0.5f }; 1375 | 1376 | virtual ~MixTexture() override {} 1377 | virtual TextureType type() const override { return TextureType::Mix; } 1378 | }; 1379 | 1380 | 1381 | struct ScaleTexture : public TextureAnyD { 1382 | ColorTex tex1 = { kInvalidIndex, {1.0f, 1.0f, 1.0f} }; 1383 | ColorTex tex2 = { kInvalidIndex, {0.0f, 0.0f, 0.0f} }; 1384 | 1385 | virtual ~ScaleTexture() override {} 1386 | virtual TextureType type() const override { return TextureType::Scale; } 1387 | }; 1388 | 1389 | 1390 | struct UVTexture : public Texture2D { 1391 | // not documented 1392 | 1393 | virtual ~UVTexture() override {} 1394 | virtual TextureType type() const override { return TextureType::UV; } 1395 | }; 1396 | 1397 | 1398 | struct WindyTexture : public Texture3D { 1399 | // not documented 1400 | 1401 | virtual ~WindyTexture() override {} 1402 | virtual TextureType type() const override { return TextureType::Windy; } 1403 | }; 1404 | 1405 | 1406 | struct WrinkledTexture : public Texture3D { 1407 | int octaves = 8; 1408 | float roughness = 0.5f; 1409 | 1410 | virtual ~WrinkledTexture() override {} 1411 | virtual TextureType type() const override { return TextureType::Wrinkled; } 1412 | }; 1413 | 1414 | 1415 | struct PTexTexture : public Texture2D { 1416 | char* filename = nullptr; 1417 | float gamma = 2.2f; 1418 | 1419 | virtual ~PTexTexture() override { delete[] filename; } 1420 | virtual TextureType type() const override { return TextureType::PTex; } 1421 | }; 1422 | 1423 | 1424 | // 1425 | // Object instancing types 1426 | // 1427 | 1428 | struct Object { 1429 | const char* name = nullptr; 1430 | Transform objectToInstance; 1431 | uint32_t firstShape = kInvalidIndex; 1432 | unsigned int numShapes = 0; 1433 | 1434 | ~Object() { 1435 | delete[] name; 1436 | } 1437 | }; 1438 | 1439 | 1440 | struct Instance { 1441 | Transform instanceToWorld; 1442 | uint32_t object = kInvalidIndex; //!< The object that this is an instance of. 1443 | uint32_t areaLight = kInvalidIndex; //!< If non-null, the instance emits light as described by this area light object. 1444 | uint32_t insideMedium = kInvalidIndex; 1445 | uint32_t outsideMedium = kInvalidIndex; 1446 | bool reverseOrientation = false; 1447 | }; 1448 | 1449 | 1450 | // 1451 | // Scene types 1452 | // 1453 | 1454 | struct Scene { 1455 | float startTime = 0.0f; 1456 | float endTime = 0.0f; 1457 | 1458 | Accelerator* accelerator = nullptr; 1459 | Camera* camera = nullptr; 1460 | Film* film = nullptr; 1461 | Filter* filter = nullptr; 1462 | Integrator* integrator = nullptr; 1463 | Sampler* sampler = nullptr; 1464 | 1465 | Medium* outsideMedium = nullptr; // Borrowed pointer 1466 | 1467 | std::vector shapes; 1468 | std::vector objects; 1469 | std::vector instances; 1470 | std::vector lights; 1471 | std::vector areaLights; 1472 | std::vector materials; 1473 | std::vector textures; 1474 | std::vector mediums; 1475 | 1476 | Scene(); 1477 | ~Scene(); 1478 | 1479 | /// Call `triangle_mesh()` on the shape at index i in the list. If the 1480 | /// result is non-null, replace the original shape with the new triangle 1481 | /// mesh. Return value indicates whether the original shape was replaced. 1482 | /// 1483 | /// This is safe to call concurrently from multiple threads, as long as 1484 | /// each thread provides a different `shapeIndex` value. 1485 | bool to_triangle_mesh(uint32_t shapeIndex); 1486 | 1487 | /// Convert all shapes with one of the given types into a triangle mesh. 1488 | /// This is equivalent to iterating over the shapes list & calling 1489 | /// `to_triangle_mesh()` on every item with a type contained in 1490 | /// `typesToConvert`. The return value is `true` if we were able to convert 1491 | /// all relevant shapes successfully, or `false` if there were any we 1492 | /// couldn't convert. 1493 | bool shapes_to_triangle_mesh(Bits typesToConvert, bool stopOnFirstError=true); 1494 | 1495 | /// Convert all shapes to triangle meshes. 1496 | bool all_to_triangle_mesh(bool stopOnFirstError=true); 1497 | 1498 | /// Shorthand for `shapes_to_triangle_mesh(ShapeType::PLYMesh)`. 1499 | bool load_all_ply_meshes(bool stopOnFirstError=true); 1500 | }; 1501 | 1502 | 1503 | // 1504 | // Error struct declaration 1505 | // 1506 | 1507 | /// The class used to represent an error during parsing. It records where in 1508 | /// the input file(s) the error occurred. 1509 | class Error { 1510 | public: 1511 | // Error takes a copy of `theFilename`, but takes ownership of `theMessage`. 1512 | Error(const char* theFilename, int64_t theOffset, const char* theMessage); 1513 | ~Error(); 1514 | 1515 | const char* filename() const; 1516 | const char* message() const; 1517 | int64_t offset() const; 1518 | int64_t line() const; 1519 | int64_t column() const; 1520 | 1521 | bool has_line_and_column() const; 1522 | void set_line_and_column(int64_t theLine, int64_t theColumn); 1523 | 1524 | int compare(const Error& rhs) const; 1525 | 1526 | private: 1527 | const char* m_filename = nullptr; //!< Name of the file the error occurred in. 1528 | const char* m_message = nullptr; //!< The error message. 1529 | int64_t m_offset = 0; //!< Char offset within the file at which the error occurred. 1530 | int64_t m_line = 0; //!< Line number the error occurred on. The first line in a file is 1, 0 indicates the line number hasn't been calculated yet. 1531 | int64_t m_column = 0; //!< Column number the error occurred on. The first column is 1, 0 indicates the column number hasn't been calculated yet. 1532 | }; 1533 | 1534 | 1535 | // 1536 | // Loader class declaration 1537 | // 1538 | 1539 | class Loader { 1540 | public: 1541 | Loader(); 1542 | ~Loader(); 1543 | 1544 | void set_buffer_capacity(size_t n); 1545 | void set_max_include_depth(uint32_t n); 1546 | 1547 | bool load(const char* filename); 1548 | 1549 | Scene* take_scene(); 1550 | Scene* borrow_scene(); 1551 | 1552 | const Error* error() const; 1553 | 1554 | private: 1555 | Scene* m_scene; 1556 | Parser* m_parser; 1557 | }; 1558 | 1559 | } // namespace minipbrt 1560 | 1561 | #endif // MINIPBRT_H 1562 | --------------------------------------------------------------------------------