├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── External ├── CMakeLists.txt ├── stb │ ├── CMakeLists.txt │ ├── stb_image.h │ └── stb_image_resize.h ├── tiny_obj_loader │ ├── CMakeLists.txt │ ├── tiny_obj_loader.cc │ └── tiny_obj_loader.h └── zeux_objparser │ ├── CMakeLists.txt │ ├── README.md │ ├── objparser.cpp │ └── objparser.h ├── LICENSE ├── README.md ├── Scripts ├── cmake-vs2015-vk.cmd ├── cmake-vs2017-vk.cmd ├── cmake-vs2019-vk.cmd └── cmake-xcode-mtl.sh └── Source ├── BVHBuilder.cpp ├── BVHBuilder.h ├── BaseApplication.cpp ├── BaseApplication.h ├── CMakeLists.txt ├── MovingAverage.h ├── RayTracedShadows.cpp ├── RayTracedShadows.h ├── Shaders ├── Blit.vert ├── Combine.frag ├── Model.frag ├── Model.vert ├── RayTracedShadows.comp ├── RayTracedShadows.rgen ├── RayTracedShadows.rmiss └── RayTracedShadowsInline.comp ├── VkRaytracing.cpp └── VkRaytracing.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Build 3 | build 4 | *.spv 5 | *.sublime-workspace 6 | /.vscode/* 7 | *.user 8 | .vs 9 | x64 10 | Tools 11 | tools 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "External/librush"] 2 | path = External/librush 3 | url = https://github.com/kayru/librush.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 3 | 4 | project(RayTracedShadows) 5 | 6 | if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 7 | set(RUSH_RENDER_API "MTL" CACHE STRING "Force Metal renderer") 8 | set(USE_VK_RAYTRACING 0) 9 | else() 10 | set(RUSH_RENDER_API "VK" CACHE STRING "Force Vulkan renderer") 11 | set(USE_VK_RAYTRACING 1) 12 | endif() 13 | 14 | add_subdirectory("External") 15 | add_subdirectory("Source") 16 | -------------------------------------------------------------------------------- /External/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory("librush") 2 | 3 | if (MSVC) 4 | add_compile_options(-W0) 5 | else() 6 | add_compile_options(-w) 7 | endif() 8 | 9 | add_subdirectory("stb") 10 | add_subdirectory("tiny_obj_loader") 11 | add_subdirectory("zeux_objparser") 12 | -------------------------------------------------------------------------------- /External/stb/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(stb INTERFACE) 2 | target_include_directories(stb INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 3 | -------------------------------------------------------------------------------- /External/tiny_obj_loader/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(tiny_obj_loader STATIC tiny_obj_loader.cc) 2 | target_include_directories(tiny_obj_loader INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 3 | -------------------------------------------------------------------------------- /External/tiny_obj_loader/tiny_obj_loader.cc: -------------------------------------------------------------------------------- 1 | #define TINYOBJLOADER_IMPLEMENTATION 2 | #include "tiny_obj_loader.h" 3 | -------------------------------------------------------------------------------- /External/tiny_obj_loader/tiny_obj_loader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2012-2015, Syoyo Fujita. 3 | // 4 | // Licensed under 2-clause BSD license. 5 | // 6 | 7 | // 8 | // version 0.9.20: Fixes creating per-face material using `usemtl`(#68) 9 | // version 0.9.17: Support n-polygon and crease tag(OpenSubdiv extension) 10 | // version 0.9.16: Make tinyobjloader header-only 11 | // version 0.9.15: Change API to handle no mtl file case correctly(#58) 12 | // version 0.9.14: Support specular highlight, bump, displacement and alpha 13 | // map(#53) 14 | // version 0.9.13: Report "Material file not found message" in `err`(#46) 15 | // version 0.9.12: Fix groups being ignored if they have 'usemtl' just before 16 | // 'g' (#44) 17 | // version 0.9.11: Invert `Tr` parameter(#43) 18 | // version 0.9.10: Fix seg fault on windows. 19 | // version 0.9.9 : Replace atof() with custom parser. 20 | // version 0.9.8 : Fix multi-materials(per-face material ID). 21 | // version 0.9.7 : Support multi-materials(per-face material ID) per 22 | // object/group. 23 | // version 0.9.6 : Support Ni(index of refraction) mtl parameter. 24 | // Parse transmittance material parameter correctly. 25 | // version 0.9.5 : Parse multiple group name. 26 | // Add support of specifying the base path to load material 27 | // file. 28 | // version 0.9.4 : Initial support of group tag(g) 29 | // version 0.9.3 : Fix parsing triple 'x/y/z' 30 | // version 0.9.2 : Add more .mtl load support 31 | // version 0.9.1 : Add initial .mtl load support 32 | // version 0.9.0 : Initial 33 | // 34 | 35 | // 36 | // Use this in *one* .cc 37 | // #define TINYOBJLOADER_IMPLEMENTATION 38 | // #include "tiny_obj_loader.h" 39 | // 40 | 41 | #ifndef TINY_OBJ_LOADER_H 42 | #define TINY_OBJ_LOADER_H 43 | 44 | #include 45 | #include 46 | #include 47 | 48 | namespace tinyobj { 49 | 50 | typedef struct { 51 | std::string name; 52 | 53 | float ambient[3]; 54 | float diffuse[3]; 55 | float specular[3]; 56 | float transmittance[3]; 57 | float emission[3]; 58 | float shininess; 59 | float ior; // index of refraction 60 | float dissolve; // 1 == opaque; 0 == fully transparent 61 | // illumination model (see http://www.fileformat.info/format/material/) 62 | int illum; 63 | 64 | int dummy; // Suppress padding warning. 65 | 66 | std::string ambient_texname; // map_Ka 67 | std::string diffuse_texname; // map_Kd 68 | std::string specular_texname; // map_Ks 69 | std::string specular_highlight_texname; // map_Ns 70 | std::string bump_texname; // map_bump, bump 71 | std::string displacement_texname; // disp 72 | std::string alpha_texname; // map_d 73 | std::map unknown_parameter; 74 | } material_t; 75 | 76 | typedef struct { 77 | std::string name; 78 | 79 | std::vector intValues; 80 | std::vector floatValues; 81 | std::vector stringValues; 82 | } tag_t; 83 | 84 | typedef struct { 85 | std::vector positions; 86 | std::vector normals; 87 | std::vector texcoords; 88 | std::vector indices; 89 | std::vector 90 | num_vertices; // The number of vertices per face. Up to 255. 91 | std::vector material_ids; // per-face material ID 92 | std::vector tags; // SubD tag 93 | } mesh_t; 94 | 95 | typedef struct { 96 | std::string name; 97 | mesh_t mesh; 98 | } shape_t; 99 | 100 | class MaterialReader { 101 | public: 102 | MaterialReader() {} 103 | virtual ~MaterialReader(); 104 | 105 | virtual bool operator()(const std::string &matId, 106 | std::vector &materials, 107 | std::map &matMap, 108 | std::string &err) = 0; 109 | }; 110 | 111 | class MaterialFileReader : public MaterialReader { 112 | public: 113 | MaterialFileReader(const std::string &mtl_basepath) 114 | : m_mtlBasePath(mtl_basepath) {} 115 | virtual ~MaterialFileReader() {} 116 | virtual bool operator()(const std::string &matId, 117 | std::vector &materials, 118 | std::map &matMap, std::string &err); 119 | 120 | private: 121 | std::string m_mtlBasePath; 122 | }; 123 | 124 | /// Loads .obj from a file. 125 | /// 'shapes' will be filled with parsed shape data 126 | /// The function returns error string. 127 | /// Returns true when loading .obj become success. 128 | /// Returns warning and error message into `err` 129 | /// 'mtl_basepath' is optional, and used for base path for .mtl file. 130 | /// 'triangulate' is optional, and used whether triangulate polygon face in .obj 131 | /// or not. 132 | bool LoadObj(std::vector &shapes, // [output] 133 | std::vector &materials, // [output] 134 | std::string &err, // [output] 135 | const char *filename, const char *mtl_basepath = NULL, 136 | bool triangulate = true); 137 | 138 | /// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve 139 | /// std::istream for materials. 140 | /// Returns true when loading .obj become success. 141 | /// Returns warning and error message into `err` 142 | bool LoadObj(std::vector &shapes, // [output] 143 | std::vector &materials, // [output] 144 | std::string &err, // [output] 145 | std::istream &inStream, MaterialReader &readMatFn, 146 | bool triangulate = true); 147 | 148 | /// Loads materials into std::map 149 | void LoadMtl(std::map &material_map, // [output] 150 | std::vector &materials, // [output] 151 | std::istream &inStream); 152 | } 153 | 154 | #ifdef TINYOBJLOADER_IMPLEMENTATION 155 | #include 156 | #include 157 | #include 158 | #include 159 | #include 160 | #include 161 | 162 | #include 163 | #include 164 | #include 165 | #include 166 | #include 167 | 168 | #include "tiny_obj_loader.h" 169 | 170 | namespace tinyobj { 171 | 172 | MaterialReader::~MaterialReader() {} 173 | 174 | #define TINYOBJ_SSCANF_BUFFER_SIZE (4096) 175 | 176 | struct vertex_index { 177 | int v_idx, vt_idx, vn_idx; 178 | vertex_index() {} 179 | vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} 180 | vertex_index(int vidx, int vtidx, int vnidx) 181 | : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} 182 | }; 183 | 184 | struct tag_sizes { 185 | tag_sizes() : num_ints(0), num_floats(0), num_strings(0) {} 186 | int num_ints; 187 | int num_floats; 188 | int num_strings; 189 | }; 190 | 191 | // for std::map 192 | static inline bool operator<(const vertex_index &a, const vertex_index &b) { 193 | if (a.v_idx != b.v_idx) 194 | return (a.v_idx < b.v_idx); 195 | if (a.vn_idx != b.vn_idx) 196 | return (a.vn_idx < b.vn_idx); 197 | if (a.vt_idx != b.vt_idx) 198 | return (a.vt_idx < b.vt_idx); 199 | 200 | return false; 201 | } 202 | 203 | struct obj_shape { 204 | std::vector v; 205 | std::vector vn; 206 | std::vector vt; 207 | }; 208 | 209 | #define IS_SPACE( x ) ( ( (x) == ' ') || ( (x) == '\t') ) 210 | #define IS_DIGIT( x ) ( (unsigned int)( (x) - '0' ) < (unsigned int)10 ) 211 | #define IS_NEW_LINE( x ) ( ( (x) == '\r') || ( (x) == '\n') || ( (x) == '\0') ) 212 | 213 | // Make index zero-base, and also support relative index. 214 | static inline int fixIndex(int idx, int n) { 215 | if (idx > 0) 216 | return idx - 1; 217 | if (idx == 0) 218 | return 0; 219 | return n + idx; // negative value = relative 220 | } 221 | 222 | static inline std::string parseString(const char *&token) { 223 | std::string s; 224 | token += strspn(token, " \t"); 225 | size_t e = strcspn(token, " \t\r"); 226 | s = std::string(token, &token[e]); 227 | token += e; 228 | return s; 229 | } 230 | 231 | static inline int parseInt(const char *&token) { 232 | token += strspn(token, " \t"); 233 | int i = atoi(token); 234 | token += strcspn(token, " \t\r"); 235 | return i; 236 | } 237 | 238 | // Tries to parse a floating point number located at s. 239 | // 240 | // s_end should be a location in the string where reading should absolutely 241 | // stop. For example at the end of the string, to prevent buffer overflows. 242 | // 243 | // Parses the following EBNF grammar: 244 | // sign = "+" | "-" ; 245 | // END = ? anything not in digit ? 246 | // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; 247 | // integer = [sign] , digit , {digit} ; 248 | // decimal = integer , ["." , integer] ; 249 | // float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; 250 | // 251 | // Valid strings are for example: 252 | // -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 253 | // 254 | // If the parsing is a success, result is set to the parsed value and true 255 | // is returned. 256 | // 257 | // The function is greedy and will parse until any of the following happens: 258 | // - a non-conforming character is encountered. 259 | // - s_end is reached. 260 | // 261 | // The following situations triggers a failure: 262 | // - s >= s_end. 263 | // - parse failure. 264 | // 265 | static bool tryParseDouble(const char *s, const char *s_end, double *result) { 266 | if (s >= s_end) { 267 | return false; 268 | } 269 | 270 | double mantissa = 0.0; 271 | // This exponent is base 2 rather than 10. 272 | // However the exponent we parse is supposed to be one of ten, 273 | // thus we must take care to convert the exponent/and or the 274 | // mantissa to a * 2^E, where a is the mantissa and E is the 275 | // exponent. 276 | // To get the final double we will use ldexp, it requires the 277 | // exponent to be in base 2. 278 | int exponent = 0; 279 | 280 | // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED 281 | // TO JUMP OVER DEFINITIONS. 282 | char sign = '+'; 283 | char exp_sign = '+'; 284 | char const *curr = s; 285 | 286 | // How many characters were read in a loop. 287 | int read = 0; 288 | // Tells whether a loop terminated due to reaching s_end. 289 | bool end_not_reached = false; 290 | 291 | /* 292 | BEGIN PARSING. 293 | */ 294 | 295 | // Find out what sign we've got. 296 | if (*curr == '+' || *curr == '-') { 297 | sign = *curr; 298 | curr++; 299 | } else if (IS_DIGIT(*curr)) { /* Pass through. */ 300 | } else { 301 | goto fail; 302 | } 303 | 304 | // Read the integer part. 305 | while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { 306 | mantissa *= 10; 307 | mantissa += static_cast(*curr - 0x30); 308 | curr++; 309 | read++; 310 | } 311 | 312 | // We must make sure we actually got something. 313 | if (read == 0) 314 | goto fail; 315 | // We allow numbers of form "#", "###" etc. 316 | if (!end_not_reached) 317 | goto assemble; 318 | 319 | // Read the decimal part. 320 | if (*curr == '.') { 321 | curr++; 322 | read = 1; 323 | while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { 324 | // NOTE: Don't use powf here, it will absolutely murder precision. 325 | mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); 326 | read++; 327 | curr++; 328 | } 329 | } else if (*curr == 'e' || *curr == 'E') { 330 | } else { 331 | goto assemble; 332 | } 333 | 334 | if (!end_not_reached) 335 | goto assemble; 336 | 337 | // Read the exponent part. 338 | if (*curr == 'e' || *curr == 'E') { 339 | curr++; 340 | // Figure out if a sign is present and if it is. 341 | if ((end_not_reached = (curr != s_end)) && (*curr == '+' || *curr == '-')) { 342 | exp_sign = *curr; 343 | curr++; 344 | } else if (IS_DIGIT(*curr)) { /* Pass through. */ 345 | } else { 346 | // Empty E is not allowed. 347 | goto fail; 348 | } 349 | 350 | read = 0; 351 | while ((end_not_reached = (curr != s_end)) && IS_DIGIT(*curr)) { 352 | exponent *= 10; 353 | exponent += static_cast(*curr - 0x30); 354 | curr++; 355 | read++; 356 | } 357 | exponent *= (exp_sign == '+' ? 1 : -1); 358 | if (read == 0) 359 | goto fail; 360 | } 361 | 362 | assemble: 363 | *result = 364 | (sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); 365 | return true; 366 | fail: 367 | return false; 368 | } 369 | static inline float parseFloat(const char *&token) { 370 | token += strspn(token, " \t"); 371 | #ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER 372 | float f = (float)atof(token); 373 | token += strcspn(token, " \t\r"); 374 | #else 375 | const char *end = token + strcspn(token, " \t\r"); 376 | double val = 0.0; 377 | tryParseDouble(token, end, &val); 378 | float f = static_cast(val); 379 | token = end; 380 | #endif 381 | return f; 382 | } 383 | 384 | static inline void parseFloat2(float &x, float &y, const char *&token) { 385 | x = parseFloat(token); 386 | y = parseFloat(token); 387 | } 388 | 389 | static inline void parseFloat3(float &x, float &y, float &z, 390 | const char *&token) { 391 | x = parseFloat(token); 392 | y = parseFloat(token); 393 | z = parseFloat(token); 394 | } 395 | 396 | static tag_sizes parseTagTriple(const char *&token) { 397 | tag_sizes ts; 398 | 399 | ts.num_ints = atoi(token); 400 | token += strcspn(token, "/ \t\r"); 401 | if (token[0] != '/') { 402 | return ts; 403 | } 404 | token++; 405 | 406 | ts.num_floats = atoi(token); 407 | token += strcspn(token, "/ \t\r"); 408 | if (token[0] != '/') { 409 | return ts; 410 | } 411 | token++; 412 | 413 | ts.num_strings = atoi(token); 414 | token += strcspn(token, "/ \t\r") + 1; 415 | 416 | return ts; 417 | } 418 | 419 | // Parse triples: i, i/j/k, i//k, i/j 420 | static vertex_index parseTriple(const char *&token, int vsize, int vnsize, 421 | int vtsize) { 422 | vertex_index vi(-1); 423 | 424 | vi.v_idx = fixIndex(atoi(token), vsize); 425 | token += strcspn(token, "/ \t\r"); 426 | if (token[0] != '/') { 427 | return vi; 428 | } 429 | token++; 430 | 431 | // i//k 432 | if (token[0] == '/') { 433 | token++; 434 | vi.vn_idx = fixIndex(atoi(token), vnsize); 435 | token += strcspn(token, "/ \t\r"); 436 | return vi; 437 | } 438 | 439 | // i/j/k or i/j 440 | vi.vt_idx = fixIndex(atoi(token), vtsize); 441 | token += strcspn(token, "/ \t\r"); 442 | if (token[0] != '/') { 443 | return vi; 444 | } 445 | 446 | // i/j/k 447 | token++; // skip '/' 448 | vi.vn_idx = fixIndex(atoi(token), vnsize); 449 | token += strcspn(token, "/ \t\r"); 450 | return vi; 451 | } 452 | 453 | static unsigned int 454 | updateVertex(std::map &vertexCache, 455 | std::vector &positions, std::vector &normals, 456 | std::vector &texcoords, 457 | const std::vector &in_positions, 458 | const std::vector &in_normals, 459 | const std::vector &in_texcoords, const vertex_index &i) { 460 | const std::map::iterator it = vertexCache.find(i); 461 | 462 | if (it != vertexCache.end()) { 463 | // found cache 464 | return it->second; 465 | } 466 | 467 | assert(in_positions.size() > static_cast(3 * i.v_idx + 2)); 468 | 469 | positions.push_back(in_positions[3 * static_cast(i.v_idx) + 0]); 470 | positions.push_back(in_positions[3 * static_cast(i.v_idx) + 1]); 471 | positions.push_back(in_positions[3 * static_cast(i.v_idx) + 2]); 472 | 473 | if ((i.vn_idx >= 0) && 474 | (static_cast(i.vn_idx * 3 + 2) < in_normals.size())) { 475 | normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 0]); 476 | normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 1]); 477 | normals.push_back(in_normals[3 * static_cast(i.vn_idx) + 2]); 478 | } 479 | 480 | if ((i.vt_idx >= 0) && 481 | (static_cast(i.vt_idx * 2 + 1) < in_texcoords.size())) { 482 | texcoords.push_back(in_texcoords[2 * static_cast(i.vt_idx) + 0]); 483 | texcoords.push_back(in_texcoords[2 * static_cast(i.vt_idx) + 1]); 484 | } 485 | 486 | unsigned int idx = static_cast(positions.size() / 3 - 1); 487 | vertexCache[i] = idx; 488 | 489 | return idx; 490 | } 491 | 492 | static void InitMaterial(material_t &material) { 493 | material.name = ""; 494 | material.ambient_texname = ""; 495 | material.diffuse_texname = ""; 496 | material.specular_texname = ""; 497 | material.specular_highlight_texname = ""; 498 | material.bump_texname = ""; 499 | material.displacement_texname = ""; 500 | material.alpha_texname = ""; 501 | for (int i = 0; i < 3; i++) { 502 | material.ambient[i] = 0.f; 503 | material.diffuse[i] = 0.f; 504 | material.specular[i] = 0.f; 505 | material.transmittance[i] = 0.f; 506 | material.emission[i] = 0.f; 507 | } 508 | material.illum = 0; 509 | material.dissolve = 1.f; 510 | material.shininess = 1.f; 511 | material.ior = 1.f; 512 | material.unknown_parameter.clear(); 513 | } 514 | 515 | static bool exportFaceGroupToShape( 516 | shape_t &shape, std::map vertexCache, 517 | const std::vector &in_positions, 518 | const std::vector &in_normals, 519 | const std::vector &in_texcoords, 520 | const std::vector > &faceGroup, 521 | std::vector &tags, const int material_id, const std::string &name, 522 | bool clearCache, bool triangulate) { 523 | if (faceGroup.empty()) { 524 | return false; 525 | } 526 | 527 | // Flatten vertices and indices 528 | for (size_t i = 0; i < faceGroup.size(); i++) { 529 | const std::vector &face = faceGroup[i]; 530 | 531 | vertex_index i0 = face[0]; 532 | vertex_index i1(-1); 533 | vertex_index i2 = face[1]; 534 | 535 | size_t npolys = face.size(); 536 | 537 | if (triangulate) { 538 | 539 | // Polygon -> triangle fan conversion 540 | for (size_t k = 2; k < npolys; k++) { 541 | i1 = i2; 542 | i2 = face[k]; 543 | 544 | unsigned int v0 = updateVertex( 545 | vertexCache, shape.mesh.positions, shape.mesh.normals, 546 | shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); 547 | unsigned int v1 = updateVertex( 548 | vertexCache, shape.mesh.positions, shape.mesh.normals, 549 | shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); 550 | unsigned int v2 = updateVertex( 551 | vertexCache, shape.mesh.positions, shape.mesh.normals, 552 | shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); 553 | 554 | shape.mesh.indices.push_back(v0); 555 | shape.mesh.indices.push_back(v1); 556 | shape.mesh.indices.push_back(v2); 557 | 558 | shape.mesh.num_vertices.push_back(3); 559 | shape.mesh.material_ids.push_back(material_id); 560 | } 561 | } else { 562 | 563 | for (size_t k = 0; k < npolys; k++) { 564 | unsigned int v = 565 | updateVertex(vertexCache, shape.mesh.positions, shape.mesh.normals, 566 | shape.mesh.texcoords, in_positions, in_normals, 567 | in_texcoords, face[k]); 568 | 569 | shape.mesh.indices.push_back(v); 570 | } 571 | 572 | shape.mesh.num_vertices.push_back(static_cast(npolys)); 573 | shape.mesh.material_ids.push_back(material_id); // per face 574 | } 575 | } 576 | 577 | shape.name = name; 578 | shape.mesh.tags.swap(tags); 579 | 580 | if (clearCache) 581 | vertexCache.clear(); 582 | 583 | return true; 584 | } 585 | 586 | void LoadMtl(std::map &material_map, 587 | std::vector &materials, std::istream &inStream) { 588 | 589 | // Create a default material anyway. 590 | material_t material; 591 | InitMaterial(material); 592 | 593 | size_t maxchars = 8192; // Alloc enough size. 594 | std::vector buf(maxchars); // Alloc enough size. 595 | while (inStream.peek() != -1) { 596 | inStream.getline(&buf[0], static_cast(maxchars)); 597 | 598 | std::string linebuf(&buf[0]); 599 | 600 | // Trim newline '\r\n' or '\n' 601 | if (linebuf.size() > 0) { 602 | if (linebuf[linebuf.size() - 1] == '\n') 603 | linebuf.erase(linebuf.size() - 1); 604 | } 605 | if (linebuf.size() > 0) { 606 | if (linebuf[linebuf.size() - 1] == '\r') 607 | linebuf.erase(linebuf.size() - 1); 608 | } 609 | 610 | // Skip if empty line. 611 | if (linebuf.empty()) { 612 | continue; 613 | } 614 | 615 | // Skip leading space. 616 | const char *token = linebuf.c_str(); 617 | token += strspn(token, " \t"); 618 | 619 | assert(token); 620 | if (token[0] == '\0') 621 | continue; // empty line 622 | 623 | if (token[0] == '#') 624 | continue; // comment line 625 | 626 | // new mtl 627 | if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { 628 | // flush previous material. 629 | if (!material.name.empty()) { 630 | material_map.insert(std::pair( 631 | material.name, static_cast(materials.size()))); 632 | materials.push_back(material); 633 | } 634 | 635 | // initial temporary material 636 | InitMaterial(material); 637 | 638 | // set new mtl name 639 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 640 | token += 7; 641 | #ifdef _MSC_VER 642 | sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); 643 | #else 644 | sscanf(token, "%s", namebuf); 645 | #endif 646 | material.name = namebuf; 647 | continue; 648 | } 649 | 650 | // ambient 651 | if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { 652 | token += 2; 653 | float r, g, b; 654 | parseFloat3(r, g, b, token); 655 | material.ambient[0] = r; 656 | material.ambient[1] = g; 657 | material.ambient[2] = b; 658 | continue; 659 | } 660 | 661 | // diffuse 662 | if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { 663 | token += 2; 664 | float r, g, b; 665 | parseFloat3(r, g, b, token); 666 | material.diffuse[0] = r; 667 | material.diffuse[1] = g; 668 | material.diffuse[2] = b; 669 | continue; 670 | } 671 | 672 | // specular 673 | if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { 674 | token += 2; 675 | float r, g, b; 676 | parseFloat3(r, g, b, token); 677 | material.specular[0] = r; 678 | material.specular[1] = g; 679 | material.specular[2] = b; 680 | continue; 681 | } 682 | 683 | // transmittance 684 | if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) { 685 | token += 2; 686 | float r, g, b; 687 | parseFloat3(r, g, b, token); 688 | material.transmittance[0] = r; 689 | material.transmittance[1] = g; 690 | material.transmittance[2] = b; 691 | continue; 692 | } 693 | 694 | // ior(index of refraction) 695 | if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { 696 | token += 2; 697 | material.ior = parseFloat(token); 698 | continue; 699 | } 700 | 701 | // emission 702 | if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { 703 | token += 2; 704 | float r, g, b; 705 | parseFloat3(r, g, b, token); 706 | material.emission[0] = r; 707 | material.emission[1] = g; 708 | material.emission[2] = b; 709 | continue; 710 | } 711 | 712 | // shininess 713 | if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { 714 | token += 2; 715 | material.shininess = parseFloat(token); 716 | continue; 717 | } 718 | 719 | // illum model 720 | if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { 721 | token += 6; 722 | material.illum = parseInt(token); 723 | continue; 724 | } 725 | 726 | // dissolve 727 | if ((token[0] == 'd' && IS_SPACE(token[1]))) { 728 | token += 1; 729 | material.dissolve = parseFloat(token); 730 | continue; 731 | } 732 | if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { 733 | token += 2; 734 | // Invert value of Tr(assume Tr is in range [0, 1]) 735 | material.dissolve = 1.0f - parseFloat(token); 736 | continue; 737 | } 738 | 739 | // ambient texture 740 | if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { 741 | token += 7; 742 | material.ambient_texname = token; 743 | continue; 744 | } 745 | 746 | // diffuse texture 747 | if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { 748 | token += 7; 749 | material.diffuse_texname = token; 750 | continue; 751 | } 752 | 753 | // specular texture 754 | if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { 755 | token += 7; 756 | material.specular_texname = token; 757 | continue; 758 | } 759 | 760 | // specular highlight texture 761 | if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { 762 | token += 7; 763 | material.specular_highlight_texname = token; 764 | continue; 765 | } 766 | 767 | // bump texture 768 | if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { 769 | token += 9; 770 | material.bump_texname = token; 771 | continue; 772 | } 773 | 774 | // alpha texture 775 | if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { 776 | token += 6; 777 | material.alpha_texname = token; 778 | continue; 779 | } 780 | 781 | // bump texture 782 | if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { 783 | token += 5; 784 | material.bump_texname = token; 785 | continue; 786 | } 787 | 788 | // displacement texture 789 | if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { 790 | token += 5; 791 | material.displacement_texname = token; 792 | continue; 793 | } 794 | 795 | // unknown parameter 796 | const char *_space = strchr(token, ' '); 797 | if (!_space) { 798 | _space = strchr(token, '\t'); 799 | } 800 | if (_space) { 801 | std::ptrdiff_t len = _space - token; 802 | std::string key(token, static_cast(len)); 803 | std::string value = _space + 1; 804 | material.unknown_parameter.insert( 805 | std::pair(key, value)); 806 | } 807 | } 808 | // flush last material. 809 | material_map.insert(std::pair( 810 | material.name, static_cast(materials.size()))); 811 | materials.push_back(material); 812 | } 813 | 814 | bool MaterialFileReader::operator()(const std::string &matId, 815 | std::vector &materials, 816 | std::map &matMap, 817 | std::string &err) { 818 | std::string filepath; 819 | 820 | if (!m_mtlBasePath.empty()) { 821 | filepath = std::string(m_mtlBasePath) + matId; 822 | } else { 823 | filepath = matId; 824 | } 825 | 826 | std::ifstream matIStream(filepath.c_str()); 827 | LoadMtl(matMap, materials, matIStream); 828 | if (!matIStream) { 829 | std::stringstream ss; 830 | ss << "WARN: Material file [ " << filepath 831 | << " ] not found. Created a default material."; 832 | err += ss.str(); 833 | } 834 | return true; 835 | } 836 | 837 | bool LoadObj(std::vector &shapes, // [output] 838 | std::vector &materials, // [output] 839 | std::string &err, const char *filename, const char *mtl_basepath, 840 | bool trianglulate) { 841 | 842 | shapes.clear(); 843 | 844 | std::stringstream errss; 845 | 846 | std::ifstream ifs(filename); 847 | if (!ifs) { 848 | errss << "Cannot open file [" << filename << "]" << std::endl; 849 | err = errss.str(); 850 | return false; 851 | } 852 | 853 | std::string basePath; 854 | if (mtl_basepath) { 855 | basePath = mtl_basepath; 856 | } 857 | MaterialFileReader matFileReader(basePath); 858 | 859 | return LoadObj(shapes, materials, err, ifs, matFileReader, trianglulate); 860 | } 861 | 862 | bool LoadObj(std::vector &shapes, // [output] 863 | std::vector &materials, // [output] 864 | std::string &err, std::istream &inStream, 865 | MaterialReader &readMatFn, bool triangulate) { 866 | std::stringstream errss; 867 | 868 | std::vector v; 869 | std::vector vn; 870 | std::vector vt; 871 | std::vector tags; 872 | std::vector > faceGroup; 873 | std::string name; 874 | 875 | // material 876 | std::map material_map; 877 | std::map vertexCache; 878 | int material = -1; 879 | 880 | shape_t shape; 881 | 882 | int maxchars = 8192; // Alloc enough size. 883 | std::vector buf(static_cast(maxchars)); // Alloc enough size. 884 | while (inStream.peek() != -1) { 885 | inStream.getline(&buf[0], maxchars); 886 | 887 | std::string linebuf(&buf[0]); 888 | 889 | // Trim newline '\r\n' or '\n' 890 | if (linebuf.size() > 0) { 891 | if (linebuf[linebuf.size() - 1] == '\n') 892 | linebuf.erase(linebuf.size() - 1); 893 | } 894 | if (linebuf.size() > 0) { 895 | if (linebuf[linebuf.size() - 1] == '\r') 896 | linebuf.erase(linebuf.size() - 1); 897 | } 898 | 899 | // Skip if empty line. 900 | if (linebuf.empty()) { 901 | continue; 902 | } 903 | 904 | // Skip leading space. 905 | const char *token = linebuf.c_str(); 906 | token += strspn(token, " \t"); 907 | 908 | assert(token); 909 | if (token[0] == '\0') 910 | continue; // empty line 911 | 912 | if (token[0] == '#') 913 | continue; // comment line 914 | 915 | // vertex 916 | if (token[0] == 'v' && IS_SPACE((token[1]))) { 917 | token += 2; 918 | float x, y, z; 919 | parseFloat3(x, y, z, token); 920 | v.push_back(x); 921 | v.push_back(y); 922 | v.push_back(z); 923 | continue; 924 | } 925 | 926 | // normal 927 | if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { 928 | token += 3; 929 | float x, y, z; 930 | parseFloat3(x, y, z, token); 931 | vn.push_back(x); 932 | vn.push_back(y); 933 | vn.push_back(z); 934 | continue; 935 | } 936 | 937 | // texcoord 938 | if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { 939 | token += 3; 940 | float x, y; 941 | parseFloat2(x, y, token); 942 | vt.push_back(x); 943 | vt.push_back(y); 944 | continue; 945 | } 946 | 947 | // face 948 | if (token[0] == 'f' && IS_SPACE((token[1]))) { 949 | token += 2; 950 | token += strspn(token, " \t"); 951 | 952 | std::vector face; 953 | face.reserve(3); 954 | 955 | while (!IS_NEW_LINE(token[0])) { 956 | vertex_index vi = parseTriple(token, static_cast(v.size() / 3), 957 | static_cast(vn.size() / 3), 958 | static_cast(vt.size() / 2)); 959 | face.push_back(vi); 960 | size_t n = strspn(token, " \t\r"); 961 | token += n; 962 | } 963 | 964 | // replace with emplace_back + std::move on C++11 965 | faceGroup.push_back(std::vector()); 966 | faceGroup[faceGroup.size() - 1].swap(face); 967 | 968 | continue; 969 | } 970 | 971 | // use mtl 972 | if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { 973 | 974 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 975 | token += 7; 976 | #ifdef _MSC_VER 977 | sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); 978 | #else 979 | sscanf(token, "%s", namebuf); 980 | #endif 981 | 982 | int newMaterialId = -1; 983 | if (material_map.find(namebuf) != material_map.end()) { 984 | newMaterialId = material_map[namebuf]; 985 | } else { 986 | // { error!! material not found } 987 | } 988 | 989 | if (newMaterialId != material) { 990 | // Create per-face material 991 | exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, 992 | material, name, true, triangulate); 993 | faceGroup.clear(); 994 | material = newMaterialId; 995 | } 996 | 997 | continue; 998 | } 999 | 1000 | // load mtl 1001 | if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { 1002 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 1003 | token += 7; 1004 | #ifdef _MSC_VER 1005 | sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); 1006 | #else 1007 | sscanf(token, "%s", namebuf); 1008 | #endif 1009 | 1010 | std::string err_mtl; 1011 | bool ok = readMatFn(namebuf, materials, material_map, err_mtl); 1012 | err += err_mtl; 1013 | 1014 | if (!ok) { 1015 | faceGroup.clear(); // for safety 1016 | return false; 1017 | } 1018 | 1019 | continue; 1020 | } 1021 | 1022 | // group name 1023 | if (token[0] == 'g' && IS_SPACE((token[1]))) { 1024 | 1025 | // flush previous face group. 1026 | bool ret = 1027 | exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, 1028 | material, name, true, triangulate); 1029 | if (ret) { 1030 | shapes.push_back(shape); 1031 | } 1032 | 1033 | shape = shape_t(); 1034 | 1035 | // material = -1; 1036 | faceGroup.clear(); 1037 | 1038 | std::vector names; 1039 | names.reserve(2); 1040 | 1041 | while (!IS_NEW_LINE(token[0])) { 1042 | std::string str = parseString(token); 1043 | names.push_back(str); 1044 | token += strspn(token, " \t\r"); // skip tag 1045 | } 1046 | 1047 | assert(names.size() > 0); 1048 | 1049 | // names[0] must be 'g', so skip the 0th element. 1050 | if (names.size() > 1) { 1051 | name = names[1]; 1052 | } else { 1053 | name = ""; 1054 | } 1055 | 1056 | continue; 1057 | } 1058 | 1059 | // object name 1060 | if (token[0] == 'o' && IS_SPACE((token[1]))) { 1061 | 1062 | // flush previous face group. 1063 | bool ret = 1064 | exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, tags, 1065 | material, name, true, triangulate); 1066 | if (ret) { 1067 | shapes.push_back(shape); 1068 | } 1069 | 1070 | // material = -1; 1071 | faceGroup.clear(); 1072 | shape = shape_t(); 1073 | 1074 | // @todo { multiple object name? } 1075 | char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; 1076 | token += 2; 1077 | #ifdef _MSC_VER 1078 | sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); 1079 | #else 1080 | sscanf(token, "%s", namebuf); 1081 | #endif 1082 | name = std::string(namebuf); 1083 | 1084 | continue; 1085 | } 1086 | 1087 | if (token[0] == 't' && IS_SPACE(token[1])) { 1088 | tag_t tag; 1089 | 1090 | char namebuf[4096]; 1091 | token += 2; 1092 | sscanf(token, "%s", namebuf); 1093 | tag.name = std::string(namebuf); 1094 | 1095 | token += tag.name.size() + 1; 1096 | 1097 | tag_sizes ts = parseTagTriple(token); 1098 | 1099 | tag.intValues.resize(static_cast(ts.num_ints)); 1100 | 1101 | for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { 1102 | tag.intValues[i] = atoi(token); 1103 | token += strcspn(token, "/ \t\r") + 1; 1104 | } 1105 | 1106 | tag.floatValues.resize(static_cast(ts.num_floats)); 1107 | for (size_t i = 0; i < static_cast(ts.num_floats); ++i) { 1108 | tag.floatValues[i] = parseFloat(token); 1109 | token += strcspn(token, "/ \t\r") + 1; 1110 | } 1111 | 1112 | tag.stringValues.resize(static_cast(ts.num_strings)); 1113 | for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { 1114 | char stringValueBuffer[4096]; 1115 | 1116 | sscanf(token, "%s", stringValueBuffer); 1117 | tag.stringValues[i] = stringValueBuffer; 1118 | token += tag.stringValues[i].size() + 1; 1119 | } 1120 | 1121 | tags.push_back(tag); 1122 | } 1123 | 1124 | // Ignore unknown command. 1125 | } 1126 | 1127 | bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, 1128 | tags, material, name, true, triangulate); 1129 | if (ret) { 1130 | shapes.push_back(shape); 1131 | } 1132 | faceGroup.clear(); // for safety 1133 | 1134 | err += errss.str(); 1135 | return true; 1136 | } 1137 | 1138 | } // namespace 1139 | 1140 | #endif 1141 | 1142 | #endif // TINY_OBJ_LOADER_H 1143 | -------------------------------------------------------------------------------- /External/zeux_objparser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(zeux_objparser STATIC objparser.h objparser.cpp) 2 | target_include_directories(zeux_objparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 3 | -------------------------------------------------------------------------------- /External/zeux_objparser/README.md: -------------------------------------------------------------------------------- 1 | Obj format parser extracted from https://github.com/zeux/meshoptimizer (commit 38ab942) 2 | -------------------------------------------------------------------------------- /External/zeux_objparser/objparser.cpp: -------------------------------------------------------------------------------- 1 | #ifndef _CRT_SECURE_NO_WARNINGS 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include "objparser.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | template 14 | static void growArray(T*& data, size_t& capacity) 15 | { 16 | size_t newcapacity = capacity == 0 ? 32 : capacity + capacity / 2; 17 | T* newdata = new T[newcapacity]; 18 | 19 | if (data) 20 | { 21 | memcpy(newdata, data, capacity * sizeof(T)); 22 | delete[] data; 23 | } 24 | 25 | data = newdata; 26 | capacity = newcapacity; 27 | } 28 | 29 | static int fixupIndex(int index, size_t size) 30 | { 31 | return (index >= 0) ? index - 1 : int(size) + index; 32 | } 33 | 34 | static int parseInt(const char* s, const char** end) 35 | { 36 | // skip whitespace 37 | while (*s == ' ' || *s == '\t') 38 | s++; 39 | 40 | // read sign bit 41 | int sign = (*s == '-'); 42 | s += (*s == '-' || *s == '+'); 43 | 44 | unsigned int result = 0; 45 | 46 | for (;;) 47 | { 48 | if (unsigned(*s - '0') < 10) 49 | result = result * 10 + (*s - '0'); 50 | else 51 | break; 52 | 53 | s++; 54 | } 55 | 56 | // return end-of-string 57 | *end = s; 58 | 59 | return sign ? -int(result) : int(result); 60 | } 61 | 62 | static float parseFloat(const char* s, const char** end) 63 | { 64 | static const double digits[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 65 | static const double powers[] = {1e0, 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, 1e+21, 1e+22}; 66 | 67 | // skip whitespace 68 | while (*s == ' ' || *s == '\t') 69 | s++; 70 | 71 | // read sign 72 | double sign = (*s == '-') ? -1 : 1; 73 | s += (*s == '-' || *s == '+'); 74 | 75 | // read integer part 76 | double result = 0; 77 | int power = 0; 78 | 79 | while (unsigned(*s - '0') < 10) 80 | { 81 | result = result * 10 + digits[*s - '0']; 82 | s++; 83 | } 84 | 85 | // read fractional part 86 | if (*s == '.') 87 | { 88 | s++; 89 | 90 | while (unsigned(*s - '0') < 10) 91 | { 92 | result = result * 10 + digits[*s - '0']; 93 | s++; 94 | power--; 95 | } 96 | } 97 | 98 | // read exponent part 99 | if ((*s | ' ') == 'e') 100 | { 101 | s++; 102 | 103 | // read exponent sign 104 | int expsign = (*s == '-') ? -1 : 1; 105 | s += (*s == '-' || *s == '+'); 106 | 107 | // read exponent 108 | int exppower = 0; 109 | 110 | while (unsigned(*s - '0') < 10) 111 | { 112 | exppower = exppower * 10 + (*s - '0'); 113 | s++; 114 | } 115 | 116 | // done! 117 | power += expsign * exppower; 118 | } 119 | 120 | // return end-of-string 121 | *end = s; 122 | 123 | // note: this is precise if result < 9e15 124 | // for longer inputs we lose a bit of precision here 125 | if (unsigned(-power) < sizeof(powers) / sizeof(powers[0])) 126 | return float(sign * result / powers[-power]); 127 | else if (unsigned(power) < sizeof(powers) / sizeof(powers[0])) 128 | return float(sign * result * powers[power]); 129 | else 130 | return float(sign * result * pow(10.0, power)); 131 | } 132 | 133 | static const char* parseFace(const char* s, int& vi, int& vti, int& vni) 134 | { 135 | while (*s == ' ' || *s == '\t') 136 | s++; 137 | 138 | vi = parseInt(s, &s); 139 | 140 | if (*s != '/') 141 | return s; 142 | s++; 143 | 144 | // handle vi//vni indices 145 | if (*s != '/') 146 | vti = parseInt(s, &s); 147 | 148 | if (*s != '/') 149 | return s; 150 | s++; 151 | 152 | vni = parseInt(s, &s); 153 | 154 | return s; 155 | } 156 | 157 | ObjFile::ObjFile() 158 | : v(0) 159 | , v_size(0) 160 | , v_cap(0) 161 | , vt(0) 162 | , vt_size(0) 163 | , vt_cap(0) 164 | , vn(0) 165 | , vn_size(0) 166 | , vn_cap(0) 167 | , f(0) 168 | , f_size(0) 169 | , f_cap(0) 170 | { 171 | } 172 | 173 | ObjFile::~ObjFile() 174 | { 175 | delete[] v; 176 | delete[] vt; 177 | delete[] vn; 178 | delete[] f; 179 | } 180 | 181 | void objParseLine(ObjFile& result, const char* line) 182 | { 183 | if (line[0] == 'v' && line[1] == ' ') 184 | { 185 | const char* s = line + 2; 186 | 187 | float x = parseFloat(s, &s); 188 | float y = parseFloat(s, &s); 189 | float z = parseFloat(s, &s); 190 | 191 | if (result.v_size + 3 > result.v_cap) 192 | growArray(result.v, result.v_cap); 193 | 194 | result.v[result.v_size++] = x; 195 | result.v[result.v_size++] = y; 196 | result.v[result.v_size++] = z; 197 | } 198 | else if (line[0] == 'v' && line[1] == 't' && line[2] == ' ') 199 | { 200 | const char* s = line + 3; 201 | 202 | float u = parseFloat(s, &s); 203 | float v = parseFloat(s, &s); 204 | float w = parseFloat(s, &s); 205 | 206 | if (result.vt_size + 3 > result.vt_cap) 207 | growArray(result.vt, result.vt_cap); 208 | 209 | result.vt[result.vt_size++] = u; 210 | result.vt[result.vt_size++] = v; 211 | result.vt[result.vt_size++] = w; 212 | } 213 | else if (line[0] == 'v' && line[1] == 'n' && line[2] == ' ') 214 | { 215 | const char* s = line + 3; 216 | 217 | float x = parseFloat(s, &s); 218 | float y = parseFloat(s, &s); 219 | float z = parseFloat(s, &s); 220 | 221 | if (result.vn_size + 3 > result.vn_cap) 222 | growArray(result.vn, result.vn_cap); 223 | 224 | result.vn[result.vn_size++] = x; 225 | result.vn[result.vn_size++] = y; 226 | result.vn[result.vn_size++] = z; 227 | } 228 | else if (line[0] == 'f' && line[1] == ' ') 229 | { 230 | const char* s = line + 2; 231 | 232 | size_t v = result.v_size / 3; 233 | size_t vt = result.vt_size / 3; 234 | size_t vn = result.vn_size / 3; 235 | 236 | int fv = 0; 237 | int f[3][3] = {}; 238 | 239 | while (*s) 240 | { 241 | int vi = 0, vti = 0, vni = 0; 242 | s = parseFace(s, vi, vti, vni); 243 | 244 | if (vi == 0) 245 | break; 246 | 247 | f[fv][0] = fixupIndex(vi, v); 248 | f[fv][1] = fixupIndex(vti, vt); 249 | f[fv][2] = fixupIndex(vni, vn); 250 | 251 | if (fv == 2) 252 | { 253 | if (result.f_size + 9 > result.f_cap) 254 | growArray(result.f, result.f_cap); 255 | 256 | memcpy(&result.f[result.f_size], f, 9 * sizeof(int)); 257 | result.f_size += 9; 258 | 259 | f[1][0] = f[2][0]; 260 | f[1][1] = f[2][1]; 261 | f[1][2] = f[2][2]; 262 | } 263 | else 264 | { 265 | fv++; 266 | } 267 | } 268 | } 269 | } 270 | 271 | bool objParseFile(ObjFile& result, const char* path) 272 | { 273 | FILE* file = fopen(path, "rb"); 274 | if (!file) 275 | return false; 276 | 277 | char buffer[65536]; 278 | size_t size = 0; 279 | 280 | while (!feof(file)) 281 | { 282 | size += fread(buffer + size, 1, sizeof(buffer) - size, file); 283 | 284 | size_t line = 0; 285 | 286 | while (line < size) 287 | { 288 | // find the end of current line 289 | void* eol = memchr(buffer + line, '\n', size - line); 290 | if (!eol) 291 | break; 292 | 293 | // zero-terminate for objParseLine 294 | size_t next = static_cast(eol) - buffer; 295 | 296 | buffer[next] = 0; 297 | 298 | // process next line 299 | objParseLine(result, buffer + line); 300 | 301 | line = next + 1; 302 | } 303 | 304 | // move prefix of the last line in the buffer to the beginning of the buffer for next iteration 305 | assert(line <= size); 306 | 307 | memmove(buffer, buffer + line, size - line); 308 | size -= line; 309 | } 310 | 311 | if (size) 312 | { 313 | // process last line 314 | assert(size < sizeof(buffer)); 315 | buffer[size] = 0; 316 | 317 | objParseLine(result, buffer); 318 | } 319 | 320 | fclose(file); 321 | return true; 322 | } 323 | 324 | bool objValidate(const ObjFile& result) 325 | { 326 | size_t v = result.v_size / 3; 327 | size_t vt = result.vt_size / 3; 328 | size_t vn = result.vn_size / 3; 329 | 330 | for (size_t i = 0; i < result.f_size; i += 3) 331 | { 332 | int vi = result.f[i + 0]; 333 | int vti = result.f[i + 1]; 334 | int vni = result.f[i + 2]; 335 | 336 | if (vi < 0) 337 | return false; 338 | 339 | if (vi >= 0 && size_t(vi) >= v) 340 | return false; 341 | 342 | if (vti >= 0 && size_t(vti) >= vt) 343 | return false; 344 | 345 | if (vni >= 0 && size_t(vni) >= vn) 346 | return false; 347 | } 348 | 349 | return true; 350 | } -------------------------------------------------------------------------------- /External/zeux_objparser/objparser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class ObjFile 6 | { 7 | public: 8 | float* v; // positions; stride 3 (xyz) 9 | size_t v_size, v_cap; 10 | 11 | float* vt; // texture coordinates; stride 3 (uvw) 12 | size_t vt_size, vt_cap; 13 | 14 | float* vn; // vertex normals; stride 3 (xyz) 15 | size_t vn_size, vn_cap; 16 | 17 | int* f; // face elements; stride 9 (3 groups of indices into v/vt/vn) 18 | size_t f_size, f_cap; 19 | 20 | ObjFile(); 21 | ~ObjFile(); 22 | 23 | private: 24 | ObjFile(const ObjFile&); 25 | ObjFile& operator=(const ObjFile&); 26 | }; 27 | 28 | void objParseLine(ObjFile& result, const char* line); 29 | bool objParseFile(ObjFile& result, const char* path); 30 | 31 | bool objValidate(const ObjFile& result); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yuriy O'Donnell 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ray Traced Shadows 2 | 3 | This demo implements BVH construction and GPU traversal for rendering hard shadows. 4 | 5 | ## BVH Construction and Layout 6 | 7 | BVH is constructed on CPU. The build process is fairly naive, but results in a high quality hierarchy that's fast to traverse. The tree is constructed using a top-down strategy, using a surface area heuristic (SAH) to find optimal split point at every level. 8 | 9 | Nodes are laid out in memory using a depth-first traversal order. Child node with the larger surface area is always on the left. This heuristic aims to find an intersected primitive for a ray in a cache-coherent manner. 10 | 11 | Each intermediate BVH node is packed into 32 bytes: 12 | 13 | struct BVHNode 14 | { 15 | vec3 bboxMin; 16 | uint primitiveId; 17 | vec3 bboxMax; 18 | uint next; 19 | }; 20 | 21 | Leaf BVH nodes are packed into 48 bytes: 22 | 23 | struct BVHNodeLeaf 24 | { 25 | vec3 edge0; 26 | uint padding0; 27 | vec3 edge1; 28 | uint next; // node miss pointer 29 | vec3 vertex; 30 | uint padding2; 31 | }; 32 | 33 | ## BVH Traversal 34 | 35 | Hard shadows are implemented by using any-hit BVH traversal for a ray on GPU. A stackless algorithm is used, which relies on the depth-first memory layout of the tree. 36 | 37 | The bounding box of each visited intermediate node is tested against a ray. On hit, the next node that must be visited is next in memory. On miss, current node's `next` pointer is used to skip part of the tree. This either jumps to the current node's right sibling or to the parent's right sibling. 38 | 39 | Each intermediate node contains a `primitiveId` field. If this field is not `0xFFFFFFFF`, then current node is reinterpreted as `BVHNodeLeaf`. Extra data for leaf nodes is stored deinterleaved (at the end of the BVH buffer). 40 | 41 | ## How to build on Windows with Visual Studio 2017 42 | 43 | Clone repository 44 | 45 | git@github.com:kayru/RayTracedShadows.git 46 | cd RayTracedShadows 47 | git submodule update --init 48 | 49 | Generate Visual Studio solution 50 | 51 | cd Scripts 52 | cmake-vs2017-vk.cmd 53 | 54 | Solution files written to `Build` directory in the root of the repository. 55 | 56 | ## Acknowledgements 57 | 58 | This demo uses similar ideas to what is described in the following work: 59 | 60 | * [The Perfect BVH, Jacco Bikker, 2016](http://www.cs.uu.nl/docs/vakken/magr/2015-2016/slides/lecture%2003%20-%20the%20perfect%20BVH.pdf) 61 | * [Implementing a practical rendering system using GLSL, Toshiya Hachisuka, 2015](http://www.ci.i.u-tokyo.ac.jp/~hachisuka/tdf2015.pdf) 62 | * [AMD RadeonRays](https://github.com/GPUOpen-LibrariesAndSDKs/RadeonRays_SDK) 63 | -------------------------------------------------------------------------------- /Scripts/cmake-vs2015-vk.cmd: -------------------------------------------------------------------------------- 1 | del ..\Build\CMakeCache.txt 2 | cmake -G "Visual Studio 14 2015 Win64" -B..\Build -H.. 3 | -------------------------------------------------------------------------------- /Scripts/cmake-vs2017-vk.cmd: -------------------------------------------------------------------------------- 1 | del ..\Build\CMakeCache.txt 2 | cmake -G "Visual Studio 15 2017 Win64" -B..\Build -H.. 3 | -------------------------------------------------------------------------------- /Scripts/cmake-vs2019-vk.cmd: -------------------------------------------------------------------------------- 1 | del ..\Build\CMakeCache.txt 2 | cmake -G "Visual Studio 16 2019" -B..\Build -H.. 3 | -------------------------------------------------------------------------------- /Scripts/cmake-xcode-mtl.sh: -------------------------------------------------------------------------------- 1 | rm ../Build/CMakeCache.txt 2 | cmake -G "Xcode" -DRUSH_RENDER_API=MTL -B../Build -H.. 3 | -------------------------------------------------------------------------------- /Source/BVHBuilder.cpp: -------------------------------------------------------------------------------- 1 | #include "BVHBuilder.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace 7 | { 8 | struct TempNode : BVHNode 9 | { 10 | u32 visitOrder = InvalidMask; 11 | u32 parent = InvalidMask; 12 | 13 | u32 left; 14 | u32 right; 15 | 16 | Vec3 bboxCenter; 17 | 18 | float primArea = 0.0f; 19 | 20 | float surfaceAreaLeft = 0.0f; 21 | float surfaceAreaRight = 0.0f; 22 | }; 23 | 24 | inline float bboxSurfaceArea(const Vec3& bboxMin, const Vec3& bboxMax) 25 | { 26 | Vec3 extents = bboxMax - bboxMin; 27 | return (extents.x * extents.y + extents.y * extents.z + extents.z * extents.x) * 2.0f; 28 | } 29 | 30 | inline float bboxSurfaceArea(const Box3& bbox) 31 | { 32 | return bboxSurfaceArea(bbox.m_min, bbox.m_max); 33 | } 34 | 35 | inline void setBounds(BVHNode& node, const Vec3& min, const Vec3& max) 36 | { 37 | node.bboxMin[0] = min.x; 38 | node.bboxMin[1] = min.y; 39 | node.bboxMin[2] = min.z; 40 | 41 | node.bboxMax[0] = max.x; 42 | node.bboxMax[1] = max.y; 43 | node.bboxMax[2] = max.z; 44 | } 45 | 46 | inline Vec3 extractVec3(__m128 v) 47 | { 48 | alignas(16) float temp[4]; 49 | _mm_store_ps(temp, v); 50 | return Vec3(temp); 51 | } 52 | 53 | Box3 calculateBounds(std::vector& nodes, u32 begin, u32 end) 54 | { 55 | Box3 bounds; 56 | if (begin == end) 57 | { 58 | bounds.m_min = Vec3(0.0f); 59 | bounds.m_max = Vec3(0.0f); 60 | } 61 | else 62 | { 63 | __m128 bboxMin = _mm_set1_ps(FLT_MAX); 64 | __m128 bboxMax = _mm_set1_ps(-FLT_MAX); 65 | for (u32 i = begin; i < end; ++i) 66 | { 67 | __m128 nodeBoundsMin = _mm_loadu_ps(&nodes[i].bboxMin.x); 68 | __m128 nodeBoundsMax = _mm_loadu_ps(&nodes[i].bboxMax.x); 69 | bboxMin = _mm_min_ps(bboxMin, nodeBoundsMin); 70 | bboxMax = _mm_max_ps(bboxMax, nodeBoundsMax); 71 | } 72 | bounds.m_min = extractVec3(bboxMin); 73 | bounds.m_max = extractVec3(bboxMax); 74 | } 75 | return bounds; 76 | } 77 | 78 | u32 split(std::vector& nodes, u32 begin, u32 end, const Box3& nodeBounds) 79 | { 80 | u32 count = end - begin; 81 | u32 bestSplit = begin; 82 | 83 | if (count <= 1000000) 84 | { 85 | u32 bestAxis = 0; 86 | u32 globalBestSplit = begin; 87 | float globalBestCost = FLT_MAX; 88 | 89 | for (u32 axis = 0; axis < 3; ++axis) 90 | { 91 | // TODO: just sort into N buckets 92 | std::sort(nodes.begin() + begin, nodes.begin() + end, 93 | [&](const TempNode& a, const TempNode& b) 94 | { 95 | return a.bboxCenter[axis] < b.bboxCenter[axis]; 96 | }); 97 | 98 | Box3 boundsLeft; 99 | boundsLeft.expandInit(); 100 | 101 | Box3 boundsRight; 102 | boundsRight.expandInit(); 103 | 104 | for (u32 indexLeft = 0; indexLeft < count; ++indexLeft) 105 | { 106 | u32 indexRight = count - indexLeft - 1; 107 | 108 | boundsLeft.expand(nodes[begin + indexLeft].bboxMin); 109 | boundsLeft.expand(nodes[begin + indexLeft].bboxMax); 110 | 111 | boundsRight.expand(nodes[begin + indexRight].bboxMin); 112 | boundsRight.expand(nodes[begin + indexRight].bboxMax); 113 | 114 | float surfaceAreaLeft = bboxSurfaceArea(boundsLeft); 115 | float surfaceAreaRight = bboxSurfaceArea(boundsRight); 116 | 117 | nodes[begin + indexLeft].surfaceAreaLeft = surfaceAreaLeft; 118 | nodes[begin + indexRight].surfaceAreaRight = surfaceAreaRight; 119 | } 120 | 121 | float bestCost = FLT_MAX; 122 | for (u32 mid = begin + 1; mid < end; ++mid) 123 | { 124 | float surfaceAreaLeft = nodes[mid - 1].surfaceAreaLeft; 125 | float surfaceAreaRight = nodes[mid].surfaceAreaRight; 126 | 127 | u32 countLeft = mid - begin; 128 | u32 countRight = end - mid; 129 | 130 | float costLeft = surfaceAreaLeft * (float)countLeft; 131 | float costRight = surfaceAreaRight * (float)countRight; 132 | 133 | float cost = costLeft + costRight; 134 | if (cost < bestCost) 135 | { 136 | bestSplit = mid; 137 | bestCost = cost; 138 | } 139 | } 140 | 141 | if (bestCost < globalBestCost) 142 | { 143 | globalBestSplit = bestSplit; 144 | globalBestCost = bestCost; 145 | bestAxis = axis; 146 | } 147 | } 148 | 149 | std::sort(nodes.begin() + begin, nodes.begin() + end, 150 | [&](const TempNode& a, const TempNode& b) 151 | { 152 | return a.bboxCenter[bestAxis] < b.bboxCenter[bestAxis]; 153 | }); 154 | 155 | return globalBestSplit; 156 | } 157 | else 158 | { 159 | Vec3 extents = nodeBounds.dimensions(); 160 | int majorAxis = (int)std::distance(extents.begin(), std::max_element(extents.begin(), extents.end())); 161 | 162 | std::sort(nodes.begin() + begin, nodes.begin() + end, 163 | [&](const TempNode& a, const TempNode& b) 164 | { 165 | return a.bboxCenter[majorAxis] < b.bboxCenter[majorAxis]; 166 | }); 167 | 168 | float splitPos = (nodeBounds.m_min[majorAxis] + nodeBounds.m_max[majorAxis]) * 0.5f; 169 | for (u32 mid = begin + 1; mid < end; ++mid) 170 | { 171 | if (nodes[mid].bboxCenter[majorAxis] >= splitPos) 172 | { 173 | return mid; 174 | } 175 | } 176 | 177 | return end - 1; 178 | }; 179 | } 180 | 181 | u32 buildInternal(std::vector& nodes, u32 begin, u32 end) 182 | { 183 | u32 count = end - begin; 184 | 185 | if (count == 1) 186 | { 187 | return begin; 188 | } 189 | 190 | Box3 bounds = calculateBounds(nodes, begin, end); 191 | 192 | u32 mid = split(nodes, begin, end, bounds); 193 | 194 | u32 nodeId = (u32)nodes.size(); 195 | nodes.push_back(TempNode()); 196 | 197 | TempNode node; 198 | 199 | node.left = buildInternal(nodes, begin, mid); 200 | node.right = buildInternal(nodes, mid, end); 201 | 202 | float surfaceAreaLeft = bboxSurfaceArea(nodes[node.left].bboxMin, nodes[node.left].bboxMax); 203 | float surfaceAreaRight = bboxSurfaceArea(nodes[node.right].bboxMin, nodes[node.right].bboxMax); 204 | 205 | if (surfaceAreaRight > surfaceAreaLeft) 206 | { 207 | std::swap(node.left, node.right); 208 | } 209 | 210 | setBounds(node, bounds.m_min, bounds.m_max); 211 | node.bboxCenter = bounds.center(); 212 | node.prim = BVHNode::InvalidMask; 213 | 214 | nodes[node.left].parent = nodeId; 215 | nodes[node.right].parent = nodeId; 216 | 217 | nodes[nodeId] = node; 218 | 219 | return nodeId; 220 | } 221 | 222 | void setDepthFirstVisitOrder(std::vector& nodes, u32 nodeId, u32 nextId, u32& order) 223 | { 224 | TempNode& node = nodes[nodeId]; 225 | 226 | node.visitOrder = order++; 227 | node.next = nextId; 228 | 229 | if (node.left != BVHNode::InvalidMask) 230 | { 231 | setDepthFirstVisitOrder(nodes, node.left, node.right, order); 232 | } 233 | 234 | if (node.right != BVHNode::InvalidMask) 235 | { 236 | setDepthFirstVisitOrder(nodes, node.right, nextId, order); 237 | } 238 | } 239 | 240 | void setDepthFirstVisitOrder(std::vector& nodes, u32 root) 241 | { 242 | u32 order = 0; 243 | setDepthFirstVisitOrder(nodes, root, BVHNode::InvalidMask, order); 244 | } 245 | 246 | } 247 | 248 | void BVHBuilder::build(const float* vertices, u32 stride, const u32* indices, u32 primCount) 249 | { 250 | auto getVertex = [vertices, stride](u32 vertexId) 251 | { 252 | return Vec3(vertices + stride*vertexId); 253 | }; 254 | 255 | m_nodes.clear(); 256 | m_nodes.reserve(primCount * 2 - 1); 257 | 258 | std::vector tempNodes; 259 | tempNodes.reserve(primCount * 2 - 1); 260 | 261 | for (u32 primId = 0; primId < primCount; ++primId) 262 | { 263 | TempNode node; 264 | Box3 box; 265 | box.expandInit(); 266 | 267 | Vec3 v0 = getVertex(indices[primId * 3 + 0]); 268 | Vec3 v1 = getVertex(indices[primId * 3 + 1]); 269 | Vec3 v2 = getVertex(indices[primId * 3 + 2]); 270 | 271 | box.expand(v0); 272 | box.expand(v1); 273 | box.expand(v2); 274 | 275 | node.primArea = Triangle::calculateArea(v0, v1, v2); 276 | 277 | setBounds(node, box.m_min, box.m_max); 278 | 279 | node.bboxCenter = box.center(); 280 | node.prim = primId; 281 | node.left = BVHNode::InvalidMask; 282 | node.right = BVHNode::InvalidMask; 283 | tempNodes.push_back(node); 284 | } 285 | 286 | const u32 rootIndex = buildInternal(tempNodes, 0, (u32)tempNodes.size()); 287 | 288 | setDepthFirstVisitOrder(tempNodes, rootIndex); 289 | 290 | m_nodes.resize(tempNodes.size()); 291 | 292 | for (u32 oldIndex = 0; oldIndex < (u32)tempNodes.size(); ++oldIndex) 293 | { 294 | const TempNode& oldNode = tempNodes[oldIndex]; 295 | 296 | BVHNode& newNode = m_nodes[oldNode.visitOrder]; 297 | 298 | Vec3 bboxMin(oldNode.bboxMin); 299 | Vec3 bboxMax(oldNode.bboxMax); 300 | setBounds(newNode, bboxMin, bboxMax); 301 | 302 | newNode.prim = oldNode.prim; 303 | newNode.next = oldNode.next == BVHNode::InvalidMask 304 | ? BVHNode::InvalidMask 305 | : tempNodes[oldNode.next].visitOrder; 306 | } 307 | 308 | m_packedNodes.reserve(m_nodes.size() + primCount); 309 | 310 | for (u32 i = 0; i < (u32)tempNodes.size(); ++i) 311 | { 312 | const BVHNode& node = m_nodes[i]; 313 | 314 | if (node.isLeaf()) 315 | { 316 | struct BVHPrimitiveNode 317 | { 318 | Vec3 edge0; 319 | u32 prim; 320 | Vec3 edge1; 321 | u32 next; 322 | }; 323 | 324 | BVHPrimitiveNode packedNode; 325 | 326 | Vec3 v0 = getVertex(indices[node.prim * 3 + 0]); 327 | Vec3 v1 = getVertex(indices[node.prim * 3 + 1]); 328 | Vec3 v2 = getVertex(indices[node.prim * 3 + 2]); 329 | 330 | packedNode.edge0 = v1 - v0; 331 | packedNode.prim = node.prim + (u32)tempNodes.size() * 2; 332 | 333 | packedNode.edge1 = v2 - v0; 334 | packedNode.next = node.next; 335 | 336 | BVHPackedNode data0, data1; 337 | memcpy(&data0, &packedNode.edge0, sizeof(BVHPackedNode)); 338 | memcpy(&data1, &packedNode.edge1, sizeof(BVHPackedNode)); 339 | 340 | m_packedNodes.push_back(data0); 341 | m_packedNodes.push_back(data1); 342 | } 343 | else 344 | { 345 | BVHNode packedNode; 346 | 347 | packedNode.bboxMin = node.bboxMin; 348 | packedNode.prim = node.prim; 349 | packedNode.bboxMax = node.bboxMax; 350 | packedNode.next = node.next; 351 | 352 | BVHPackedNode data0, data1; 353 | memcpy(&data0, &packedNode.bboxMin, sizeof(BVHPackedNode)); 354 | memcpy(&data1, &packedNode.bboxMax, sizeof(BVHPackedNode)); 355 | 356 | m_packedNodes.push_back(data0); 357 | m_packedNodes.push_back(data1); 358 | } 359 | } 360 | 361 | for (u32 primId = 0; primId < primCount; ++primId) 362 | { 363 | Vec3 v0 = getVertex(indices[primId * 3 + 0]); 364 | BVHPackedNode data; 365 | memcpy(&data, &v0, sizeof(BVHPackedNode)); 366 | m_packedNodes.push_back(data); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /Source/BVHBuilder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | struct BVHNode 9 | { 10 | static const u32 LeafMask = 0x80000000; 11 | static const u32 InvalidMask = 0xFFFFFFFF; 12 | 13 | Vec3 bboxMin; 14 | u32 prim = InvalidMask; 15 | 16 | Vec3 bboxMax; 17 | u32 next = InvalidMask; 18 | 19 | bool isLeaf() const { return prim != InvalidMask; } 20 | }; 21 | 22 | struct BVHPackedNode 23 | { 24 | u32 a, b, c, d; 25 | }; 26 | 27 | struct BVHBuilder 28 | { 29 | std::vector m_nodes; 30 | std::vector m_packedNodes; 31 | void build(const float* vertices, u32 stride, const u32* indices, u32 primCount); 32 | }; 33 | 34 | 35 | -------------------------------------------------------------------------------- /Source/BaseApplication.cpp: -------------------------------------------------------------------------------- 1 | #include "BaseApplication.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | BaseApplication::BaseApplication() 8 | : m_dev(Platform_GetGfxDevice()), m_ctx(Platform_GetGfxContext()), m_window(Platform_GetWindow()) 9 | { 10 | m_window->retain(); 11 | Gfx_Retain(m_dev); 12 | Gfx_Retain(m_ctx); 13 | 14 | m_prim = new PrimitiveBatch(); 15 | m_font = new BitmapFontRenderer(BitmapFontRenderer::createEmbeddedFont(true, 0, 1)); 16 | 17 | // Depth stencil states 18 | 19 | { 20 | GfxDepthStencilDesc desc; 21 | desc.enable = false; 22 | desc.writeEnable = false; 23 | desc.compareFunc = GfxCompareFunc::Always; 24 | m_depthStencilStates.disable = Gfx_CreateDepthStencilState(desc); 25 | } 26 | 27 | { 28 | GfxDepthStencilDesc desc; 29 | desc.enable = true; 30 | desc.writeEnable = true; 31 | desc.compareFunc = GfxCompareFunc::LessEqual; 32 | m_depthStencilStates.writeLessEqual = Gfx_CreateDepthStencilState(desc); 33 | } 34 | 35 | { 36 | GfxDepthStencilDesc desc; 37 | desc.enable = true; 38 | desc.writeEnable = true; 39 | desc.compareFunc = GfxCompareFunc::Always; 40 | m_depthStencilStates.writeAlways = Gfx_CreateDepthStencilState(desc); 41 | } 42 | 43 | { 44 | GfxDepthStencilDesc desc; 45 | desc.enable = true; 46 | desc.writeEnable = false; 47 | desc.compareFunc = GfxCompareFunc::LessEqual; 48 | m_depthStencilStates.testLessEqual = Gfx_CreateDepthStencilState(desc); 49 | } 50 | 51 | // Blend states 52 | 53 | { 54 | GfxBlendStateDesc desc = GfxBlendStateDesc::makeOpaque(); 55 | m_blendStates.opaque = Gfx_CreateBlendState(desc); 56 | } 57 | 58 | { 59 | GfxBlendStateDesc desc = GfxBlendStateDesc::makeLerp(); 60 | m_blendStates.lerp = Gfx_CreateBlendState(desc); 61 | } 62 | 63 | { 64 | GfxBlendStateDesc desc = GfxBlendStateDesc::makeAdditive(); 65 | m_blendStates.additive = Gfx_CreateBlendState(desc); 66 | } 67 | 68 | // Sampler states 69 | 70 | { 71 | GfxSamplerDesc desc = GfxSamplerDesc::makePoint(); 72 | desc.wrapU = GfxTextureWrap::Clamp; 73 | desc.wrapV = GfxTextureWrap::Clamp; 74 | desc.wrapW = GfxTextureWrap::Clamp; 75 | m_samplerStates.pointClamp = Gfx_CreateSamplerState(desc); 76 | } 77 | 78 | { 79 | GfxSamplerDesc desc = GfxSamplerDesc::makeLinear(); 80 | desc.wrapU = GfxTextureWrap::Clamp; 81 | desc.wrapV = GfxTextureWrap::Clamp; 82 | desc.wrapW = GfxTextureWrap::Clamp; 83 | m_samplerStates.linearClamp = Gfx_CreateSamplerState(desc); 84 | } 85 | 86 | { 87 | GfxSamplerDesc desc = GfxSamplerDesc::makeLinear(); 88 | desc.wrapU = GfxTextureWrap::Wrap; 89 | desc.wrapV = GfxTextureWrap::Wrap; 90 | desc.wrapW = GfxTextureWrap::Wrap; 91 | m_samplerStates.linearWrap = Gfx_CreateSamplerState(desc); 92 | } 93 | 94 | { 95 | GfxSamplerDesc desc = GfxSamplerDesc::makeLinear(); 96 | desc.wrapU = GfxTextureWrap::Wrap; 97 | desc.wrapV = GfxTextureWrap::Wrap; 98 | desc.wrapW = GfxTextureWrap::Wrap; 99 | desc.anisotropy = 4.0f; 100 | m_samplerStates.anisotropicWrap = Gfx_CreateSamplerState(desc); 101 | } 102 | } 103 | 104 | BaseApplication::~BaseApplication() 105 | { 106 | delete m_font; 107 | delete m_prim; 108 | 109 | Gfx_Release(m_ctx); 110 | Gfx_Release(m_dev); 111 | m_window->release(); 112 | } 113 | -------------------------------------------------------------------------------- /Source/BaseApplication.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class ShaderCompiler; 7 | 8 | namespace Rush 9 | { 10 | class PrimitiveBatch; 11 | class BitmapFontRenderer; 12 | } 13 | 14 | class BaseApplication : public Application 15 | { 16 | RUSH_DISALLOW_COPY_AND_ASSIGN(BaseApplication); 17 | 18 | public: 19 | BaseApplication(); 20 | ~BaseApplication(); 21 | 22 | protected: 23 | struct DepthStencilStates 24 | { 25 | GfxOwn testLessEqual; 26 | GfxOwn writeLessEqual; 27 | GfxOwn writeAlways; 28 | GfxOwn disable; 29 | } m_depthStencilStates; 30 | 31 | struct SamplerStates 32 | { 33 | GfxOwn pointClamp; 34 | GfxOwn linearClamp; 35 | GfxOwn linearWrap; 36 | GfxOwn anisotropicWrap; 37 | } m_samplerStates; 38 | 39 | struct BlendStates 40 | { 41 | GfxOwn lerp; 42 | GfxOwn opaque; 43 | GfxOwn additive; 44 | } m_blendStates; 45 | 46 | GfxDevice* m_dev; 47 | GfxContext* m_ctx; 48 | Window* m_window; 49 | PrimitiveBatch* m_prim; 50 | BitmapFontRenderer* m_font; 51 | }; 52 | -------------------------------------------------------------------------------- /Source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(app RayTracedShadows) 2 | 3 | add_executable(${app} 4 | BaseApplication.cpp 5 | BaseApplication.h 6 | BVHBuilder.cpp 7 | BVHBuilder.h 8 | MovingAverage.h 9 | RayTracedShadows.cpp 10 | RayTracedShadows.h 11 | ) 12 | 13 | set(shaderDependencies 14 | # Add explicit dependencies here 15 | ) 16 | 17 | set(shaders 18 | Shaders/Blit.vert 19 | Shaders/Combine.frag 20 | Shaders/Model.vert 21 | Shaders/Model.frag 22 | Shaders/RayTracedShadows.comp 23 | ) 24 | 25 | if (USE_VK_RAYTRACING) 26 | set(shaders ${shaders} 27 | Shaders/RayTracedShadows.rgen 28 | Shaders/RayTracedShadows.rmiss 29 | Shaders/RayTracedShadowsInline.comp 30 | ) 31 | target_sources(${app} PRIVATE 32 | VkRaytracing.h 33 | VkRaytracing.cpp 34 | ) 35 | target_compile_definitions(${app} PRIVATE 36 | USE_VK_RAYTRACING=1 37 | ) 38 | else() 39 | target_compile_definitions(${app} PRIVATE 40 | USE_VK_RAYTRACING=0 41 | ) 42 | endif() 43 | 44 | target_sources(${app} PRIVATE ${shaders}) 45 | source_group("Shaders" FILES ${shaders} ${shaderDependencies}) 46 | 47 | find_program(GLSLC NAMES glslc NO_SYSTEM_ENVIRONMENT_PATH PATHS 48 | "../Tools" 49 | $ENV{VULKAN_SDK}/Bin 50 | $ENV{VK_SDK_PATH}/Bin 51 | $ENV{PATH} 52 | "~/bin" 53 | ) 54 | 55 | if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 56 | find_program(spirv-cross NAMES spirv-cross PATHS 57 | $ENV{VK_SDK_PATH}/Bin 58 | $ENV{PATH} 59 | "~/bin" 60 | ) 61 | function(shader_compile_rule shaderName dependencies) 62 | add_custom_command( 63 | OUTPUT ${CMAKE_CFG_INTDIR}/${shaderName}.metal 64 | COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CFG_INTDIR}/Shaders 65 | COMMAND ${GLSLC} -o ${CMAKE_CFG_INTDIR}/${shaderName}.spv ${CMAKE_CURRENT_SOURCE_DIR}/${shaderName} 66 | COMMAND ${spirv-cross} --metal ${CMAKE_CFG_INTDIR}/${shaderName}.spv > ${CMAKE_CFG_INTDIR}/${shaderName}.metal 67 | MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/${shaderName} 68 | DEPENDS ${dependencies} 69 | ) 70 | endfunction(shader_compile_rule) 71 | else() 72 | function(shader_compile_rule shaderName dependencies) 73 | add_custom_command( 74 | OUTPUT ${CMAKE_CFG_INTDIR}/${shaderName}.spv 75 | COMMAND ${GLSLC} --target-env=vulkan1.2 -o ${CMAKE_CFG_INTDIR}/${shaderName}.spv ${CMAKE_CURRENT_SOURCE_DIR}/${shaderName} 76 | MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/${shaderName} 77 | DEPENDS ${dependencies} 78 | ) 79 | endfunction(shader_compile_rule) 80 | endif() 81 | 82 | foreach(shader ${shaders}) 83 | shader_compile_rule(${shader} "${shaderDependencies}") 84 | endforeach() 85 | 86 | target_compile_definitions(${app} PRIVATE 87 | RUSH_USING_NAMESPACE # Automatically use Rush namespace 88 | ) 89 | 90 | target_link_libraries(${app} 91 | Rush 92 | stb 93 | tiny_obj_loader 94 | zeux_objparser 95 | ) 96 | -------------------------------------------------------------------------------- /Source/MovingAverage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | struct MovingAverage 7 | { 8 | MovingAverage() 9 | { 10 | reset(); 11 | } 12 | 13 | inline void reset() 14 | { 15 | idx = 0; 16 | sum = 0; 17 | for( size_t i=0; i 8 | #include 9 | #include 10 | 11 | #define STB_IMAGE_IMPLEMENTATION 12 | #include 13 | 14 | #define STB_IMAGE_RESIZE_IMPLEMENTATION 15 | #include 16 | 17 | #define USE_ZEUX_OBJPARSER 1 18 | 19 | #if USE_ZEUX_OBJPARSER 20 | #include 21 | #else // USE_ZEUX_OBJPARSER 22 | #include 23 | #endif // USE_ZEUX_OBJPARSER 24 | 25 | AppConfig g_appConfig; 26 | 27 | #ifdef __GNUC__ 28 | #define sprintf_s sprintf 29 | #endif 30 | 31 | #if RUSH_RENDER_API == RUSH_RENDER_API_MTL 32 | #define MAKE_SHADER_NAME(x) x ".metal" 33 | #else 34 | #define MAKE_SHADER_NAME(x) x ".spv" 35 | #endif 36 | 37 | int main(int argc, char** argv) 38 | { 39 | g_appConfig.name = "RayTracedShadows (" RUSH_RENDER_API_NAME ")"; 40 | 41 | g_appConfig.width = 1280; 42 | g_appConfig.height = 720; 43 | g_appConfig.argc = argc; 44 | g_appConfig.argv = argv; 45 | g_appConfig.resizable = true; 46 | 47 | #ifndef NDEBUG 48 | g_appConfig.debug = true; 49 | Log::breakOnError = true; 50 | #endif 51 | 52 | return Platform_Main(g_appConfig); 53 | } 54 | 55 | struct TimingScope 56 | { 57 | TimingScope(MovingAverageBuffer& output) 58 | : m_output(output) 59 | {} 60 | 61 | ~TimingScope() 62 | { 63 | m_output.add(m_timer.time()); 64 | } 65 | 66 | MovingAverageBuffer& m_output; 67 | Timer m_timer; 68 | }; 69 | 70 | RayTracedShadowsApp::RayTracedShadowsApp() 71 | : m_boundingBox(Vec3(0.0f), Vec3(0.0f)) 72 | { 73 | Gfx_SetPresentInterval(m_presentInterval); 74 | 75 | #if USE_VK_RAYTRACING 76 | const GfxCapability& caps = Gfx_GetCapability(); 77 | if (caps.rayTracing) 78 | { 79 | m_vkRaytracing = new VkRaytracing(); 80 | m_mode = ShadowRenderMode::Hardware; 81 | } 82 | #endif // USE_VK_RAYTRACING 83 | 84 | m_windowEvents.setOwner(m_window); 85 | 86 | const u32 whiteTexturePixels[4] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; 87 | GfxTextureDesc textureDesc = GfxTextureDesc::make2D(2, 2); 88 | m_defaultWhiteTexture = Gfx_CreateTexture(textureDesc, whiteTexturePixels); 89 | 90 | createRenderTargets(m_window->getSize()); 91 | 92 | const char* shaderDirectory = Platform_GetExecutableDirectory(); 93 | auto shaderFromFile = [shaderDirectory](const char* filename) 94 | { 95 | std::string fullFilename = std::string(shaderDirectory) + "/" + std::string(filename); 96 | Log::message("Loading shader '%s'", filename); 97 | 98 | GfxShaderSource source; 99 | 100 | #if RUSH_RENDER_API == RUSH_RENDER_API_MTL 101 | source.type = GfxShaderSourceType_MSL; 102 | bool isText = true; 103 | #else 104 | source.type = GfxShaderSourceType_SPV; 105 | bool isText = false; 106 | #endif 107 | 108 | FileIn f(fullFilename.c_str()); 109 | if (f.valid()) 110 | { 111 | u32 fileSize = (u32)f.length(); 112 | source.resize(fileSize + (isText ? 1 : 0), 0); 113 | f.read(&source[0], fileSize); 114 | } 115 | 116 | if (source.empty()) 117 | { 118 | Log::error("Failed to load shader '%s'", filename); 119 | } 120 | 121 | return source; 122 | }; 123 | 124 | { 125 | GfxOwn modelVS; 126 | modelVS = Gfx_CreateVertexShader(shaderFromFile(MAKE_SHADER_NAME("Shaders/Model.vert"))); 127 | 128 | GfxOwn modelPS; 129 | modelPS = Gfx_CreatePixelShader(shaderFromFile(MAKE_SHADER_NAME("Shaders/Model.frag"))); 130 | 131 | GfxVertexFormatDesc modelVFDesc; 132 | modelVFDesc.add(0, GfxVertexFormatDesc::DataType::Float3, GfxVertexFormatDesc::Semantic::Position, 0); 133 | modelVFDesc.add(0, GfxVertexFormatDesc::DataType::Float3, GfxVertexFormatDesc::Semantic::Normal, 0); 134 | modelVFDesc.add(0, GfxVertexFormatDesc::DataType::Float2, GfxVertexFormatDesc::Semantic::Texcoord, 0); 135 | 136 | GfxOwn modelVF; 137 | modelVF = Gfx_CreateVertexFormat(modelVFDesc); 138 | 139 | GfxShaderBindingDesc bindings; 140 | bindings.descriptorSets[0].constantBuffers = 2; 141 | bindings.descriptorSets[0].samplers = 1; 142 | bindings.descriptorSets[0].textures = 1; 143 | m_techniqueModel = Gfx_CreateTechnique(GfxTechniqueDesc(modelPS.get(), modelVS.get(), modelVF.get(), bindings)); 144 | } 145 | 146 | { 147 | GfxOwn cs; 148 | cs = Gfx_CreateComputeShader(shaderFromFile(MAKE_SHADER_NAME("Shaders/RayTracedShadows.comp"))); 149 | 150 | GfxShaderBindingDesc bindings; 151 | bindings.descriptorSets[0].constantBuffers = 1; 152 | bindings.descriptorSets[0].samplers = 1; 153 | bindings.descriptorSets[0].textures = 1; 154 | bindings.descriptorSets[0].rwImages = 1; 155 | bindings.descriptorSets[0].rwBuffers = 1; 156 | m_techniqueRayTracedShadows = Gfx_CreateTechnique(GfxTechniqueDesc(cs.get(), bindings, {8, 8, 1})); 157 | } 158 | 159 | { 160 | GfxOwn vf; 161 | vf = Gfx_CreateVertexFormat(GfxVertexFormatDesc()); 162 | 163 | GfxOwn vs; 164 | vs = Gfx_CreateVertexShader(shaderFromFile(MAKE_SHADER_NAME("Shaders/Blit.vert"))); 165 | 166 | { 167 | GfxOwn ps; 168 | ps = Gfx_CreatePixelShader(shaderFromFile(MAKE_SHADER_NAME("Shaders/Combine.frag"))); 169 | 170 | GfxShaderBindingDesc bindings; 171 | bindings.descriptorSets[0].constantBuffers = 1; 172 | bindings.descriptorSets[0].samplers = 1; 173 | bindings.descriptorSets[0].textures = 3; 174 | m_techniqueCombine = Gfx_CreateTechnique(GfxTechniqueDesc(ps.get(), vs.get(), vf.get(), bindings)); 175 | } 176 | } 177 | 178 | #if USE_VK_RAYTRACING 179 | if (m_vkRaytracing) 180 | { 181 | GfxShaderSource rgen = shaderFromFile(MAKE_SHADER_NAME("Shaders/RayTracedShadows.rgen")); 182 | GfxShaderSource rmiss = shaderFromFile(MAKE_SHADER_NAME("Shaders/RayTracedShadows.rmiss")); 183 | 184 | m_vkRaytracing->createPipeline(rgen, rmiss); 185 | 186 | if (caps.rayTracingInline) 187 | { 188 | GfxOwn cs; 189 | cs = Gfx_CreateComputeShader(shaderFromFile(MAKE_SHADER_NAME("Shaders/RayTracedShadowsInline.comp"))); 190 | 191 | GfxShaderBindingDesc bindings; 192 | bindings.descriptorSets[0].constantBuffers = 1; 193 | bindings.descriptorSets[0].samplers = 1; 194 | bindings.descriptorSets[0].textures = 1; 195 | bindings.descriptorSets[0].rwImages = 1; 196 | bindings.descriptorSets[0].accelerationStructures = 1; 197 | m_techniqueRayTracedShadowsInline = Gfx_CreateTechnique(GfxTechniqueDesc(cs.get(), bindings, { 8, 8, 1 })); 198 | } 199 | } 200 | #endif // USE_VK_RAYTRACING 201 | 202 | { 203 | GfxBufferDesc cbDesc(GfxBufferFlags::TransientConstant, GfxFormat_Unknown, 1, sizeof(ModelConstants)); 204 | m_modelGlobalConstantBuffer = Gfx_CreateBuffer(cbDesc); 205 | } 206 | 207 | { 208 | GfxBufferDesc cbDesc(GfxBufferFlags::TransientConstant, GfxFormat_Unknown, 1, sizeof(RayTracingConstants)); 209 | m_rayTracingConstantBuffer= Gfx_CreateBuffer(cbDesc); 210 | } 211 | 212 | if (g_appConfig.argc >= 2) 213 | { 214 | const char* modelFilename = g_appConfig.argv[1]; 215 | m_statusString = std::string("Model: ") + modelFilename; 216 | m_valid = loadModel(modelFilename); 217 | 218 | Vec3 dimensions = m_boundingBox.dimensions(); 219 | float longestSide = dimensions.reduceMax(); 220 | 221 | if (longestSide != 0) 222 | { 223 | m_cameraScale = longestSide / 100.0f; 224 | //Vec3 center = m_boundingBox.center(); 225 | //float scale = 100.0f / longestSide; 226 | //m_worldTransform = Mat4::scaleTranslate(scale, -center*scale); 227 | //m_cameraMan.setMoveSpeed(); 228 | } 229 | 230 | m_boundingBox.m_min = m_worldTransform * m_boundingBox.m_min; 231 | m_boundingBox.m_max = m_worldTransform * m_boundingBox.m_max; 232 | } 233 | else 234 | { 235 | m_statusString = "Usage: RayTracedShadows "; 236 | } 237 | 238 | float aspect = m_window->getAspect(); 239 | float fov = 1.0f; 240 | 241 | m_camera = Camera(aspect, fov, 0.25f, 10000.0f); 242 | m_camera.lookAt(Vec3(m_boundingBox.m_max) + Vec3(2.0f), m_boundingBox.center()); 243 | m_interpolatedCamera = m_camera; 244 | 245 | m_lightCamera.lookAt(Vec3(0.0f), Vec3(1.0f)); 246 | 247 | Log::message("Initialization complete"); 248 | } 249 | 250 | RayTracedShadowsApp::~RayTracedShadowsApp() 251 | { 252 | #if USE_VK_RAYTRACING 253 | delete m_vkRaytracing; 254 | #endif // USE_VK_RAYTRACING 255 | 256 | m_windowEvents.setOwner(nullptr); 257 | } 258 | 259 | void RayTracedShadowsApp::update() 260 | { 261 | TimingScope timingScope(m_stats.cpuTotal); 262 | 263 | m_stats.gpuGbuffer.add(Gfx_Stats().customTimer[Timestamp_Gbuffer]); 264 | m_stats.gpuShadows.add(Gfx_Stats().customTimer[Timestamp_Shadows]); 265 | m_stats.gpuTotal.add(Gfx_Stats().lastFrameGpuTime); 266 | 267 | Gfx_ResetStats(); 268 | 269 | const float dt = (float)m_timer.time(); 270 | m_timer.reset(); 271 | 272 | bool wantResize = false; 273 | Tuple2i pendingSize; 274 | 275 | for (const WindowEvent& e : m_windowEvents) 276 | { 277 | switch (e.type) 278 | { 279 | case WindowEventType_KeyDown: 280 | if (e.code == Key_1) 281 | { 282 | m_mode = ShadowRenderMode::Compute; 283 | } 284 | else if (e.code == Key_2) 285 | { 286 | m_mode = ShadowRenderMode::Hardware; 287 | } 288 | else if (e.code == Key_3) 289 | { 290 | m_mode = ShadowRenderMode::HardwareInline; 291 | } 292 | else if (e.code == Key_V) 293 | { 294 | m_presentInterval = !m_presentInterval; 295 | Gfx_SetPresentInterval(m_presentInterval); 296 | } 297 | break; 298 | case WindowEventType_Resize: 299 | wantResize = true; 300 | pendingSize = Tuple2i{ (int)e.width, (int)e.height }; 301 | break; 302 | 303 | case WindowEventType_MouseDown: 304 | if (e.button == 1) 305 | { 306 | m_prevMousePos = m_window->getMouseState().pos; 307 | } 308 | break; 309 | 310 | case WindowEventType_Scroll: 311 | if (e.scroll.y > 0) 312 | { 313 | m_cameraScale *= 1.25f; 314 | } 315 | else 316 | { 317 | m_cameraScale *= 0.9f; 318 | } 319 | Log::message("Camera scale: %f", m_cameraScale); 320 | break; 321 | default: 322 | break; 323 | } 324 | } 325 | 326 | if (wantResize) 327 | { 328 | createRenderTargets(pendingSize); 329 | } 330 | 331 | float clipNear = 0.25f * m_cameraScale; 332 | float clipFar = 10000.0f * m_cameraScale; 333 | m_camera.setClip(clipNear, clipFar); 334 | m_camera.setAspect(m_window->getAspect()); 335 | m_cameraMan.setMoveSpeed(20.0f * m_cameraScale); 336 | m_cameraMan.update(&m_camera, dt, 337 | m_window->getKeyboardState(), 338 | m_window->getMouseState()); 339 | 340 | m_interpolatedCamera.blendTo(m_camera, 0.1f, 0.125f); 341 | 342 | if (m_window->getMouseState().buttons[1]) 343 | { 344 | Vec2 mouseDelta = m_window->getMouseState().pos - m_prevMousePos; 345 | if (mouseDelta != Vec2(0.0f)) 346 | { 347 | mouseDelta *= 0.005f; 348 | m_lightCamera.rotateOnAxis(mouseDelta.x, Vec3(0, 1, 0)); 349 | m_lightCamera.rotateOnAxis(-mouseDelta.y, m_lightCamera.getRight()); 350 | } 351 | } 352 | 353 | m_prevMousePos = m_window->getMouseState().pos; 354 | 355 | m_windowEvents.clear(); 356 | 357 | const GfxCapability& caps = Gfx_GetCapability(); 358 | 359 | Mat4 matView = m_interpolatedCamera.buildViewMatrix(); 360 | Mat4 matProj = m_interpolatedCamera.buildProjMatrix(); 361 | m_matViewProj = matView * matProj; 362 | m_matViewProjInv = m_matViewProj.inverse(); 363 | 364 | render(); 365 | } 366 | 367 | 368 | void RayTracedShadowsApp::createRenderTargets(Tuple2i size) 369 | { 370 | GfxTextureDesc desc; 371 | desc.type = TextureType::Tex2D; 372 | desc.width = size.x; 373 | desc.height = size.y; 374 | desc.depth = 1; 375 | desc.mips = 1; 376 | 377 | desc.format = GfxFormat_RGBA8_Unorm; 378 | desc.usage = GfxUsageFlags::RenderTarget | GfxUsageFlags::ShaderResource; 379 | m_gbufferBaseColor = Gfx_CreateTexture(desc); 380 | 381 | desc.format = GfxFormat_RGBA16_Float; 382 | desc.usage = GfxUsageFlags::RenderTarget | GfxUsageFlags::ShaderResource; 383 | m_gbufferNormal = Gfx_CreateTexture(desc); 384 | 385 | desc.format = GfxFormat_RGBA32_Float; 386 | desc.usage = GfxUsageFlags::RenderTarget | GfxUsageFlags::ShaderResource; 387 | m_gbufferPosition = Gfx_CreateTexture(desc); 388 | 389 | desc.format = GfxFormat_D32_Float; 390 | desc.usage = GfxUsageFlags::DepthStencil | GfxUsageFlags::ShaderResource; 391 | m_gbufferDepth = Gfx_CreateTexture(desc); 392 | 393 | desc.format = GfxFormat_R8_Unorm; 394 | desc.usage = GfxUsageFlags::ShaderResource | GfxUsageFlags::StorageImage; 395 | m_shadowMask = Gfx_CreateTexture(desc); 396 | } 397 | 398 | const char* toString(ShadowRenderMode mode) 399 | { 400 | switch (mode) 401 | { 402 | case ShadowRenderMode::Compute: return "Compute"; 403 | case ShadowRenderMode::Hardware: return "Hardware"; 404 | case ShadowRenderMode::HardwareInline: return "HardwareInline"; 405 | default: 406 | RUSH_BREAK; 407 | return "unknown"; 408 | } 409 | } 410 | 411 | void RayTracedShadowsApp::render() 412 | { 413 | #if USE_VK_RAYTRACING 414 | if (m_vkRaytracingDirty && m_vkRaytracing) 415 | { 416 | m_vkRaytracing->build(m_ctx, 417 | m_vertexBuffer.get(), m_vertexCount, GfxFormat_RGB32_Float, u32(sizeof(Vertex)), 418 | m_indexBuffer.get(), m_indexCount, GfxFormat_R32_Uint); 419 | m_vkRaytracingDirty = false; 420 | } 421 | #endif // USE_VK_RAYTRACING 422 | 423 | if (m_valid) 424 | { 425 | renderGbuffer(); 426 | 427 | if (m_mode == ShadowRenderMode::HardwareInline) 428 | { 429 | renderShadowMaskHardwareInline(); 430 | } 431 | else if (m_mode == ShadowRenderMode::Hardware) 432 | { 433 | renderShadowMaskHardware(); 434 | } 435 | else 436 | { 437 | renderShadowMaskCompute(); 438 | } 439 | } 440 | 441 | Gfx_AddImageBarrier(m_ctx, m_gbufferBaseColor, GfxResourceState_ShaderRead); 442 | Gfx_AddImageBarrier(m_ctx, m_gbufferNormal, GfxResourceState_ShaderRead); 443 | Gfx_AddImageBarrier(m_ctx, m_shadowMask, GfxResourceState_ShaderRead); 444 | 445 | GfxPassDesc passDesc; 446 | passDesc.clearDepth = 1.0f; 447 | passDesc.clearColors[0] = ColorRGBA8(11, 22, 33); 448 | passDesc.flags = GfxPassFlags::ClearAll; 449 | Gfx_BeginPass(m_ctx, passDesc); 450 | 451 | // Combine gbuffer with shadow mask 452 | if (m_valid) 453 | { 454 | Gfx_SetDepthStencilState(m_ctx, m_depthStencilStates.disable); 455 | Gfx_SetBlendState(m_ctx, m_blendStates.opaque); 456 | Gfx_SetTechnique(m_ctx, m_techniqueCombine); 457 | Gfx_SetSampler(m_ctx, 0, m_samplerStates.pointClamp); 458 | Gfx_SetTexture(m_ctx, 0, m_gbufferBaseColor); 459 | Gfx_SetTexture(m_ctx, 1, m_gbufferNormal); 460 | Gfx_SetTexture(m_ctx, 2, m_shadowMask); 461 | Gfx_SetConstantBuffer(m_ctx, 0, m_rayTracingConstantBuffer); 462 | Gfx_Draw(m_ctx, 0, 3); 463 | } 464 | 465 | // Draw UI on top 466 | { 467 | TimingScope timingScope(m_stats.cpuUI); 468 | 469 | Gfx_SetBlendState(m_ctx, m_blendStates.lerp); 470 | Gfx_SetDepthStencilState(m_ctx, m_depthStencilStates.disable); 471 | 472 | m_prim->begin2D(m_window->getSize()); 473 | 474 | m_font->setScale(2.0f); 475 | m_font->draw(m_prim, Vec2(10.0f), m_statusString.c_str()); 476 | 477 | double raysTraced = m_window->getWidth() * m_window->getHeight(); 478 | double raysPerSecond = raysTraced / m_stats.gpuShadows.get(); 479 | 480 | m_font->setScale(1.0f); 481 | char timingString[1024]; 482 | const GfxStats& stats = Gfx_Stats(); 483 | sprintf_s(timingString, 484 | "VSync: %s\n" 485 | "Draw calls: %d\n" 486 | "Vertices: %d\n" 487 | "Mode: %s\n" 488 | "GPU shadows: %.2f ms\n" 489 | "MRays / sec: %.4f\n" 490 | "GPU total: %.2f ms\n" 491 | "CPU time: %.2f ms\n" 492 | "> Model: %.2f ms\n" 493 | "> UI: %.2f ms", 494 | m_presentInterval == 0 ? "OFF" : "ON", 495 | stats.drawCalls, 496 | stats.vertices, 497 | toString(m_mode), 498 | m_stats.gpuShadows.get() * 1000.0f, 499 | raysPerSecond / 1000000.0, 500 | m_stats.gpuTotal.get() * 1000.0f, 501 | m_stats.cpuTotal.get() * 1000.0f, 502 | m_stats.cpuModel.get() * 1000.0f, 503 | m_stats.cpuUI.get() * 1000.0f); 504 | m_font->draw(m_prim, Vec2(10.0f, 30.0f), timingString); 505 | 506 | m_prim->end2D(); 507 | } 508 | 509 | Gfx_EndPass(m_ctx); 510 | } 511 | 512 | void RayTracedShadowsApp::renderGbuffer() 513 | { 514 | GfxPassDesc passDesc; 515 | passDesc.clearDepth = 1.0f; 516 | passDesc.clearColors[0] = ColorRGBA::Black(); 517 | passDesc.clearColors[1] = ColorRGBA::Black(); 518 | passDesc.color[0] = m_gbufferBaseColor.get(); 519 | passDesc.color[1] = m_gbufferNormal.get(); 520 | passDesc.color[2] = m_gbufferPosition.get(); 521 | passDesc.depth = m_gbufferDepth.get(); 522 | passDesc.flags = GfxPassFlags::ClearAll; 523 | Gfx_BeginPass(m_ctx, passDesc); 524 | Gfx_BeginTimer(m_ctx, Timestamp_Gbuffer); 525 | 526 | ModelConstants constants; 527 | constants.matViewProj = m_matViewProj.transposed(); 528 | constants.matWorld = m_worldTransform.transposed(); 529 | constants.cameraPosition = Vec4(m_interpolatedCamera.getPosition(), 0.0f); 530 | 531 | Gfx_UpdateBuffer(m_ctx, m_modelGlobalConstantBuffer, &constants, sizeof(constants)); 532 | 533 | Gfx_SetViewport(m_ctx, GfxViewport(m_window->getSize())); 534 | Gfx_SetScissorRect(m_ctx, m_window->getSize()); 535 | 536 | Gfx_SetDepthStencilState(m_ctx, m_depthStencilStates.writeLessEqual); 537 | 538 | if (m_valid) 539 | { 540 | TimingScope timingScope(m_stats.cpuModel); 541 | 542 | Gfx_SetBlendState(m_ctx, m_blendStates.opaque); 543 | 544 | Gfx_SetTechnique(m_ctx, m_techniqueModel); 545 | Gfx_SetVertexStream(m_ctx, 0, m_vertexBuffer); 546 | Gfx_SetIndexStream(m_ctx, m_indexBuffer); 547 | Gfx_SetConstantBuffer(m_ctx, 0, m_modelGlobalConstantBuffer); 548 | 549 | for (const MeshSegment& segment : m_segments) 550 | { 551 | GfxTexture texture = m_defaultWhiteTexture.get(); 552 | 553 | const Material& material = (segment.material == 0xFFFFFFFF) ? m_defaultMaterial : m_materials[segment.material]; 554 | if (material.albedoTexture.valid()) 555 | { 556 | texture = material.albedoTexture.get(); 557 | } 558 | Gfx_SetConstantBuffer(m_ctx, 1, material.constantBuffer); 559 | 560 | Gfx_SetSampler(m_ctx, 0, m_samplerStates.anisotropicWrap); 561 | Gfx_SetTexture(m_ctx, 0, texture); 562 | Gfx_DrawIndexed(m_ctx, segment.indexCount, segment.indexOffset, 0, m_vertexCount); 563 | } 564 | } 565 | 566 | Gfx_EndTimer(m_ctx, Timestamp_Gbuffer); 567 | Gfx_EndPass(m_ctx); 568 | } 569 | 570 | void RayTracedShadowsApp::renderShadowMaskCompute() 571 | { 572 | Gfx_BeginTimer(m_ctx, Timestamp_Shadows); 573 | 574 | const GfxTextureDesc& desc = Gfx_GetTextureDesc(m_shadowMask); 575 | 576 | RayTracingConstants constants; 577 | constants.cameraDirection = Vec4(m_interpolatedCamera.getForward(), 0.0f); 578 | constants.lightDirection = Vec4(m_lightCamera.getForward(), 0.0f); 579 | constants.cameraPosition = Vec4(m_interpolatedCamera.getPosition(), 0.0f); 580 | constants.renderTargetSize = Vec4((float)desc.width, (float)desc.height, 1.0f / desc.width, 1.0f / desc.height); 581 | Gfx_UpdateBufferT(m_ctx, m_rayTracingConstantBuffer, constants); 582 | 583 | Gfx_SetConstantBuffer(m_ctx, 0, m_rayTracingConstantBuffer); 584 | Gfx_SetSampler(m_ctx, 0, m_samplerStates.pointClamp); 585 | Gfx_SetTexture(m_ctx, 0, m_gbufferPosition); 586 | Gfx_SetStorageImage(m_ctx, 0, m_shadowMask); 587 | Gfx_SetStorageBuffer(m_ctx, 0, m_bvhBuffer); 588 | Gfx_SetTechnique(m_ctx, m_techniqueRayTracedShadows); 589 | 590 | u32 w = divUp(desc.width, 8); 591 | u32 h = divUp(desc.height, 8); 592 | Gfx_Dispatch(m_ctx, w, h, 1); 593 | 594 | Gfx_EndTimer(m_ctx, Timestamp_Shadows); 595 | } 596 | 597 | void RayTracedShadowsApp::renderShadowMaskHardware() 598 | { 599 | Gfx_BeginTimer(m_ctx, Timestamp_Shadows); 600 | 601 | const GfxTextureDesc& desc = Gfx_GetTextureDesc(m_shadowMask); 602 | 603 | RayTracingConstants constants; 604 | constants.cameraDirection = Vec4(m_interpolatedCamera.getForward(), 0.0f); 605 | constants.lightDirection = Vec4(m_lightCamera.getForward(), 0.0f); 606 | constants.cameraPosition = Vec4(m_interpolatedCamera.getPosition(), 0.0f); 607 | constants.renderTargetSize = Vec4((float)desc.width, (float)desc.height, 1.0f / desc.width, 1.0f / desc.height); 608 | Gfx_UpdateBufferT(m_ctx, m_rayTracingConstantBuffer, constants); 609 | 610 | #if USE_VK_RAYTRACING 611 | m_vkRaytracing->dispatch(m_ctx, 612 | desc.width, desc.height, 613 | m_rayTracingConstantBuffer.get(), 614 | m_samplerStates.pointClamp.get(), 615 | m_gbufferPosition.get(), 616 | m_shadowMask.get()); 617 | #endif // USE_VK_RAYTRACING 618 | 619 | Gfx_EndTimer(m_ctx, Timestamp_Shadows); 620 | } 621 | 622 | void RayTracedShadowsApp::renderShadowMaskHardwareInline() 623 | { 624 | #if USE_VK_RAYTRACING 625 | Gfx_BeginTimer(m_ctx, Timestamp_Shadows); 626 | 627 | if (m_vkRaytracing) 628 | { 629 | const GfxTextureDesc& desc = Gfx_GetTextureDesc(m_shadowMask); 630 | 631 | RayTracingConstants constants; 632 | constants.cameraDirection = Vec4(m_interpolatedCamera.getForward(), 0.0f); 633 | constants.lightDirection = Vec4(m_lightCamera.getForward(), 0.0f); 634 | constants.cameraPosition = Vec4(m_interpolatedCamera.getPosition(), 0.0f); 635 | constants.renderTargetSize = Vec4((float)desc.width, (float)desc.height, 1.0f / desc.width, 1.0f / desc.height); 636 | Gfx_UpdateBufferT(m_ctx, m_rayTracingConstantBuffer, constants); 637 | 638 | Gfx_SetConstantBuffer(m_ctx, 0, m_rayTracingConstantBuffer); 639 | Gfx_SetSampler(m_ctx, 0, m_samplerStates.pointClamp); 640 | Gfx_SetTexture(m_ctx, 0, m_gbufferPosition); 641 | Gfx_SetStorageImage(m_ctx, 0, m_shadowMask); 642 | Gfx_SetAccelerationStructure(m_ctx, 0, m_vkRaytracing->m_tlas); 643 | Gfx_SetTechnique(m_ctx, m_techniqueRayTracedShadowsInline); 644 | 645 | u32 w = divUp(desc.width, 8); 646 | u32 h = divUp(desc.height, 8); 647 | Gfx_Dispatch(m_ctx, w, h, 1); 648 | } 649 | 650 | Gfx_EndTimer(m_ctx, Timestamp_Shadows); 651 | 652 | #endif // USE_VK_RAYTRACING 653 | } 654 | 655 | static std::string directoryFromFilename(const std::string& filename) 656 | { 657 | size_t pos = filename.find_last_of("/\\"); 658 | if (pos != std::string::npos) 659 | { 660 | return filename.substr(0, pos + 1); 661 | } 662 | else 663 | { 664 | return std::string(); 665 | } 666 | } 667 | 668 | GfxRef RayTracedShadowsApp::loadTexture(const std::string& filename) 669 | { 670 | auto it = m_textures.find(filename); 671 | if (it == m_textures.end()) 672 | { 673 | Log::message("Loading texture '%s'", filename.c_str()); 674 | 675 | int w, h, comp; 676 | stbi_set_flip_vertically_on_load(true); 677 | u8* pixels = stbi_load(filename.c_str(), &w, &h, &comp, 4); 678 | 679 | GfxRef texture; 680 | 681 | if (pixels) 682 | { 683 | std::vector> mips; 684 | mips.reserve(16); 685 | 686 | std::vector textureData; 687 | textureData.reserve(16); 688 | 689 | { 690 | GfxTextureData item; 691 | item.pixels = pixels; 692 | textureData.push_back(item); 693 | } 694 | 695 | u32 mipWidth = w; 696 | u32 mipHeight = h; 697 | 698 | while (mipWidth != 1 && mipHeight != 1) 699 | { 700 | u32 nextMipWidth = max(1, mipWidth / 2); 701 | u32 nextMipHeight = max(1, mipHeight / 2); 702 | 703 | u8* nextMip = new u8[nextMipWidth * nextMipHeight * 4]; 704 | mips.push_back(std::unique_ptr(nextMip)); 705 | 706 | const u32 mipPitch = mipWidth * 4; 707 | const u32 nextMipPitch = nextMipWidth * 4; 708 | int resizeResult = stbir_resize_uint8( 709 | (const u8*)textureData.back().pixels, mipWidth, mipHeight, mipPitch, 710 | nextMip, nextMipWidth, nextMipHeight, nextMipPitch, 4); 711 | RUSH_ASSERT(resizeResult); 712 | 713 | { 714 | GfxTextureData item; 715 | item.pixels = nextMip; 716 | item.mip = (u32)textureData.size(); 717 | textureData.push_back(item); 718 | } 719 | 720 | mipWidth = nextMipWidth; 721 | mipHeight = nextMipHeight; 722 | } 723 | 724 | GfxTextureDesc desc = GfxTextureDesc::make2D(w, h); 725 | desc.mips = (u32)textureData.size(); 726 | texture.retain(Gfx_CreateTexture(desc, textureData.data(), (u32)textureData.size())); 727 | m_textures.insert(std::make_pair(filename, texture)); 728 | 729 | stbi_image_free(pixels); 730 | } 731 | 732 | return texture; 733 | } 734 | else 735 | { 736 | return it->second; 737 | } 738 | } 739 | 740 | inline u64 hashFnv1a64(const void* message, size_t length, u64 state = 0xcbf29ce484222325) 741 | { 742 | const u8* bytes = (const u8*)message; 743 | for (size_t i = 0; i < length; ++i) 744 | { 745 | state ^= bytes[i]; 746 | state *= 0x100000001b3; 747 | } 748 | return state; 749 | } 750 | 751 | bool RayTracedShadowsApp::loadModel(const char* filename) 752 | { 753 | Log::message("Loading model '%s'", filename); 754 | 755 | m_boundingBox.expandInit(); 756 | 757 | const GfxBufferDesc materialCbDesc(GfxBufferFlags::Constant, GfxFormat_Unknown, 1, sizeof(MaterialConstants)); 758 | 759 | std::vector vertices; 760 | std::vector indices; 761 | 762 | const double timeLoadBegin = m_timer.time(); 763 | 764 | #if USE_ZEUX_OBJPARSER 765 | 766 | ObjFile objFile; 767 | bool loaded = objParseFile(objFile, filename); 768 | 769 | if (!loaded) 770 | { 771 | Log::error("Could not load model from '%s'", filename); 772 | return false; 773 | } 774 | 775 | if (!objValidate(objFile)) 776 | { 777 | Log::error("Could not load model from '%s' (invalid file data)\n", filename); 778 | return false; 779 | } 780 | 781 | // Obj parser produces a non-indexed mesh (no vertex deduplication). 782 | // Meshoptimizer can be used to create vertex and index buffers for rasterization. 783 | const u32 vertexCount = u32(objFile.f_size) / 3; 784 | const u32 triangleCount = vertexCount / 3; 785 | 786 | vertices.reserve(vertexCount); 787 | indices.reserve(vertexCount); 788 | 789 | const bool haveNormals = objFile.vn_size; 790 | 791 | for (u32 i = 0; i < vertexCount; ++i) 792 | { 793 | const int vpi = objFile.f[i * 3 + 0]; // vertex position index 794 | const int vti = objFile.f[i * 3 + 1]; // vertex texcoord index 795 | const int vni = objFile.f[i * 3 + 2]; // vertex normal index 796 | 797 | Vertex v = {}; 798 | 799 | v.position.x = objFile.v[vpi * 3 + 0]; 800 | v.position.y = objFile.v[vpi * 3 + 1]; 801 | v.position.z = objFile.v[vpi * 3 + 2]; 802 | 803 | if (haveNormals && vni >= 0) 804 | { 805 | v.normal.x = objFile.vn[vni * 3 + 0]; 806 | v.normal.y = objFile.vn[vni * 3 + 1]; 807 | v.normal.z = objFile.vn[vni * 3 + 2]; 808 | } 809 | else 810 | { 811 | v.normal = Vec3(0.0); 812 | } 813 | 814 | if (vti >= 0) 815 | { 816 | v.texcoord.x = objFile.vt[vti * 3 + 0]; 817 | v.texcoord.y = objFile.vt[vti * 3 + 1]; 818 | } 819 | 820 | m_boundingBox.expand(v.position); 821 | 822 | vertices.push_back(v); 823 | indices.push_back(i); 824 | } 825 | 826 | if (!haveNormals) 827 | { 828 | for (u32 i = 0; i < triangleCount; ++i) 829 | { 830 | u32 idxA = i * 3 + 0; 831 | u32 idxB = i * 3 + 1; 832 | u32 idxC = i * 3 + 2; 833 | 834 | Vec3 a = vertices[idxA].position; 835 | Vec3 b = vertices[idxB].position; 836 | Vec3 c = vertices[idxC].position; 837 | 838 | Vec3 normal = cross(b - a, c - b); 839 | 840 | normal = normalize(normal); 841 | 842 | vertices[idxA].normal += normal; 843 | vertices[idxB].normal += normal; 844 | vertices[idxC].normal += normal; 845 | } 846 | 847 | for (u32 i = 0; i < (u32)vertices.size(); ++i) 848 | { 849 | vertices[i].normal = normalize(vertices[i].normal); 850 | } 851 | } 852 | 853 | { 854 | MeshSegment segment = {}; 855 | segment.material = 0xFFFFFFFF; 856 | segment.indexOffset = 0; 857 | segment.indexCount = u32(indices.size()); 858 | m_segments.push_back(segment); 859 | } 860 | 861 | #else // USE_ZEUX_OBJPARSER 862 | 863 | std::vector shapes; 864 | std::vector materials; 865 | std::string errors; 866 | 867 | std::string directory = directoryFromFilename(filename); 868 | 869 | bool loaded = tinyobj::LoadObj(shapes, materials, errors, filename, directory.c_str()); 870 | if (!loaded) 871 | { 872 | Log::error("Could not load model from '%s'\n%s\n", filename, errors.c_str()); 873 | return false; 874 | } 875 | 876 | for (auto& objMaterial : materials) 877 | { 878 | MaterialConstants constants; 879 | constants.baseColor.x = objMaterial.diffuse[0]; 880 | constants.baseColor.y = objMaterial.diffuse[1]; 881 | constants.baseColor.z = objMaterial.diffuse[2]; 882 | 883 | Material material; 884 | if (!objMaterial.diffuse_texname.empty()) 885 | { 886 | material.albedoTexture = loadTexture(directory + objMaterial.diffuse_texname); 887 | } 888 | 889 | { 890 | u64 constantHash = hashFnv1a64(&constants, sizeof(constants)); 891 | auto it = m_materialConstantBuffers.find(constantHash); 892 | if (it == m_materialConstantBuffers.end()) 893 | { 894 | GfxBuffer cb = Gfx_CreateBuffer(materialCbDesc, &constants); 895 | m_materialConstantBuffers[constantHash].retain(cb); 896 | material.constantBuffer.retain(cb); 897 | } 898 | else 899 | { 900 | material.constantBuffer = it->second; 901 | } 902 | } 903 | 904 | m_materials.push_back(material); 905 | } 906 | 907 | for (const auto& shape : shapes) 908 | { 909 | u32 firstVertex = (u32)vertices.size(); 910 | const auto& mesh = shape.mesh; 911 | 912 | const u32 vertexCount = (u32)mesh.positions.size() / 3; 913 | 914 | const bool haveTexcoords = !mesh.texcoords.empty(); 915 | const bool haveNormals = mesh.positions.size() == mesh.normals.size(); 916 | 917 | for (u32 i = 0; i < vertexCount; ++i) 918 | { 919 | Vertex v; 920 | 921 | v.position.x = mesh.positions[i * 3 + 0]; 922 | v.position.y = mesh.positions[i * 3 + 1]; 923 | v.position.z = mesh.positions[i * 3 + 2]; 924 | 925 | m_boundingBox.expand(v.position); 926 | 927 | if (haveTexcoords) 928 | { 929 | v.texcoord.x = mesh.texcoords[i * 2 + 0]; 930 | v.texcoord.y = mesh.texcoords[i * 2 + 1]; 931 | } 932 | else 933 | { 934 | v.texcoord = Vec2(0.0f); 935 | } 936 | 937 | if (haveNormals) 938 | { 939 | v.normal.x = mesh.normals[i * 3 + 0]; 940 | v.normal.y = mesh.normals[i * 3 + 1]; 941 | v.normal.z = mesh.normals[i * 3 + 2]; 942 | } 943 | else 944 | { 945 | v.normal = Vec3(0.0); 946 | } 947 | 948 | v.position.x = -v.position.x; 949 | v.normal.x = -v.normal.x; 950 | 951 | vertices.push_back(v); 952 | } 953 | 954 | if (!haveNormals) 955 | { 956 | const u32 triangleCount = (u32)mesh.indices.size() / 3; 957 | for (u32 i = 0; i < triangleCount; ++i) 958 | { 959 | u32 idxA = firstVertex + mesh.indices[i * 3 + 0]; 960 | u32 idxB = firstVertex + mesh.indices[i * 3 + 2]; 961 | u32 idxC = firstVertex + mesh.indices[i * 3 + 1]; 962 | 963 | Vec3 a = vertices[idxA].position; 964 | Vec3 b = vertices[idxB].position; 965 | Vec3 c = vertices[idxC].position; 966 | 967 | Vec3 normal = cross(b - a, c - b); 968 | 969 | normal = normalize(normal); 970 | 971 | vertices[idxA].normal += normal; 972 | vertices[idxB].normal += normal; 973 | vertices[idxC].normal += normal; 974 | } 975 | 976 | for (u32 i = firstVertex; i < (u32)vertices.size(); ++i) 977 | { 978 | vertices[i].normal = normalize(vertices[i].normal); 979 | } 980 | } 981 | 982 | u32 currentMaterialId = 0xFFFFFFFF; 983 | 984 | const u32 triangleCount = (u32)mesh.indices.size() / 3; 985 | for (u32 triangleIt = 0; triangleIt < triangleCount; ++triangleIt) 986 | { 987 | if (mesh.material_ids[triangleIt] != currentMaterialId || m_segments.empty()) 988 | { 989 | currentMaterialId = mesh.material_ids[triangleIt]; 990 | m_segments.push_back(MeshSegment()); 991 | m_segments.back().material = currentMaterialId; 992 | m_segments.back().indexOffset = (u32)indices.size(); 993 | m_segments.back().indexCount = 0; 994 | } 995 | 996 | indices.push_back(mesh.indices[triangleIt * 3 + 0] + firstVertex); 997 | indices.push_back(mesh.indices[triangleIt * 3 + 2] + firstVertex); 998 | indices.push_back(mesh.indices[triangleIt * 3 + 1] + firstVertex); 999 | 1000 | m_segments.back().indexCount += 3; 1001 | } 1002 | } 1003 | 1004 | #endif // USE_ZEUX_OBJPARSER 1005 | 1006 | const double timeObjParseEnd = m_timer.time(); 1007 | 1008 | m_vertexCount = (u32)vertices.size(); 1009 | m_indexCount = (u32)indices.size(); 1010 | 1011 | Log::message("Model loaded in %f sec. (%d vertices, %d triangles)", timeObjParseEnd - timeLoadBegin, m_vertexCount, m_indexCount/3); 1012 | 1013 | { 1014 | MaterialConstants constants; 1015 | constants.baseColor = Vec4(1.0f); 1016 | m_defaultMaterial.constantBuffer.retain(Gfx_CreateBuffer(materialCbDesc, &constants)); 1017 | m_defaultMaterial.albedoTexture.retain(m_defaultWhiteTexture); 1018 | } 1019 | 1020 | 1021 | GfxBufferDesc vbDesc(GfxBufferFlags::Vertex, GfxFormat_Unknown, m_vertexCount, sizeof(Vertex)); 1022 | m_vertexBuffer = Gfx_CreateBuffer(vbDesc, vertices.data()); 1023 | 1024 | GfxBufferDesc ibDesc(GfxBufferFlags::Index, GfxFormat_R32_Uint, m_indexCount, 4); 1025 | m_indexBuffer = Gfx_CreateBuffer(ibDesc, indices.data()); 1026 | 1027 | const double timeBufferCreateEnd = m_timer.time(); 1028 | 1029 | Log::message("Building BVH ..."); 1030 | 1031 | { 1032 | BVHBuilder bvhBuilder; 1033 | bvhBuilder.build( 1034 | reinterpret_cast(vertices.data()), 1035 | sizeof(Vertex) / sizeof(float), 1036 | indices.data(), 1037 | (u32)indices.size() / 3); 1038 | 1039 | GfxBufferDesc desc; 1040 | desc.flags = GfxBufferFlags::Storage; 1041 | desc.format = GfxFormat_Unknown; 1042 | desc.stride = sizeof(bvhBuilder.m_packedNodes[0]); 1043 | desc.count = (u32)bvhBuilder.m_packedNodes.size(); 1044 | m_bvhBuffer = Gfx_CreateBuffer(desc, bvhBuilder.m_packedNodes.data()); 1045 | } 1046 | 1047 | #if USE_VK_RAYTRACING 1048 | m_vkRaytracingDirty = true; 1049 | #endif // USE_VK_RAYTRACING 1050 | 1051 | const double timeBVHBuildEnd = m_timer.time(); 1052 | 1053 | Log::message("BVH built in %f sec.", timeBVHBuildEnd - timeBufferCreateEnd); 1054 | 1055 | return true; 1056 | } 1057 | -------------------------------------------------------------------------------- /Source/RayTracedShadows.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "BaseApplication.h" 18 | #include "BVHBuilder.h" 19 | #include "MovingAverage.h" 20 | 21 | class VkRaytracing; 22 | 23 | enum class ShadowRenderMode 24 | { 25 | Compute, 26 | Hardware, 27 | HardwareInline, 28 | }; 29 | 30 | using MovingAverageBuffer = MovingAverage; 31 | 32 | class RayTracedShadowsApp : public BaseApplication 33 | { 34 | public: 35 | 36 | RayTracedShadowsApp(); 37 | ~RayTracedShadowsApp(); 38 | 39 | void update() override; 40 | 41 | private: 42 | 43 | void createRenderTargets(Tuple2i size); 44 | 45 | enum Timestamp 46 | { 47 | Timestamp_Gbuffer, 48 | Timestamp_Shadows, 49 | Timestamp_Lighting, 50 | }; 51 | 52 | void render(); 53 | 54 | void renderGbuffer(); 55 | 56 | struct RayTracingConstants 57 | { 58 | Vec4 cameraPosition; 59 | Vec4 cameraDirection; 60 | Vec4 lightDirection; // direction in XYZ, bias in W 61 | Vec4 renderTargetSize; 62 | }; 63 | 64 | void renderShadowMaskCompute(); 65 | void renderShadowMaskHardware(); 66 | void renderShadowMaskHardwareInline(); 67 | 68 | bool loadModel(const char* filename); 69 | GfxRef loadTexture(const std::string& filename); 70 | 71 | Timer m_timer; 72 | 73 | struct Stats 74 | { 75 | MovingAverageBuffer gpuGbuffer; 76 | MovingAverageBuffer gpuShadows; 77 | MovingAverageBuffer gpuTotal; 78 | MovingAverageBuffer cpuTotal; 79 | MovingAverageBuffer cpuUI; 80 | MovingAverageBuffer cpuModel; 81 | } m_stats; 82 | 83 | Camera m_camera; 84 | Camera m_interpolatedCamera; 85 | Camera m_lightCamera; 86 | 87 | CameraManipulator m_cameraMan; 88 | 89 | GfxOwn m_techniqueModel; 90 | GfxOwn m_techniqueRayTracedShadows; 91 | GfxOwn m_techniqueRayTracedShadowsInline; 92 | GfxOwn m_techniqueCombine; 93 | 94 | GfxOwn m_defaultWhiteTexture; 95 | 96 | GfxOwn m_vertexBuffer; 97 | GfxOwn m_indexBuffer; 98 | 99 | GfxOwn m_modelGlobalConstantBuffer; 100 | 101 | GfxOwn m_rayTracingConstantBuffer; 102 | 103 | Mat4 m_matViewProj = Mat4::identity(); 104 | Mat4 m_matViewProjInv = Mat4::identity(); 105 | 106 | u32 m_indexCount = 0; 107 | u32 m_vertexCount = 0; 108 | 109 | struct ModelConstants 110 | { 111 | Mat4 matViewProj = Mat4::identity(); 112 | Mat4 matWorld = Mat4::identity(); 113 | Vec4 cameraPosition = Vec4(0.0f); 114 | }; 115 | 116 | Mat4 m_worldTransform = Mat4::identity(); 117 | 118 | Box3 m_boundingBox; 119 | 120 | struct Vertex 121 | { 122 | Vec3 position; 123 | Vec3 normal; 124 | Vec2 texcoord; 125 | }; 126 | 127 | std::string m_statusString; 128 | bool m_valid = false; 129 | 130 | std::unordered_map> m_textures; 131 | std::unordered_map> m_materialConstantBuffers; 132 | 133 | GfxOwn m_shadowMask; 134 | GfxOwn m_gbufferDepth; 135 | GfxOwn m_gbufferNormal; 136 | GfxOwn m_gbufferPosition; 137 | GfxOwn m_gbufferBaseColor; 138 | 139 | struct MaterialConstants 140 | { 141 | Vec4 baseColor; 142 | }; 143 | 144 | struct Material 145 | { 146 | GfxRef albedoTexture; 147 | GfxRef constantBuffer; 148 | }; 149 | 150 | std::vector m_materials; 151 | Material m_defaultMaterial; 152 | 153 | struct MeshSegment 154 | { 155 | u32 material = 0; 156 | u32 indexOffset = 0; 157 | u32 indexCount = 0; 158 | }; 159 | 160 | std::vector m_segments; 161 | 162 | WindowEventListener m_windowEvents; 163 | 164 | float m_cameraScale = 1.0f; 165 | 166 | GfxOwn m_bvhBuffer; 167 | 168 | Vec2 m_prevMousePos = Vec2(0.0f); 169 | 170 | #if USE_VK_RAYTRACING 171 | VkRaytracing* m_vkRaytracing = nullptr; 172 | bool m_vkRaytracingDirty = false; 173 | #endif // USE_VK_RAYTRACING 174 | 175 | ShadowRenderMode m_mode = ShadowRenderMode::Compute; 176 | u32 m_presentInterval = 1; 177 | }; 178 | -------------------------------------------------------------------------------- /Source/Shaders/Blit.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | // Generate a fullscreen triangle 4 | void main() 5 | { 6 | if (gl_VertexIndex == 0) gl_Position = vec4(-3.0, -1.0, 1.0, 1.0); 7 | else if (gl_VertexIndex == 1) gl_Position = vec4(1.0, -1.0, 1.0, 1.0); 8 | else gl_Position = vec4(1.0, 3.0, 1.0, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /Source/Shaders/Combine.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (binding = 0) uniform Constants 4 | { 5 | vec4 cameraPosition; 6 | vec4 cameraDirection; 7 | vec4 lightDirection; 8 | vec4 renderTargetSize; 9 | }; 10 | 11 | layout (binding = 1) uniform sampler defaultSampler; 12 | layout (binding = 2) uniform texture2D gbufferBaseColorTexture; 13 | layout (binding = 3) uniform texture2D gbufferNormalTexture; 14 | layout (binding = 4) uniform texture2D shadowMaskTexture; 15 | 16 | layout (location = 0) out vec4 fragColor; 17 | 18 | void main() 19 | { 20 | vec2 texcoord = gl_FragCoord.xy * renderTargetSize.zw; 21 | 22 | vec3 worldNormal = texture(sampler2D(gbufferNormalTexture, defaultSampler), texcoord).xyz; 23 | vec3 baseColor = texture(sampler2D(gbufferBaseColorTexture, defaultSampler), texcoord).xyz; 24 | float shadowMask = texture(sampler2D(shadowMaskTexture, defaultSampler), texcoord).x; 25 | 26 | vec4 result; 27 | 28 | float directLight = 1.25 * max(0.0, dot(worldNormal, lightDirection.xyz)) * shadowMask; 29 | float ambientLight = 0.15 + 0.05 * (1.0 - max(0.0f, dot(worldNormal, -cameraDirection.xyz))); 30 | 31 | result.xyz = baseColor * vec3(directLight + ambientLight); 32 | result.w = 1.0; 33 | 34 | if (worldNormal.rgb == vec3(0.0)) 35 | discard; 36 | 37 | fragColor = result; 38 | } 39 | -------------------------------------------------------------------------------- /Source/Shaders/Model.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (binding = 0) uniform Global 4 | { 5 | mat4 g_matViewProj; 6 | mat4 g_matWorld; 7 | vec4 g_cameraPosition; 8 | }; 9 | 10 | layout (binding = 1) uniform Material 11 | { 12 | vec4 g_baseColor; 13 | }; 14 | 15 | layout (binding = 2) uniform sampler defaultSampler; 16 | layout (binding = 3) uniform texture2D albedoTexture; 17 | 18 | layout (location = 0) in vec2 v_tex0; 19 | layout (location = 1) in vec3 v_nor0; 20 | layout (location = 2) in vec3 v_worldPos; 21 | 22 | layout (location = 0) out vec4 fragColor0; 23 | layout (location = 1) out vec4 fragColor1; 24 | layout (location = 2) out vec4 fragColor2; 25 | 26 | void main() 27 | { 28 | vec4 outBaseColor = vec4(0.0); 29 | vec4 outNormal = vec4(0.0); 30 | vec4 outCameraRelativePosition = vec4(0.0); 31 | 32 | outBaseColor = g_baseColor * texture(sampler2D(albedoTexture, defaultSampler), v_tex0); 33 | outNormal.xyz = normalize(v_nor0); 34 | 35 | outCameraRelativePosition.xyz = v_worldPos.xyz - g_cameraPosition.xyz; 36 | 37 | fragColor0 = outBaseColor; 38 | fragColor1 = gl_FrontFacing ? outNormal : -outNormal; 39 | fragColor2 = outCameraRelativePosition; 40 | } 41 | -------------------------------------------------------------------------------- /Source/Shaders/Model.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (binding = 0) uniform Global 4 | { 5 | mat4 g_matViewProj; 6 | mat4 g_matWorld; 7 | }; 8 | 9 | layout (location = 0) in vec3 a_pos0; 10 | layout (location = 1) in vec3 a_nor0; 11 | layout (location = 2) in vec2 a_tex0; 12 | 13 | layout (location = 0) out vec2 v_tex0; 14 | layout (location = 1) out vec3 v_nor0; 15 | layout (location = 2) out vec3 v_worldPos; 16 | 17 | void main() 18 | { 19 | vec3 worldPos = (vec4(a_pos0, 1) * g_matWorld).xyz; 20 | 21 | gl_Position = vec4(worldPos, 1) * g_matViewProj; 22 | 23 | v_tex0 = a_tex0; 24 | v_nor0 = a_nor0; 25 | v_worldPos = worldPos; 26 | } 27 | -------------------------------------------------------------------------------- /Source/Shaders/RayTracedShadows.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (binding = 0) uniform Constants 4 | { 5 | vec4 cameraPosition; 6 | vec4 cameraDirection; 7 | vec4 lightDirection; 8 | vec4 renderTargetSize; 9 | }; 10 | 11 | layout(binding = 1) uniform sampler defaultSampler; 12 | layout(binding = 2) uniform texture2D gbufferPositionTexture; 13 | layout(binding = 3, r8) uniform image2D outputShadowMask; 14 | 15 | // unpacked node 16 | struct BVHNode 17 | { 18 | vec4 bboxMin; 19 | vec4 bboxMax; 20 | }; 21 | 22 | // packed nodes, followed by vertex array (one per triangle) 23 | layout (std140, binding = 4) buffer BVHBuffer 24 | { 25 | vec4 bvhNodes[]; 26 | }; 27 | 28 | struct Ray 29 | { 30 | vec4 o; 31 | vec4 d; 32 | }; 33 | 34 | struct Triangle 35 | { 36 | vec3 v0; 37 | vec3 e0; 38 | vec3 e1; 39 | }; 40 | 41 | bool intersectRayTri(Ray r, vec3 v0, vec3 e0, vec3 e1) 42 | { 43 | const vec3 s1 = cross(r.d.xyz, e1); 44 | const float invd = 1.0 / (dot(s1, e0)); 45 | const vec3 d = r.o.xyz - v0; 46 | const float b1 = dot(d, s1) * invd; 47 | const vec3 s2 = cross(d, e0); 48 | const float b2 = dot(r.d.xyz, s2) * invd; 49 | const float temp = dot(e1, s2) * invd; 50 | 51 | if (b1 < 0.0 || b1 > 1.0 || b2 < 0.0 || b1 + b2 > 1.0 || temp < 0.0 || temp > r.o.w) 52 | { 53 | return false; 54 | } 55 | else 56 | { 57 | return true; 58 | } 59 | } 60 | 61 | bool intersectRayBox(Ray r, vec3 invdir, vec3 pmin, vec3 pmax) 62 | { 63 | const vec3 f = (pmax.xyz - r.o.xyz) * invdir; 64 | const vec3 n = (pmin.xyz - r.o.xyz) * invdir; 65 | 66 | const vec3 tmax = max(f, n); 67 | const vec3 tmin = min(f, n); 68 | 69 | const float t1 = min(tmax.x, min(tmax.y, tmax.z)); 70 | const float t0 = max(max(tmin.x, max(tmin.y, tmin.z)), 0.0f); 71 | 72 | return t1 >= t0; 73 | } 74 | 75 | bool intersectAny(Ray ray) 76 | { 77 | const vec3 invdir = 1.0 / ray.d.xyz; 78 | 79 | uint nodeIndex = 0; 80 | 81 | while(nodeIndex != 0xFFFFFFFF) 82 | { 83 | BVHNode node; 84 | node.bboxMin = bvhNodes[nodeIndex*2+0]; 85 | node.bboxMax = bvhNodes[nodeIndex*2+1]; 86 | 87 | uint primitiveIndex = floatBitsToUint(node.bboxMin.w); 88 | 89 | if (primitiveIndex != 0xFFFFFFFF) // leaf node 90 | { 91 | vec4 data2 = bvhNodes[primitiveIndex]; 92 | Triangle tri; 93 | tri.e0 = node.bboxMin.xyz; 94 | tri.e1 = node.bboxMax.xyz; 95 | tri.v0 = data2.xyz; 96 | if (intersectRayTri(ray, tri.v0, tri.e0, tri.e1)) 97 | { 98 | return true; 99 | } 100 | } 101 | else if (intersectRayBox(ray, invdir, node.bboxMin.xyz, node.bboxMax.xyz)) 102 | { 103 | ++nodeIndex; 104 | continue; 105 | } 106 | 107 | nodeIndex = floatBitsToUint(node.bboxMax.w); 108 | } 109 | 110 | return false; 111 | } 112 | 113 | float computeEpsilonForValue(float f, uint exponentDiff) 114 | { 115 | uint u = floatBitsToUint(f); 116 | uint exponent = bitfieldExtract(u, 23, 8); 117 | exponent -= min(exponentDiff, exponent); 118 | u = bitfieldInsert(u, exponent, 23, 8); 119 | return uintBitsToFloat(u); 120 | } 121 | 122 | float max3(vec3 v) 123 | { 124 | return max(max(v.x, v.y), v.z); 125 | } 126 | 127 | layout(local_size_x = 8, local_size_y = 8) in; 128 | void main() 129 | { 130 | ivec2 pixelIndex = ivec2(gl_GlobalInvocationID.xy); 131 | 132 | Ray ray; 133 | 134 | vec3 direction = lightDirection.xyz; 135 | vec3 cameraRelativePosition = texelFetch(sampler2D(gbufferPositionTexture, defaultSampler), pixelIndex, 0).xyz; 136 | vec3 origin = cameraPosition.xyz + cameraRelativePosition; 137 | 138 | float shadowRayBias = max( 139 | computeEpsilonForValue(max3(abs(origin)), 13), 140 | computeEpsilonForValue(max3(abs(cameraRelativePosition)), 13)); 141 | 142 | // TODO: we should be pushing the ray away in the direction of the surface normal 143 | origin += lightDirection.xyz * shadowRayBias; 144 | 145 | ray.o = vec4(origin.x, origin.y, origin.z, 1e9); 146 | ray.d = vec4(direction.x, direction.y, direction.z, 0.0); 147 | 148 | int result = intersectAny(ray) ? 0 : 1; 149 | 150 | imageStore(outputShadowMask, pixelIndex, ivec4(result)); 151 | } 152 | -------------------------------------------------------------------------------- /Source/Shaders/RayTracedShadows.rgen: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_ray_tracing : enable 3 | 4 | layout (binding = 0) uniform Constants 5 | { 6 | vec4 cameraPosition; 7 | vec4 cameraDirection; 8 | vec4 lightDirection; 9 | vec4 renderTargetSize; 10 | }; 11 | 12 | layout(binding = 1) uniform sampler defaultSampler; 13 | layout(binding = 2) uniform texture2D gbufferPositionTexture; 14 | layout(binding = 3, r8) uniform image2D outputShadowMask; 15 | 16 | layout(set=0, binding = 4) uniform accelerationStructureEXT TLAS; 17 | 18 | layout(location = 0) rayPayloadEXT uint payload; 19 | 20 | float computeEpsilonForValue(float f, uint exponentDiff) 21 | { 22 | uint u = floatBitsToUint(f); 23 | uint exponent = bitfieldExtract(u, 23, 8); 24 | exponent -= min(exponentDiff, exponent); 25 | u = bitfieldInsert(u, exponent, 23, 8); 26 | return uintBitsToFloat(u); 27 | } 28 | 29 | float max3(vec3 v) 30 | { 31 | return max(max(v.x, v.y), v.z); 32 | } 33 | 34 | void main() 35 | { 36 | ivec2 pixelIndex = ivec2(gl_LaunchIDEXT.xy); 37 | 38 | vec3 direction = lightDirection.xyz; 39 | vec3 cameraRelativePosition = texelFetch(sampler2D(gbufferPositionTexture, defaultSampler), pixelIndex, 0).xyz; 40 | vec3 origin = cameraPosition.xyz + cameraRelativePosition; 41 | 42 | // TODO: we should be pushing the ray away in the direction of the surface normal 43 | float shadowRayBias = max( 44 | computeEpsilonForValue(max3(abs(origin)), 13), 45 | computeEpsilonForValue(max3(abs(cameraRelativePosition)), 13)); 46 | 47 | uint rayFlags = gl_RayFlagsOpaqueEXT | gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsSkipClosestHitShaderEXT; 48 | 49 | payload = 0; 50 | 51 | traceRayEXT(TLAS, 52 | rayFlags, // uint rayFlags 53 | ~0u, // uint cullMask 54 | 0u, // uint sbtRecordOffset 55 | 0u, // uint sbtRecordStride 56 | 0u, // uint missIndex 57 | origin, // vec3 origin 58 | shadowRayBias, // float Tmin 59 | direction, // vec3 direction 60 | 1e9, // float Tmax 61 | 0 // int payload 62 | ); 63 | 64 | ivec4 result = ivec4(payload); 65 | imageStore(outputShadowMask, pixelIndex, result); 66 | } 67 | -------------------------------------------------------------------------------- /Source/Shaders/RayTracedShadows.rmiss: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_ray_tracing : enable 3 | 4 | layout(location = 0) rayPayloadInEXT uint payload; 5 | 6 | void main() 7 | { 8 | payload = 1; 9 | } 10 | -------------------------------------------------------------------------------- /Source/Shaders/RayTracedShadowsInline.comp: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_ray_query : enable 3 | 4 | layout (binding = 0) uniform Constants 5 | { 6 | vec4 cameraPosition; 7 | vec4 cameraDirection; 8 | vec4 lightDirection; 9 | vec4 renderTargetSize; 10 | }; 11 | 12 | layout(binding = 1) uniform sampler defaultSampler; 13 | layout(binding = 2) uniform texture2D gbufferPositionTexture; 14 | layout(binding = 3, r8) uniform image2D outputShadowMask; 15 | 16 | layout(set=0, binding = 4) uniform accelerationStructureEXT TLAS; 17 | 18 | float computeEpsilonForValue(float f, uint exponentDiff) 19 | { 20 | uint u = floatBitsToUint(f); 21 | uint exponent = bitfieldExtract(u, 23, 8); 22 | exponent -= min(exponentDiff, exponent); 23 | u = bitfieldInsert(u, exponent, 23, 8); 24 | return uintBitsToFloat(u); 25 | } 26 | 27 | float max3(vec3 v) 28 | { 29 | return max(max(v.x, v.y), v.z); 30 | } 31 | 32 | layout(local_size_x = 8, local_size_y = 8) in; 33 | void main() 34 | { 35 | ivec2 pixelIndex = ivec2(gl_GlobalInvocationID.xy); 36 | 37 | vec3 direction = lightDirection.xyz; 38 | vec3 cameraRelativePosition = texelFetch(sampler2D(gbufferPositionTexture, defaultSampler), pixelIndex, 0).xyz; 39 | vec3 origin = cameraPosition.xyz + cameraRelativePosition; 40 | 41 | // TODO: we should be pushing the ray away in the direction of the surface normal 42 | float shadowRayBias = max( 43 | computeEpsilonForValue(max3(abs(origin)), 13), 44 | computeEpsilonForValue(max3(abs(cameraRelativePosition)), 13)); 45 | 46 | uint rayFlags = gl_RayFlagsOpaqueNV | gl_RayFlagsTerminateOnFirstHitNV; 47 | 48 | rayQueryEXT rayQuery; 49 | rayQueryInitializeEXT(rayQuery, 50 | TLAS, 51 | rayFlags, // uint rayFlags 52 | ~0u, // uint cullMask 53 | origin, // vec3 origin 54 | shadowRayBias, // float Tmin 55 | direction, // vec3 direction 56 | 1e9 // float Tmax 57 | ); 58 | 59 | rayQueryProceedEXT(rayQuery); 60 | uint IntersectionType = rayQueryGetIntersectionTypeEXT(rayQuery, true); 61 | int payload = IntersectionType == gl_RayQueryCommittedIntersectionNoneEXT ? 1 : 0; 62 | 63 | ivec4 result = ivec4(payload); 64 | 65 | imageStore(outputShadowMask, pixelIndex, result); 66 | } 67 | -------------------------------------------------------------------------------- /Source/VkRaytracing.cpp: -------------------------------------------------------------------------------- 1 | #include "VkRaytracing.h" 2 | #include 3 | #include 4 | #include 5 | 6 | void VkRaytracing::createPipeline(const GfxShaderSource& rgen, const GfxShaderSource& rmiss) 7 | { 8 | GfxDevice* device = Platform_GetGfxDevice(); 9 | VkDevice vulkanDevice = device->m_vulkanDevice; 10 | 11 | GfxRayTracingPipelineDesc desc; 12 | desc.rayGen = rgen; 13 | desc.miss = rmiss; 14 | desc.bindings.descriptorSets[0].constantBuffers = 1; 15 | desc.bindings.descriptorSets[0].samplers = 1; 16 | desc.bindings.descriptorSets[0].textures = 1; 17 | desc.bindings.descriptorSets[0].rwImages = 1; 18 | desc.bindings.descriptorSets[0].accelerationStructures = 1; 19 | 20 | m_pipeline = Gfx_CreateRayTracingPipeline(desc); 21 | 22 | // TODO: get the native pipeline out of the device while abstraction layer is WIP 23 | RayTracingPipelineVK& pipeline = device->m_rayTracingPipelines[m_pipeline.get()]; 24 | 25 | const u32 shaderHandleSize = Gfx_GetCapability().rtShaderHandleSize; 26 | 27 | // TODO: SBT should take into account alignment requirements 28 | GfxBufferDesc sbtBufferDesc; 29 | sbtBufferDesc.flags = GfxBufferFlags::None; 30 | sbtBufferDesc.count = u32(pipeline.shaderHandles.size() / shaderHandleSize); 31 | sbtBufferDesc.stride = shaderHandleSize; 32 | 33 | m_sbtBuffer = Gfx_CreateBuffer(sbtBufferDesc, pipeline.shaderHandles.data()); 34 | } 35 | 36 | void VkRaytracing::build(GfxContext* ctx, 37 | GfxBuffer vertexBuffer, u32 vertexCount, GfxFormat positionFormat, u32 vertexStride, 38 | GfxBuffer indexBuffer, u32 indexCount, GfxFormat indexFormat) 39 | { 40 | GfxRayTracingGeometryDesc geometryDesc; 41 | geometryDesc.vertexBuffer = vertexBuffer; 42 | geometryDesc.vertexCount = vertexCount; 43 | geometryDesc.vertexFormat = positionFormat; 44 | geometryDesc.vertexStride = vertexStride; 45 | geometryDesc.indexBuffer = indexBuffer; 46 | geometryDesc.indexCount = indexCount; 47 | geometryDesc.indexFormat = indexFormat; 48 | geometryDesc.isOpaque = true; 49 | 50 | GfxAccelerationStructureDesc blasDesc; 51 | blasDesc.type = GfxAccelerationStructureType::BottomLevel; 52 | blasDesc.geometries = &geometryDesc; 53 | blasDesc.geometyCount = 1; 54 | 55 | m_blas = Gfx_CreateAccelerationStructure(blasDesc); 56 | 57 | GfxAccelerationStructureDesc tlasDesc; 58 | tlasDesc.type = GfxAccelerationStructureType::TopLevel; 59 | tlasDesc.instanceCount = 1; 60 | 61 | m_tlas = Gfx_CreateAccelerationStructure(tlasDesc); 62 | 63 | GfxOwn instanceBuffer = Gfx_CreateBuffer(GfxBufferDesc(GfxBufferFlags::Transient, 0, 0)); 64 | { 65 | auto instanceData = Gfx_BeginUpdateBuffer(ctx, instanceBuffer.get(), tlasDesc.instanceCount); 66 | instanceData[0].init(); 67 | instanceData[0].accelerationStructureHandle = Gfx_GetAccelerationStructureHandle(m_blas); 68 | Gfx_EndUpdateBuffer(ctx, instanceBuffer); 69 | } 70 | 71 | Gfx_BuildAccelerationStructure(ctx, m_blas); 72 | Gfx_AddFullPipelineBarrier(ctx); 73 | 74 | Gfx_BuildAccelerationStructure(ctx, m_tlas, instanceBuffer); 75 | Gfx_AddFullPipelineBarrier(ctx); 76 | } 77 | 78 | void VkRaytracing::dispatch(GfxContext* ctx, 79 | u32 width, u32 height, 80 | GfxBuffer constants, 81 | GfxSampler pointSampler, 82 | GfxTexture positionTexture, 83 | GfxTexture outputShadowMask) 84 | { 85 | Gfx_SetConstantBuffer(ctx, 0, constants); 86 | Gfx_SetSampler(ctx, 0, pointSampler); 87 | Gfx_SetTexture(ctx, 0, positionTexture); 88 | Gfx_SetStorageImage(ctx, 0, outputShadowMask); 89 | Gfx_SetAccelerationStructure(ctx, 0, m_tlas); 90 | Gfx_TraceRays(ctx, m_pipeline, m_sbtBuffer, width, height, 1); 91 | } 92 | 93 | void VkRaytracing::reset() 94 | { 95 | // TODO: Enqueue destruction to avoid wait-for-idle 96 | Gfx_Finish(); 97 | 98 | m_pipeline.reset(); 99 | m_tlas.reset(); 100 | m_blas.reset(); 101 | m_sbtBuffer.reset(); 102 | } 103 | 104 | -------------------------------------------------------------------------------- /Source/VkRaytracing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class VkRaytracing 8 | { 9 | public: 10 | 11 | ~VkRaytracing() { reset(); } 12 | 13 | void createPipeline(const GfxShaderSource& rgen, const GfxShaderSource& rmiss); 14 | 15 | void build(GfxContext* ctx, 16 | GfxBuffer vertexBuffer, u32 vertexCount, GfxFormat positionFormat, u32 vertexStride, 17 | GfxBuffer indexBuffer, u32 indexCount, GfxFormat indexFormat); 18 | 19 | void dispatch(GfxContext* ctx, 20 | u32 width, u32 height, 21 | GfxBuffer constants, 22 | GfxSampler pointSampler, 23 | GfxTexture positionTexture, 24 | GfxTexture outputShadowMask); 25 | 26 | GfxOwn m_blas; 27 | GfxOwn m_tlas; 28 | 29 | // Pipeline and SBT 30 | 31 | GfxOwn m_pipeline; 32 | GfxOwn m_sbtBuffer; 33 | 34 | private: 35 | 36 | void reset(); 37 | 38 | }; 39 | 40 | --------------------------------------------------------------------------------