├── .gitattributes ├── .gitignore ├── README.md ├── compile.bat ├── compile_and_run.bat ├── external ├── tiny_obj_loader.cc └── tiny_obj_loader.h ├── models ├── GP.mtl └── GP.obj ├── run.bat ├── shell.bat ├── src ├── CIE.h ├── main.cpp └── model.h └── thumbnail.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | external/glm/* 3 | external/embree2/* 4 | lib/* 5 | .vscode/* 6 | 7 | *.ppm 8 | *.dll 9 | *.lib -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Albedo 2 | === 3 | 4 | A community spectral CPU path tracer, made by the Graphics Programming Discord. This is designed to be a path tracer that people can learn from. 5 | 6 | Please contribute. If this project doesn't make progress at least once a year, deccer bulies me 😢. Help stop the violence. 7 | 8 | ![](thumbnail.png) 9 | (inb4 "all I see is noise") 10 | 11 | To-do List 12 | --- 13 | 14 | - Ray tracer 15 | - BDPT, MIS, and much much more! 16 | - More and better sampling strategies 17 | - Simple lenses and film 18 | - Tonemapper 19 | - Post-process denoiser 20 | - Models and materials 21 | - Use a better material system than bell curves 22 | - Microfacet importance sampling 23 | - System 24 | - Some UI showing current render/progress 25 | - Output to .png instead of ppm -------------------------------------------------------------------------------- /compile.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set FLAGS=/EHsc /O2 /Zi 4 | set LIBRARIES=embree.lib 5 | 6 | mkdir build 7 | pushd build 8 | cl -nologo /c ../external/tiny_obj_loader.cc %FLAGS% 9 | cl -nologo ../src/main.cpp tiny_obj_loader.obj %FLAGS% /I ../external %LIBRARIES% /link /LIBPATH:../lib 10 | popd 11 | -------------------------------------------------------------------------------- /compile_and_run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo ===== CLEANING ===== 4 | rmdir build /s /q 5 | 6 | echo ===== COMPILING ===== 7 | call compile.bat 8 | 9 | echo ===== RUNNING ===== 10 | call run.bat -------------------------------------------------------------------------------- /external/tiny_obj_loader.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012-2015, Syoyo Fujita. 3 | // 4 | // Licensed under 2-clause BSD liecense. 5 | // 6 | 7 | // 8 | // version 0.9.13: Report "Material file not found message" in `err`(#46) 9 | // version 0.9.12: Fix groups being ignored if they have 'usemtl' just before 'g' (#44) 10 | // version 0.9.11: Invert `Tr` parameter(#43) 11 | // version 0.9.10: Fix seg fault on windows. 12 | // version 0.9.9 : Replace atof() with custom parser. 13 | // version 0.9.8 : Fix multi-materials(per-face material ID). 14 | // version 0.9.7 : Support multi-materials(per-face material ID) per 15 | // object/group. 16 | // version 0.9.6 : Support Ni(index of refraction) mtl parameter. 17 | // Parse transmittance material parameter correctly. 18 | // version 0.9.5 : Parse multiple group name. 19 | // Add support of specifying the base path to load material file. 20 | // version 0.9.4 : Initial suupport of group tag(g) 21 | // version 0.9.3 : Fix parsing triple 'x/y/z' 22 | // version 0.9.2 : Add more .mtl load support 23 | // version 0.9.1 : Add initial .mtl load support 24 | // version 0.9.0 : Initial 25 | // 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "tiny_obj_loader.h" 40 | 41 | namespace tinyobj { 42 | 43 | #define TINYOBJ_SSCANF_BUFFER_SIZE (4096) 44 | 45 | struct vertex_index { 46 | int v_idx, vt_idx, vn_idx; 47 | vertex_index(){}; 48 | vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx){}; 49 | vertex_index(int vidx, int vtidx, int vnidx) 50 | : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx){}; 51 | }; 52 | // for std::map 53 | static inline bool operator<(const vertex_index &a, const vertex_index &b) { 54 | if (a.v_idx != b.v_idx) 55 | return (a.v_idx < b.v_idx); 56 | if (a.vn_idx != b.vn_idx) 57 | return (a.vn_idx < b.vn_idx); 58 | if (a.vt_idx != b.vt_idx) 59 | return (a.vt_idx < b.vt_idx); 60 | 61 | return false; 62 | } 63 | 64 | struct obj_shape { 65 | std::vector v; 66 | std::vector vn; 67 | std::vector vt; 68 | }; 69 | 70 | static inline bool isSpace(const char c) { return (c == ' ') || (c == '\t'); } 71 | 72 | static inline bool isNewLine(const char c) { 73 | return (c == '\r') || (c == '\n') || (c == '\0'); 74 | } 75 | 76 | // Make index zero-base, and also support relative index. 77 | static inline int fixIndex(int idx, int n) { 78 | if (idx > 0) return idx - 1; 79 | if (idx == 0) return 0; 80 | return n + idx; // negative value = relative 81 | } 82 | 83 | static inline std::string parseString(const char *&token) { 84 | std::string s; 85 | token += strspn(token, " \t"); 86 | size_t e = strcspn(token, " \t\r"); 87 | s = std::string(token, &token[e]); 88 | token += e; 89 | return s; 90 | } 91 | 92 | static inline int parseInt(const char *&token) { 93 | token += strspn(token, " \t"); 94 | int i = atoi(token); 95 | token += strcspn(token, " \t\r"); 96 | return i; 97 | } 98 | 99 | 100 | // Tries to parse a floating point number located at s. 101 | // 102 | // s_end should be a location in the string where reading should absolutely 103 | // stop. For example at the end of the string, to prevent buffer overflows. 104 | // 105 | // Parses the following EBNF grammar: 106 | // sign = "+" | "-" ; 107 | // END = ? anything not in digit ? 108 | // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; 109 | // integer = [sign] , digit , {digit} ; 110 | // decimal = integer , ["." , integer] ; 111 | // float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; 112 | // 113 | // Valid strings are for example: 114 | // -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 115 | // 116 | // If the parsing is a success, result is set to the parsed value and true 117 | // is returned. 118 | // 119 | // The function is greedy and will parse until any of the following happens: 120 | // - a non-conforming character is encountered. 121 | // - s_end is reached. 122 | // 123 | // The following situations triggers a failure: 124 | // - s >= s_end. 125 | // - parse failure. 126 | // 127 | static bool tryParseDouble(const char *s, const char *s_end, double *result) 128 | { 129 | if (s >= s_end) 130 | { 131 | return false; 132 | } 133 | 134 | double mantissa = 0.0; 135 | // This exponent is base 2 rather than 10. 136 | // However the exponent we parse is supposed to be one of ten, 137 | // thus we must take care to convert the exponent/and or the 138 | // mantissa to a * 2^E, where a is the mantissa and E is the 139 | // exponent. 140 | // To get the final double we will use ldexp, it requires the 141 | // exponent to be in base 2. 142 | int exponent = 0; 143 | 144 | // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED 145 | // TO JUMP OVER DEFINITIONS. 146 | char sign = '+'; 147 | char exp_sign = '+'; 148 | char const *curr = s; 149 | 150 | // How many characters were read in a loop. 151 | int read = 0; 152 | // Tells whether a loop terminated due to reaching s_end. 153 | bool end_not_reached = false; 154 | 155 | /* 156 | BEGIN PARSING. 157 | */ 158 | 159 | // Find out what sign we've got. 160 | if (*curr == '+' || *curr == '-') 161 | { 162 | sign = *curr; 163 | curr++; 164 | } 165 | else if (isdigit(*curr)) { /* Pass through. */ } 166 | else 167 | { 168 | goto fail; 169 | } 170 | 171 | // Read the integer part. 172 | while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) 173 | { 174 | mantissa *= 10; 175 | mantissa += static_cast(*curr - 0x30); 176 | curr++; read++; 177 | } 178 | 179 | // We must make sure we actually got something. 180 | if (read == 0) 181 | goto fail; 182 | // We allow numbers of form "#", "###" etc. 183 | if (!end_not_reached) 184 | goto assemble; 185 | 186 | // Read the decimal part. 187 | if (*curr == '.') 188 | { 189 | curr++; 190 | read = 1; 191 | while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) 192 | { 193 | // NOTE: Don't use powf here, it will absolutely murder precision. 194 | mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); 195 | read++; curr++; 196 | } 197 | } 198 | else if (*curr == 'e' || *curr == 'E') {} 199 | else 200 | { 201 | goto assemble; 202 | } 203 | 204 | if (!end_not_reached) 205 | goto assemble; 206 | 207 | // Read the exponent part. 208 | if (*curr == 'e' || *curr == 'E') 209 | { 210 | curr++; 211 | // Figure out if a sign is present and if it is. 212 | if ((end_not_reached = (curr != s_end)) && (*curr == '+' || *curr == '-')) 213 | { 214 | exp_sign = *curr; 215 | curr++; 216 | } 217 | else if (isdigit(*curr)) { /* Pass through. */ } 218 | else 219 | { 220 | // Empty E is not allowed. 221 | goto fail; 222 | } 223 | 224 | read = 0; 225 | while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) 226 | { 227 | exponent *= 10; 228 | exponent += static_cast(*curr - 0x30); 229 | curr++; read++; 230 | } 231 | exponent *= (exp_sign == '+'? 1 : -1); 232 | if (read == 0) 233 | goto fail; 234 | } 235 | 236 | assemble: 237 | *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); 238 | return true; 239 | fail: 240 | return false; 241 | } 242 | static inline float parseFloat(const char *&token) { 243 | token += strspn(token, " \t"); 244 | #ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER 245 | float f = (float)atof(token); 246 | token += strcspn(token, " \t\r"); 247 | #else 248 | const char *end = token + strcspn(token, " \t\r"); 249 | double val = 0.0; 250 | tryParseDouble(token, end, &val); 251 | float f = static_cast(val); 252 | token = end; 253 | #endif 254 | return f; 255 | } 256 | 257 | 258 | static inline void parseFloat2(float &x, float &y, const char *&token) { 259 | x = parseFloat(token); 260 | y = parseFloat(token); 261 | } 262 | 263 | static inline void parseFloat3(float &x, float &y, float &z, 264 | const char *&token) { 265 | x = parseFloat(token); 266 | y = parseFloat(token); 267 | z = parseFloat(token); 268 | } 269 | 270 | // Parse triples: i, i/j/k, i//k, i/j 271 | static vertex_index parseTriple(const char *&token, int vsize, int vnsize, 272 | int vtsize) { 273 | vertex_index vi(-1); 274 | 275 | vi.v_idx = fixIndex(atoi(token), vsize); 276 | token += strcspn(token, "/ \t\r"); 277 | if (token[0] != '/') { 278 | return vi; 279 | } 280 | token++; 281 | 282 | // i//k 283 | if (token[0] == '/') { 284 | token++; 285 | vi.vn_idx = fixIndex(atoi(token), vnsize); 286 | token += strcspn(token, "/ \t\r"); 287 | return vi; 288 | } 289 | 290 | // i/j/k or i/j 291 | vi.vt_idx = fixIndex(atoi(token), vtsize); 292 | token += strcspn(token, "/ \t\r"); 293 | if (token[0] != '/') { 294 | return vi; 295 | } 296 | 297 | // i/j/k 298 | token++; // skip '/' 299 | vi.vn_idx = fixIndex(atoi(token), vnsize); 300 | token += strcspn(token, "/ \t\r"); 301 | return vi; 302 | } 303 | 304 | static unsigned int 305 | updateVertex(std::map &vertexCache, 306 | std::vector &positions, std::vector &normals, 307 | std::vector &texcoords, 308 | const std::vector &in_positions, 309 | const std::vector &in_normals, 310 | const std::vector &in_texcoords, const vertex_index &i) { 311 | const std::map::iterator it = vertexCache.find(i); 312 | 313 | if (it != vertexCache.end()) { 314 | // found cache 315 | return it->second; 316 | } 317 | 318 | assert(in_positions.size() > (unsigned int)(3 * i.v_idx + 2)); 319 | 320 | positions.push_back(in_positions[3 * i.v_idx + 0]); 321 | positions.push_back(in_positions[3 * i.v_idx + 1]); 322 | positions.push_back(in_positions[3 * i.v_idx + 2]); 323 | 324 | if (i.vn_idx >= 0) { 325 | normals.push_back(in_normals[3 * i.vn_idx + 0]); 326 | normals.push_back(in_normals[3 * i.vn_idx + 1]); 327 | normals.push_back(in_normals[3 * i.vn_idx + 2]); 328 | } 329 | 330 | if (i.vt_idx >= 0) { 331 | texcoords.push_back(in_texcoords[2 * i.vt_idx + 0]); 332 | texcoords.push_back(in_texcoords[2 * i.vt_idx + 1]); 333 | } 334 | 335 | unsigned int idx = static_cast(positions.size() / 3 - 1); 336 | vertexCache[i] = idx; 337 | 338 | return idx; 339 | } 340 | 341 | void InitMaterial(material_t &material) { 342 | material.name = ""; 343 | material.ambient_texname = ""; 344 | material.diffuse_texname = ""; 345 | material.specular_texname = ""; 346 | material.normal_texname = ""; 347 | for (int i = 0; i < 3; i++) { 348 | material.ambient[i] = 0.f; 349 | material.diffuse[i] = 0.f; 350 | material.specular[i] = 0.f; 351 | material.transmittance[i] = 0.f; 352 | material.emission[i] = 0.f; 353 | } 354 | material.illum = 0; 355 | material.dissolve = 1.f; 356 | material.shininess = 1.f; 357 | material.ior = 1.f; 358 | material.unknown_parameter.clear(); 359 | } 360 | 361 | static bool exportFaceGroupToShape( 362 | shape_t &shape, std::map vertexCache, 363 | const std::vector &in_positions, 364 | const std::vector &in_normals, 365 | const std::vector &in_texcoords, 366 | const std::vector > &faceGroup, 367 | const int material_id, const std::string &name, bool clearCache) { 368 | if (faceGroup.empty()) { 369 | return false; 370 | } 371 | 372 | // Flatten vertices and indices 373 | for (size_t i = 0; i < faceGroup.size(); i++) { 374 | const std::vector &face = faceGroup[i]; 375 | 376 | vertex_index i0 = face[0]; 377 | vertex_index i1(-1); 378 | vertex_index i2 = face[1]; 379 | 380 | size_t npolys = face.size(); 381 | 382 | // Polygon -> triangle fan conversion 383 | for (size_t k = 2; k < npolys; k++) { 384 | i1 = i2; 385 | i2 = face[k]; 386 | 387 | unsigned int v0 = updateVertex( 388 | vertexCache, shape.mesh.positions, shape.mesh.normals, 389 | shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); 390 | unsigned int v1 = updateVertex( 391 | vertexCache, shape.mesh.positions, shape.mesh.normals, 392 | shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); 393 | unsigned int v2 = updateVertex( 394 | vertexCache, shape.mesh.positions, shape.mesh.normals, 395 | shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); 396 | 397 | shape.mesh.indices.push_back(v0); 398 | shape.mesh.indices.push_back(v1); 399 | shape.mesh.indices.push_back(v2); 400 | 401 | shape.mesh.material_ids.push_back(material_id); 402 | } 403 | } 404 | 405 | shape.name = name; 406 | 407 | if (clearCache) 408 | vertexCache.clear(); 409 | 410 | return true; 411 | } 412 | 413 | std::string LoadMtl(std::map &material_map, 414 | std::vector &materials, 415 | std::istream &inStream) { 416 | std::stringstream err; 417 | 418 | // Create a default material anyway. 419 | material_t material; 420 | InitMaterial(material); 421 | 422 | int maxchars = 8192; // Alloc enough size. 423 | std::vector buf(maxchars); // Alloc enough size. 424 | while (inStream.peek() != -1) { 425 | inStream.getline(&buf[0], maxchars); 426 | 427 | std::string linebuf(&buf[0]); 428 | 429 | // Trim newline '\r\n' or '\n' 430 | if (linebuf.size() > 0) { 431 | if (linebuf[linebuf.size() - 1] == '\n') 432 | linebuf.erase(linebuf.size() - 1); 433 | } 434 | if (linebuf.size() > 0) { 435 | if (linebuf[linebuf.size() - 1] == '\r') 436 | linebuf.erase(linebuf.size() - 1); 437 | } 438 | 439 | // Skip if empty line. 440 | if (linebuf.empty()) { 441 | continue; 442 | } 443 | 444 | // Skip leading space. 445 | const char *token = linebuf.c_str(); 446 | token += strspn(token, " \t"); 447 | 448 | assert(token); 449 | if (token[0] == '\0') 450 | continue; // empty line 451 | 452 | if (token[0] == '#') 453 | continue; // comment line 454 | 455 | // new mtl 456 | if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { 457 | // flush previous material. 458 | if (!material.name.empty()) { 459 | material_map.insert( 460 | std::pair(material.name, static_cast(materials.size()))); 461 | materials.push_back(material); 462 | } 463 | 464 | // initial temporary material 465 | InitMaterial(material); 466 | 467 | // set new mtl name 468 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 469 | token += 7; 470 | #ifdef _MSC_VER 471 | sscanf_s(token, "%s", namebuf, _countof(namebuf)); 472 | #else 473 | sscanf(token, "%s", namebuf); 474 | #endif 475 | material.name = namebuf; 476 | continue; 477 | } 478 | 479 | // ambient 480 | if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { 481 | token += 2; 482 | float r, g, b; 483 | parseFloat3(r, g, b, token); 484 | material.ambient[0] = r; 485 | material.ambient[1] = g; 486 | material.ambient[2] = b; 487 | continue; 488 | } 489 | 490 | // diffuse 491 | if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { 492 | token += 2; 493 | float r, g, b; 494 | parseFloat3(r, g, b, token); 495 | material.diffuse[0] = r; 496 | material.diffuse[1] = g; 497 | material.diffuse[2] = b; 498 | continue; 499 | } 500 | 501 | // specular 502 | if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { 503 | token += 2; 504 | float r, g, b; 505 | parseFloat3(r, g, b, token); 506 | material.specular[0] = r; 507 | material.specular[1] = g; 508 | material.specular[2] = b; 509 | continue; 510 | } 511 | 512 | // transmittance 513 | if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { 514 | token += 2; 515 | float r, g, b; 516 | parseFloat3(r, g, b, token); 517 | material.transmittance[0] = r; 518 | material.transmittance[1] = g; 519 | material.transmittance[2] = b; 520 | continue; 521 | } 522 | 523 | // ior(index of refraction) 524 | if (token[0] == 'N' && token[1] == 'i' && isSpace((token[2]))) { 525 | token += 2; 526 | material.ior = parseFloat(token); 527 | continue; 528 | } 529 | 530 | // emission 531 | if (token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { 532 | token += 2; 533 | float r, g, b; 534 | parseFloat3(r, g, b, token); 535 | material.emission[0] = r; 536 | material.emission[1] = g; 537 | material.emission[2] = b; 538 | continue; 539 | } 540 | 541 | // shininess 542 | if (token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { 543 | token += 2; 544 | material.shininess = parseFloat(token); 545 | continue; 546 | } 547 | 548 | // illum model 549 | if (0 == strncmp(token, "illum", 5) && isSpace(token[5])) { 550 | token += 6; 551 | material.illum = parseInt(token); 552 | continue; 553 | } 554 | 555 | // dissolve 556 | if ((token[0] == 'd' && isSpace(token[1]))) { 557 | token += 1; 558 | material.dissolve = parseFloat(token); 559 | continue; 560 | } 561 | if (token[0] == 'T' && token[1] == 'r' && isSpace(token[2])) { 562 | token += 2; 563 | // Invert value of Tr(assume Tr is in range [0, 1]) 564 | material.dissolve = 1.0 - parseFloat(token); 565 | continue; 566 | } 567 | 568 | // ambient texture 569 | if ((0 == strncmp(token, "map_Ka", 6)) && isSpace(token[6])) { 570 | token += 7; 571 | material.ambient_texname = token; 572 | continue; 573 | } 574 | 575 | // diffuse texture 576 | if ((0 == strncmp(token, "map_Kd", 6)) && isSpace(token[6])) { 577 | token += 7; 578 | material.diffuse_texname = token; 579 | continue; 580 | } 581 | 582 | // specular texture 583 | if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { 584 | token += 7; 585 | material.specular_texname = token; 586 | continue; 587 | } 588 | 589 | // normal texture 590 | if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { 591 | token += 7; 592 | material.normal_texname = token; 593 | continue; 594 | } 595 | 596 | // unknown parameter 597 | const char *_space = strchr(token, ' '); 598 | if (!_space) { 599 | _space = strchr(token, '\t'); 600 | } 601 | if (_space) { 602 | std::ptrdiff_t len = _space - token; 603 | std::string key(token, len); 604 | std::string value = _space + 1; 605 | material.unknown_parameter.insert( 606 | std::pair(key, value)); 607 | } 608 | } 609 | // flush last material. 610 | material_map.insert( 611 | std::pair(material.name, static_cast(materials.size()))); 612 | materials.push_back(material); 613 | 614 | return err.str(); 615 | } 616 | 617 | std::string MaterialFileReader::operator()(const std::string &matId, 618 | std::vector &materials, 619 | std::map &matMap) { 620 | std::string filepath; 621 | 622 | if (!m_mtlBasePath.empty()) { 623 | filepath = std::string(m_mtlBasePath) + matId; 624 | } else { 625 | filepath = matId; 626 | } 627 | 628 | std::ifstream matIStream(filepath.c_str()); 629 | std::string err = LoadMtl(matMap, materials, matIStream); 630 | if (!matIStream) { 631 | std::stringstream ss; 632 | ss << "WARN: Material file [ " << filepath << " ] not found. Created a default material."; 633 | err += ss.str(); 634 | } 635 | return err; 636 | } 637 | 638 | std::string LoadObj(std::vector &shapes, 639 | std::vector &materials, // [output] 640 | const char *filename, const char *mtl_basepath) { 641 | 642 | shapes.clear(); 643 | 644 | std::stringstream err; 645 | 646 | std::ifstream ifs(filename); 647 | if (!ifs) { 648 | err << "Cannot open file [" << filename << "]" << std::endl; 649 | return err.str(); 650 | } 651 | 652 | std::string basePath; 653 | if (mtl_basepath) { 654 | basePath = mtl_basepath; 655 | } 656 | MaterialFileReader matFileReader(basePath); 657 | 658 | return LoadObj(shapes, materials, ifs, matFileReader); 659 | } 660 | 661 | std::string LoadObj(std::vector &shapes, 662 | std::vector &materials, // [output] 663 | std::istream &inStream, MaterialReader &readMatFn) { 664 | std::stringstream err; 665 | 666 | std::vector v; 667 | std::vector vn; 668 | std::vector vt; 669 | std::vector > faceGroup; 670 | std::string name; 671 | 672 | // material 673 | std::map material_map; 674 | std::map vertexCache; 675 | int material = -1; 676 | 677 | shape_t shape; 678 | 679 | int maxchars = 8192; // Alloc enough size. 680 | std::vector buf(maxchars); // Alloc enough size. 681 | while (inStream.peek() != -1) { 682 | inStream.getline(&buf[0], maxchars); 683 | 684 | std::string linebuf(&buf[0]); 685 | 686 | // Trim newline '\r\n' or '\n' 687 | if (linebuf.size() > 0) { 688 | if (linebuf[linebuf.size() - 1] == '\n') 689 | linebuf.erase(linebuf.size() - 1); 690 | } 691 | if (linebuf.size() > 0) { 692 | if (linebuf[linebuf.size() - 1] == '\r') 693 | linebuf.erase(linebuf.size() - 1); 694 | } 695 | 696 | // Skip if empty line. 697 | if (linebuf.empty()) { 698 | continue; 699 | } 700 | 701 | // Skip leading space. 702 | const char *token = linebuf.c_str(); 703 | token += strspn(token, " \t"); 704 | 705 | assert(token); 706 | if (token[0] == '\0') 707 | continue; // empty line 708 | 709 | if (token[0] == '#') 710 | continue; // comment line 711 | 712 | // vertex 713 | if (token[0] == 'v' && isSpace((token[1]))) { 714 | token += 2; 715 | float x, y, z; 716 | parseFloat3(x, y, z, token); 717 | v.push_back(x); 718 | v.push_back(y); 719 | v.push_back(z); 720 | continue; 721 | } 722 | 723 | // normal 724 | if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) { 725 | token += 3; 726 | float x, y, z; 727 | parseFloat3(x, y, z, token); 728 | vn.push_back(x); 729 | vn.push_back(y); 730 | vn.push_back(z); 731 | continue; 732 | } 733 | 734 | // texcoord 735 | if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) { 736 | token += 3; 737 | float x, y; 738 | parseFloat2(x, y, token); 739 | vt.push_back(x); 740 | vt.push_back(y); 741 | continue; 742 | } 743 | 744 | // face 745 | if (token[0] == 'f' && isSpace((token[1]))) { 746 | token += 2; 747 | token += strspn(token, " \t"); 748 | 749 | std::vector face; 750 | while (!isNewLine(token[0])) { 751 | vertex_index vi = 752 | parseTriple(token, static_cast(v.size() / 3), static_cast(vn.size() / 3), static_cast(vt.size() / 2)); 753 | face.push_back(vi); 754 | size_t n = strspn(token, " \t\r"); 755 | token += n; 756 | } 757 | 758 | faceGroup.push_back(face); 759 | 760 | continue; 761 | } 762 | 763 | // use mtl 764 | if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { 765 | 766 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 767 | token += 7; 768 | #ifdef _MSC_VER 769 | sscanf_s(token, "%s", namebuf, _countof(namebuf)); 770 | #else 771 | sscanf(token, "%s", namebuf); 772 | #endif 773 | 774 | // Create face group per material. 775 | bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, 776 | faceGroup, material, name, true); 777 | if (ret) { 778 | shapes.push_back(shape); 779 | } 780 | shape = shape_t(); 781 | faceGroup.clear(); 782 | 783 | if (material_map.find(namebuf) != material_map.end()) { 784 | material = material_map[namebuf]; 785 | } else { 786 | // { error!! material not found } 787 | material = -1; 788 | } 789 | 790 | continue; 791 | } 792 | 793 | // load mtl 794 | if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { 795 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 796 | token += 7; 797 | #ifdef _MSC_VER 798 | sscanf_s(token, "%s", namebuf, _countof(namebuf)); 799 | #else 800 | sscanf(token, "%s", namebuf); 801 | #endif 802 | 803 | std::string err_mtl = readMatFn(namebuf, materials, material_map); 804 | if (!err_mtl.empty()) { 805 | faceGroup.clear(); // for safety 806 | return err_mtl; 807 | } 808 | 809 | continue; 810 | } 811 | 812 | // group name 813 | if (token[0] == 'g' && isSpace((token[1]))) { 814 | 815 | // flush previous face group. 816 | bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, 817 | faceGroup, material, name, true); 818 | if (ret) { 819 | shapes.push_back(shape); 820 | } 821 | 822 | shape = shape_t(); 823 | 824 | // material = -1; 825 | faceGroup.clear(); 826 | 827 | std::vector names; 828 | while (!isNewLine(token[0])) { 829 | std::string str = parseString(token); 830 | names.push_back(str); 831 | token += strspn(token, " \t\r"); // skip tag 832 | } 833 | 834 | assert(names.size() > 0); 835 | 836 | // names[0] must be 'g', so skip the 0th element. 837 | if (names.size() > 1) { 838 | name = names[1]; 839 | } else { 840 | name = ""; 841 | } 842 | 843 | continue; 844 | } 845 | 846 | // object name 847 | if (token[0] == 'o' && isSpace((token[1]))) { 848 | 849 | // flush previous face group. 850 | bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, 851 | faceGroup, material, name, true); 852 | if (ret) { 853 | shapes.push_back(shape); 854 | } 855 | 856 | // material = -1; 857 | faceGroup.clear(); 858 | shape = shape_t(); 859 | 860 | // @todo { multiple object name? } 861 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 862 | token += 2; 863 | #ifdef _MSC_VER 864 | sscanf_s(token, "%s", namebuf, _countof(namebuf)); 865 | #else 866 | sscanf(token, "%s", namebuf); 867 | #endif 868 | name = std::string(namebuf); 869 | 870 | continue; 871 | } 872 | 873 | // Ignore unknown command. 874 | } 875 | 876 | bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, 877 | material, name, true); 878 | if (ret) { 879 | shapes.push_back(shape); 880 | } 881 | faceGroup.clear(); // for safety 882 | 883 | return err.str(); 884 | } 885 | } 886 | -------------------------------------------------------------------------------- /external/tiny_obj_loader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012-2015, Syoyo Fujita. 3 | // 4 | // Licensed under 2-clause BSD liecense. 5 | // 6 | #ifndef _TINY_OBJ_LOADER_H 7 | #define _TINY_OBJ_LOADER_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace tinyobj { 14 | 15 | typedef struct { 16 | std::string name; 17 | 18 | float ambient[3]; 19 | float diffuse[3]; 20 | float specular[3]; 21 | float transmittance[3]; 22 | float emission[3]; 23 | float shininess; 24 | float ior; // index of refraction 25 | float dissolve; // 1 == opaque; 0 == fully transparent 26 | // illumination model (see http://www.fileformat.info/format/material/) 27 | int illum; 28 | 29 | std::string ambient_texname; 30 | std::string diffuse_texname; 31 | std::string specular_texname; 32 | std::string normal_texname; 33 | std::map unknown_parameter; 34 | } material_t; 35 | 36 | typedef struct { 37 | std::vector positions; 38 | std::vector normals; 39 | std::vector texcoords; 40 | std::vector indices; 41 | std::vector material_ids; // per-mesh material ID 42 | } mesh_t; 43 | 44 | typedef struct { 45 | std::string name; 46 | mesh_t mesh; 47 | } shape_t; 48 | 49 | class MaterialReader { 50 | public: 51 | MaterialReader() {} 52 | virtual ~MaterialReader() {} 53 | 54 | virtual std::string operator()(const std::string &matId, 55 | std::vector &materials, 56 | std::map &matMap) = 0; 57 | }; 58 | 59 | class MaterialFileReader : public MaterialReader { 60 | public: 61 | MaterialFileReader(const std::string &mtl_basepath) 62 | : m_mtlBasePath(mtl_basepath) {} 63 | virtual ~MaterialFileReader() {} 64 | virtual std::string operator()(const std::string &matId, 65 | std::vector &materials, 66 | std::map &matMap); 67 | 68 | private: 69 | std::string m_mtlBasePath; 70 | }; 71 | 72 | /// Loads .obj from a file. 73 | /// 'shapes' will be filled with parsed shape data 74 | /// The function returns error string. 75 | /// Returns empty string when loading .obj success. 76 | /// 'mtl_basepath' is optional, and used for base path for .mtl file. 77 | std::string LoadObj(std::vector &shapes, // [output] 78 | std::vector &materials, // [output] 79 | const char *filename, const char *mtl_basepath = NULL); 80 | 81 | /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve 82 | /// std::istream for materials. 83 | /// Returns empty string when loading .obj success. 84 | std::string LoadObj(std::vector &shapes, // [output] 85 | std::vector &materials, // [output] 86 | std::istream &inStream, MaterialReader &readMatFn); 87 | 88 | /// Loads materials into std::map 89 | /// Returns an empty string if successful 90 | std::string LoadMtl(std::map &material_map, 91 | std::vector &materials, std::istream &inStream); 92 | } 93 | 94 | #endif // _TINY_OBJ_LOADER_H 95 | -------------------------------------------------------------------------------- /models/GP.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 6 3 | 4 | newmtl back_wall_material 5 | Ns 3.921569 6 | Ka 0.000000 0.000000 0.000000 7 | Kd 0.226584 0.222021 0.212892 8 | Ks 0.000000 0.000000 0.000000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl dragon_material 15 | Ns 0.000000 16 | Ka 0.000000 0.000000 0.000000 17 | Kd 0.800000 0.800000 0.800000 18 | Ks 0.800000 0.800000 0.800000 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.000000 21 | d 1.000000 22 | illum 2 23 | 24 | newmtl leftWall 25 | Ns 3.921569 26 | Ka 0.000000 0.000000 0.000000 27 | Kd 0.403200 0.041600 0.032000 28 | Ks 0.000000 0.000000 0.000000 29 | Ke 0.000000 0.000000 0.000000 30 | Ni 1.500000 31 | d 1.000000 32 | illum 2 33 | 34 | newmtl light_material 35 | Ns 96.078431 36 | Ka 0.000000 0.000000 0.000000 37 | Kd 0.000000 0.000000 0.000000 38 | Ks 0.000000 0.000000 0.000000 39 | Ke 64.000000 64.000000 64.000000 40 | Ni 1.000000 41 | d 1.000000 42 | illum 1 43 | 44 | newmtl rightWall 45 | Ns 3.921569 46 | Ka 0.000000 0.000000 0.000000 47 | Kd 0.089600 0.288000 0.058240 48 | Ks 0.000000 0.000000 0.000000 49 | Ke 0.000000 0.000000 0.000000 50 | Ni 1.500000 51 | d 1.000000 52 | illum 2 53 | 54 | newmtl white_wall_material 55 | Ns 3.921569 56 | Ka 0.000000 0.000000 0.000000 57 | Kd 0.464000 0.454400 0.435200 58 | Ks 0.000000 0.000000 0.000000 59 | Ke 0.000000 0.000000 0.000000 60 | Ni 1.000000 61 | d 1.000000 62 | illum 2 63 | -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | build\main.exe -------------------------------------------------------------------------------- /shell.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64 3 | -------------------------------------------------------------------------------- /src/CIE.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // begins at 390 nm, with step size of 5, up to 830 4 | float x_bar[] = { 3.77e-03, 9.38e-03, 2.21e-02, 4.74e-02, 8.95e-02, 1.45e-01, 2.04e-01, 2.49e-01, 2.92e-01, 3.23e-01, 3.48e-01, 3.42e-01, 3.22e-01, 2.83e-01, 2.49e-01, 2.22e-01, 1.81e-01, 1.29e-01, 8.18e-02, 4.60e-02, 2.08e-02, 7.10e-03, 2.46e-03, 3.65e-03, 1.56e-02, 4.32e-02, 7.96e-02, 1.27e-01, 1.82e-01, 2.41e-01, 3.10e-01, 3.80e-01, 4.49e-01, 5.28e-01, 6.13e-01, 7.02e-01, 7.97e-01, 8.85e-01, 9.64e-01, 1.05e00, 1.11e00, 1.14e00, 1.15e00, 1.13e00, 1.08e00, 1.01e00, 9.14e-01, 8.14e-01, 6.92e-01, 5.76e-01, 4.73e-01, 3.84e-01, 3.00e-01, 2.28e-01, 1.71e-01, 1.26e-01, 9.22e-02, 6.64e-02, 4.71e-02, 3.29e-02, 2.26e-02, 1.58e-02, 1.10e-02, 7.61e-03, 5.21e-03, 3.57e-03, 2.46e-03, 1.70e-03, 1.19e-03, 8.27e-04, 5.76e-04, 4.06e-04, 2.86e-04, 2.02e-04, 1.44e-04, 1.02e-04, 7.35e-05, 5.26e-05, 3.81e-05, 2.76e-05, 2.00e-05, 1.46e-05, 1.07e-05, 7.86e-06, 5.77e-06, 4.26e-06, 3.17e-06, 2.36e-06, 1.76e-06 }; 5 | float y_bar[] = { 4.15e-04, 1.06e-03, 2.45e-03, 4.97e-03, 9.08e-03, 1.43e-02, 2.03e-02, 2.61e-02, 3.32e-02, 4.16e-02, 5.03e-02, 5.74e-02, 6.47e-02, 7.24e-02, 8.51e-02, 1.06e-01, 1.30e-01, 1.54e-01, 1.79e-01, 2.06e-01, 2.38e-01, 2.85e-01, 3.48e-01, 4.28e-01, 5.20e-01, 6.21e-01, 7.18e-01, 7.95e-01, 8.58e-01, 9.07e-01, 9.54e-01, 9.81e-01, 9.89e-01, 9.99e-01, 9.97e-01, 9.90e-01, 9.73e-01, 9.42e-01, 8.96e-01, 8.59e-01, 8.12e-01, 7.54e-01, 6.92e-01, 6.27e-01, 5.58e-01, 4.90e-01, 4.23e-01, 3.61e-01, 2.98e-01, 2.42e-01, 1.94e-01, 1.55e-01, 1.19e-01, 8.98e-02, 6.67e-02, 4.90e-02, 3.56e-02, 2.55e-02, 1.81e-02, 1.26e-02, 8.66e-03, 6.03e-03, 4.20e-03, 2.91e-03, 2.00e-03, 1.37e-03, 9.45e-04, 6.54e-04, 4.56e-04, 3.18e-04, 2.22e-04, 1.57e-04, 1.10e-04, 7.83e-05, 5.58e-05, 3.98e-05, 2.86e-05, 2.05e-05, 1.49e-05, 1.08e-05, 7.86e-06, 5.74e-06, 4.21e-06, 3.11e-06, 2.29e-06, 1.69e-06, 1.26e-06, 9.42e-07, 7.05e-07 }; 6 | float z_bar[] = { 1.85e-02, 4.61e-02, 1.10e-01, 2.37e-01, 4.51e-01, 7.38e-01, 1.05e00, 1.31e00, 1.55e00, 1.75e00, 1.92e00, 1.92e00, 1.85e00, 1.66e00, 1.52e00, 1.43e00, 1.25e00, 9.99e-01, 7.55e-01, 5.62e-01, 4.10e-01, 3.11e-01, 2.38e-01, 1.72e-01, 1.18e-01, 8.28e-02, 5.65e-02, 3.75e-02, 2.44e-02, 1.57e-02, 9.85e-03, 6.13e-03, 3.79e-03, 2.33e-03, 1.43e-03, 8.82e-04, 5.45e-04, 3.39e-04, 2.12e-04, 1.34e-04, 8.49e-05, 5.46e-05, 3.55e-05, 2.33e-05, 1.55e-05, 1.05e-05, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00, 0.00e00 }; 7 | 8 | vec3 wavelength_to_xyz(float lambda) 9 | { 10 | int i = (lambda - 390.f) / 5.f; 11 | return vec3(x_bar[i], y_bar[i], z_bar[i]); 12 | } 13 | 14 | vec3 xyz_to_rgb(vec3 xyz) 15 | { 16 | vec3 rgb = mat3( 17 | 3.2406, -0.9689, 0.0557, 18 | -1.5372, 1.8758, -0.2040, 19 | -0.4986, 0.0415, 1.0570) * xyz; 20 | 21 | float correct = min(min(rgb.x, rgb.y), rgb.z); 22 | if (correct > 0.f) 23 | correct = 0; 24 | rgb = rgb - vec3(correct, correct, correct); 25 | return rgb; 26 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using namespace std; 5 | 6 | #include "glm\glm.hpp" 7 | using glm::vec3; 8 | using namespace glm; 9 | 10 | #include 11 | #include 12 | 13 | #include "CIE.h" 14 | #include "model.h" 15 | 16 | const int IMAGE_WIDTH = 400; 17 | const int IMAGE_HEIGHT = 400; 18 | 19 | const int NUM_SAMPLES = 100; 20 | 21 | const float MAX_DIST = 8000; 22 | const float PI = 3.14159; 23 | 24 | vec3 buffer[IMAGE_WIDTH][IMAGE_HEIGHT]; 25 | RTCScene scene; 26 | 27 | float nrand() 28 | { 29 | return (float)rand() / RAND_MAX; 30 | } 31 | 32 | // non-normalized bell curve 33 | float bell(float x, float m, float s) 34 | { 35 | return exp(-(x - m)*(x - m) / (2 * s*s)); 36 | } 37 | float pdf_gaussian(float x, float m, float s) 38 | { 39 | return bell(x, m, s) / (s * sqrt(2 * PI)); 40 | } 41 | float cdf_gaussian(float x, float m, float s) 42 | { 43 | return erfc(-(x - m) / (s * sqrt(2))) / 2; 44 | } 45 | double gaussian_rand(double m, double s) 46 | { 47 | double u1, u2; 48 | do 49 | { 50 | u1 = nrand(); 51 | u2 = nrand(); 52 | } 53 | while ( u1 <= 0.001 ); 54 | 55 | float z0 = sqrt(-2.0 * log(u1)) * cos(2 * PI * u2); 56 | return z0 * s + m; 57 | } 58 | 59 | //----------------------------------------------------------------------------- 60 | // Intersection stuff 61 | //----------------------------------------------------------------------------- 62 | float BRDF(float lambda, material m, vec3 inDir, vec3 outDir) 63 | { 64 | return m.diffuse_albedo * bell(lambda, m.diffuse_mean, m.diffuse_stddev) / PI; 65 | } 66 | float emmision(float lambda, material mat) 67 | { 68 | return mat.light_intensity; 69 | } 70 | 71 | RTCRay make_ray(vec3 o, vec3 dir) 72 | { 73 | RTCRay ray; 74 | ray.org[0] = o.x; ray.org[1] = o.y; ray.org[2] = o.z; 75 | ray.dir[0] = dir.x; ray.dir[1] = dir.y; ray.dir[2] = dir.z; 76 | ray.tnear = 0; 77 | ray.tfar = MAX_DIST; 78 | ray.geomID = RTC_INVALID_GEOMETRY_ID; 79 | return ray; 80 | } 81 | 82 | struct intersection_info 83 | { 84 | float t; 85 | vec2 uv; 86 | vec3 pos; 87 | vec3 normal; 88 | material mat; 89 | }; 90 | void get_intersection_info(vec3 o, vec3 ray, intersection_info* ret) 91 | { 92 | RTCRay rtcNegODir = make_ray(o, ray); 93 | rtcIntersect(scene, rtcNegODir); 94 | 95 | if (rtcNegODir.geomID == -1) 96 | { 97 | ret->t = -1; 98 | return; 99 | } 100 | 101 | ret->t = rtcNegODir.tfar - 0.001f; 102 | 103 | ret->pos = o + ray * ret->t; 104 | ret->normal = normalize(vec3(-rtcNegODir.Ng[0], -rtcNegODir.Ng[1], -rtcNegODir.Ng[2])); 105 | if (dot(ret->normal, ray * -1.0f) < 0) 106 | ret->normal *= -1.0f; 107 | 108 | model m = models[rtcNegODir.geomID]; 109 | ret->mat = m.mat; 110 | } 111 | 112 | //----------------------------------------------------------------------------- 113 | // Coloring and ray tracing stuff 114 | //----------------------------------------------------------------------------- 115 | // returns any vector perpendicular to the norm (or tangent to surface) 116 | vec3 get_tangent(vec3 norm) 117 | { 118 | vec3 tangent; 119 | vec3 c1 = cross(norm, vec3(0, 0, 1)); 120 | vec3 c2 = cross(norm, vec3(0, 1, 0)); 121 | if (dot(c1, c1) > dot(c2, c2)) 122 | tangent = c1; 123 | else 124 | tangent = c2; 125 | return tangent; 126 | } 127 | vec3 rand_cosine_weighted_ray(vec3 norm) 128 | { 129 | float rx = 1, rz = 1; 130 | while (rx*rx + rz*rz >= 1) 131 | { 132 | rx = 2 * nrand() - 1.0f; 133 | rz = 2 * nrand() - 1.0f; 134 | } 135 | float ry = sqrt(1 - rx*rx - rz*rz); 136 | 137 | vec3 tangent = get_tangent(norm); 138 | vec3 bitangent = cross(norm, tangent); 139 | 140 | vec3 castRay = normalize(tangent*rx + bitangent*rz + norm*ry); 141 | return castRay; 142 | } 143 | vec3 rand_hemisphere_ray(vec3 norm) 144 | { 145 | float rx = 1, ry = 1, rz = 1; 146 | while (rx*rx + ry*ry + rz*rz >= 1) 147 | { 148 | rx = 2 * nrand() - 1; 149 | ry = 2 * nrand() - 1; 150 | rz = 2 * nrand() - 1; 151 | } 152 | vec3 ray = vec3(rx, ry, rz); 153 | 154 | ray = normalize(ray); 155 | if (dot(ray, norm) < 0) 156 | ray *= -1; 157 | 158 | return ray; 159 | } 160 | 161 | struct light_path_node 162 | { 163 | vec3 pos; 164 | vec3 normal; 165 | vec3 ray_towards_camera; 166 | vec3 ray_towards_light; 167 | material mat; 168 | 169 | // thing to multiply against emmision to get total weight 170 | // includes this vert's probability, but not thie vert's BRDF and projected area component 171 | float accumulated_weight; 172 | 173 | light_path_node() {} 174 | }; 175 | 176 | // only selects lights now 177 | void construct_light_path(float lambda, vector& path) 178 | { 179 | vec3 o; 180 | vec3 ray; 181 | 182 | // Select a triangle 183 | { 184 | light_triangle t; 185 | float r = nrand() * total_light_area; 186 | for (int i = 0; i < light_triangles.size(); ++i) 187 | { 188 | if (r > light_triangles[i].area && i != light_triangles.size() - 1) 189 | { 190 | r -= light_triangles[i].area; 191 | continue; 192 | } 193 | 194 | t = light_triangles[i]; 195 | break; 196 | } 197 | 198 | float r1 = nrand(); 199 | float r2 = nrand(); 200 | o = t.p0 + r1 * (t.p1 - t.p0) + r2 * (t.p2 - t.p0); 201 | 202 | light_path_node n; 203 | n.pos = o; 204 | n.normal = normalize(cross(t.p1 - t.p0, t.p2 - t.p0)); 205 | //n.ray_towards_camera = rand_hemisphere_ray(n.normal); 206 | n.mat = models[t.model_id].mat; 207 | n.accumulated_weight = emmision(lambda, n.mat) * total_light_area; 208 | path.push_back(n); 209 | 210 | ray = n.ray_towards_camera; 211 | } 212 | } 213 | void construct_camera_path(float lambda, vec3 o, vec3 ray, vector& path) 214 | { 215 | // add origin 216 | { 217 | light_path_node n; 218 | n.pos = o; 219 | n.normal = ray; 220 | n.ray_towards_light = ray; 221 | n.accumulated_weight = 1; 222 | 223 | path.push_back(n); 224 | } 225 | 226 | float accumulated_weight = 1; 227 | while (true) 228 | { 229 | // Intersection 230 | intersection_info info; 231 | get_intersection_info(o, ray, &info); 232 | if (info.t < -0.1) 233 | break; 234 | 235 | vec3 p = info.pos; 236 | vec3 normal = info.normal; 237 | 238 | light_path_node n; 239 | n.pos = p; 240 | n.normal = normal; 241 | n.ray_towards_camera = -ray; 242 | n.ray_towards_light = vec3(0); 243 | n.mat = info.mat; 244 | n.accumulated_weight = accumulated_weight; 245 | path.push_back(n); 246 | light_path_node* cur_n = &(path[path.size() - 1]); // we still need to modify this node a bit 247 | 248 | float emm = emmision(lambda, info.mat); 249 | if (dot(emm, emm) > 0.001) 250 | break; 251 | 252 | vec3 nextRay = rand_cosine_weighted_ray(normal); 253 | float weight = BRDF(lambda, n.mat, nextRay, -ray) * PI; 254 | 255 | cur_n->ray_towards_light = nextRay; 256 | accumulated_weight *= weight; 257 | 258 | // Store data for next iteration 259 | o = p; 260 | ray = nextRay; 261 | 262 | // Russian Roulette 263 | // todo: weight this also by percieved brightness of lambda for good measure 264 | float r = nrand(); 265 | float russian = min(1.f, accumulated_weight); 266 | if (russian < r) 267 | break; 268 | 269 | accumulated_weight /= russian; 270 | } 271 | } 272 | 273 | float radiance(float lambda, vec3 o, vec3 ray) 274 | { 275 | vector camera_path; 276 | camera_path.reserve(10); 277 | construct_camera_path(lambda, o, ray, camera_path); 278 | 279 | light_path_node last_node = camera_path.back(); 280 | 281 | // if we hit a light by chance, just accumulate it 282 | float emm = emmision(lambda, last_node.mat); 283 | if (emm > 0.01) 284 | return emm * last_node.accumulated_weight; 285 | 286 | if (camera_path.size() == 1) 287 | return 0; // it escaped to infinity too early to preempt it 288 | 289 | // so the path didn't hit a light. now let's append a light sample to the path 290 | vector light_path; 291 | construct_light_path(lambda, light_path); 292 | light_path_node light_node = light_path.front(); 293 | 294 | // make sure there's line of sight to the selected light sample 295 | vec3 light_ray = light_node.pos - last_node.pos; 296 | vec3 light_ray_norm = normalize(light_ray); 297 | intersection_info info; 298 | get_intersection_info(last_node.pos, light_ray_norm, &info); 299 | 300 | if (abs(info.t - length(light_ray)) < 0.01) 301 | { 302 | float camera_weight = last_node.accumulated_weight; 303 | float light_weight = light_node.accumulated_weight; 304 | float brdf = BRDF(lambda, last_node.mat, last_node.ray_towards_camera, light_ray_norm); 305 | return brdf * camera_weight * light_weight * 306 | max(0.f, dot(last_node.normal, light_ray_norm) * dot(light_node.normal, light_ray_norm)) / 307 | dot(light_ray, light_ray); 308 | } 309 | 310 | // just give up at this point 311 | return 0; 312 | } 313 | 314 | int main() 315 | { 316 | RTCDevice device = rtcNewDevice(NULL); 317 | scene = rtcDeviceNewScene(device, RTC_SCENE_STATIC, RTC_INTERSECT1); 318 | 319 | addObj(scene, "models/GP.obj", vec3(0, 0, 0)); 320 | 321 | rtcCommit(scene); 322 | 323 | for (int i = 0; i < NUM_SAMPLES; ++i) 324 | { 325 | if (i % 10 == 0) 326 | printf("Iteration %d\n", i); 327 | 328 | for (int x = 0; x < IMAGE_WIDTH; ++x) 329 | { 330 | for (int y = 0; y < IMAGE_HEIGHT; ++y) 331 | { 332 | vec3 ray = vec3((float)(x - IMAGE_WIDTH / 2) / IMAGE_WIDTH, (float)(y - IMAGE_HEIGHT / 2) / IMAGE_HEIGHT, -1.0); 333 | ray = normalize(ray); 334 | vec3 o = vec3(0, 1, 2.9); 335 | 336 | #if 0 337 | float lambda = nrand() * 440 + 390; 338 | float lambda_prob = 1.0 / 440.0; 339 | #else 340 | // less efficient, but lower variance 341 | intersection_info info; 342 | get_intersection_info(o, ray, &info); 343 | if (info.t < -0.1) 344 | continue; 345 | 346 | // generate random lambda according to diffuse response 347 | float lambda = 0; 348 | material mat = info.mat; 349 | do 350 | { 351 | lambda = gaussian_rand(mat.diffuse_mean, mat.diffuse_stddev); 352 | } while (lambda < 390 || 830 < lambda); 353 | float scale = cdf_gaussian(830, mat.diffuse_mean, mat.diffuse_stddev) - 354 | cdf_gaussian(390, mat.diffuse_mean, mat.diffuse_stddev); 355 | float lambda_prob = pdf_gaussian(lambda, mat.diffuse_mean, mat.diffuse_stddev) / scale; 356 | #endif 357 | 358 | float cur_radiance = radiance(lambda, o, ray) / lambda_prob; 359 | buffer[x][y] += cur_radiance * wavelength_to_xyz(lambda) / (float)NUM_SAMPLES; 360 | } 361 | } 362 | } 363 | 364 | // World's worst tonemapping 365 | for (int x = 0; x < IMAGE_WIDTH; ++x) 366 | { 367 | for (int y = 0; y < IMAGE_HEIGHT; ++y) 368 | { 369 | buffer[x][y] = xyz_to_rgb(buffer[x][y]); 370 | buffer[x][y] /= buffer[x][y] + vec3(1, 1, 1); 371 | } 372 | } 373 | 374 | // PPM file 375 | ofstream file("image.ppm"); 376 | file << "P3 " << IMAGE_WIDTH << " " << IMAGE_HEIGHT << " 255" << endl; 377 | 378 | for (int y = IMAGE_HEIGHT - 1; y >= 0; --y) 379 | { 380 | for (int x = 0; x < IMAGE_WIDTH; ++x) 381 | { 382 | if ((int)(buffer[x][y].x * 255) == 0x80000000 || 383 | (int)(buffer[x][y].y * 255) == 0x80000000 || 384 | (int)(buffer[x][y].z * 255) == 0x80000000) 385 | { 386 | //cout << "a divide by zero happened" << endl; 387 | /*for (int i = 0; i < 50; ++i) 388 | file << "\n";*/ 389 | file << 0 << " " << 0 << " " << 0 << " "; 390 | } 391 | else 392 | file << (int)(buffer[x][y].x * 255) << " " << (int)(buffer[x][y].y * 255) << " " << (int)(buffer[x][y].z * 255) << " "; 393 | } 394 | } 395 | 396 | file.close(); 397 | 398 | printf("Finished\n"); 399 | } -------------------------------------------------------------------------------- /src/model.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | using std::string; 6 | using std::vector; 7 | 8 | #include 9 | using namespace glm; 10 | 11 | #include "tiny_obj_loader.h" 12 | 13 | #include 14 | #include 15 | 16 | using namespace tinyobj; 17 | 18 | struct embVert 19 | { 20 | float x, y, z, a; 21 | }; 22 | struct embTriangle 23 | { 24 | int v0, v1, v2; 25 | }; 26 | 27 | struct material 28 | { 29 | float light_intensity = 0; 30 | float diffuse_albedo = 1.f; 31 | float diffuse_mean = 540; 32 | float diffuse_stddev = 40; 33 | }; 34 | struct model 35 | { 36 | material mat; 37 | unsigned int geom_id; 38 | }; 39 | vector models; 40 | 41 | struct light_triangle 42 | { 43 | vec3 p0, p1, p2; 44 | float area; 45 | int model_id; 46 | }; 47 | vector light_triangles; 48 | float total_light_area = 0.f; 49 | 50 | void addObj(RTCScene& scene, string filename, vec3 origin = vec3(), float scale = 1) 51 | { 52 | printf("Loading .obj file: %s\n", filename.c_str()); 53 | 54 | vector shapes; 55 | vector materials; 56 | 57 | string err = LoadObj(shapes, materials, filename.c_str(), "models/"); 58 | 59 | if (!err.empty()) 60 | { 61 | printf("\n\nTINYOBJ ERROR: %s\n\n", err.c_str()); 62 | } 63 | 64 | printf("Loaded .obj file. Transferring to Embree.\n"); 65 | for (int i = 0; i < shapes.size(); ++i) 66 | { 67 | int mesh = rtcNewTriangleMesh(scene, 68 | RTC_GEOMETRY_STATIC, 69 | shapes[i].mesh.indices.size() / 3, 70 | shapes[i].mesh.positions.size() / 3); 71 | 72 | // setup vertex buffer 73 | embVert* verts = (embVert*)rtcMapBuffer(scene, mesh, RTC_VERTEX_BUFFER); 74 | for (int v = 0; v < shapes[i].mesh.positions.size(); ++v) 75 | shapes[i].mesh.positions[v] = shapes[i].mesh.positions[v] * scale + origin.x; 76 | 77 | for (int v = 0; v < shapes[i].mesh.positions.size() / 3; ++v) 78 | { 79 | verts[v].x = shapes[i].mesh.positions[3 * v + 0]; 80 | verts[v].y = shapes[i].mesh.positions[3 * v + 1]; 81 | verts[v].z = shapes[i].mesh.positions[3 * v + 2]; 82 | } 83 | rtcUnmapBuffer(scene, mesh, RTC_VERTEX_BUFFER); 84 | 85 | // setup index buffer 86 | embTriangle* tris = (embTriangle*)rtcMapBuffer(scene, mesh, RTC_INDEX_BUFFER); 87 | for (int v = 0; v < shapes[i].mesh.indices.size() / 3; ++v) 88 | { 89 | tris[v].v0 = shapes[i].mesh.indices[3 * v + 0]; 90 | tris[v].v1 = shapes[i].mesh.indices[3 * v + 1]; 91 | tris[v].v2 = shapes[i].mesh.indices[3 * v + 2]; 92 | } 93 | rtcUnmapBuffer(scene, mesh, RTC_INDEX_BUFFER); 94 | 95 | // create model 96 | model cur_model; 97 | cur_model.geom_id = mesh; 98 | 99 | int material_id = shapes[i].mesh.material_ids[0]; 100 | float e = materials[material_id].emission[0]; 101 | cur_model.mat.light_intensity = 3.f * e / 440.f; 102 | if (e > 0.1) 103 | { 104 | // Add the triangles to the light triangle cache 105 | for (int v = 0; v < shapes[i].mesh.indices.size() / 3; ++v) 106 | { 107 | int v0 = shapes[i].mesh.indices[3 * v + 0]; 108 | int v1 = shapes[i].mesh.indices[3 * v + 1]; 109 | int v2 = shapes[i].mesh.indices[3 * v + 2]; 110 | 111 | light_triangle t; 112 | t.p0 = vec3(verts[v0].x, verts[v0].y, verts[v0].z); 113 | t.p1 = vec3(verts[v1].x, verts[v1].y, verts[v1].z); 114 | t.p2 = vec3(verts[v2].x, verts[v2].y, verts[v2].z); 115 | t.area = length(cross(t.p1 - t.p0, t.p2 - t.p0)) / 2.f; 116 | t.model_id = models.size() - 1; 117 | light_triangles.push_back(t); 118 | 119 | total_light_area += t.area; 120 | } 121 | } 122 | 123 | // converting between rgb colors and wavelength responses go here 124 | // for now, just hardcoding some colors 125 | if (i == 3) 126 | { 127 | cur_model.mat.diffuse_albedo = 0.8; 128 | cur_model.mat.diffuse_mean = 680; 129 | cur_model.mat.diffuse_stddev = 40; 130 | } 131 | else if (i == 5) 132 | { 133 | cur_model.mat.diffuse_albedo = 0.8; 134 | cur_model.mat.diffuse_mean = 540; 135 | cur_model.mat.diffuse_stddev = 40; 136 | } 137 | else 138 | { 139 | cur_model.mat.diffuse_albedo = 0.5; 140 | cur_model.mat.diffuse_mean = 600; 141 | cur_model.mat.diffuse_stddev = 200; 142 | } 143 | 144 | models.push_back(cur_model); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GraphicsProgramming/Albedo/e071a98b98722203dd696d18e8812d3adc6b37f5/thumbnail.png --------------------------------------------------------------------------------