diff --git a/CMakeLists.txt b/CMakeLists.txt index d3d976c..017fd59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,8 @@ endif() if (WIN32) list(APPEND CORELIBS legacy_stdio_definitions.lib) + #My code here + list(APPEND CORELIBS "${CUDA_TOOLKIT_ROOT_DIR}/lib/x64/cudadevrt.lib") endif() # Crucial magic for CUDA linking diff --git a/README.md b/README.md index 110697c..679a1e6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,266 @@ CUDA Path Tracer -================ +====================== **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 3** -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Xiao Zhang + * [LinkedIn](https://www.linkedin.com/in/xiao-zhang-674bb8148/) +* Tested on: Windows 10, i7-7700K @ 4.20GHz 16.0GB, GTX 1080 15.96GB (my own PC) -### (TODO: Your README) +Analysis +====================== +* blocksize1d is set to 128 unchanged -*DO NOT* leave the README to the last minute! It is a crucial part of the -project, and we will not be able to grade you without a good README. +* image order is direct light integrator, full light integrator and naive integrator +* rendering time is measured in second + +--- + +## 1. Mat scene 800x800 pixel 200 spp 8 recursion + +### overview + +![](img/my_mat.jpg) + +### rendering time + +![](img/1.JPG) + +### analysis + +* integrators + +This scene shows 4 types of material that are implemented in this project currently, diffuse, emmissive, specular reflective and specular transmissive. It also shows 3 types of integrators that are implemented in this project as well, direct light integrator, full light integrator and naive integrator. Direct light integrator shades the material based on the sampling of the light sources. And it only traces the path once. Since perfect specular material has zero chance to take the path decided by sampling the light theoretically and there is no second path segment to compensite that, their color is just black. In the full light integrator however, global illumination is factored in by tracing the path multiple times. At the end of each depth, we sample the material to choose the next ray direction. This allows for the perfect specular material to have a chance to sample the light or other material in the next depth to get their color. Naive integrator shades the material by sampling their materials. It multiplies the color that a path sampled together to give the final color. However, if the path doesn't hit a light source until its end, the color of the path will simply be set to black. This is why it produces the most noisy image. In full light integrator, the colors are added together, so whether the path hit a light or not, it always contribute to the final color. This is why it has less noise in the image. + +* stream compaction + +In this scene, stream compaction doesn't do well in full light integrator and naive integrator, but does really good for direct light integrator. Because the time spent on performing stream compact is more than the time saved by launching less threads in subsequent depth for full light and naive integrator, considering the total depth is only 8. But in direct light integrator, every path will be terminated after the first depth, it will benefit more if we just reduce the total threads number to zero so that it doesn't launch any threads for subsequent depth. + +* material batching + +No only in this scene but also other scenes, sorting the path and intersections according to material types causes long rendering time. My guess is that the divergence in shading codes for different materials are not radical enough and the total number of the types of present materials are not large enough to make the process benefit from material batching. However there is an interesting issue I discovered by using thrust sort. According to an online resource, thrust use radix sort if you provide an integer array as key and merge sort if otherwise. So to compare the performace of these two, I used a separate integer array to store material types so that I can use radix sort and I overloaded the "<" operator for ShadedIntersection class so that I can use merge sort. Surprisingly, the radix sort is actually slower than the merge sort. My guess is that when I use a separate integer array to sort both ShadedIntersection and PathSegment, I have to use iterator and tuple to sort these two value array based on one key array. This may caused some performance issue. Also, using another global material type array can be slow since there is more global memory access. + +* first path caching + +In all cases, caching the first path with stream compaction enabled improves the performance compared with only enabling stream compaction. But since there is only 200 spp, the improvement is not significant. + +### images + +* direct light integrator + +![](img/my_mat.2018-10-03_06-45-23z.2018-10-03_06-45-34z.201_spp.integrator1_compact0_batch0_cache0.png) + +* full light integrator + +![](img/my_mat.2018-10-03_06-47-02z.2018-10-03_06-47-18z.201_spp.integrator3_compact0_batch0_cache0.png) + +* naive integrator + +![](img/my_mat.2018-10-03_06-40-15z.2018-10-03_06-40-28z.201_spp.integrator0_compact0_batch0_cache0.png) + +--- + +## 2. Two light scene 800x800 pixel 200 spp 64 recursion + +### overview + +![](img/my_scene.jpg) + +### rendering time + +![](img/2.JPG) + +### analysis + +* integrators + +Same as the mat scene. Direct light integrator produces incomplete image. Naive integrator produces noisy image. Full light integrator provides best image. + +* stream compaction + +The stream compaction saves a lot of rendering time since the total depth is 64, which means if we don't teminate a zero contribution path, it will stay there for a longer time than in the mat scene. This is why stream compaction saves time in this scene but wastes time in the mat scene. + +* material batching + +Same as the mat scene. Material batching doesn't help when we have so few material types and the shading code divergence is not radical. + +* first path caching + +Same as the mat scene. Caching the first path with 200 spp does not help that much. + +### images + +* direct light integrator + +![](img/my_scene.2018-10-03_03-31-37z.2018-10-03_03-32-39z.201_spp.integrator1_compact0_batch0_cache0.png) + +* full light integrator + +![](img/my_scene.2018-10-03_03-24-29z.2018-10-03_03-25-47z.201_spp.integrator3_compact0_batch0_cache0.png) + +* naive integrator + +![](img/my_scene.2018-10-03_03-12-40z.2018-10-03_03-13-50z.201_spp.integrator0_compact0_batch0_cache0.png) + +--- + +## 3. Two light scene 800x800 pixel 200 spp 8 recursion + +### overview + +![](img/my_scene_8.jpg) + +### rendering time + +![](img/3.JPG) + +### analysis + +* integrators + +Same as the mat scene. Direct light integrator produces incomplete image. Naive integrator produces noisy image. Full light integrator provides best image. + +* stream compaction + +Just like the mat scene the total depth is 8 now, the stream compaction starts to waste time because the time spent on stream compaction is more than the time saved by launching less threads. + +* material batching + +Same as the mat scene. Material batching doesn't help when we have so few material types and the shading code divergence is not radical. + +* first path caching + +Same as the mat scene. Caching the first path with 200 spp does not help that much. + +### images + +* direct light integrator + +![](img/my_scene_8.2018-10-03_05-21-17z.2018-10-03_05-21-28z.201_spp.integrator1_compact0_batch0_cache0.png) + +* full light integrator + +![](img/my_scene_8.2018-10-03_04-10-04z.2018-10-03_04-10-21z.201_spp.integrator3_compact0_batch0_cache0.png) + +* naive integrator + +![](img/my_scene_8.2018-10-03_04-04-10z.2018-10-03_04-04-23z.201_spp.integrator0_compact0_batch0_cache0.png) + +--- + +## 4. Rex scene 800x800 pixel 200 spp 8 recursion (28974 triangles in total) + +### overview + +![](img/my_scene_rex_8.jpg) + +### rendering time + +![](img/4.JPG) + +### analysis + +* integrators + +One of the most obvious result is that when there is a lot of geometries in the scene, full light integrator cost more time than naive integrator. This is because in full light integrator, every time we sample a light or a material, we have to an intersection detection called shadow feeler to provide shadow information. This is very costly especially when we have a lot of geometries. Basically, we will do two extra intersection detections in every depth. Even with k-d tree and bounding volume hierachy the time comsumption is still significant. Apart from this, the results are same as the mat scene. Direct light integrator produces incomplete image. Naive integrator produces noisy image. Full light integrator provides best image. + +* stream compaction + +Unlike the mat scene and the two light scene, even the total depth is still 8, the stream compaction will save time by launching less threads. This is simply because we are doing too much work in one depth, mainly because the intersection detection with large amount of geometries. Full light integrator benefits more from stream compaction because it has two extra intersection detection per depth, which means more work can be saved. + +* material batching + +Same as the mat scene. Material batching doesn't help when we have so few material types and the shading code divergence is not radical. + +* first path caching + +Same as the mat scene. Caching the first path with 200 spp does not help that much. + +### images + +* direct light integrator + +![](img/my_scene_rex_8.2018-10-03_05-17-07z.2018-10-03_05-17-29z.201_spp.integrator1_compact0_batch0_cache0.png) + +* full light integrator + +![](img/my_scene_rex_8.2018-10-03_04-44-58z.2018-10-03_04-51-50z.201_spp.integrator3_compact0_batch0_cache0.png) + +* naive integrator + +![](img/my_scene_rex_8.2018-10-03_04-28-46z.2018-10-03_04-31-37z.201_spp.integrator0_compact0_batch0_cache0.png) + +--- + +## 5. Reflective rex sceen 800x800 pixel 200 spp 8 recursion (28974 triangles in total) + +### overview + +![](img/my_scene_rex_r.jpg) + +### rendering time + +![](img/5.JPG) + +### analysis + +* integrators + +Same as the diffuse rex scene, full light integrator cost more time than naive integrator because the extra intersection detections with large amount of geometries. But for full light integrator the rendering time is actually 50 to 100 seconds less in this reflective scene than in the diffuse scene. Because in full light integrator, perfect specular material can skip lots of calculation and step right to the next depth since they are not sampling the light. Apart from this, the results are same as the mat scene. Direct light integrator produces incomplete image. Naive integrator produces noisy image. Full light integrator provides best image. + +* stream compaction + +Just like the diffuse rex scene, even the total depth is still 8, the stream compaction will save time by launching less threads. + +* material batching + +Same as the mat scene. Material batching doesn't help when we have so few material types and the shading code divergence is not radical. + +* first path caching + +Same as the mat scene. Caching the first path with 200 spp does not help that much. + +### images + +* direct light integrator + +![](img/my_scene_rex_r_8.2018-10-03_06-10-12z.2018-10-03_06-10-31z.201_spp.integrator1_compact0_batch0_cache0.png) + +* full light integrator + +![](img/my_scene_rex_r_8.2018-10-03_05-44-30z.2018-10-03_05-49-48z.201_spp.integrator3_compact0_batch0_cache0.png) + +* naive integrator + +![](img/my_scene_rex_r_8.2018-10-03_05-27-43z.2018-10-03_05-30-36z.201_spp.integrator0_compact0_batch0_cache0.png) + +--- + +## 6. Comparison with CPU path tracer + +### overview + +* 1024x1024 pixel 400 spp 5 recursions for both + +* compact and cache are enabled for CUDA path tracer + +### cuda path tracer 10 min 51 sec + +![](img/example.2018-10-03_16-40-59z.2018-10-03_16-51-50z.401_spp.integrator3_compact1_batch0_cache1.png) + +### cpu path tracer 63.5 hour + +![](img/3_1024_20_5_63.5h.png) + +--- + +## 7. Summary + +* Stream compaction is optimal when there is a lot of work to do during one depth or when your total depth is large. + +* Material batching is optimal when there are lots of different material types in the scene and the divergence of shading code is radical. In terms of sorting algorithm, if thrust is used, there is no need to allocate an integer array just to use the raidx sort, overload the "<" operator of a class and sort the objects of this class will not slow you down too much if any. + +* Caching the first path is optimal when the spp value is high enough. + +* Last but not least, full light integrator is not necessarily the fastest. But with the same resolution, spp and total depth, it will produce a less noisy image. diff --git a/img/1.JPG b/img/1.JPG new file mode 100644 index 0000000..c77b8ca Binary files /dev/null and b/img/1.JPG differ diff --git a/img/2.JPG b/img/2.JPG new file mode 100644 index 0000000..4d88205 Binary files /dev/null and b/img/2.JPG differ diff --git a/img/3.JPG b/img/3.JPG new file mode 100644 index 0000000..50bbd7c Binary files /dev/null and b/img/3.JPG differ diff --git a/img/3_1024_20_5_63.5h.png b/img/3_1024_20_5_63.5h.png new file mode 100644 index 0000000..c74de77 Binary files /dev/null and b/img/3_1024_20_5_63.5h.png differ diff --git a/img/4.JPG b/img/4.JPG new file mode 100644 index 0000000..e3aeda7 Binary files /dev/null and b/img/4.JPG differ diff --git a/img/5.JPG b/img/5.JPG new file mode 100644 index 0000000..32c1e9e Binary files /dev/null and b/img/5.JPG differ diff --git a/img/example.2018-10-03_16-40-59z.2018-10-03_16-51-50z.401_spp.integrator3_compact1_batch0_cache1.png b/img/example.2018-10-03_16-40-59z.2018-10-03_16-51-50z.401_spp.integrator3_compact1_batch0_cache1.png new file mode 100644 index 0000000..0addf16 Binary files /dev/null and b/img/example.2018-10-03_16-40-59z.2018-10-03_16-51-50z.401_spp.integrator3_compact1_batch0_cache1.png differ diff --git a/img/my_mat.2018-10-03_06-40-15z.2018-10-03_06-40-28z.201_spp.integrator0_compact0_batch0_cache0.png b/img/my_mat.2018-10-03_06-40-15z.2018-10-03_06-40-28z.201_spp.integrator0_compact0_batch0_cache0.png new file mode 100644 index 0000000..1bdf8ee Binary files /dev/null and b/img/my_mat.2018-10-03_06-40-15z.2018-10-03_06-40-28z.201_spp.integrator0_compact0_batch0_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-40-48z.2018-10-03_06-41-14z.201_spp.integrator0_compact1_batch0_cache0.png b/img/my_mat.2018-10-03_06-40-48z.2018-10-03_06-41-14z.201_spp.integrator0_compact1_batch0_cache0.png new file mode 100644 index 0000000..e389d69 Binary files /dev/null and b/img/my_mat.2018-10-03_06-40-48z.2018-10-03_06-41-14z.201_spp.integrator0_compact1_batch0_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-41-22z.2018-10-03_06-42-50z.201_spp.integrator0_compact1_batch1_cache0.png b/img/my_mat.2018-10-03_06-41-22z.2018-10-03_06-42-50z.201_spp.integrator0_compact1_batch1_cache0.png new file mode 100644 index 0000000..dfb666b Binary files /dev/null and b/img/my_mat.2018-10-03_06-41-22z.2018-10-03_06-42-50z.201_spp.integrator0_compact1_batch1_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-43-01z.2018-10-03_06-44-24z.201_spp.integrator0_compact1_batch2_cache0.png b/img/my_mat.2018-10-03_06-43-01z.2018-10-03_06-44-24z.201_spp.integrator0_compact1_batch2_cache0.png new file mode 100644 index 0000000..9b22a41 Binary files /dev/null and b/img/my_mat.2018-10-03_06-43-01z.2018-10-03_06-44-24z.201_spp.integrator0_compact1_batch2_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-44-35z.2018-10-03_06-45-00z.201_spp.integrator0_compact1_batch0_cache1.png b/img/my_mat.2018-10-03_06-44-35z.2018-10-03_06-45-00z.201_spp.integrator0_compact1_batch0_cache1.png new file mode 100644 index 0000000..e389d69 Binary files /dev/null and b/img/my_mat.2018-10-03_06-44-35z.2018-10-03_06-45-00z.201_spp.integrator0_compact1_batch0_cache1.png differ diff --git a/img/my_mat.2018-10-03_06-45-23z.2018-10-03_06-45-34z.201_spp.integrator1_compact0_batch0_cache0.png b/img/my_mat.2018-10-03_06-45-23z.2018-10-03_06-45-34z.201_spp.integrator1_compact0_batch0_cache0.png new file mode 100644 index 0000000..77efc71 Binary files /dev/null and b/img/my_mat.2018-10-03_06-45-23z.2018-10-03_06-45-34z.201_spp.integrator1_compact0_batch0_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-45-45z.2018-10-03_06-45-49z.201_spp.integrator1_compact1_batch0_cache0.png b/img/my_mat.2018-10-03_06-45-45z.2018-10-03_06-45-49z.201_spp.integrator1_compact1_batch0_cache0.png new file mode 100644 index 0000000..77efc71 Binary files /dev/null and b/img/my_mat.2018-10-03_06-45-45z.2018-10-03_06-45-49z.201_spp.integrator1_compact1_batch0_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-45-54z.2018-10-03_06-46-13z.201_spp.integrator1_compact1_batch1_cache0.png b/img/my_mat.2018-10-03_06-45-54z.2018-10-03_06-46-13z.201_spp.integrator1_compact1_batch1_cache0.png new file mode 100644 index 0000000..ae1274b Binary files /dev/null and b/img/my_mat.2018-10-03_06-45-54z.2018-10-03_06-46-13z.201_spp.integrator1_compact1_batch1_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-46-20z.2018-10-03_06-46-36z.201_spp.integrator1_compact1_batch2_cache0.png b/img/my_mat.2018-10-03_06-46-20z.2018-10-03_06-46-36z.201_spp.integrator1_compact1_batch2_cache0.png new file mode 100644 index 0000000..e29f559 Binary files /dev/null and b/img/my_mat.2018-10-03_06-46-20z.2018-10-03_06-46-36z.201_spp.integrator1_compact1_batch2_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-46-49z.2018-10-03_06-46-52z.201_spp.integrator1_compact1_batch0_cache1.png b/img/my_mat.2018-10-03_06-46-49z.2018-10-03_06-46-52z.201_spp.integrator1_compact1_batch0_cache1.png new file mode 100644 index 0000000..77efc71 Binary files /dev/null and b/img/my_mat.2018-10-03_06-46-49z.2018-10-03_06-46-52z.201_spp.integrator1_compact1_batch0_cache1.png differ diff --git a/img/my_mat.2018-10-03_06-47-02z.2018-10-03_06-47-18z.201_spp.integrator3_compact0_batch0_cache0.png b/img/my_mat.2018-10-03_06-47-02z.2018-10-03_06-47-18z.201_spp.integrator3_compact0_batch0_cache0.png new file mode 100644 index 0000000..868c947 Binary files /dev/null and b/img/my_mat.2018-10-03_06-47-02z.2018-10-03_06-47-18z.201_spp.integrator3_compact0_batch0_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-47-27z.2018-10-03_06-47-51z.201_spp.integrator3_compact1_batch0_cache0.png b/img/my_mat.2018-10-03_06-47-27z.2018-10-03_06-47-51z.201_spp.integrator3_compact1_batch0_cache0.png new file mode 100644 index 0000000..7b6ddde Binary files /dev/null and b/img/my_mat.2018-10-03_06-47-27z.2018-10-03_06-47-51z.201_spp.integrator3_compact1_batch0_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-47-58z.2018-10-03_06-49-14z.201_spp.integrator3_compact1_batch1_cache0.png b/img/my_mat.2018-10-03_06-47-58z.2018-10-03_06-49-14z.201_spp.integrator3_compact1_batch1_cache0.png new file mode 100644 index 0000000..4d1ec20 Binary files /dev/null and b/img/my_mat.2018-10-03_06-47-58z.2018-10-03_06-49-14z.201_spp.integrator3_compact1_batch1_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-49-22z.2018-10-03_06-50-31z.201_spp.integrator3_compact1_batch2_cache0.png b/img/my_mat.2018-10-03_06-49-22z.2018-10-03_06-50-31z.201_spp.integrator3_compact1_batch2_cache0.png new file mode 100644 index 0000000..b56aac4 Binary files /dev/null and b/img/my_mat.2018-10-03_06-49-22z.2018-10-03_06-50-31z.201_spp.integrator3_compact1_batch2_cache0.png differ diff --git a/img/my_mat.2018-10-03_06-50-43z.2018-10-03_06-51-06z.201_spp.integrator3_compact1_batch0_cache1.png b/img/my_mat.2018-10-03_06-50-43z.2018-10-03_06-51-06z.201_spp.integrator3_compact1_batch0_cache1.png new file mode 100644 index 0000000..7b6ddde Binary files /dev/null and b/img/my_mat.2018-10-03_06-50-43z.2018-10-03_06-51-06z.201_spp.integrator3_compact1_batch0_cache1.png differ diff --git a/img/my_mat.jpg b/img/my_mat.jpg new file mode 100644 index 0000000..437538e Binary files /dev/null and b/img/my_mat.jpg differ diff --git a/img/my_scene.2018-10-03_03-12-40z.2018-10-03_03-13-50z.201_spp.integrator0_compact0_batch0_cache0.png b/img/my_scene.2018-10-03_03-12-40z.2018-10-03_03-13-50z.201_spp.integrator0_compact0_batch0_cache0.png new file mode 100644 index 0000000..50116d7 Binary files /dev/null and b/img/my_scene.2018-10-03_03-12-40z.2018-10-03_03-13-50z.201_spp.integrator0_compact0_batch0_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-15-03z.2018-10-03_03-15-39z.201_spp.integrator0_compact1_batch0_cache0.png b/img/my_scene.2018-10-03_03-15-03z.2018-10-03_03-15-39z.201_spp.integrator0_compact1_batch0_cache0.png new file mode 100644 index 0000000..bd55375 Binary files /dev/null and b/img/my_scene.2018-10-03_03-15-03z.2018-10-03_03-15-39z.201_spp.integrator0_compact1_batch0_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-16-42z.2018-10-03_03-18-38z.201_spp.integrator0_compact1_batch1_cache0.png b/img/my_scene.2018-10-03_03-16-42z.2018-10-03_03-18-38z.201_spp.integrator0_compact1_batch1_cache0.png new file mode 100644 index 0000000..af2dcb1 Binary files /dev/null and b/img/my_scene.2018-10-03_03-16-42z.2018-10-03_03-18-38z.201_spp.integrator0_compact1_batch1_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-19-41z.2018-10-03_03-21-27z.201_spp.integrator0_compact1_batch2_cache0.png b/img/my_scene.2018-10-03_03-19-41z.2018-10-03_03-21-27z.201_spp.integrator0_compact1_batch2_cache0.png new file mode 100644 index 0000000..a9ef45a Binary files /dev/null and b/img/my_scene.2018-10-03_03-19-41z.2018-10-03_03-21-27z.201_spp.integrator0_compact1_batch2_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-22-21z.2018-10-03_03-22-57z.201_spp.integrator0_compact1_batch0_cache1.png b/img/my_scene.2018-10-03_03-22-21z.2018-10-03_03-22-57z.201_spp.integrator0_compact1_batch0_cache1.png new file mode 100644 index 0000000..bd55375 Binary files /dev/null and b/img/my_scene.2018-10-03_03-22-21z.2018-10-03_03-22-57z.201_spp.integrator0_compact1_batch0_cache1.png differ diff --git a/img/my_scene.2018-10-03_03-24-29z.2018-10-03_03-25-47z.201_spp.integrator3_compact0_batch0_cache0.png b/img/my_scene.2018-10-03_03-24-29z.2018-10-03_03-25-47z.201_spp.integrator3_compact0_batch0_cache0.png new file mode 100644 index 0000000..9957501 Binary files /dev/null and b/img/my_scene.2018-10-03_03-24-29z.2018-10-03_03-25-47z.201_spp.integrator3_compact0_batch0_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-26-10z.2018-10-03_03-26-41z.201_spp.integrator3_compact1_batch0_cache0.png b/img/my_scene.2018-10-03_03-26-10z.2018-10-03_03-26-41z.201_spp.integrator3_compact1_batch0_cache0.png new file mode 100644 index 0000000..59b9a48 Binary files /dev/null and b/img/my_scene.2018-10-03_03-26-10z.2018-10-03_03-26-41z.201_spp.integrator3_compact1_batch0_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-26-57z.2018-10-03_03-28-31z.201_spp.integrator3_compact1_batch1_cache0.png b/img/my_scene.2018-10-03_03-26-57z.2018-10-03_03-28-31z.201_spp.integrator3_compact1_batch1_cache0.png new file mode 100644 index 0000000..9ca6b29 Binary files /dev/null and b/img/my_scene.2018-10-03_03-26-57z.2018-10-03_03-28-31z.201_spp.integrator3_compact1_batch1_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-28-43z.2018-10-03_03-30-07z.201_spp.integrator3_compact1_batch2_cache0.png b/img/my_scene.2018-10-03_03-28-43z.2018-10-03_03-30-07z.201_spp.integrator3_compact1_batch2_cache0.png new file mode 100644 index 0000000..a2f5702 Binary files /dev/null and b/img/my_scene.2018-10-03_03-28-43z.2018-10-03_03-30-07z.201_spp.integrator3_compact1_batch2_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-30-40z.2018-10-03_03-31-10z.201_spp.integrator3_compact1_batch0_cache1.png b/img/my_scene.2018-10-03_03-30-40z.2018-10-03_03-31-10z.201_spp.integrator3_compact1_batch0_cache1.png new file mode 100644 index 0000000..59b9a48 Binary files /dev/null and b/img/my_scene.2018-10-03_03-30-40z.2018-10-03_03-31-10z.201_spp.integrator3_compact1_batch0_cache1.png differ diff --git a/img/my_scene.2018-10-03_03-31-37z.2018-10-03_03-32-39z.201_spp.integrator1_compact0_batch0_cache0.png b/img/my_scene.2018-10-03_03-31-37z.2018-10-03_03-32-39z.201_spp.integrator1_compact0_batch0_cache0.png new file mode 100644 index 0000000..f05ec33 Binary files /dev/null and b/img/my_scene.2018-10-03_03-31-37z.2018-10-03_03-32-39z.201_spp.integrator1_compact0_batch0_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-33-34z.2018-10-03_03-33-37z.201_spp.integrator1_compact1_batch0_cache0.png b/img/my_scene.2018-10-03_03-33-34z.2018-10-03_03-33-37z.201_spp.integrator1_compact1_batch0_cache0.png new file mode 100644 index 0000000..f05ec33 Binary files /dev/null and b/img/my_scene.2018-10-03_03-33-34z.2018-10-03_03-33-37z.201_spp.integrator1_compact1_batch0_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-34-08z.2018-10-03_03-34-27z.201_spp.integrator1_compact1_batch1_cache0.png b/img/my_scene.2018-10-03_03-34-08z.2018-10-03_03-34-27z.201_spp.integrator1_compact1_batch1_cache0.png new file mode 100644 index 0000000..abb3669 Binary files /dev/null and b/img/my_scene.2018-10-03_03-34-08z.2018-10-03_03-34-27z.201_spp.integrator1_compact1_batch1_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-34-49z.2018-10-03_03-35-05z.201_spp.integrator1_compact1_batch2_cache0.png b/img/my_scene.2018-10-03_03-34-49z.2018-10-03_03-35-05z.201_spp.integrator1_compact1_batch2_cache0.png new file mode 100644 index 0000000..38a390c Binary files /dev/null and b/img/my_scene.2018-10-03_03-34-49z.2018-10-03_03-35-05z.201_spp.integrator1_compact1_batch2_cache0.png differ diff --git a/img/my_scene.2018-10-03_03-35-33z.2018-10-03_03-35-36z.201_spp.integrator1_compact1_batch0_cache1.png b/img/my_scene.2018-10-03_03-35-33z.2018-10-03_03-35-36z.201_spp.integrator1_compact1_batch0_cache1.png new file mode 100644 index 0000000..f05ec33 Binary files /dev/null and b/img/my_scene.2018-10-03_03-35-33z.2018-10-03_03-35-36z.201_spp.integrator1_compact1_batch0_cache1.png differ diff --git a/img/my_scene.jpg b/img/my_scene.jpg new file mode 100644 index 0000000..e4e525d Binary files /dev/null and b/img/my_scene.jpg differ diff --git a/img/my_scene_8.2018-10-03_04-04-10z.2018-10-03_04-04-23z.201_spp.integrator0_compact0_batch0_cache0.png b/img/my_scene_8.2018-10-03_04-04-10z.2018-10-03_04-04-23z.201_spp.integrator0_compact0_batch0_cache0.png new file mode 100644 index 0000000..512f949 Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-04-10z.2018-10-03_04-04-23z.201_spp.integrator0_compact0_batch0_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_04-04-58z.2018-10-03_04-05-22z.201_spp.integrator0_compact1_batch0_cache0.png b/img/my_scene_8.2018-10-03_04-04-58z.2018-10-03_04-05-22z.201_spp.integrator0_compact1_batch0_cache0.png new file mode 100644 index 0000000..376726a Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-04-58z.2018-10-03_04-05-22z.201_spp.integrator0_compact1_batch0_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_04-06-05z.2018-10-03_04-07-25z.201_spp.integrator0_compact1_batch1_cache0.png b/img/my_scene_8.2018-10-03_04-06-05z.2018-10-03_04-07-25z.201_spp.integrator0_compact1_batch1_cache0.png new file mode 100644 index 0000000..67290c4 Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-06-05z.2018-10-03_04-07-25z.201_spp.integrator0_compact1_batch1_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_04-07-43z.2018-10-03_04-08-57z.201_spp.integrator0_compact1_batch2_cache0.png b/img/my_scene_8.2018-10-03_04-07-43z.2018-10-03_04-08-57z.201_spp.integrator0_compact1_batch2_cache0.png new file mode 100644 index 0000000..a92d824 Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-07-43z.2018-10-03_04-08-57z.201_spp.integrator0_compact1_batch2_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_04-09-17z.2018-10-03_04-09-40z.201_spp.integrator0_compact1_batch0_cache1.png b/img/my_scene_8.2018-10-03_04-09-17z.2018-10-03_04-09-40z.201_spp.integrator0_compact1_batch0_cache1.png new file mode 100644 index 0000000..376726a Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-09-17z.2018-10-03_04-09-40z.201_spp.integrator0_compact1_batch0_cache1.png differ diff --git a/img/my_scene_8.2018-10-03_04-10-04z.2018-10-03_04-10-21z.201_spp.integrator3_compact0_batch0_cache0.png b/img/my_scene_8.2018-10-03_04-10-04z.2018-10-03_04-10-21z.201_spp.integrator3_compact0_batch0_cache0.png new file mode 100644 index 0000000..a1cbd75 Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-10-04z.2018-10-03_04-10-21z.201_spp.integrator3_compact0_batch0_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_04-10-29z.2018-10-03_04-10-52z.201_spp.integrator3_compact1_batch0_cache0.png b/img/my_scene_8.2018-10-03_04-10-29z.2018-10-03_04-10-52z.201_spp.integrator3_compact1_batch0_cache0.png new file mode 100644 index 0000000..4fa6fd9 Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-10-29z.2018-10-03_04-10-52z.201_spp.integrator3_compact1_batch0_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_04-11-13z.2018-10-03_04-12-27z.201_spp.integrator3_compact1_batch1_cache0.png b/img/my_scene_8.2018-10-03_04-11-13z.2018-10-03_04-12-27z.201_spp.integrator3_compact1_batch1_cache0.png new file mode 100644 index 0000000..85160d9 Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-11-13z.2018-10-03_04-12-27z.201_spp.integrator3_compact1_batch1_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_04-12-42z.2018-10-03_04-13-49z.201_spp.integrator3_compact1_batch2_cache0.png b/img/my_scene_8.2018-10-03_04-12-42z.2018-10-03_04-13-49z.201_spp.integrator3_compact1_batch2_cache0.png new file mode 100644 index 0000000..7d0c3d4 Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-12-42z.2018-10-03_04-13-49z.201_spp.integrator3_compact1_batch2_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_04-14-05z.2018-10-03_04-14-26z.201_spp.integrator3_compact1_batch0_cache1.png b/img/my_scene_8.2018-10-03_04-14-05z.2018-10-03_04-14-26z.201_spp.integrator3_compact1_batch0_cache1.png new file mode 100644 index 0000000..4fa6fd9 Binary files /dev/null and b/img/my_scene_8.2018-10-03_04-14-05z.2018-10-03_04-14-26z.201_spp.integrator3_compact1_batch0_cache1.png differ diff --git a/img/my_scene_8.2018-10-03_05-21-17z.2018-10-03_05-21-28z.201_spp.integrator1_compact0_batch0_cache0.png b/img/my_scene_8.2018-10-03_05-21-17z.2018-10-03_05-21-28z.201_spp.integrator1_compact0_batch0_cache0.png new file mode 100644 index 0000000..8dafb80 Binary files /dev/null and b/img/my_scene_8.2018-10-03_05-21-17z.2018-10-03_05-21-28z.201_spp.integrator1_compact0_batch0_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_05-21-41z.2018-10-03_05-21-44z.201_spp.integrator1_compact1_batch0_cache0.png b/img/my_scene_8.2018-10-03_05-21-41z.2018-10-03_05-21-44z.201_spp.integrator1_compact1_batch0_cache0.png new file mode 100644 index 0000000..8dafb80 Binary files /dev/null and b/img/my_scene_8.2018-10-03_05-21-41z.2018-10-03_05-21-44z.201_spp.integrator1_compact1_batch0_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_05-21-54z.2018-10-03_05-22-13z.201_spp.integrator1_compact1_batch1_cache0.png b/img/my_scene_8.2018-10-03_05-21-54z.2018-10-03_05-22-13z.201_spp.integrator1_compact1_batch1_cache0.png new file mode 100644 index 0000000..58664a1 Binary files /dev/null and b/img/my_scene_8.2018-10-03_05-21-54z.2018-10-03_05-22-13z.201_spp.integrator1_compact1_batch1_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_05-22-28z.2018-10-03_05-22-45z.201_spp.integrator1_compact1_batch2_cache0.png b/img/my_scene_8.2018-10-03_05-22-28z.2018-10-03_05-22-45z.201_spp.integrator1_compact1_batch2_cache0.png new file mode 100644 index 0000000..e6da755 Binary files /dev/null and b/img/my_scene_8.2018-10-03_05-22-28z.2018-10-03_05-22-45z.201_spp.integrator1_compact1_batch2_cache0.png differ diff --git a/img/my_scene_8.2018-10-03_05-22-59z.2018-10-03_05-23-02z.201_spp.integrator1_compact1_batch0_cache1.png b/img/my_scene_8.2018-10-03_05-22-59z.2018-10-03_05-23-02z.201_spp.integrator1_compact1_batch0_cache1.png new file mode 100644 index 0000000..8dafb80 Binary files /dev/null and b/img/my_scene_8.2018-10-03_05-22-59z.2018-10-03_05-23-02z.201_spp.integrator1_compact1_batch0_cache1.png differ diff --git a/img/my_scene_8.jpg b/img/my_scene_8.jpg new file mode 100644 index 0000000..c8405e4 Binary files /dev/null and b/img/my_scene_8.jpg differ diff --git a/img/my_scene_rex_8.2018-10-03_04-28-46z.2018-10-03_04-31-37z.201_spp.integrator0_compact0_batch0_cache0.png b/img/my_scene_rex_8.2018-10-03_04-28-46z.2018-10-03_04-31-37z.201_spp.integrator0_compact0_batch0_cache0.png new file mode 100644 index 0000000..8fd7267 Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_04-28-46z.2018-10-03_04-31-37z.201_spp.integrator0_compact0_batch0_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_04-32-09z.2018-10-03_04-34-32z.201_spp.integrator0_compact1_batch0_cache0.png b/img/my_scene_rex_8.2018-10-03_04-32-09z.2018-10-03_04-34-32z.201_spp.integrator0_compact1_batch0_cache0.png new file mode 100644 index 0000000..844b26d Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_04-32-09z.2018-10-03_04-34-32z.201_spp.integrator0_compact1_batch0_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_04-35-05z.2018-10-03_04-38-28z.201_spp.integrator0_compact1_batch1_cache0.png b/img/my_scene_rex_8.2018-10-03_04-35-05z.2018-10-03_04-38-28z.201_spp.integrator0_compact1_batch1_cache0.png new file mode 100644 index 0000000..a5ff240 Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_04-35-05z.2018-10-03_04-38-28z.201_spp.integrator0_compact1_batch1_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_04-38-45z.2018-10-03_04-41-55z.201_spp.integrator0_compact1_batch2_cache0.png b/img/my_scene_rex_8.2018-10-03_04-38-45z.2018-10-03_04-41-55z.201_spp.integrator0_compact1_batch2_cache0.png new file mode 100644 index 0000000..05b12fa Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_04-38-45z.2018-10-03_04-41-55z.201_spp.integrator0_compact1_batch2_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_04-42-15z.2018-10-03_04-44-35z.201_spp.integrator0_compact1_batch0_cache1.png b/img/my_scene_rex_8.2018-10-03_04-42-15z.2018-10-03_04-44-35z.201_spp.integrator0_compact1_batch0_cache1.png new file mode 100644 index 0000000..844b26d Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_04-42-15z.2018-10-03_04-44-35z.201_spp.integrator0_compact1_batch0_cache1.png differ diff --git a/img/my_scene_rex_8.2018-10-03_04-44-58z.2018-10-03_04-51-50z.201_spp.integrator3_compact0_batch0_cache0.png b/img/my_scene_rex_8.2018-10-03_04-44-58z.2018-10-03_04-51-50z.201_spp.integrator3_compact0_batch0_cache0.png new file mode 100644 index 0000000..efbf2c2 Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_04-44-58z.2018-10-03_04-51-50z.201_spp.integrator3_compact0_batch0_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_04-52-09z.2018-10-03_04-57-23z.201_spp.integrator3_compact1_batch0_cache0.png b/img/my_scene_rex_8.2018-10-03_04-52-09z.2018-10-03_04-57-23z.201_spp.integrator3_compact1_batch0_cache0.png new file mode 100644 index 0000000..3ba6e37 Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_04-52-09z.2018-10-03_04-57-23z.201_spp.integrator3_compact1_batch0_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_04-59-41z.2018-10-03_05-05-32z.201_spp.integrator3_compact1_batch1_cache0.png b/img/my_scene_rex_8.2018-10-03_04-59-41z.2018-10-03_05-05-32z.201_spp.integrator3_compact1_batch1_cache0.png new file mode 100644 index 0000000..06bcce0 Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_04-59-41z.2018-10-03_05-05-32z.201_spp.integrator3_compact1_batch1_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_05-05-52z.2018-10-03_05-11-15z.201_spp.integrator3_compact1_batch2_cache0.png b/img/my_scene_rex_8.2018-10-03_05-05-52z.2018-10-03_05-11-15z.201_spp.integrator3_compact1_batch2_cache0.png new file mode 100644 index 0000000..4c0df04 Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_05-05-52z.2018-10-03_05-11-15z.201_spp.integrator3_compact1_batch2_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_05-11-36z.2018-10-03_05-16-45z.201_spp.integrator3_compact1_batch0_cache1.png b/img/my_scene_rex_8.2018-10-03_05-11-36z.2018-10-03_05-16-45z.201_spp.integrator3_compact1_batch0_cache1.png new file mode 100644 index 0000000..3ba6e37 Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_05-11-36z.2018-10-03_05-16-45z.201_spp.integrator3_compact1_batch0_cache1.png differ diff --git a/img/my_scene_rex_8.2018-10-03_05-17-07z.2018-10-03_05-17-29z.201_spp.integrator1_compact0_batch0_cache0.png b/img/my_scene_rex_8.2018-10-03_05-17-07z.2018-10-03_05-17-29z.201_spp.integrator1_compact0_batch0_cache0.png new file mode 100644 index 0000000..87049ac Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_05-17-07z.2018-10-03_05-17-29z.201_spp.integrator1_compact0_batch0_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_05-17-56z.2018-10-03_05-18-10z.201_spp.integrator1_compact1_batch0_cache0.png b/img/my_scene_rex_8.2018-10-03_05-17-56z.2018-10-03_05-18-10z.201_spp.integrator1_compact1_batch0_cache0.png new file mode 100644 index 0000000..87049ac Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_05-17-56z.2018-10-03_05-18-10z.201_spp.integrator1_compact1_batch0_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_05-18-24z.2018-10-03_05-18-54z.201_spp.integrator1_compact1_batch1_cache0.png b/img/my_scene_rex_8.2018-10-03_05-18-24z.2018-10-03_05-18-54z.201_spp.integrator1_compact1_batch1_cache0.png new file mode 100644 index 0000000..eb71188 Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_05-18-24z.2018-10-03_05-18-54z.201_spp.integrator1_compact1_batch1_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_05-19-06z.2018-10-03_05-19-33z.201_spp.integrator1_compact1_batch2_cache0.png b/img/my_scene_rex_8.2018-10-03_05-19-06z.2018-10-03_05-19-33z.201_spp.integrator1_compact1_batch2_cache0.png new file mode 100644 index 0000000..6922a8a Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_05-19-06z.2018-10-03_05-19-33z.201_spp.integrator1_compact1_batch2_cache0.png differ diff --git a/img/my_scene_rex_8.2018-10-03_05-19-46z.2018-10-03_05-19-57z.201_spp.integrator1_compact1_batch0_cache1.png b/img/my_scene_rex_8.2018-10-03_05-19-46z.2018-10-03_05-19-57z.201_spp.integrator1_compact1_batch0_cache1.png new file mode 100644 index 0000000..87049ac Binary files /dev/null and b/img/my_scene_rex_8.2018-10-03_05-19-46z.2018-10-03_05-19-57z.201_spp.integrator1_compact1_batch0_cache1.png differ diff --git a/img/my_scene_rex_8.jpg b/img/my_scene_rex_8.jpg new file mode 100644 index 0000000..f80dba2 Binary files /dev/null and b/img/my_scene_rex_8.jpg differ diff --git a/img/my_scene_rex_r.jpg b/img/my_scene_rex_r.jpg new file mode 100644 index 0000000..cb8177b Binary files /dev/null and b/img/my_scene_rex_r.jpg differ diff --git a/img/my_scene_rex_r_8.2018-10-03_05-27-43z.2018-10-03_05-30-36z.201_spp.integrator0_compact0_batch0_cache0.png b/img/my_scene_rex_r_8.2018-10-03_05-27-43z.2018-10-03_05-30-36z.201_spp.integrator0_compact0_batch0_cache0.png new file mode 100644 index 0000000..f26a990 Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_05-27-43z.2018-10-03_05-30-36z.201_spp.integrator0_compact0_batch0_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_05-31-12z.2018-10-03_05-33-37z.201_spp.integrator0_compact1_batch0_cache0.png b/img/my_scene_rex_r_8.2018-10-03_05-31-12z.2018-10-03_05-33-37z.201_spp.integrator0_compact1_batch0_cache0.png new file mode 100644 index 0000000..826135e Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_05-31-12z.2018-10-03_05-33-37z.201_spp.integrator0_compact1_batch0_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_05-33-54z.2018-10-03_05-37-17z.201_spp.integrator0_compact1_batch1_cache0.png b/img/my_scene_rex_r_8.2018-10-03_05-33-54z.2018-10-03_05-37-17z.201_spp.integrator0_compact1_batch1_cache0.png new file mode 100644 index 0000000..0788e91 Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_05-33-54z.2018-10-03_05-37-17z.201_spp.integrator0_compact1_batch1_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_05-37-47z.2018-10-03_05-40-57z.201_spp.integrator0_compact1_batch2_cache0.png b/img/my_scene_rex_r_8.2018-10-03_05-37-47z.2018-10-03_05-40-57z.201_spp.integrator0_compact1_batch2_cache0.png new file mode 100644 index 0000000..4de5395 Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_05-37-47z.2018-10-03_05-40-57z.201_spp.integrator0_compact1_batch2_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_05-41-08z.2018-10-03_05-43-30z.201_spp.integrator0_compact1_batch0_cache1.png b/img/my_scene_rex_r_8.2018-10-03_05-41-08z.2018-10-03_05-43-30z.201_spp.integrator0_compact1_batch0_cache1.png new file mode 100644 index 0000000..826135e Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_05-41-08z.2018-10-03_05-43-30z.201_spp.integrator0_compact1_batch0_cache1.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_05-44-30z.2018-10-03_05-49-48z.201_spp.integrator3_compact0_batch0_cache0.png b/img/my_scene_rex_r_8.2018-10-03_05-44-30z.2018-10-03_05-49-48z.201_spp.integrator3_compact0_batch0_cache0.png new file mode 100644 index 0000000..0d32e7f Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_05-44-30z.2018-10-03_05-49-48z.201_spp.integrator3_compact0_batch0_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_05-50-37z.2018-10-03_05-54-46z.201_spp.integrator3_compact1_batch0_cache0.png b/img/my_scene_rex_r_8.2018-10-03_05-50-37z.2018-10-03_05-54-46z.201_spp.integrator3_compact1_batch0_cache0.png new file mode 100644 index 0000000..39e2dd0 Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_05-50-37z.2018-10-03_05-54-46z.201_spp.integrator3_compact1_batch0_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_05-55-07z.2018-10-03_05-59-45z.201_spp.integrator3_compact1_batch1_cache0.png b/img/my_scene_rex_r_8.2018-10-03_05-55-07z.2018-10-03_05-59-45z.201_spp.integrator3_compact1_batch1_cache0.png new file mode 100644 index 0000000..1d8b74f Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_05-55-07z.2018-10-03_05-59-45z.201_spp.integrator3_compact1_batch1_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_06-00-09z.2018-10-03_06-04-28z.201_spp.integrator3_compact1_batch2_cache0.png b/img/my_scene_rex_r_8.2018-10-03_06-00-09z.2018-10-03_06-04-28z.201_spp.integrator3_compact1_batch2_cache0.png new file mode 100644 index 0000000..58770f1 Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_06-00-09z.2018-10-03_06-04-28z.201_spp.integrator3_compact1_batch2_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_06-04-44z.2018-10-03_06-08-50z.201_spp.integrator3_compact1_batch0_cache1.png b/img/my_scene_rex_r_8.2018-10-03_06-04-44z.2018-10-03_06-08-50z.201_spp.integrator3_compact1_batch0_cache1.png new file mode 100644 index 0000000..39e2dd0 Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_06-04-44z.2018-10-03_06-08-50z.201_spp.integrator3_compact1_batch0_cache1.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_06-10-12z.2018-10-03_06-10-31z.201_spp.integrator1_compact0_batch0_cache0.png b/img/my_scene_rex_r_8.2018-10-03_06-10-12z.2018-10-03_06-10-31z.201_spp.integrator1_compact0_batch0_cache0.png new file mode 100644 index 0000000..71155bf Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_06-10-12z.2018-10-03_06-10-31z.201_spp.integrator1_compact0_batch0_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_06-10-42z.2018-10-03_06-10-54z.201_spp.integrator1_compact1_batch0_cache0.png b/img/my_scene_rex_r_8.2018-10-03_06-10-42z.2018-10-03_06-10-54z.201_spp.integrator1_compact1_batch0_cache0.png new file mode 100644 index 0000000..71155bf Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_06-10-42z.2018-10-03_06-10-54z.201_spp.integrator1_compact1_batch0_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_06-11-06z.2018-10-03_06-11-33z.201_spp.integrator1_compact1_batch1_cache0.png b/img/my_scene_rex_r_8.2018-10-03_06-11-06z.2018-10-03_06-11-33z.201_spp.integrator1_compact1_batch1_cache0.png new file mode 100644 index 0000000..71e7a73 Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_06-11-06z.2018-10-03_06-11-33z.201_spp.integrator1_compact1_batch1_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_06-11-42z.2018-10-03_06-12-05z.201_spp.integrator1_compact1_batch2_cache0.png b/img/my_scene_rex_r_8.2018-10-03_06-11-42z.2018-10-03_06-12-05z.201_spp.integrator1_compact1_batch2_cache0.png new file mode 100644 index 0000000..ae3fe5d Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_06-11-42z.2018-10-03_06-12-05z.201_spp.integrator1_compact1_batch2_cache0.png differ diff --git a/img/my_scene_rex_r_8.2018-10-03_06-12-24z.2018-10-03_06-12-32z.201_spp.integrator1_compact1_batch0_cache1.png b/img/my_scene_rex_r_8.2018-10-03_06-12-24z.2018-10-03_06-12-32z.201_spp.integrator1_compact1_batch0_cache1.png new file mode 100644 index 0000000..71155bf Binary files /dev/null and b/img/my_scene_rex_r_8.2018-10-03_06-12-24z.2018-10-03_06-12-32z.201_spp.integrator1_compact1_batch0_cache1.png differ diff --git a/scenes/cornell.txt b/scenes/my_cornell_reflective_5000_8.txt similarity index 66% rename from scenes/cornell.txt rename to scenes/my_cornell_reflective_5000_8.txt index 83ff820..0db26b5 100644 --- a/scenes/cornell.txt +++ b/scenes/my_cornell_reflective_5000_8.txt @@ -1,52 +1,52 @@ // Emissive material (light) MATERIAL 0 -RGB 1 1 1 -SPECEX 0 -SPECRGB 0 0 0 -REFL 0 -REFR 0 -REFRIOR 0 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.8 0.8 0.8 EMITTANCE 5 +TYPE 1 // Diffuse white MATERIAL 1 RGB .98 .98 .98 -SPECEX 0 -SPECRGB 0 0 0 -REFL 0 -REFR 0 -REFRIOR 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 EMITTANCE 0 +TYPE 0 // Diffuse red MATERIAL 2 RGB .85 .35 .35 -SPECEX 0 -SPECRGB 0 0 0 -REFL 0 -REFR 0 -REFRIOR 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 EMITTANCE 0 +TYPE 0 // Diffuse green MATERIAL 3 RGB .35 .85 .35 -SPECEX 0 -SPECRGB 0 0 0 -REFL 0 -REFR 0 -REFRIOR 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 EMITTANCE 0 +TYPE 0 -// Specular white +// Specular reflective white MATERIAL 4 -RGB .98 .98 .98 -SPECEX 0 -SPECRGB .98 .98 .98 -REFL 1 -REFR 0 -REFRIOR 0 +RGB 0 0 0 +SPECRRGB .98 .98 .98 +SPECTRGB .98 .98 .98 +IOR 0.5 +EMITRGB 0 0 0 EMITTANCE 0 +TYPE 2 // Camera CAMERA @@ -54,7 +54,7 @@ RES 800 800 FOVY 45 ITERATIONS 5000 DEPTH 8 -FILE cornell +FILE my_scene EYE 0.0 5 10.5 LOOKAT 0 5 0 UP 0 1 0 @@ -112,6 +112,6 @@ SCALE .01 10 10 OBJECT 6 sphere material 4 -TRANS -1 4 -1 +TRANS -2 3 -2 ROTAT 0 0 0 SCALE 3 3 3 diff --git a/scenes/my_cornell_transmissive_5000_64.txt b/scenes/my_cornell_transmissive_5000_64.txt new file mode 100644 index 0000000..8c2559b --- /dev/null +++ b/scenes/my_cornell_transmissive_5000_64.txt @@ -0,0 +1,117 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.8 0.8 0.8 +EMITTANCE 5 +TYPE 1 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Specular refractive white +MATERIAL 4 +RGB 0 0 0 +SPECRRGB .98 .98 .98 +SPECTRGB .98 .98 .98 +IOR 0.5 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 3 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 5000 +DEPTH 64 +FILE my_scene +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 0 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 3 .3 3 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Sphere +OBJECT 6 +sphere +material 4 +TRANS 0 5 0 +ROTAT 0 0 0 +SCALE 3 3 3 diff --git a/scenes/my_sample_light_mat_100_8.txt b/scenes/my_sample_light_mat_100_8.txt new file mode 100644 index 0000000..d2f85cf --- /dev/null +++ b/scenes/my_sample_light_mat_100_8.txt @@ -0,0 +1,143 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.8 0.8 0.8 +EMITTANCE 8 +TYPE 1 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Specular reflective white +MATERIAL 4 +RGB 0 0 0 +SPECRRGB .98 .98 .98 +SPECTRGB .98 .98 .98 +IOR 0.5 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 2 + +// Specular transmissive white +MATERIAL 5 +RGB 0 0 0 +SPECRRGB .98 .98 .98 +SPECTRGB .98 .98 .98 +IOR 0.5 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 3 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 200 +DEPTH 8 +FILE my_mat +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +square +material 0 +TRANS 0 9.9 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Sphere +OBJECT 6 +sphere +material 4 +TRANS -3 5 0 +ROTAT 0 0 0 +SCALE 2 2 2 + +// Sphere +OBJECT 7 +sphere +material 1 +TRANS 0 5 0 +ROTAT 0 0 0 +SCALE 2 2 2 + +// Sphere +OBJECT 8 +sphere +material 5 +TRANS 3 5 0 +ROTAT 0 0 0 +SCALE 2 2 2 \ No newline at end of file diff --git a/scenes/my_sample_light_rex_200_8.txt b/scenes/my_sample_light_rex_200_8.txt new file mode 100644 index 0000000..a3562cf --- /dev/null +++ b/scenes/my_sample_light_rex_200_8.txt @@ -0,0 +1,117 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.8 0.8 0.8 +EMITTANCE 8 +TYPE 1 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Specular reflection +MATERIAL 4 +RGB 0 0 0 +SPECRRGB .98 .98 .98 +SPECTRGB 0 0 0 +IOR 0.5 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 2 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 200 +DEPTH 8 +FILE my_scene_rex_8 +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +square +material 0 +TRANS 0 9.9 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Obj mesh +OBJECT 6 +mesh rex.obj +material 1 +TRANS -0.2 0.85 1.3 +ROTAT 0 -6 0 +SCALE 0.2 0.2 0.2 diff --git a/scenes/my_sample_light_rex_reflective_1024_400_5.txt b/scenes/my_sample_light_rex_reflective_1024_400_5.txt new file mode 100644 index 0000000..97a22d7 --- /dev/null +++ b/scenes/my_sample_light_rex_reflective_1024_400_5.txt @@ -0,0 +1,134 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.9 0.7 0.2 +EMITTANCE 8 +TYPE 1 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Specular reflection +MATERIAL 4 +RGB 0 0 0 +SPECRRGB .98 .98 .98 +SPECTRGB 0 0 0 +IOR 0.5 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 2 + +// Emissive material (light) +MATERIAL 5 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.2 0.7 0.9 +EMITTANCE 8 +TYPE 1 + +// Camera +CAMERA +RES 1024 1024 +FOVY 45 +ITERATIONS 400 +DEPTH 5 +FILE example +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + +// Ceiling light left +OBJECT 0 +square +material 0 +TRANS -2 9.9 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Obj mesh +OBJECT 6 +mesh rex.obj +material 4 +TRANS -0.2 0.85 1.3 +ROTAT 0 -6 0 +SCALE 0.2 0.2 0.2 + +// Ceiling light right +OBJECT 7 +square +material 5 +TRANS 2 9.9 0 +ROTAT 90 0 0 +SCALE 3 3 1 \ No newline at end of file diff --git a/scenes/my_sample_light_rex_reflective_200_8.txt b/scenes/my_sample_light_rex_reflective_200_8.txt new file mode 100644 index 0000000..a4c9999 --- /dev/null +++ b/scenes/my_sample_light_rex_reflective_200_8.txt @@ -0,0 +1,117 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.8 0.8 0.8 +EMITTANCE 8 +TYPE 1 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Specular reflection +MATERIAL 4 +RGB 0 0 0 +SPECRRGB .98 .98 .98 +SPECTRGB 0 0 0 +IOR 0.5 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 2 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 200 +DEPTH 8 +FILE my_scene_rex_r_8 +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +square +material 0 +TRANS 0 9.9 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Obj mesh +OBJECT 6 +mesh rex.obj +material 4 +TRANS -0.2 0.85 1.3 +ROTAT 0 -6 0 +SCALE 0.2 0.2 0.2 diff --git a/scenes/my_sample_light_two_200_64.txt b/scenes/my_sample_light_two_200_64.txt new file mode 100644 index 0000000..b02f549 --- /dev/null +++ b/scenes/my_sample_light_two_200_64.txt @@ -0,0 +1,141 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.9 0.7 0.2 +EMITTANCE 10 +TYPE 1 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Emissive cyan +MATERIAL 4 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.2 0.7 0.9 +EMITTANCE 10 +TYPE 1 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 200 +DEPTH 64 +FILE my_scene +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +//OBJECT 0 +//cube +//material 0 +//TRANS 0 10 0 +//ROTAT 0 0 0 +//SCALE 3 .3 3 + +// Square ceiling light 1 +OBJECT 0 +square +material 0 +TRANS -2 9.9 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Square ceiling light 2 +OBJECT 6 +square +material 4 +TRANS 2 9.9 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Short cube +OBJECT 7 +cube +material 1 +TRANS 2 1.5 -0.75 +ROTAT 0 -17.5 0 +SCALE 3 3 3 + +// Long cube +OBJECT 8 +cube +material 1 +TRANS -2 3 -3 +ROTAT 0 27.5 0 +SCALE 3 6 3 diff --git a/scenes/my_sample_light_two_200_8.txt b/scenes/my_sample_light_two_200_8.txt new file mode 100644 index 0000000..5862673 --- /dev/null +++ b/scenes/my_sample_light_two_200_8.txt @@ -0,0 +1,141 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.9 0.7 0.2 +EMITTANCE 10 +TYPE 1 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0 0 0 +EMITTANCE 0 +TYPE 0 + +// Emissive cyan +MATERIAL 4 +RGB 0 0 0 +SPECRRGB 0 0 0 +SPECTRGB 0 0 0 +IOR 0 +EMITRGB 0.2 0.7 0.9 +EMITTANCE 10 +TYPE 1 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 200 +DEPTH 8 +FILE my_scene_8 +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +//OBJECT 0 +//cube +//material 0 +//TRANS 0 10 0 +//ROTAT 0 0 0 +//SCALE 3 .3 3 + +// Square ceiling light 1 +OBJECT 0 +square +material 0 +TRANS -2 9.9 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Square ceiling light 2 +OBJECT 6 +square +material 4 +TRANS 2 9.9 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Short cube +OBJECT 7 +cube +material 1 +TRANS 2 1.5 -0.75 +ROTAT 0 -17.5 0 +SCALE 3 3 3 + +// Long cube +OBJECT 8 +cube +material 1 +TRANS -2 3 -3 +ROTAT 0 27.5 0 +SCALE 3 6 3 diff --git a/scenes/sphere.txt b/scenes/sphere.txt deleted file mode 100644 index a74b545..0000000 --- a/scenes/sphere.txt +++ /dev/null @@ -1,28 +0,0 @@ -// Emissive material (light) -MATERIAL 0 -RGB 1 1 1 -SPECEX 0 -SPECRGB 0 0 0 -REFL 0 -REFR 0 -REFRIOR 0 -EMITTANCE 5 - -// Camera -CAMERA -RES 800 800 -FOVY 45 -ITERATIONS 5000 -DEPTH 8 -FILE sphere -EYE 0.0 5 10.5 -LOOKAT 0 5 0 -UP 0 1 0 - -// Sphere -OBJECT 0 -sphere -material 0 -TRANS 0 0 0 -ROTAT 0 0 0 -SCALE 3 3 3 diff --git a/src/BVHKDTree.cpp b/src/BVHKDTree.cpp new file mode 100644 index 0000000..23e8e17 --- /dev/null +++ b/src/BVHKDTree.cpp @@ -0,0 +1,56 @@ +#include "BVHKDTree.h" + +BVHKDTree::BVHKDTree() +{ +} + +BVHKDTree::~BVHKDTree() +{ +} + +bool xSort(const Triangle& a, const Triangle& b) { return a.center.x < b.center.x; } +bool ySort(const Triangle& a, const Triangle& b) { return a.center.y < b.center.y; } +bool zSort(const Triangle& a, const Triangle& b) { return a.center.z < b.center.z; } + +//need to add an offset so that indices are for the one large triangle array for all the meshes in the scene instead of only one mesh +int BVHKDTree::buildTree(vector &triangles, const int axis, const int indexOffset, const int start, const int end, glm::vec3 *min, glm::vec3 *max)//[start, end] +{ + if (start>end) return -1; + //else if (start == end) return start + indexOffset; //because in this case we still want to set min and max + + int root = 0; + + switch (axis) + { + case 0: + sort(triangles.begin() + start, triangles.begin() + end + 1, xSort);//[start, end + 1) + break; + case 1: + sort(triangles.begin() + start, triangles.begin() + end + 1, ySort);//[start, end + 1) + break; + case 2: + sort(triangles.begin() + start, triangles.begin() + end + 1, zSort);//[start, end + 1) + break; + } + + int median = (start + end) / 2; + + root = median; + + glm::vec3 minL = triangles[root].min; + glm::vec3 minR = triangles[root].min; + glm::vec3 maxL = triangles[root].max; + glm::vec3 maxR = triangles[root].max; + //[start, start + median - 1] + triangles[root].leftIndex = buildTree(triangles, (axis + 1) % 3, indexOffset, start, median - 1, &minL, &maxL); + //[start + median + 1, start + end] + triangles[root].rightIndex = buildTree(triangles, (axis + 1) % 3, indexOffset, median + 1, end, &minR, &maxR); + + triangles[root].min = glm::min(triangles[root].min, glm::min(minL, minR)); + triangles[root].max = glm::max(triangles[root].max, glm::max(maxL, maxR)); + + if (min != nullptr) *min = triangles[root].min; + if (max != nullptr) *max = triangles[root].max; + + return root + indexOffset; +} diff --git a/src/BVHKDTree.h b/src/BVHKDTree.h new file mode 100644 index 0000000..9cd710b --- /dev/null +++ b/src/BVHKDTree.h @@ -0,0 +1,16 @@ +#pragma once + +#include "utilities.h" +#include "sceneStructs.h" +#include + +using namespace std; + +class BVHKDTree +{ +private: + BVHKDTree(); +public: + ~BVHKDTree(); + static int buildTree(vector &triangles, const int axis, const int indexOffset, const int start, const int end, glm::vec3* min = nullptr, glm::vec3* max = nullptr); +}; \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a1cb3fb..8167306 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,9 +15,13 @@ set(SOURCE_FILES "preview.cpp" "utilities.cpp" "utilities.h" + "tiny_obj_loader.cc" + "tiny_obj_loader.h" + "BVHKDTree.h" + "BVHKDTree.cpp" ) cuda_add_library(src ${SOURCE_FILES} - OPTIONS -arch=sm_20 + OPTIONS -arch=sm_61 -lineinfo #my code here ) diff --git a/src/intersections.h b/src/intersections.h index 6f23872..4e8aee9 100644 --- a/src/intersections.h +++ b/src/intersections.h @@ -6,6 +6,48 @@ #include "sceneStructs.h" #include "utilities.h" +#define MAX_STACK_SIZE 100 + +__host__ __device__ bool myEqual(float a, float b) +{ + if (glm::abs(a - b) < EPSILON) + return true; + else + return false; +} + +template +__host__ __device__ T barycentricInterpolate(const glm::vec3 &P, const glm::vec3 p[3], const T v[3]) +{ + float S = 0.5f * glm::length(glm::cross(p[0] - p[1], p[2] - p[1])); + float s0 = 0.5f * glm::length(glm::cross(p[1] - P, p[2] - P)) / S; + float s1 = 0.5f * glm::length(glm::cross(p[2] - P, p[0] - P)) / S; + float s2 = 0.5f * glm::length(glm::cross(p[0] - P, p[1] - P)) / S; + return v[0] * s0 + v[1] * s1 + v[2] * s2; +} + +//overload +template +__host__ __device__ T barycentricInterpolate(const glm::vec3 &P, const glm::vec3 p[3]) +{ + float S = 0.5f * glm::length(glm::cross(p[0] - p[1], p[2] - p[1])); + float s0 = 0.5f * glm::length(glm::cross(p[1] - P, p[2] - P)) / S; + float s1 = 0.5f * glm::length(glm::cross(p[2] - P, p[0] - P)) / S; + float s2 = 0.5f * glm::length(glm::cross(p[0] - P, p[1] - P)) / S; + return T() * s0 + T() * s1 + T() * s2; +} + +//full specialization of overload +template<> +__host__ __device__ float barycentricInterpolate(const glm::vec3 &P, const glm::vec3 p[3]) +{ + float S = 0.5f * glm::length(glm::cross(p[0] - p[1], p[2] - p[1])); + float s0 = 0.5f * glm::length(glm::cross(p[1] - P, p[2] - P)) / S; + float s1 = 0.5f * glm::length(glm::cross(p[2] - P, p[0] - P)) / S; + float s2 = 0.5f * glm::length(glm::cross(p[0] - P, p[1] - P)) / S; + return s0 + s1 + s2; +} + /** * Handy-dandy hash function that provides seeds for random number generation. */ @@ -35,6 +77,64 @@ __host__ __device__ glm::vec3 multiplyMV(glm::mat4 m, glm::vec4 v) { return glm::vec3(m * v); } +//My code here +__host__ __device__ void ComputeTBN(const glm::mat4 &T, const glm::mat4 &invTransT, glm::vec3 &t, glm::vec3 &b, glm::vec3 &n) +{ + glm::vec3 norL = glm::normalize(n); + glm::vec3 tanL(0,-1,0); + + if (glm::abs(glm::abs(glm::dot(norL, glm::vec3(1.0, 0, 0))) - 1.0) > 0.5)//local normal is not parallel to x axis// (norL != glm::vec3(1,0,0) && norL != glm::vec3(-1,0,0))// + tanL = glm::vec3(1.0, 0, 0); + else + tanL = glm::vec3(0, 1.0, 0); + + tanL = glm::normalize(glm::cross(norL, tanL)); + + n = glm::normalize(multiplyMV(invTransT, glm::vec4(norL, 0))); + t = glm::normalize(multiplyMV(T, glm::vec4(tanL, 0))); + b = glm::normalize(multiplyMV(T, glm::vec4(glm::cross(norL, tanL), 0))); +} + +//My code here +__host__ __device__ void ComputeTriangleTBNUVOutside(const glm::mat4 &T, const glm::mat4 &invTransT, Ray& r, const Triangle& triangle, const glm::vec3& P, glm::vec3 &t, glm::vec3 &b, glm::vec3 &n, glm::vec2 &uv, bool &outside) +{ + glm::vec3 normal = glm::normalize(barycentricInterpolate(P, triangle.v, triangle.n)); + uv = barycentricInterpolate(P, triangle.v, triangle.t); + outside = glm::dot(r.direction, triangle.planeNormal) > 0 ? false : true; + + ////calculate tangent and bitangent according to the uv axes direction of the triangle + //glm::vec2 deltaUV1 = triangle.t[1] - triangle.t[0]; + //glm::vec2 deltaUV2 = triangle.t[2] - triangle.t[0]; + //glm::vec3 deltaP1 = triangle.v[1] - triangle.v[0]; + //glm::vec3 deltaP2 = triangle.v[2] - triangle.v[0]; + + //glm::vec3 tangent = glm::normalize((deltaUV2.y * deltaP1 - deltaUV1.y * deltaP2) / (deltaUV2.y * deltaUV1.x - deltaUV1.y * deltaUV2.x)); + ////(deltaUV2.y * deltaUV1.x - deltaUV1.y * deltaUV2.x) can't be 0 + ////otherwise it will not be a triangle + ////but deltaUV2.y can + ////when deltaUV2.y is 0, deltaUV1.y can't be 0 + //glm::vec3 bitangent = glm::normalize(deltaUV2.y == 0 ? (deltaP1 - deltaUV1.x * t) / deltaUV1.y : (deltaP2 - deltaUV2.x * t) / deltaUV2.y); + + //n = glm::normalize(multiplyMV(invTransT, glm::vec4(normal, 0))); + //t = glm::normalize(multiplyMV(T, glm::vec4(tangent, 0))); + //b = glm::normalize(multiplyMV(T, glm::vec4(bitangent, 0))); + + //traditional way + glm::vec3 norL = normal; + glm::vec3 tanL(0, -1, 0); + + if (glm::abs(glm::abs(glm::dot(norL, glm::vec3(1.0, 0, 0))) - 1.0) > 0.5)//local normal is not parallel to x axis// (norL != glm::vec3(1,0,0) && norL != glm::vec3(-1,0,0))// + tanL = glm::vec3(1.0, 0, 0); + else + tanL = glm::vec3(0, 1.0, 0); + + tanL = glm::normalize(glm::cross(norL, tanL)); + + n = glm::normalize(multiplyMV(invTransT, glm::vec4(norL, 0))); + t = glm::normalize(multiplyMV(T, glm::vec4(tanL, 0))); + b = glm::normalize(multiplyMV(T, glm::vec4(glm::cross(norL, tanL), 0))); +} + // CHECKITOUT /** * Test intersection between a ray and a transformed cube. Untransformed, @@ -45,8 +145,10 @@ __host__ __device__ glm::vec3 multiplyMV(glm::mat4 m, glm::vec4 v) { * @param outside Output param for whether the ray came from outside. * @return Ray parameter `t` value. -1 if no intersection. */ -__host__ __device__ float boxIntersectionTest(Geom box, Ray r, - glm::vec3 &intersectionPoint, glm::vec3 &normal, bool &outside) { +__host__ __device__ float boxIntersectionTest(const Geom& box, const Ray& r, + glm::vec3 &intersectionPoint, glm::vec3 &normal, + glm::vec3 &tangent, glm::vec3 &bitangent,//My code here. + bool &outside) { Ray q; q.origin = multiplyMV(box.inverseTransform, glm::vec4(r.origin , 1.0f)); q.direction = glm::normalize(multiplyMV(box.inverseTransform, glm::vec4(r.direction, 0.0f))); @@ -75,20 +177,30 @@ __host__ __device__ float boxIntersectionTest(Geom box, Ray r, } } - if (tmax >= tmin && tmax > 0) { - outside = true; - if (tmin <= 0) { - tmin = tmax; - tmin_n = tmax_n; - outside = false; - } - intersectionPoint = multiplyMV(box.transform, glm::vec4(getPointOnRay(q, tmin), 1.0f)); - normal = glm::normalize(multiplyMV(box.transform, glm::vec4(tmin_n, 0.0f))); - return glm::length(r.origin - intersectionPoint); - } + if (tmax >= tmin && tmax > 0) { + outside = true; + if (tmin <= 0) { + tmin = tmax; + tmin_n = tmax_n; + outside = false; + } + + intersectionPoint = multiplyMV(box.transform, glm::vec4(getPointOnRay(q, tmin), 1.0f)); + //normal = glm::normalize(multiplyMV(box.transform, glm::vec4(tmin_n, 0.0f))); //what the hell? translating normal should always use invTransposeT + //My code here. + normal = tmin_n; + ComputeTBN(box.transform, box.invTranspose, tangent, bitangent, normal); + + //if (box.translation.y > 14) + // printf("%f,%f,%f--%f,%f,%f--%f,%f,%f\n", tangent.x, tangent.y, tangent.z, bitangent.x, bitangent.y, bitangent.z, normal.x, normal.y, normal.z); + + return glm::length(r.origin - intersectionPoint); + + } return -1; } + // CHECKITOUT /** * Test intersection between a ray and a transformed sphere. Untransformed, @@ -99,8 +211,10 @@ __host__ __device__ float boxIntersectionTest(Geom box, Ray r, * @param outside Output param for whether the ray came from outside. * @return Ray parameter `t` value. -1 if no intersection. */ -__host__ __device__ float sphereIntersectionTest(Geom sphere, Ray r, - glm::vec3 &intersectionPoint, glm::vec3 &normal, bool &outside) { +__host__ __device__ float sphereIntersectionTest(const Geom& sphere, const Ray& r, + glm::vec3 &intersectionPoint, glm::vec3 &normal, + glm::vec3 &tangent, glm::vec3 &bitangent,//My code here. + bool &outside) { float radius = .5; glm::vec3 ro = multiplyMV(sphere.inverseTransform, glm::vec4(r.origin, 1.0f)); @@ -135,10 +249,308 @@ __host__ __device__ float sphereIntersectionTest(Geom sphere, Ray r, glm::vec3 objspaceIntersection = getPointOnRay(rt, t); intersectionPoint = multiplyMV(sphere.transform, glm::vec4(objspaceIntersection, 1.f)); - normal = glm::normalize(multiplyMV(sphere.invTranspose, glm::vec4(objspaceIntersection, 0.f))); - if (!outside) { + //normal = glm::normalize(multiplyMV(sphere.invTranspose, glm::vec4(objspaceIntersection, 0.f))); + //My code here. + normal = objspaceIntersection; + if (!outside) { normal = -normal; } + ComputeTBN(sphere.transform, sphere.invTranspose, tangent, bitangent, normal); return glm::length(r.origin - intersectionPoint); } + + +__host__ __device__ float squareIntersectionTest(const Geom& square, const Ray& r, + glm::vec3 &intersectionPoint, glm::vec3 &normal, + glm::vec3 &tangent, glm::vec3 &bitangent,//My code here. + bool &outside) +{ + + float half_width = 0.5f; + + glm::vec3 origin = multiplyMV(square.inverseTransform, glm::vec4(r.origin, 1.0f)); + glm::vec3 direction = glm::normalize(multiplyMV(square.inverseTransform, glm::vec4(r.direction, 0.0f))); + + float t = -1; + glm::vec3 p; + + if (direction.z < 0) + { + normal = glm::vec3(0, 0, 1); + t = glm::dot(-origin, normal) / glm::dot(direction, normal); + } + else if (direction.z > 0) + { + normal = glm::vec3(0, 0, -1); + t = glm::dot(-origin, normal) / glm::dot(direction, normal); + } + else + { + return -1; + } + + if (t > 0) + { + p = origin + direction * t; + if (p.y >= -half_width && p.y <= half_width && p.x >= -half_width && p.x <= half_width) + { + outside = false; + intersectionPoint = multiplyMV(square.transform, glm::vec4(p, 1.f)); + ComputeTBN(square.transform, square.invTranspose, tangent, bitangent, normal); + } + else + { + return -1; + } + } + else + { + return -1; + } + + return glm::length(r.origin - intersectionPoint); +} + +__host__ __device__ bool aabbIntersectionTest(const Ray& r, const glm::vec3& minAABB, const glm::vec3& maxAABB) +{ + + const glm::vec3 &origin = r.origin; + const glm::vec3 &direction = r.direction; + + if (origin.xminAABB.x && origin.y>minAABB.y && origin.z>minAABB.z) + return true; + + if (direction.x < 0) + { + glm::vec3 normal(1, 0, 0); + float t = glm::dot(maxAABB - origin, normal) / glm::dot(direction, normal); + if (t > 0) + { + glm::vec3 p = origin + direction * t; + if (p.y >= minAABB.y&&p.y <= maxAABB.y&&p.z >= minAABB.z&&p.z <= maxAABB.z) + { + return true; + } + } + } + else if (direction.x > 0) + { + glm::vec3 normal(-1, 0, 0); + float t = glm::dot(minAABB - origin, normal) / glm::dot(direction, normal); + if (t > 0) + { + glm::vec3 p = origin + direction * t; + if (p.y >= minAABB.y&&p.y <= maxAABB.y&&p.z >= minAABB.z&&p.z <= maxAABB.z) + { + return true; + } + } + } + + if (direction.y < 0) + { + glm::vec3 normal(0, 1, 0); + float t = glm::dot(maxAABB - origin, normal) / glm::dot(direction, normal); + if (t > 0) + { + glm::vec3 p = origin + direction * t; + if (p.x >= minAABB.x&&p.x <= maxAABB.x&&p.z >= minAABB.z&&p.z <= maxAABB.z) + { + return true; + + } + } + + } + else if (direction.y > 0) + { + glm::vec3 normal(0, -1, 0); + float t = glm::dot(minAABB - origin, normal) / glm::dot(direction, normal); + if (t > 0) + { + glm::vec3 p = origin + direction * t; + if (p.x >= minAABB.x&&p.x <= maxAABB.x&&p.z >= minAABB.z&&p.z <= maxAABB.z) + { + return true; + } + } + } + + if (direction.z < 0) + { + glm::vec3 normal(0, 0, 1); + float t = glm::dot(maxAABB - origin, normal) / glm::dot(direction, normal); + if (t > 0) + { + glm::vec3 p = origin + direction * t; + if (p.y >= minAABB.y&&p.y <= maxAABB.y&&p.x >= minAABB.x&&p.x <= maxAABB.x) + { + return true; + } + } + } + else if (direction.z > 0) + { + glm::vec3 normal(0, 0, -1); + float t = glm::dot(minAABB - origin, normal) / glm::dot(direction, normal); + if (t > 0) + { + glm::vec3 p = origin + direction * t; + if (p.y >= minAABB.y&&p.y <= maxAABB.y&&p.x >= minAABB.x&&p.x <= maxAABB.x) + { + return true; + } + } + } + + return false; +} + +__host__ __device__ float triangleIntersectionTest(const Triangle& triangle, const Ray& r) +{ + //1. Ray-plane intersection + float t = glm::dot(triangle.planeNormal, (triangle.v[0] - r.origin)) / glm::dot(triangle.planeNormal, r.direction); + if (t < 0) return -1; + + //2. Barycentric test + glm::vec3 P = r.origin + t * r.direction; + float sum = barycentricInterpolate(P, triangle.v); + + if (myEqual(sum, 1.0f)) + { + return t; + } + + return -1; +} + +__host__ __device__ float nodeIntersectionTest(const Triangle * triangles, const int triangleIndex, const Ray& r, int* hitIndex) +{ + if (triangleIndex == -1) + return -1; + + const Triangle triangle = triangles[triangleIndex]; + + float t = -1, t_tmp = -1; + int hit = -1, hit_tmp = -1; + + //root + if (aabbIntersectionTest(r, triangle.min, triangle.max)) + { + + t = triangleIntersectionTest(triangle, r); + if (t > 0) + { + hit = triangleIndex; + } + + //left sub-tree + if (triangle.leftIndex != -1) + { + t_tmp = nodeIntersectionTest(triangles, triangle.leftIndex, r, &hit_tmp); + if ((t_tmp > 0 && t < 0) || (t_tmp > 0 && t > 0 && t_tmp < t)) + { + t = t_tmp; + hit = hit_tmp; + } + } + + //right sub-tree + if (triangle.rightIndex != -1) + { + t_tmp = nodeIntersectionTest(triangles, triangle.rightIndex, r, &hit_tmp); + if ((t_tmp > 0 && t < 0) || (t_tmp > 0 && t > 0 && t_tmp < t)) + { + t = t_tmp; + hit = hit_tmp; + } + } + + //printf("triangleIndex: %d, hit: %d, t: %f\n", triangleIndex, hit, t); + } + *hitIndex = hit; + return t; +} + +__host__ __device__ float nodeIntersectionTestLoop(const Triangle * triangles, const int triangleIndex, const Ray& r, int* hitIndex) +{ + if (triangleIndex == -1) + return -1; + + int stack[MAX_STACK_SIZE] = { -1 }; + stack[0] = triangleIndex; + int top = 0; + + float t = -1; + int hit = -1; + + while (top > -1) + { + int currentIndex = stack[top]; + Triangle current = triangles[currentIndex]; + if(aabbIntersectionTest(r, current.min, current.max)) + { + float t_tmp = triangleIntersectionTest(current, r); + top--; + if (t_tmp > 0 && ((t > 0 && t_tmp < t)||(t<0)))//t_tmp is valid and smaller than previous t_tmp, or t_tmp is valid and no previous t_tmp exists + { + hit = currentIndex; + t = t_tmp; + } + + if (current.leftIndex != -1 && top < MAX_STACK_SIZE - 1) + { + stack[++top] = current.leftIndex; + } + + if (current.rightIndex != -1 && top < MAX_STACK_SIZE - 1) + { + stack[++top] = current.rightIndex; + } + } + else + { + top--; + } + } + + *hitIndex = hit; + return t; +} + +__host__ __device__ float meshIntersectionTest(const Triangle * triangles, const Geom& mesh, const Ray& r, + glm::vec3 &intersectionPoint, glm::vec3 &normal, + glm::vec3 &tangent, glm::vec3 &bitangent, glm::vec2 &uv,//My code here. + bool &outside) { + + glm::vec3 ro = multiplyMV(mesh.inverseTransform, glm::vec4(r.origin, 1.0f)); + glm::vec3 rd = glm::normalize(multiplyMV(mesh.inverseTransform, glm::vec4(r.direction, 0.0f))); + + Ray rt; + rt.origin = ro; + rt.direction = rd; + + int hit = -1; + float t = nodeIntersectionTestLoop(triangles, mesh.root, rt, &hit); //nodeIntersectionTest(triangles, mesh.root, rt, &hit); + + if (t > 0)//hit + { + glm::vec3 objspaceIntersection = rt.origin + t * rt.direction; + //compute tangent, bitangent and normal for intersection, then transform them into world space + ComputeTriangleTBNUVOutside(mesh.transform, mesh.invTranspose, rt, triangles[hit], objspaceIntersection, tangent, bitangent, normal, uv, outside); + //printf("hit: %d, outside: %d\n", hit, outside); + if (!outside) + { + normal = -normal; + } + //convert ot world space + intersectionPoint = multiplyMV(mesh.transform, glm::vec4(objspaceIntersection, 1.f)); + return glm::length(r.origin - intersectionPoint); + } + else//not hit + { + return -1; + } +} diff --git a/src/main.cpp b/src/main.cpp index fe8e85e..e2edcc6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,11 @@ #include "preview.h" #include +#define MATERIAL_BATCHING_OPTION_COUNT 3 +#define INTEGRATOR_TYPE_COUNT 4 //0-niave, 1-direct-light, 2-direct-light-mis, 3-full light + static std::string startTimeString; +static std::string finishTimeString; // For camera controls static bool leftMousePressed = false; @@ -26,180 +30,222 @@ int iteration; int width; int height; +//My code here +static bool enable_stream_compact = false; +static int enable_material_batching = 0; +static bool enable_cache_first_path = false; +static int integrator_type = 0; + //------------------------------- //-------------MAIN-------------- //------------------------------- int main(int argc, char** argv) { - startTimeString = currentTimeString(); - - if (argc < 2) { - printf("Usage: %s SCENEFILE.txt\n", argv[0]); - return 1; - } - - const char *sceneFile = argv[1]; - - // Load scene file - scene = new Scene(sceneFile); - - // Set up camera stuff from loaded path tracer settings - iteration = 0; - renderState = &scene->state; - Camera &cam = renderState->camera; - width = cam.resolution.x; - height = cam.resolution.y; - - glm::vec3 view = cam.view; - glm::vec3 up = cam.up; - glm::vec3 right = glm::cross(view, up); - up = glm::cross(right, view); - - cameraPosition = cam.position; - - // compute phi (horizontal) and theta (vertical) relative 3D axis - // so, (0 0 1) is forward, (0 1 0) is up - glm::vec3 viewXZ = glm::vec3(view.x, 0.0f, view.z); - glm::vec3 viewZY = glm::vec3(0.0f, view.y, view.z); - phi = glm::acos(glm::dot(glm::normalize(viewXZ), glm::vec3(0, 0, -1))); - theta = glm::acos(glm::dot(glm::normalize(viewZY), glm::vec3(0, 1, 0))); - ogLookAt = cam.lookAt; - zoom = glm::length(cam.position - ogLookAt); - - // Initialize CUDA and GL components - init(); - - // GLFW main loop - mainLoop(); - - return 0; + startTimeString = currentTimeString(); + + char *sceneFile = "../scenes/my_sample_light_rex_reflective_1024_400_5.txt";//default scene relative to lauching directory which is ".....\build\" + + if (argc == 2) { + //printf("Usage: %s SCENEFILE.txt\n", argv[0]); + //return 1; + sceneFile = argv[1]; + } + else + { + printf("Usage: %s SCENEFILE.txt\n", argv[0]); + } + + // Load scene file + scene = new Scene(sceneFile); + + // Set up camera stuff from loaded path tracer settings + iteration = 0; + renderState = &scene->state; + Camera &cam = renderState->camera; + width = cam.resolution.x; + height = cam.resolution.y; + + glm::vec3 view = cam.view; + glm::vec3 up = cam.up; + glm::vec3 right = glm::cross(view, up); + up = glm::cross(right, view); + + cameraPosition = cam.position; + + // compute phi (horizontal) and theta (vertical) relative 3D axis + // so, (0 0 1) is forward, (0 1 0) is up + glm::vec3 viewXZ = glm::vec3(view.x, 0.0f, view.z); + glm::vec3 viewZY = glm::vec3(0.0f, view.y, view.z); + phi = glm::acos(glm::dot(glm::normalize(viewXZ), glm::vec3(0, 0, -1))); + theta = glm::acos(glm::dot(glm::normalize(viewZY), glm::vec3(0, 1, 0))); + ogLookAt = cam.lookAt; + zoom = glm::length(cam.position - ogLookAt); + + // Initialize CUDA and GL components + init(); + + // GLFW main loop + mainLoop(); + + return 0; } void saveImage() { - float samples = iteration; - // output image file - image img(width, height); - - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - int index = x + (y * width); - glm::vec3 pix = renderState->image[index]; - img.setPixel(width - 1 - x, y, glm::vec3(pix) / samples); - } - } - - std::string filename = renderState->imageName; - std::ostringstream ss; - ss << filename << "." << startTimeString << "." << samples << "samp"; - filename = ss.str(); - - // CHECKITOUT - img.savePNG(filename); - //img.saveHDR(filename); // Save a Radiance HDR file + float samples = iteration; + // output image file + image img(width, height); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int index = x + (y * width); + glm::vec3 pix = renderState->image[index]; + img.setPixel(width - 1 - x, y, glm::vec3(pix) / samples); + } + } + + std::string filename = renderState->imageName; + std::ostringstream ss; + ss << filename << "." << startTimeString << "." << finishTimeString << "." << samples << "_spp.integrator" << integrator_type << "_compact" << enable_stream_compact << "_batch" << enable_material_batching << "_cache" << enable_cache_first_path; + filename = ss.str(); + + // CHECKITOUT + img.savePNG(filename); + //img.saveHDR(filename); // Save a Radiance HDR file } void runCuda() { - if (camchanged) { - iteration = 0; - Camera &cam = renderState->camera; - cameraPosition.x = zoom * sin(phi) * sin(theta); - cameraPosition.y = zoom * cos(theta); - cameraPosition.z = zoom * cos(phi) * sin(theta); - - cam.view = -glm::normalize(cameraPosition); - glm::vec3 v = cam.view; - glm::vec3 u = glm::vec3(0, 1, 0);//glm::normalize(cam.up); - glm::vec3 r = glm::cross(v, u); - cam.up = glm::cross(r, v); - cam.right = r; - - cam.position = cameraPosition; - cameraPosition += cam.lookAt; - cam.position = cameraPosition; - camchanged = false; - } - - // Map OpenGL buffer object for writing from CUDA on a single GPU - // No data is moved (Win & Linux). When mapped to CUDA, OpenGL should not use this buffer - - if (iteration == 0) { - pathtraceFree(); - pathtraceInit(scene); - } - - if (iteration < renderState->iterations) { - uchar4 *pbo_dptr = NULL; - iteration++; - cudaGLMapBufferObject((void**)&pbo_dptr, pbo); - - // execute the kernel - int frame = 0; - pathtrace(pbo_dptr, frame, iteration); - - // unmap buffer object - cudaGLUnmapBufferObject(pbo); - } else { - saveImage(); - pathtraceFree(); - cudaDeviceReset(); - exit(EXIT_SUCCESS); - } + if (camchanged) { + iteration = 0; + Camera &cam = renderState->camera; + cameraPosition.x = zoom * sin(phi) * sin(theta); + cameraPosition.y = zoom * cos(theta); + cameraPosition.z = zoom * cos(phi) * sin(theta); + + cam.view = -glm::normalize(cameraPosition); + glm::vec3 v = cam.view; + glm::vec3 u = glm::vec3(0, 1, 0);//glm::normalize(cam.up); + glm::vec3 r = glm::cross(v, u); + cam.up = glm::cross(r, v); + cam.right = r; + + cam.position = cameraPosition; + cameraPosition += cam.lookAt; + cam.position = cameraPosition; + camchanged = false; + } + + // Map OpenGL buffer object for writing from CUDA on a single GPU + // No data is moved (Win & Linux). When mapped to CUDA, OpenGL should not use this buffer + + if (iteration == 0) { + startTimeString = currentTimeString();//My code here + pathtraceFree(); + pathtraceInit(scene); + } + + if (iteration < renderState->iterations) { + uchar4 *pbo_dptr = NULL; + iteration++; + cudaGLMapBufferObject((void**)&pbo_dptr, pbo); + + // execute the kernel + int frame = 0; + pathtrace(pbo_dptr, + frame, iteration, + enable_stream_compact, + enable_material_batching, + enable_cache_first_path, + integrator_type); + + // unmap buffer object + cudaGLUnmapBufferObject(pbo); + } + else if (iteration == renderState->iterations) { + //My code here. + //saveImage(); + finishTimeString = currentTimeString(); + iteration++; + //pathtraceFree(); + //cudaDeviceReset(); + //exit(EXIT_SUCCESS); + } + else + { + //do nothing + } } void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (action == GLFW_PRESS) { - switch (key) { - case GLFW_KEY_ESCAPE: - saveImage(); - glfwSetWindowShouldClose(window, GL_TRUE); - break; - case GLFW_KEY_S: - saveImage(); - break; - case GLFW_KEY_SPACE: - camchanged = true; - renderState = &scene->state; - Camera &cam = renderState->camera; - cam.lookAt = ogLookAt; - break; - } - } + if (action == GLFW_PRESS) { + switch (key) { + case GLFW_KEY_Q: + enable_stream_compact = !enable_stream_compact; + printf("enable_stream_compact: %d\n", enable_stream_compact); + break; + case GLFW_KEY_W: + enable_material_batching = (enable_material_batching+1) % MATERIAL_BATCHING_OPTION_COUNT; + printf("enable_material_batching: %d\n", enable_material_batching); + break; + case GLFW_KEY_E: + enable_cache_first_path = !enable_cache_first_path; + printf("enable_cache_first_path: %d\n", enable_cache_first_path); + break; + case GLFW_KEY_R: + integrator_type = (integrator_type + 1) % INTEGRATOR_TYPE_COUNT; + printf("integrator_type: %d\n", integrator_type); + iteration = 0;//restart the rendering + break; + case GLFW_KEY_ESCAPE: + saveImage(); + glfwSetWindowShouldClose(window, GL_TRUE); + break; + case GLFW_KEY_S: + saveImage(); + break; + case GLFW_KEY_SPACE: + camchanged = true; + renderState = &scene->state; + Camera &cam = renderState->camera; + cam.lookAt = ogLookAt; + break; + } + } } void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { - leftMousePressed = (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS); - rightMousePressed = (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS); - middleMousePressed = (button == GLFW_MOUSE_BUTTON_MIDDLE && action == GLFW_PRESS); + leftMousePressed = (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS); + rightMousePressed = (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS); + middleMousePressed = (button == GLFW_MOUSE_BUTTON_MIDDLE && action == GLFW_PRESS); } void mousePositionCallback(GLFWwindow* window, double xpos, double ypos) { - if (xpos == lastX || ypos == lastY) return; // otherwise, clicking back into window causes re-start - if (leftMousePressed) { - // compute new camera parameters - phi -= (xpos - lastX) / width; - theta -= (ypos - lastY) / height; - theta = std::fmax(0.001f, std::fmin(theta, PI)); - camchanged = true; - } - else if (rightMousePressed) { - zoom += (ypos - lastY) / height; - zoom = std::fmax(0.1f, zoom); - camchanged = true; - } - else if (middleMousePressed) { - renderState = &scene->state; - Camera &cam = renderState->camera; - glm::vec3 forward = cam.view; - forward.y = 0.0f; - forward = glm::normalize(forward); - glm::vec3 right = cam.right; - right.y = 0.0f; - right = glm::normalize(right); - - cam.lookAt -= (float) (xpos - lastX) * right * 0.01f; - cam.lookAt += (float) (ypos - lastY) * forward * 0.01f; - camchanged = true; - } - lastX = xpos; - lastY = ypos; + if (xpos == lastX || ypos == lastY) return; // otherwise, clicking back into window causes re-start + if (leftMousePressed) { + // compute new camera parameters + phi -= (xpos - lastX) / width; + theta -= (ypos - lastY) / height; + theta = std::fmax(0.001f, std::fmin(theta, PI)); + camchanged = true; + } + else if (rightMousePressed) { + zoom += (ypos - lastY) / height; + zoom = std::fmax(0.1f, zoom); + camchanged = true; + } + else if (middleMousePressed) { + renderState = &scene->state; + Camera &cam = renderState->camera; + glm::vec3 forward = cam.view; + forward.y = 0.0f; + forward = glm::normalize(forward); + glm::vec3 right = cam.right; + right.y = 0.0f; + right = glm::normalize(right); + + cam.lookAt -= (float)(xpos - lastX) * right * 0.01f; + cam.lookAt += (float)(ypos - lastY) * forward * 0.01f; + camchanged = true; + } + lastX = xpos; + lastY = ypos; } diff --git a/src/pathtrace.cu b/src/pathtrace.cu index c1ec122..e459ac0 100644 --- a/src/pathtrace.cu +++ b/src/pathtrace.cu @@ -4,6 +4,7 @@ #include #include #include +#include #include "sceneStructs.h" #include "scene.h" @@ -15,56 +16,542 @@ #include "interactions.h" #define ERRORCHECK 1 - -#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) +#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + void checkCUDAErrorFn(const char *msg, const char *file, int line) { #if ERRORCHECK - cudaDeviceSynchronize(); - cudaError_t err = cudaGetLastError(); - if (cudaSuccess == err) { - return; - } - - fprintf(stderr, "CUDA error"); - if (file) { - fprintf(stderr, " (%s:%d)", file, line); - } - fprintf(stderr, ": %s: %s\n", msg, cudaGetErrorString(err)); + cudaDeviceSynchronize(); + cudaError_t err = cudaGetLastError(); + if (cudaSuccess == err) { + return; + } + + fprintf(stderr, "CUDA error"); + if (file) { + fprintf(stderr, " (%s:%d)", file, line); + } + fprintf(stderr, ": %s: %s\n", msg, cudaGetErrorString(err)); # ifdef _WIN32 - getchar(); + getchar(); # endif - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); #endif } +//My code here +unsigned int myHash(unsigned int a) { + a = (a + 0x7ed55d16) + (a << 12); + a = (a ^ 0xc761c23c) ^ (a >> 19); + a = (a + 0x165667b1) + (a << 5); + a = (a + 0xd3a2646c) ^ (a << 9); + a = (a + 0xfd7046c5) + (a << 3); + a = (a ^ 0xb55a4f09) ^ (a >> 16); + return a; +} + +//My code here +void debugNodeIntersectionTest(int times, const Triangle * triangles, const int rootIndex) +{ + printf("debug node intersection test start!\n"); + + for (int i = 0; i < times; i++) + { + Ray r; + int hitIndex; + int h = myHash((1 << 31) | (i << 22) | i) ^ myHash(i); + thrust::default_random_engine rng = thrust::default_random_engine(h); + thrust::uniform_real_distribution u01(0.f, 1.f); + + r.origin = glm::vec3(u01(rng), u01(rng), u01(rng)); + r.direction = glm::normalize(glm::vec3(u01(rng), u01(rng), u01(rng))); + + nodeIntersectionTest(triangles, rootIndex, r, &hitIndex); + printf("ray %d: %d\n", i, hitIndex); + } + + printf("debug node intersection test done!\n"); +} + + +//My code here +__host__ __device__ float PowerHeuristic(int nf, float fPdf, int ng, float gPdf) +{ + //TODO + float f = nf * fPdf, g = ng * gPdf; + return (f*f) / (f*f + g * g); +} + +//My code here +__host__ __device__ bool operator<(const ShadeableIntersection& lhs, const ShadeableIntersection& rhs) +{ + return lhs.materialId < rhs.materialId; +} + +//My code here +__host__ __device__ bool isNan(const glm::vec3& v) +{ + if (glm::isnan(v.x) || glm::isnan(v.y) || glm::isnan(v.z)) + return true; + else + return false; +} + +//My code here +__host__ __device__ bool isNan(float v) +{ + if (glm::isnan(v)) + return true; + else + return false; +} + +//My code here +__host__ __device__ bool isZero(const glm::vec3& v) +{ + if (glm::abs(v.x) < EPSILON && glm::abs(v.y) < EPSILON && glm::abs(v.z) < EPSILON) + return true; + else + return false; +} + +//My code here +__host__ __device__ bool isZero(float v) +{ + if (glm::abs(v) < EPSILON) + return true; + else + return false; +} + +//My code here +__host__ __device__ glm::vec2 squareToDiskUniform(const glm::vec2 &sample) +{ + return glm::sqrt(sample.y) * glm::vec2(glm::cos(2.f*glm::pi()*sample.x), glm::sin(2.f*glm::pi()*sample.x)); +} + +//My code here +__host__ __device__ glm::vec2 squareToDiskConcentric(const glm::vec2 &sample) +{ + //TODO + float phi = 0; + float a = 2.f*sample.x - 1.f; + float b = 2.f*sample.y - 1.f; + float r = 0; + + if (a > -b) + { + if (a > b) + { + r = a; + phi = (glm::pi() / 4.f) * (b / a); + } + else + { + r = b; + phi = (glm::pi() / 4.f) * (2.f - a / b); + } + } + else + { + if (a < b) + { + r = -a; + phi = (glm::pi() / 4.f) * (4.f + (b / a)); + } + else + { + r = -b; + if (b != 0) + phi = (glm::pi() / 4.f) * (6.f - (a / b)); + else + phi = 0; + } + } + return glm::vec2(r*glm::cos(phi), r*glm::sin(phi)); +} + +//My code here +__host__ __device__ glm::vec3 squareToHemisphereCosine(const glm::vec2 &sample) +{ + glm::vec2 temp = squareToDiskConcentric(sample);// squareToDiskUniform(sample);// + float z = glm::sqrt(1 - temp.x*temp.x - temp.y*temp.y); + return glm::vec3(temp.x, temp.y, z); +} + +//My code here +__host__ __device__ float squareToHemisphereCosinePDF(const glm::vec3 &sample) +{ + return glm::abs(glm::dot(sample, glm::vec3(0, 0, 1))) / glm::pi(); +} + +//My code here +__global__ void kernelMapToBooleanAndGather(int n, glm::vec3* image, int* dev_paths_exist, const PathSegment* dev_paths) +{ + int index = blockIdx.x * blockDim.x + threadIdx.x; + if (index >= n) return; + + if (dev_paths[index].remainingBounces > 0) + { + dev_paths_exist[index] = 1; + } + else + { + dev_paths_exist[index] = 0; + image[dev_paths[index].pixelIndex] += dev_paths[index].color; + } +} + +//My code here +__global__ void kernelScatter(int n, PathSegment* dev_paths_out, const PathSegment* dev_paths_in, const int* dev_paths_exist, const int* dev_paths_indices) +{ + int index = blockIdx.x * blockDim.x + threadIdx.x; + if (index >= n) return; + + if (dev_paths_exist[index]) + { + dev_paths_out[dev_paths_indices[index] - 1] = dev_paths_in[index];//-1 because we are using inclusive scan + } + +} + +//My code here +__host__ __device__ void SampleMaterialOld(PathSegment &pathSegment, const ShadeableIntersection &intersection, const Material &material, const glm::vec2 &xi) +{ + glm::vec3 Li = pathSegment.color; + float pdf = 0; + glm::vec3 wi(0, 0, 0); + glm::vec3 p(0, 0, 0); + + glm::mat3 tangentToWorld(intersection.surfaceTangent, intersection.surfaceBitangent, intersection.surfaceNormal); + + + if (material.type == 0) //diffuse + { + glm::vec3 color = material.color / glm::pi(); + wi = squareToHemisphereCosine(xi); + pdf = squareToHemisphereCosinePDF(wi);//take the pdf before convert to world coord + wi = tangentToWorld * wi; + wi = glm::normalize(wi); + Li = color * Li / pdf * glm::abs(glm::dot(wi, intersection.surfaceNormal)); + p = intersection.point + MY_OFFSET * intersection.surfaceNormal; + } + else if (material.type == 1)//emissive + { + glm::vec3 color = material.emissive.color * material.emissive.emittance; + Li = color * Li; + pathSegment.hitLight = true;//early termination, mark it so that result won't be set to black + pathSegment.remainingBounces = 0; + p = intersection.point + MY_OFFSET * intersection.surfaceNormal; + } + else if (material.type == 2)//specular reflection + { + glm::vec3 color = material.specularReflective.color / glm::abs(glm::dot(-pathSegment.ray.direction, intersection.surfaceNormal)); + wi = glm::reflect(pathSegment.ray.direction, intersection.surfaceNormal); + wi = glm::normalize(wi); + pdf = 1; + Li = color * Li / pdf * glm::abs(glm::dot(wi, intersection.surfaceNormal)); + p = intersection.point + MY_OFFSET * intersection.surfaceNormal; + } + else if (material.type == 3)//specular refraction + { + glm::vec3 color = material.specularTransmissive.color / glm::abs(glm::dot(-pathSegment.ray.direction, intersection.surfaceNormal)); + wi = intersection.outside ? + glm::refract(pathSegment.ray.direction, intersection.surfaceNormal, material.specularTransmissive.indexOfRefraction) : + glm::refract(pathSegment.ray.direction, intersection.surfaceNormal, 1.f / material.specularTransmissive.indexOfRefraction); + if (isZero(wi)) + { + pathSegment.remainingBounces = 0; + } + wi = glm::normalize(wi); + pdf = 1; + Li = color * Li / pdf * glm::abs(glm::dot(wi, -intersection.surfaceNormal));//when refract, normal is on the opposite side of wi + p = intersection.point - MY_OFFSET * intersection.surfaceNormal;//when refract, normal is on the opposite side of wi + } + + pathSegment.color = Li; + pathSegment.ray.origin = p; + pathSegment.ray.direction = wi; +} + +//My code here +__host__ __device__ glm::vec3 SampleMaterial(const Ray& ray, const ShadeableIntersection &intersection, const Material &material, const glm::vec2 &xi, glm::vec3 &wi, float *pdf) +{ + glm::vec3 color_tmp(0, 0, 0); + float pdf_tmp = 0; + glm::vec3 wi_tmp(0, 0, 0); + + glm::mat3 tangentToWorld(intersection.surfaceTangent, intersection.surfaceBitangent, intersection.surfaceNormal); + + if (material.type == 0) //diffuse + { + color_tmp = material.color / glm::pi(); + wi_tmp = squareToHemisphereCosine(xi); + pdf_tmp = squareToHemisphereCosinePDF(wi_tmp);//take the pdf before convert to world coord + wi_tmp = tangentToWorld * wi_tmp; + } + else if (material.type == 1)//emissive + { + color_tmp = material.emissive.color * material.emissive.emittance; + } + else if (material.type == 2)//specular reflection + { + color_tmp = material.specularReflective.color / glm::abs(glm::dot(-ray.direction, intersection.surfaceNormal)); + wi_tmp = glm::reflect(ray.direction, intersection.surfaceNormal); + pdf_tmp = 1; + } + else if (material.type == 3)//specular refraction + { + color_tmp = material.specularTransmissive.color / glm::abs(glm::dot(-ray.direction, intersection.surfaceNormal)); + wi_tmp = intersection.outside ? + glm::refract(ray.direction, intersection.surfaceNormal, material.specularTransmissive.indexOfRefraction) : + glm::refract(ray.direction, intersection.surfaceNormal, 1.f / material.specularTransmissive.indexOfRefraction); + if (isZero(wi_tmp))//total internal refraction + { + color_tmp = glm::vec3(0, 0, 0); + } + pdf_tmp = 1; + } + + wi = glm::normalize(wi_tmp); + if(pdf!=nullptr) *pdf = pdf_tmp; + return color_tmp; +} + +//My code here +__host__ __device__ glm::vec3 NotSampleMaterial(const ShadeableIntersection &intersection, const Material &material, const glm::vec3 &wi, float *pdf) +{ + glm::vec3 color_tmp(0, 0, 0); + float pdf_tmp = 0; + + glm::mat3 tangentToWorld(intersection.surfaceTangent, intersection.surfaceBitangent, intersection.surfaceNormal); + glm::mat3 worldToTangent = glm::inverse(tangentToWorld); + glm::vec3 wiL = glm::normalize(worldToTangent * wi); + + if (material.type == 0) //diffuse + { + color_tmp = material.color / glm::pi(); + pdf_tmp = squareToHemisphereCosinePDF(wiL); + } + else if (material.type == 1)//emissive + { + color_tmp = material.emissive.color * material.emissive.emittance; + } + else if (material.type == 2)//specular reflection + { + //we are samping light not material, so this will not happen + color_tmp = glm::vec3(0, 0, 0); + pdf_tmp = 0; + } + else if (material.type == 3)//specular refraction + { + //we are samping light not material, so this will not happen + color_tmp = glm::vec3(0, 0, 0); + pdf_tmp = 0; + } + + if (pdf != nullptr) *pdf = pdf_tmp; + return color_tmp; +} + +//My code here +__host__ __device__ bool ShadowFeeler(const Ray& r, const Geom * geoms, int geomsSize, const Triangle * triangles, int * hitGeomId) +{ + float t = -1; + float t_min = FLT_MAX; + int hit_geom_index = -1; + + glm::vec3 tmp_intersect; + glm::vec3 tmp_normal; + glm::vec3 tmp_tangent; + glm::vec3 tmp_bitangent; + bool tmp_outside; + glm::vec2 tmp_uv; + + for (int i = 0; i < geomsSize; i++) + { + Geom geom = geoms[i]; + + if (geom.type == CUBE) + { + t = boxIntersectionTest(geom, r, tmp_intersect, tmp_normal, tmp_tangent, tmp_bitangent, tmp_outside); + } + else if (geom.type == SPHERE) + { + t = sphereIntersectionTest(geom, r, tmp_intersect, tmp_normal, tmp_tangent, tmp_bitangent, tmp_outside); + } + else if (geom.type == MESH) + { + t = meshIntersectionTest(triangles, geom, r, tmp_intersect, tmp_normal, tmp_tangent, tmp_bitangent, tmp_uv, tmp_outside); + } + else if (geom.type == SQUARE) + { + t = squareIntersectionTest(geom, r, tmp_intersect, tmp_normal, tmp_tangent, tmp_bitangent, tmp_outside); + } + + if (t > 0.0f && t_min > t) + { + t_min = t; + hit_geom_index = i; + } + } + + if (hit_geom_index != -1) + { + if (hitGeomId != nullptr) + { + *hitGeomId = hit_geom_index; + } + return true; + } + + return false; +} + +//My code here +__host__ __device__ glm::vec3 SampleLight(const Geom &light, const ShadeableIntersection &intersection, const Material &lightMaterial, const glm::vec2 xi, glm::vec3 &wi, float *pdf) +{ + glm::vec3 p_tmp(0, 0, 0); + glm::vec3 n_tmp(0, 0, 0); + float pdf_tmp = 1; + glm::vec3 result(0, 0, 0); + + if (light.type == SQUARE) + { + glm::vec4 plocal((xi.x - 0.5), (xi.y - 0.5), 0, 1); + glm::vec4 nlocal(0, 0, 1, 0);//this direction could also be 0,0,-1 but it does not matter in this case + + p_tmp = multiplyMV(light.transform, plocal); + n_tmp = glm::normalize(multiplyMV(light.invTranspose, nlocal)); + + float area = glm::abs(light.scale.x * light.scale.y); + pdf_tmp = 1.0 / area; + } + else if (light.type == CUBE) + { + // do nothing + } + else if (light.type == SPHERE) + { + // do nothing + } + else if (light.type == MESH) + { + // do nothing + } + + float absDot = glm::abs(glm::dot(n_tmp, glm::normalize(intersection.point - p_tmp))); + + wi = glm::normalize(p_tmp - intersection.point);//from material to light + + if (isZero(absDot) || isNan(absDot)) + { + result = glm::vec3(0, 0, 0); + if (pdf != nullptr) *pdf = 0; + } + else + { + result = lightMaterial.emissive.emittance * lightMaterial.emissive.color; + //solid angle + float distance = glm::length(p_tmp - intersection.point); + if (pdf !=nullptr) *pdf = distance * distance * pdf_tmp / absDot; + } + + return result; +} + +//My code here +__host__ __device__ glm::vec3 NotSampleLight(const Geom &light, const ShadeableIntersection &intersection, const Material &lightMaterial, const glm::vec3 &wi, float *pdf) +{ + //glm::mat3 tangentToWorld(intersection.surfaceTangent, intersection.surfaceBitangent, intersection.surfaceNormal); + glm::vec3 p_tmp(0, 0, 0); + glm::vec3 n_tmp(0, 0, 0); + float pdf_tmp = 1; + glm::vec3 result(0, 0, 0); + float t_tmp = -1; + Ray tmp_r; + tmp_r.origin = intersection.point; + tmp_r.direction = wi; + glm::vec3 tmp_intersect; + glm::vec3 tmp_normal; + glm::vec3 tmp_tangent; + glm::vec3 tmp_bitangent; + bool tmp_outside; + + if (light.type == SQUARE) + { + t_tmp = squareIntersectionTest(light, tmp_r, tmp_intersect, tmp_normal, tmp_tangent, tmp_bitangent, tmp_outside); + + if (t_tmp > 0.0f) + { + p_tmp = tmp_intersect; + } + + glm::vec4 nlocal(0, 0, 1, 0); + + n_tmp = glm::normalize(multiplyMV(light.invTranspose, nlocal));//this direction could also be 0,0,-1 but it does not matter in this case + + float area = glm::abs(light.scale.x * light.scale.y); + pdf_tmp = 1.0 / area; + } + else if (light.type == CUBE) + { + // do nothing + } + else if (light.type == SPHERE) + { + // do nothing + } + else if (light.type == MESH) + { + // do nothing + } + + float absDot = glm::abs(glm::dot(n_tmp, glm::normalize(intersection.point - p_tmp))); + + if (isZero(absDot) || isNan(absDot)) + { + result = glm::vec3(0, 0, 0); + if (pdf != nullptr) *pdf = 0; + } + else + { + result = lightMaterial.emissive.emittance * lightMaterial.emissive.color; + //solid angle + float distance = glm::length(p_tmp - intersection.point); + if (pdf != nullptr) *pdf = distance * distance * pdf_tmp / absDot; + } + + return result; +} + __host__ __device__ thrust::default_random_engine makeSeededRandomEngine(int iter, int index, int depth) { - int h = utilhash((1 << 31) | (depth << 22) | iter) ^ utilhash(index); - return thrust::default_random_engine(h); + int h = utilhash((1 << 31) | (depth << 22) | iter) ^ utilhash(index); + return thrust::default_random_engine(h); } //Kernel that writes the image to the OpenGL PBO directly. __global__ void sendImageToPBO(uchar4* pbo, glm::ivec2 resolution, - int iter, glm::vec3* image) { - int x = (blockIdx.x * blockDim.x) + threadIdx.x; - int y = (blockIdx.y * blockDim.y) + threadIdx.y; + int iter, glm::vec3* image) { + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; - if (x < resolution.x && y < resolution.y) { - int index = x + (y * resolution.x); - glm::vec3 pix = image[index]; + if (x < resolution.x && y < resolution.y) { + int index = x + (y * resolution.x); + glm::vec3 pix = image[index]; - glm::ivec3 color; - color.x = glm::clamp((int) (pix.x / iter * 255.0), 0, 255); - color.y = glm::clamp((int) (pix.y / iter * 255.0), 0, 255); - color.z = glm::clamp((int) (pix.z / iter * 255.0), 0, 255); + glm::ivec3 color; + color.x = glm::clamp((int)(pix.x / iter * 255.0), 0, 255); + color.y = glm::clamp((int)(pix.y / iter * 255.0), 0, 255); + color.z = glm::clamp((int)(pix.z / iter * 255.0), 0, 255); - // Each thread writes one pixel location in the texture (textel) - pbo[index].w = 0; - pbo[index].x = color.x; - pbo[index].y = color.y; - pbo[index].z = color.z; - } + // Each thread writes one pixel location in the texture (textel) + pbo[index].w = 0; + pbo[index].x = color.x; + pbo[index].y = color.y; + pbo[index].z = color.z; + } } static Scene * hst_scene = NULL; @@ -75,40 +562,69 @@ static PathSegment * dev_paths = NULL; static ShadeableIntersection * dev_intersections = NULL; // TODO: static variables for device memory, any extra info you need, etc // ... +static int* dev_paths_exist = NULL; +static PathSegment * dev_paths_temp = NULL; +static int* dev_paths_indices = NULL; +static int* dev_intersections_material_id = NULL; +static PathSegment * dev_paths_cache = NULL; +static ShadeableIntersection * dev_intersections_cache = NULL; +static bool paths_cached = false; +static Triangle * dev_triangles = NULL; +static Geom * dev_lights = NULL; void pathtraceInit(Scene *scene) { - hst_scene = scene; - const Camera &cam = hst_scene->state.camera; - const int pixelcount = cam.resolution.x * cam.resolution.y; + hst_scene = scene; + const Camera &cam = hst_scene->state.camera; + const int pixelcount = cam.resolution.x * cam.resolution.y; + + cudaMalloc(&dev_image, pixelcount * sizeof(glm::vec3)); + cudaMemset(dev_image, 0, pixelcount * sizeof(glm::vec3)); - cudaMalloc(&dev_image, pixelcount * sizeof(glm::vec3)); - cudaMemset(dev_image, 0, pixelcount * sizeof(glm::vec3)); + cudaMalloc(&dev_paths, pixelcount * sizeof(PathSegment)); - cudaMalloc(&dev_paths, pixelcount * sizeof(PathSegment)); + cudaMalloc(&dev_geoms, scene->geoms.size() * sizeof(Geom)); + cudaMemcpy(dev_geoms, scene->geoms.data(), scene->geoms.size() * sizeof(Geom), cudaMemcpyHostToDevice); - cudaMalloc(&dev_geoms, scene->geoms.size() * sizeof(Geom)); - cudaMemcpy(dev_geoms, scene->geoms.data(), scene->geoms.size() * sizeof(Geom), cudaMemcpyHostToDevice); + cudaMalloc(&dev_materials, scene->materials.size() * sizeof(Material)); + cudaMemcpy(dev_materials, scene->materials.data(), scene->materials.size() * sizeof(Material), cudaMemcpyHostToDevice); - cudaMalloc(&dev_materials, scene->materials.size() * sizeof(Material)); - cudaMemcpy(dev_materials, scene->materials.data(), scene->materials.size() * sizeof(Material), cudaMemcpyHostToDevice); + cudaMalloc(&dev_intersections, pixelcount * sizeof(ShadeableIntersection)); + cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); - cudaMalloc(&dev_intersections, pixelcount * sizeof(ShadeableIntersection)); - cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); + // TODO: initialize any extra device memeory you need - // TODO: initialize any extra device memeory you need + cudaMalloc(&dev_paths_exist, pixelcount * sizeof(int)); + cudaMalloc(&dev_paths_temp, pixelcount * sizeof(PathSegment)); + cudaMalloc(&dev_paths_indices, pixelcount * sizeof(int)); + cudaMalloc(&dev_intersections_material_id, pixelcount * sizeof(int)); + cudaMalloc(&dev_paths_cache, pixelcount * sizeof(PathSegment)); + cudaMalloc(&dev_intersections_cache, pixelcount * sizeof(ShadeableIntersection)); + paths_cached = false; + cudaMalloc(&dev_triangles, scene->triangles.size() * sizeof(Triangle)); + cudaMemcpy(dev_triangles, scene->triangles.data(), scene->triangles.size() * sizeof(Triangle), cudaMemcpyHostToDevice); + cudaMalloc(&dev_lights, scene->lights.size() * sizeof(Geom)); + cudaMemcpy(dev_lights, scene->lights.data(), scene->lights.size() * sizeof(Geom), cudaMemcpyHostToDevice); - checkCUDAError("pathtraceInit"); + checkCUDAError("pathtraceInit"); } void pathtraceFree() { - cudaFree(dev_image); // no-op if dev_image is null - cudaFree(dev_paths); - cudaFree(dev_geoms); - cudaFree(dev_materials); - cudaFree(dev_intersections); - // TODO: clean up any extra device memory you created + cudaFree(dev_image); // no-op if dev_image is null + cudaFree(dev_paths); + cudaFree(dev_geoms); + cudaFree(dev_materials); + cudaFree(dev_intersections); + // TODO: clean up any extra device memory you created + cudaFree(dev_paths_exist); + cudaFree(dev_paths_temp); + cudaFree(dev_paths_indices); + cudaFree(dev_intersections_material_id); + cudaFree(dev_paths_cache); + cudaFree(dev_intersections_cache); + cudaFree(dev_triangles); + cudaFree(dev_lights); - checkCUDAError("pathtraceFree"); + checkCUDAError("pathtraceFree"); } /** @@ -119,152 +635,904 @@ void pathtraceFree() { * motion blur - jitter rays "in time" * lens effect - jitter ray origin positions based on a lens */ -__global__ void generateRayFromCamera(Camera cam, int iter, int traceDepth, PathSegment* pathSegments) +__global__ void generateRayFromCamera(Camera cam, int iter, int traceDepth, PathSegment* pathSegments, glm::vec3 initColor) { int x = (blockIdx.x * blockDim.x) + threadIdx.x; int y = (blockIdx.y * blockDim.y) + threadIdx.y; if (x < cam.resolution.x && y < cam.resolution.y) { int index = x + (y * cam.resolution.x); - PathSegment & segment = pathSegments[index]; + //PathSegment & segment = pathSegments[index];//why ? + PathSegment segment; segment.ray.origin = cam.position; - segment.color = glm::vec3(1.0f, 1.0f, 1.0f); + segment.color = initColor;// glm::vec3(1.0f, 1.0f, 1.0f); // for multi-importance sampling + segment.throughput = glm::vec3(1, 1, 1); + segment.hitSpecular = false; // TODO: implement antialiasing by jittering the ray segment.ray.direction = glm::normalize(cam.view - cam.right * cam.pixelLength.x * ((float)x - (float)cam.resolution.x * 0.5f) - cam.up * cam.pixelLength.y * ((float)y - (float)cam.resolution.y * 0.5f) - ); + ); segment.pixelIndex = index; segment.remainingBounces = traceDepth; + segment.hitLight = false; + for (int i = 0; i < MAX_RECORD_DEPTH; i++) + { + segment.geomIds[i] = -1; + segment.outsides[i] = true; + } + pathSegments[index] = segment; } } -// TODO: -// computeIntersections handles generating ray intersections ONLY. -// Generating new rays is handled in your shader(s). -// Feel free to modify the code below. __global__ void computeIntersections( int depth , int num_paths , PathSegment * pathSegments - , Geom * geoms + , const Geom * geoms + , const Triangle * triangles//My code here + , const Material * materials , int geoms_size , ShadeableIntersection * intersections - ) + , int * intersections_material_id +) { int path_index = blockIdx.x * blockDim.x + threadIdx.x; if (path_index < num_paths) { + //My code here. Pre-fetch PathSegment pathSegment = pathSegments[path_index]; + ShadeableIntersection intersection = intersections[path_index]; + int intersection_material_id = intersections_material_id[path_index]; - float t; - glm::vec3 intersect_point; - glm::vec3 normal; - float t_min = FLT_MAX; - int hit_geom_index = -1; - bool outside = true; + if (pathSegment.remainingBounces > 0) + { + float t; + glm::vec3 intersect_point; + glm::vec3 normal; + float t_min = FLT_MAX; + int hit_geom_index = -1; + //My code here. + glm::vec3 tangent; + glm::vec3 bitangent; + Geom hit_geom; + bool outside; + glm::vec2 uv; - glm::vec3 tmp_intersect; - glm::vec3 tmp_normal; + glm::vec3 tmp_intersect; + //My code here. + glm::vec3 tmp_normal; + glm::vec3 tmp_tangent; + glm::vec3 tmp_bitangent; + bool tmp_outside; + glm::vec2 tmp_uv; - // naive parse through global geoms + // naive parse through global geoms - for (int i = 0; i < geoms_size; i++) + for (int i = 0; i < geoms_size; i++) + { + // is using reference faster in GPU? + Geom geom = geoms[i]; + + if (geom.type == CUBE) + { + t = boxIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, tmp_tangent, tmp_bitangent, tmp_outside); + } + else if (geom.type == SPHERE) + { + t = sphereIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, tmp_tangent, tmp_bitangent, tmp_outside); + } + // TODO: add more intersection tests here... triangle? metaball? CSG? + else if (geom.type == MESH) + { + t = meshIntersectionTest(triangles, geom, pathSegment.ray, tmp_intersect, tmp_normal, tmp_tangent, tmp_bitangent, tmp_uv, tmp_outside); + } + else if (geom.type == SQUARE) + { + t = squareIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, tmp_tangent, tmp_bitangent, tmp_outside); + } + + // Compute the minimum t from the intersection tests to determine what + // scene geometry object was hit first. + if (t > 0.0f && t_min > t) + { + t_min = t; + hit_geom_index = i; + hit_geom = geom; + intersect_point = tmp_intersect; + normal = tmp_normal; + tangent = tmp_tangent; + bitangent = tmp_bitangent; + outside = tmp_outside; + uv = tmp_uv; + } + } + + if (hit_geom_index == -1) + { + intersection.t = -1.0f; + intersection_material_id = -1;//here + } + else + { + //The ray hits something + intersection.t = t_min; + intersection.materialId = hit_geom.materialid; + intersection.surfaceNormal = normal; + //My code here. Also I don't think access global memory multiple times is a good idea even though its value may be cached. + //I think it's better to have a local copy stored in a register and write it back to global buffer at the end. + intersection.point = intersect_point; + intersection.surfaceTangent = tangent; + intersection.surfaceBitangent = bitangent; + intersection.geomId = hit_geom_index; + intersection.outside = outside; + intersection_material_id = materials[hit_geom.materialid].type;//type is enough to batch a group of paths + intersection.uv = uv; + + //record the path for debugging + if (depth <= MAX_RECORD_DEPTH) + { + pathSegment.geomIds[depth - 1] = hit_geom_index; + pathSegment.outsides[depth - 1] = outside; + } + } + } + else { - Geom & geom = geoms[i]; + //My code here. This brach won't happen if the stream compaction clears all path whose remaingBounces is 0 + intersection.t = -1.0f; + } - if (geom.type == CUBE) + //My code here. Write back. + pathSegments[path_index] = pathSegment; + intersections[path_index] = intersection; + intersections_material_id[path_index] = intersection_material_id; + } +} + +__global__ void shadeMaterialOld( + int iter + , int num_paths + , ShadeableIntersection * shadeableIntersections + , PathSegment * pathSegments + , Material * materials +) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_paths) + { + PathSegment pathSegment = pathSegments[idx]; + if (pathSegment.remainingBounces > 0) + { + ShadeableIntersection intersection = shadeableIntersections[idx]; + + if (intersection.t > 0.0f) { - t = boxIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); + thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, pathSegment.remainingBounces); + Material material = materials[intersection.materialId]; + thrust::uniform_real_distribution u01(0.f, 1.f); + glm::vec2 xi(u01(rng), u01(rng)); + SampleMaterialOld(pathSegment, intersection, material, xi); + pathSegment.remainingBounces--; + if (pathSegment.remainingBounces == 0 && pathSegment.hitLight == false) pathSegment.color = glm::vec3(0, 0, 0); } - else if (geom.type == SPHERE) + else { - t = sphereIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); + //if implemented stream compaction, this branch will not be executed + pathSegment.color = glm::vec3(0.0f); + pathSegment.remainingBounces = 0; } - // TODO: add more intersection tests here... triangle? metaball? CSG? - // Compute the minimum t from the intersection tests to determine what - // scene geometry object was hit first. - if (t > 0.0f && t_min > t) + pathSegments[idx] = pathSegment; + } + } +} + +__global__ void shadeMaterialNaive( + int iter + , int num_paths + , ShadeableIntersection * shadeableIntersections + , PathSegment * pathSegments + , Material * materials +) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_paths) + { + PathSegment pathSegment = pathSegments[idx]; + if (pathSegment.remainingBounces > 0) + { + ShadeableIntersection intersection = shadeableIntersections[idx]; + + if (intersection.t > 0.0f) + { + thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, pathSegment.remainingBounces); + Material material = materials[intersection.materialId]; + thrust::uniform_real_distribution u01(0.f, 1.f); + glm::vec2 xi(u01(rng), u01(rng)); + + ///////////////////////////////////////////////////////////////////////////////////////// + glm::vec3 wi(0, 0, 0); + float pdf = 1; + glm::vec3 color = SampleMaterial(pathSegment.ray, intersection, material, xi, wi, &pdf); + + if (isZero(color))//total internal reflection, early terminate + { + pathSegment.remainingBounces = 0; + } + else + if (material.type == 1)//if the material is emissive(light), early terminate + { + pathSegment.hitLight = true; + pathSegment.color = pathSegment.color * color; + pathSegment.remainingBounces = 0; + } + else if (material.type == 3)//if the material is transmissive + { + pathSegment.color = pathSegment.color * color / pdf * glm::abs(glm::dot(wi, -intersection.surfaceNormal));//when refract, normal is on the opposite side of wi + pathSegment.ray.origin = intersection.point - MY_OFFSET * intersection.surfaceNormal;//when refract, normal is on the opposite side of wi + pathSegment.ray.direction = wi; + pathSegment.remainingBounces--; + } + else//if the material is others + { + pathSegment.color = pathSegment.color * color / pdf * glm::abs(glm::dot(wi, intersection.surfaceNormal)); + pathSegment.ray.origin = intersection.point + MY_OFFSET * intersection.surfaceNormal; + pathSegment.ray.direction = wi; + pathSegment.remainingBounces--; + } + ///////////////////////////////////////////////////////////////////////////////////////// + + if (pathSegment.remainingBounces == 0 && pathSegment.hitLight == false) pathSegment.color = glm::vec3(0, 0, 0); + } + else { - t_min = t; - hit_geom_index = i; - intersect_point = tmp_intersect; - normal = tmp_normal; + //if implemented stream compaction, this branch will not be executed + pathSegment.color = glm::vec3(0.0f); + pathSegment.remainingBounces = 0; } + + pathSegments[idx] = pathSegment; } + } +} - if (hit_geom_index == -1) +__global__ void shadeMaterialDirectLight( + int iter + , int num_paths + , ShadeableIntersection * shadeableIntersections + , PathSegment * pathSegments + , Material * materials + // My code here + , const Geom * lights + , int lightsSize + , const Geom * geoms + , int geomsSize + , Triangle * triangles +) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_paths) + { + PathSegment pathSegment = pathSegments[idx]; + if (pathSegment.remainingBounces > 0) { - intersections[path_index].t = -1.0f; + ShadeableIntersection intersection = shadeableIntersections[idx]; + + if (intersection.t > 0.0f) + { + thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, pathSegment.remainingBounces); + Material material = materials[intersection.materialId]; + thrust::uniform_real_distribution u01(0.f, 1.f); + glm::vec2 xi(u01(rng), u01(rng)); + glm::vec2 xii(u01(rng), u01(rng)); + glm::vec2 xiii(u01(rng), u01(rng)); + int randomLightIndex = u01(rng) * lightsSize;//half open + Geom light = lights[randomLightIndex]; + Material lightMaterial = materials[light.materialid]; + glm::vec3 throughput = pathSegment.throughput; + + ///////////////////////////////////////////////////////////////////////////////////////// + glm::vec3 wiLight_SampleLight(0, 0, 0); + glm::vec3 wiLight_SampleMaterial(0, 0, 0); + glm::vec3 wiGlobal(0, 0, 0); + float pdfGlobal = 1; + float pdfLight_SampleMaterial = 1; + float pdfLight_SampleLight = 1; + float pdfMaterial_SampleMaterial = 1; + float pdfMaterial_SampleLight = 1; + glm::vec3 Li(0, 0, 0); + glm::vec3 Li_SampleLight(0, 0, 0); + glm::vec3 Li_SampleMaterial(0, 0, 0); + glm::vec3 colorMaterial_SampleMaterial = SampleMaterial(pathSegment.ray, intersection, material, xi, wiLight_SampleMaterial, &pdfMaterial_SampleMaterial); + + // A. Sample light and material + if (material.type == 1)// hit a light source + { + //!!!!!!!!!!!!!!!!!!!!!!!!!IMPORTANT!!!!!!!!!!!!!!!!!!!!!!!!! + //Since your path tracer computes the direct lighting a given + //intersection receives as its own term, your path tracer must + //not include too much light. This means that every ray which + //already computed the direct lighting term should not incorporate + //the Le term of the light transport equation into its light + //contribution. In other words, unless a particular ray came + //directly from the camera or from a perfectly specular surface, + //Le should be ignored. + + //in direct light integrator, this will always be true + //if (pathSegment.remainingBounces == maxBounce || pathSegment.hitSpecular)//haven't account for specular bounce //done + Li = colorMaterial_SampleMaterial; + pathSegment.remainingBounces = 0; + } + else// hit others + { + glm::vec3 o = material.type == 3 ? + intersection.point - MY_OFFSET * intersection.surfaceNormal : + intersection.point + MY_OFFSET * intersection.surfaceNormal; + int hitGeomId = -1; + + // I. Compute Li + if (material.type == 2 || material.type == 3)// Specular + { + // 0. Set flag + pathSegment.hitSpecular = true; + } + else// Non specular + { + // 0. Set flag + pathSegment.hitSpecular = false; + + // 1. Sample light + glm::vec3 colorLight_SampleLight = SampleLight(light, intersection, lightMaterial, xii, wiLight_SampleLight, &pdfLight_SampleLight); + pdfLight_SampleLight /= lightsSize; + Ray rLight_SampleLight; + rLight_SampleLight.origin = o; + rLight_SampleLight.direction = wiLight_SampleLight; + + //shadow feeler + if (ShadowFeeler(rLight_SampleLight, geoms, geomsSize, triangles, &hitGeomId) && hitGeomId == light.id) + { + //if hit the light, therefore no shadow + //pathSegment.hitLight = true; + glm::vec3 colorMaterial_SampleLight = NotSampleMaterial(intersection, material, wiLight_SampleLight, &pdfMaterial_SampleLight); + colorMaterial_SampleLight *= glm::abs(glm::dot(wiLight_SampleLight, intersection.surfaceNormal)); + if (pdfLight_SampleLight != 0) + { + //in direct light integrator, we don't do MIS + //float weight_SampleLight = PowerHeuristic(1, pdfLight_SampleLight, 1, pdfMaterial_SampleLight); + //Li_SampleLight = weight_SampleLight * colorMaterial_SampleLight * colorLight_SampleLight / pdfLight_SampleLight; + Li_SampleLight = colorMaterial_SampleLight * colorLight_SampleLight / pdfLight_SampleLight; + } + else + { + Li_SampleLight = colorMaterial_SampleLight * colorLight_SampleLight; + } + } + else + { + //if hit nothing or hit other geom + //pathSegment.hitLight = false; + Li_SampleLight = glm::vec3(0, 0, 0); + } + + //in direct light integrator, we don't sample material + // 2. Sample material, since we already sampled one, we are using that result + //if (pdfLight_SampleLight != 0) + //{ + // colorMaterial_SampleMaterial *= glm::abs(glm::dot(wiLight_SampleMaterial, intersection.surfaceNormal)); + // Ray rLight_SampleMaterial; + // rLight_SampleMaterial.origin = o; + // rLight_SampleMaterial.direction = wiLight_SampleMaterial; + + // //shadow feeler + // if (ShadowFeeler(rLight_SampleMaterial, geoms, geomsSize, triangles, &hitGeomId) && hitGeomId == light.id) + // { + // glm::vec3 colorLight_SampleMaterial = NotSampleLight(light, intersection, lightMaterial, wiLight_SampleMaterial, &pdfLight_SampleMaterial); + // pdfLight_SampleMaterial /= lightsSize; + // if (pdfLight_SampleMaterial != 0) + // { + // float weight_SampleMaterial = PowerHeuristic(1, pdfMaterial_SampleMaterial, 1, pdfLight_SampleMaterial); + // Li_SampleMaterial = weight_SampleMaterial * colorMaterial_SampleMaterial * colorLight_SampleMaterial / pdfMaterial_SampleMaterial; + // } + // else + // { + // //do nothing + // Li_SampleMaterial = glm::vec3(0, 0, 0); + // } + // } + // else + // { + // Li_SampleMaterial = glm::vec3(0, 0, 0); + // } + //} + + // 3. Add together + Li = Li_SampleLight + Li_SampleMaterial; + } + + // II. Choose new direction for next depth and update path + glm::vec3 temp = SampleMaterial(pathSegment.ray, intersection, material, xiii, wiGlobal, &pdfGlobal); + + // III. Early termination + if (isZero(temp)) + { + // total internal reflection + pathSegment.remainingBounces = 0; + } + else + { + pathSegment.remainingBounces--; + pathSegment.throughput *= temp * glm::abs(glm::dot(wiGlobal, intersection.surfaceNormal)) / pdfGlobal; + pathSegment.ray.direction = wiGlobal; + pathSegment.ray.origin = o;//if transmissive handled differently + + //printf("%f,%f,%f\n", pathSegment.throughput.x, pathSegment.throughput.y, pathSegment.throughput.z); + + //in direct light integrator, we don't need early termination + // 6. When using multi-importance sampling, results are added instead of multiplied, so termination can not be based on whether hit a light or not. Should use ruassian roulette. + //if (maxBounce - pathSegment.remainingBounces > START_RUASSIAN_ROULETTE_AFTER) + //{ + // float russian = u01(rng); + // float maxThroughput = glm::max(glm::max(pathSegment.throughput.x, pathSegment.throughput.y), pathSegment.throughput.z); + + // if (maxThroughput < russian) + // pathSegment.remainingBounces = 0;//early termination + + // pathSegment.throughput /= maxThroughput; + //} + + } + + } + + // B. No matter whether hit a light or a general geomtry, update the final color + pathSegment.color += Li * throughput; + ///////////////////////////////////////////////////////////////////////////////////////// + + } + else + { + //If implemented stream compaction, this branch will not be executed - WRONG!!! + //^^^WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!^^^ + //If implemented stream compaction, this branch will still be executed. + //Because the order is ComputeIntersection->ShadeMaterial->CompactPath, + //so when using full light integrator, color should not be set to 0 when + //no intersection is detected, but should leave it as it is. This way the + //previously accumulated color would still be valid. + + //pathSegment.color = glm::vec3(0.0f);//this is wrong + pathSegment.remainingBounces = 0; + } + + //in direct light integrator, we only do one trace + pathSegment.remainingBounces = 0;//over write this value no matter what it was + pathSegments[idx] = pathSegment; } - else + } +} + +__global__ void shadeMaterialDirectLightMIS( + int iter + , int num_paths + , ShadeableIntersection * shadeableIntersections + , PathSegment * pathSegments + , Material * materials + // My code here + , const Geom * lights + , int lightsSize + , const Geom * geoms + , int geomsSize + , Triangle * triangles +) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_paths) + { + PathSegment pathSegment = pathSegments[idx]; + if (pathSegment.remainingBounces > 0) { - //The ray hits something - intersections[path_index].t = t_min; - intersections[path_index].materialId = geoms[hit_geom_index].materialid; - intersections[path_index].surfaceNormal = normal; + ShadeableIntersection intersection = shadeableIntersections[idx]; + + if (intersection.t > 0.0f) + { + thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, pathSegment.remainingBounces); + Material material = materials[intersection.materialId]; + thrust::uniform_real_distribution u01(0.f, 1.f); + glm::vec2 xi(u01(rng), u01(rng)); + glm::vec2 xii(u01(rng), u01(rng)); + glm::vec2 xiii(u01(rng), u01(rng)); + int randomLightIndex = u01(rng) * lightsSize;//half open + Geom light = lights[randomLightIndex]; + Material lightMaterial = materials[light.materialid]; + glm::vec3 throughput = pathSegment.throughput; + + ///////////////////////////////////////////////////////////////////////////////////////// + glm::vec3 wiLight_SampleLight(0, 0, 0); + glm::vec3 wiLight_SampleMaterial(0, 0, 0); + glm::vec3 wiGlobal(0, 0, 0); + float pdfGlobal = 1; + float pdfLight_SampleMaterial = 1; + float pdfLight_SampleLight = 1; + float pdfMaterial_SampleMaterial = 1; + float pdfMaterial_SampleLight = 1; + glm::vec3 Li(0, 0, 0); + glm::vec3 Li_SampleLight(0, 0, 0); + glm::vec3 Li_SampleMaterial(0, 0, 0); + glm::vec3 colorMaterial_SampleMaterial = SampleMaterial(pathSegment.ray, intersection, material, xi, wiLight_SampleMaterial, &pdfMaterial_SampleMaterial); + + // A. Sample light and material + if (material.type == 1)// hit a light source + { + //!!!!!!!!!!!!!!!!!!!!!!!!!IMPORTANT!!!!!!!!!!!!!!!!!!!!!!!!! + //Since your path tracer computes the direct lighting a given + //intersection receives as its own term, your path tracer must + //not include too much light. This means that every ray which + //already computed the direct lighting term should not incorporate + //the Le term of the light transport equation into its light + //contribution. In other words, unless a particular ray came + //directly from the camera or from a perfectly specular surface, + //Le should be ignored. + + //in direct light MIS, this is always true + //if (pathSegment.remainingBounces == maxBounce || pathSegment.hitSpecular)//haven't account for specular bounce //done + Li = colorMaterial_SampleMaterial; + pathSegment.remainingBounces = 0; + } + else// hit others + { + glm::vec3 o = material.type == 3 ? + intersection.point - MY_OFFSET * intersection.surfaceNormal : + intersection.point + MY_OFFSET * intersection.surfaceNormal; + int hitGeomId = -1; + + // I. Compute Li + if (material.type == 2 || material.type == 3)// Specular + { + // 0. Set flag + pathSegment.hitSpecular = true; + } + else// Non specular + { + // 0. Set flag + pathSegment.hitSpecular = false; + + // 1. Sample light + glm::vec3 colorLight_SampleLight = SampleLight(light, intersection, lightMaterial, xii, wiLight_SampleLight, &pdfLight_SampleLight); + pdfLight_SampleLight /= lightsSize; + Ray rLight_SampleLight; + rLight_SampleLight.origin = o; + rLight_SampleLight.direction = wiLight_SampleLight; + + //shadow feeler + if (ShadowFeeler(rLight_SampleLight, geoms, geomsSize, triangles, &hitGeomId) && hitGeomId == light.id) + { + //if hit the light, therefore no shadow + //pathSegment.hitLight = true; + glm::vec3 colorMaterial_SampleLight = NotSampleMaterial(intersection, material, wiLight_SampleLight, &pdfMaterial_SampleLight); + colorMaterial_SampleLight *= glm::abs(glm::dot(wiLight_SampleLight, intersection.surfaceNormal)); + if (pdfLight_SampleLight != 0) + { + float weight_SampleLight = PowerHeuristic(1, pdfLight_SampleLight, 1, pdfMaterial_SampleLight); + Li_SampleLight = weight_SampleLight * colorMaterial_SampleLight * colorLight_SampleLight / pdfLight_SampleLight; + } + else + { + Li_SampleLight = colorMaterial_SampleLight * colorLight_SampleLight; + } + } + else + { + //if hit nothing or hit other geom + //pathSegment.hitLight = false; + Li_SampleLight = glm::vec3(0, 0, 0); + } + + // 2. Sample material, since we already sampled one, we are using that result + if (pdfLight_SampleLight != 0) + { + colorMaterial_SampleMaterial *= glm::abs(glm::dot(wiLight_SampleMaterial, intersection.surfaceNormal)); + Ray rLight_SampleMaterial; + rLight_SampleMaterial.origin = o; + rLight_SampleMaterial.direction = wiLight_SampleMaterial; + + //shadow feeler + if (ShadowFeeler(rLight_SampleMaterial, geoms, geomsSize, triangles, &hitGeomId) && hitGeomId == light.id) + { + glm::vec3 colorLight_SampleMaterial = NotSampleLight(light, intersection, lightMaterial, wiLight_SampleMaterial, &pdfLight_SampleMaterial); + pdfLight_SampleMaterial /= lightsSize; + if (pdfLight_SampleMaterial != 0) + { + float weight_SampleMaterial = PowerHeuristic(1, pdfMaterial_SampleMaterial, 1, pdfLight_SampleMaterial); + Li_SampleMaterial = weight_SampleMaterial * colorMaterial_SampleMaterial * colorLight_SampleMaterial / pdfMaterial_SampleMaterial; + } + else + { + //do nothing + Li_SampleMaterial = glm::vec3(0, 0, 0); + } + } + else + { + Li_SampleMaterial = glm::vec3(0, 0, 0); + } + } + + // 3. Add together + Li = Li_SampleLight + Li_SampleMaterial; + } + + // II. Choose new direction for next depth and update path + glm::vec3 temp = SampleMaterial(pathSegment.ray, intersection, material, xiii, wiGlobal, &pdfGlobal); + + // III. Early termination + if (isZero(temp)) + { + // total internal reflection + pathSegment.remainingBounces = 0; + } + else + { + pathSegment.remainingBounces--; + pathSegment.throughput *= temp * glm::abs(glm::dot(wiGlobal, intersection.surfaceNormal)) / pdfGlobal; + pathSegment.ray.direction = wiGlobal; + pathSegment.ray.origin = o;//if transmissive handled differently + + //printf("%f,%f,%f\n", pathSegment.throughput.x, pathSegment.throughput.y, pathSegment.throughput.z); + + //in direct light MIS, we don't need early termination + //When using multi-importance sampling, results are added instead of multiplied, so termination can not be based on whether hit a light or not. Should use ruassian roulette. + //if (maxBounce - pathSegment.remainingBounces > START_RUASSIAN_ROULETTE_AFTER) + //{ + // float russian = u01(rng); + // float maxThroughput = glm::max(glm::max(pathSegment.throughput.x, pathSegment.throughput.y), pathSegment.throughput.z); + + // if (maxThroughput < russian) + // pathSegment.remainingBounces = 0;//early termination + + // pathSegment.throughput /= maxThroughput; + //} + + } + + } + + // B. No matter whether hit a light or a general geomtry, update the final color + pathSegment.color += Li * throughput; + ///////////////////////////////////////////////////////////////////////////////////////// + + } + else + { + //If implemented stream compaction, this branch will not be executed - WRONG!!! + //^^^WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!^^^ + //If implemented stream compaction, this branch will still be executed. + //Because the order is ComputeIntersection->ShadeMaterial->CompactPath, + //so when using full light integrator, color should not be set to 0 when + //no intersection is detected, but should leave it as it is. This way the + //previously accumulated color would still be valid. + + //pathSegment.color = glm::vec3(0.0f);//this is wrong + pathSegment.remainingBounces = 0; + } + + //in direct light MIS, we only do one trace + pathSegment.remainingBounces = 0;//over write this value no matter what it was + pathSegments[idx] = pathSegment; } } } -// LOOK: "fake" shader demonstrating what you might do with the info in -// a ShadeableIntersection, as well as how to use thrust's random number -// generator. Observe that since the thrust random number generator basically -// adds "noise" to the iteration, the image should start off noisy and get -// cleaner as more iterations are computed. -// -// Note that this shader does NOT do a BSDF evaluation! -// Your shaders should handle that - this can allow techniques such as -// bump mapping. -__global__ void shadeFakeMaterial ( - int iter - , int num_paths +__global__ void shadeMaterialFullLight( + int iter + , int num_paths , ShadeableIntersection * shadeableIntersections , PathSegment * pathSegments , Material * materials - ) -{ - int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx < num_paths) - { - ShadeableIntersection intersection = shadeableIntersections[idx]; - if (intersection.t > 0.0f) { // if the intersection exists... - // Set up the RNG - // LOOK: this is how you use thrust's RNG! Please look at - // makeSeededRandomEngine as well. - thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, 0); - thrust::uniform_real_distribution u01(0, 1); - - Material material = materials[intersection.materialId]; - glm::vec3 materialColor = material.color; - - // If the material indicates that the object was a light, "light" the ray - if (material.emittance > 0.0f) { - pathSegments[idx].color *= (materialColor * material.emittance); - } - // Otherwise, do some pseudo-lighting computation. This is actually more - // like what you would expect from shading in a rasterizer like OpenGL. - // TODO: replace this! you should be able to start with basically a one-liner - else { - float lightTerm = glm::dot(intersection.surfaceNormal, glm::vec3(0.0f, 1.0f, 0.0f)); - pathSegments[idx].color *= (materialColor * lightTerm) * 0.3f + ((1.0f - intersection.t * 0.02f) * materialColor) * 0.7f; - pathSegments[idx].color *= u01(rng); // apply some noise because why not - } - // If there was no intersection, color the ray black. - // Lots of renderers use 4 channel color, RGBA, where A = alpha, often - // used for opacity, in which case they can indicate "no opacity". - // This can be useful for post-processing and image compositing. - } else { - pathSegments[idx].color = glm::vec3(0.0f); - } - } + // My code here + , const Geom * lights + , int lightsSize + , const Geom * geoms + , int geomsSize + , Triangle * triangles + , int maxBounce +) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_paths) + { + PathSegment pathSegment = pathSegments[idx]; + if (pathSegment.remainingBounces > 0) + { + ShadeableIntersection intersection = shadeableIntersections[idx]; + + if (intersection.t > 0.0f) + { + thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, pathSegment.remainingBounces); + Material material = materials[intersection.materialId]; + thrust::uniform_real_distribution u01(0.f, 1.f); + glm::vec2 xi(u01(rng), u01(rng)); + glm::vec2 xii(u01(rng), u01(rng)); + glm::vec2 xiii(u01(rng), u01(rng)); + int randomLightIndex = u01(rng) * lightsSize;//half open + Geom light = lights[randomLightIndex]; + Material lightMaterial = materials[light.materialid]; + glm::vec3 throughput = pathSegment.throughput; + + ///////////////////////////////////////////////////////////////////////////////////////// + glm::vec3 wiLight_SampleLight(0, 0, 0); + glm::vec3 wiLight_SampleMaterial(0, 0, 0); + glm::vec3 wiGlobal(0, 0, 0); + float pdfGlobal = 1; + float pdfLight_SampleMaterial = 1; + float pdfLight_SampleLight = 1; + float pdfMaterial_SampleMaterial = 1; + float pdfMaterial_SampleLight = 1; + glm::vec3 Li(0, 0, 0); + glm::vec3 Li_SampleLight(0, 0, 0); + glm::vec3 Li_SampleMaterial(0, 0, 0); + glm::vec3 colorMaterial_SampleMaterial = SampleMaterial(pathSegment.ray, intersection, material, xi, wiLight_SampleMaterial, &pdfMaterial_SampleMaterial); + + // A. Sample light and material + if (material.type == 1)// hit a light source + { + //!!!!!!!!!!!!!!!!!!!!!!!!!IMPORTANT!!!!!!!!!!!!!!!!!!!!!!!!! + //Since your path tracer computes the direct lighting a given + //intersection receives as its own term, your path tracer must + //not include too much light. This means that every ray which + //already computed the direct lighting term should not incorporate + //the Le term of the light transport equation into its light + //contribution. In other words, unless a particular ray came + //directly from the camera or from a perfectly specular surface, + //Le should be ignored. + if(pathSegment.remainingBounces==maxBounce || pathSegment.hitSpecular)//haven't account for specular bounce //done + Li = colorMaterial_SampleMaterial; + pathSegment.remainingBounces = 0; + } + else// hit others + { + glm::vec3 o = material.type == 3 ? + intersection.point - MY_OFFSET * intersection.surfaceNormal : + intersection.point + MY_OFFSET * intersection.surfaceNormal; + int hitGeomId = -1; + + // I. Compute Li + if (material.type == 2 || material.type == 3)// Specular + { + // 0. Set flag + pathSegment.hitSpecular = true; + } + else// Non specular + { + // 0. Set flag + pathSegment.hitSpecular = false; + + // 1. Sample light + glm::vec3 colorLight_SampleLight = SampleLight(light, intersection, lightMaterial, xii, wiLight_SampleLight, &pdfLight_SampleLight); + pdfLight_SampleLight /= lightsSize; + Ray rLight_SampleLight; + rLight_SampleLight.origin = o; + rLight_SampleLight.direction = wiLight_SampleLight; + + //shadow feeler + if (ShadowFeeler(rLight_SampleLight, geoms, geomsSize, triangles, &hitGeomId) && hitGeomId == light.id) + { + //if hit the light, therefore no shadow + //pathSegment.hitLight = true; + glm::vec3 colorMaterial_SampleLight = NotSampleMaterial(intersection, material, wiLight_SampleLight, &pdfMaterial_SampleLight); + colorMaterial_SampleLight *= glm::abs(glm::dot(wiLight_SampleLight, intersection.surfaceNormal)); + if (pdfLight_SampleLight != 0) + { + float weight_SampleLight = PowerHeuristic(1, pdfLight_SampleLight, 1, pdfMaterial_SampleLight); + Li_SampleLight = weight_SampleLight * colorMaterial_SampleLight * colorLight_SampleLight / pdfLight_SampleLight; + } + else + { + Li_SampleLight = colorMaterial_SampleLight * colorLight_SampleLight; + } + } + else + { + //if hit nothing or hit other geom + //pathSegment.hitLight = false; + Li_SampleLight = glm::vec3(0, 0, 0); + } + + // 2. Sample material, since we already sampled one, we are using that result + if (pdfLight_SampleLight != 0) + { + colorMaterial_SampleMaterial *= glm::abs(glm::dot(wiLight_SampleMaterial, intersection.surfaceNormal)); + Ray rLight_SampleMaterial; + rLight_SampleMaterial.origin = o; + rLight_SampleMaterial.direction = wiLight_SampleMaterial; + + //shadow feeler + if (ShadowFeeler(rLight_SampleMaterial, geoms, geomsSize, triangles, &hitGeomId) && hitGeomId == light.id) + { + glm::vec3 colorLight_SampleMaterial = NotSampleLight(light, intersection, lightMaterial, wiLight_SampleMaterial, &pdfLight_SampleMaterial); + pdfLight_SampleMaterial /= lightsSize; + if (pdfLight_SampleMaterial != 0) + { + float weight_SampleMaterial = PowerHeuristic(1, pdfMaterial_SampleMaterial, 1, pdfLight_SampleMaterial); + Li_SampleMaterial = weight_SampleMaterial * colorMaterial_SampleMaterial * colorLight_SampleMaterial / pdfMaterial_SampleMaterial; + } + else + { + //do nothing + Li_SampleMaterial = glm::vec3(0, 0, 0); + } + } + else + { + Li_SampleMaterial = glm::vec3(0, 0, 0); + } + } + + // 3. Add together + Li = Li_SampleLight + Li_SampleMaterial; + } + + // II. Choose new direction for next depth and update path + glm::vec3 temp = SampleMaterial(pathSegment.ray, intersection, material, xiii, wiGlobal, &pdfGlobal); + + // III. Early termination + if (isZero(temp)) + { + // total internal reflection + pathSegment.remainingBounces = 0; + } + else + { + pathSegment.remainingBounces--; + pathSegment.throughput *= temp * glm::abs(glm::dot(wiGlobal, intersection.surfaceNormal)) / pdfGlobal; + pathSegment.ray.direction = wiGlobal; + pathSegment.ray.origin = o;//if transmissive handled differently + + //printf("%f,%f,%f\n", pathSegment.throughput.x, pathSegment.throughput.y, pathSegment.throughput.z); + // 6. When using multi-importance sampling, results are added instead of multiplied, so termination can not be based on whether hit a light or not. Should use ruassian roulette. + if (maxBounce - pathSegment.remainingBounces > START_RUASSIAN_ROULETTE_AFTER) + { + float russian = u01(rng); + float maxThroughput = glm::max(glm::max(pathSegment.throughput.x, pathSegment.throughput.y), pathSegment.throughput.z); + + if (maxThroughput < russian) + pathSegment.remainingBounces = 0;//early termination + + pathSegment.throughput /= maxThroughput; + } + + } + + } + + // B. No matter whether hit a light or a general geomtry, update the final color + pathSegment.color += Li * throughput; + ///////////////////////////////////////////////////////////////////////////////////////// + + } + else + { + //If implemented stream compaction, this branch will not be executed - WRONG!!! + //^^^WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!WRONG!!!^^^ + //If implemented stream compaction, this branch will still be executed. + //Because the order is ComputeIntersection->ShadeMaterial->CompactPath, + //so when using full light integrator, color should not be set to 0 when + //no intersection is detected, but should leave it as it is. This way the + //previously accumulated color would still be valid. + + //pathSegment.color = glm::vec3(0.0f);//this is wrong + pathSegment.remainingBounces = 0; + } + + pathSegments[idx] = pathSegment; + } + } } + // Add the current iteration's output to the overall image __global__ void finalGather(int nPaths, glm::vec3 * image, PathSegment * iterationPaths) { @@ -281,113 +1549,214 @@ __global__ void finalGather(int nPaths, glm::vec3 * image, PathSegment * iterati * Wrapper for the __global__ call that sets up the kernel calls and does a ton * of memory management */ -void pathtrace(uchar4 *pbo, int frame, int iter) { - const int traceDepth = hst_scene->state.traceDepth; - const Camera &cam = hst_scene->state.camera; - const int pixelcount = cam.resolution.x * cam.resolution.y; + + +void ComputeIntersection(dim3 gridSize, int blockSize1d, int depth, int num_paths, PathSegment * paths, Geom * geoms, Triangle * _triangles, Material * materials, int geomsSize, ShadeableIntersection * intersections, int * intersections_material_id) +{ + // tracing + computeIntersections << > > (depth, num_paths, paths, geoms, _triangles, materials, geomsSize, intersections, intersections_material_id); + checkCUDAError("trace one bounce"); + cudaDeviceSynchronize(); +} + +void BatchMaterialOne(int num_paths, PathSegment * paths, ShadeableIntersection * intersections, int * intersections_material_id) +{ + //arrage dev_intersections and dev_paths so that intersections with same materials stay together + auto it = thrust::make_zip_iterator(thrust::make_tuple(intersections, paths)); + thrust::device_ptr dev_intersections_material_id_ptr(intersections_material_id); + thrust::sort_by_key(thrust::device, dev_intersections_material_id_ptr, dev_intersections_material_id_ptr + num_paths, it); +} + +void BatchMaterialTwo(int num_paths, PathSegment * paths, ShadeableIntersection * intersections) +{ + //arrage dev_intersections and dev_paths so that intersections with same materials stay together + thrust::device_ptr dev_intersection_ptr(intersections); + thrust::device_ptr dev_paths_ptr(paths); + thrust::sort_by_key(thrust::device, dev_intersection_ptr, dev_intersection_ptr + num_paths, dev_paths_ptr); +} + +void ShadePathNaive(dim3 gridSize, int blockSize1d, int iter, int num_paths, ShadeableIntersection * intersections, PathSegment * paths, Material * materials) +{ + //My code here + //naive integrator + shadeMaterialNaive << > > (iter, num_paths, intersections, paths, materials); + //shadeMaterialOld << > > (iter, num_paths, intersections, paths, materials); + cudaDeviceSynchronize(); +} + +void ShadePathDirectLight(dim3 gridSize, int blockSize1d, int iter, int num_paths, ShadeableIntersection * intersections, PathSegment * paths, Material * materials, Geom * lights, int lightsSize, Geom * geoms, int geomsSize, Triangle * triangles) +{ + //My code here + //direct light integrator + shadeMaterialDirectLight << > > (iter, num_paths, intersections, paths, materials, lights, lightsSize, geoms, geomsSize, triangles); + cudaDeviceSynchronize(); +} + +void ShadePathDirectLightMIS(dim3 gridSize, int blockSize1d, int iter, int num_paths, ShadeableIntersection * intersections, PathSegment * paths, Material * materials, Geom * lights, int lightsSize, Geom * geoms, int geomsSize, Triangle * triangles) +{ + //My code here + //direct light mis integrator + shadeMaterialDirectLightMIS << > > (iter, num_paths, intersections, paths, materials, lights, lightsSize, geoms, geomsSize, triangles); + cudaDeviceSynchronize(); +} + +void ShadePathFullLight(dim3 gridSize, int blockSize1d, int iter, int num_paths, ShadeableIntersection * intersections, PathSegment * paths, Material * materials, Geom * lights, int lightsSize, Geom * geoms, int geomsSize, Triangle * triangles, int maxBounce) +{ + //My code here + //full light integrator + shadeMaterialFullLight << > > (iter, num_paths, intersections, paths, materials, lights, lightsSize, geoms, geomsSize, triangles, maxBounce); + cudaDeviceSynchronize(); +} + +void CompactPath(dim3 gridSize, int blockSize1d, int * num_paths, glm::vec3 * image, int * paths_exist, int * paths_indices, PathSegment ** paths, PathSegment ** paths_temp) +{ + //stream compaction here + //use dev_paths_exist to stream compact dev_paths + kernelMapToBooleanAndGather << > > (*num_paths, image, paths_exist, *paths); + cudaDeviceSynchronize(); + + //use inclusive scan so that it is eaiser to get the total number of path + //this will require -1 when using the indices array + thrust::inclusive_scan(thrust::device_pointer_cast(paths_exist), thrust::device_pointer_cast(paths_exist) + *num_paths, thrust::device_pointer_cast(paths_indices)); + int num_paths_temp = *num_paths; + cudaMemcpy(&num_paths_temp, dev_paths_indices + *num_paths - 1, sizeof(int), cudaMemcpyDeviceToHost); + cudaDeviceSynchronize(); + + kernelScatter << > > (*num_paths, *paths_temp, *paths, paths_exist, paths_indices); + cudaDeviceSynchronize(); + + PathSegment* temp = *paths; + *paths = *paths_temp; + *paths_temp = temp; + *num_paths = num_paths_temp; + + //printf("%d\n", num_paths); +} + +void pathtrace(uchar4 *pbo, int frame, int iter, + bool enable_stream_compact, + int enable_material_batching, + bool enable_cache_first_path, + int integrator_type) +{ + const int traceDepth = hst_scene->state.traceDepth; + const Camera &cam = hst_scene->state.camera; + const int pixelcount = cam.resolution.x * cam.resolution.y; // 2D block for generating ray from camera - const dim3 blockSize2d(8, 8); - const dim3 blocksPerGrid2d( - (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, - (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); + const dim3 blockSize2d(8, 8); + const dim3 blocksPerGrid2d((cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); // 1D block for path tracing const int blockSize1d = 128; - /////////////////////////////////////////////////////////////////////////// - - // Recap: - // * Initialize array of path rays (using rays that come out of the camera) - // * You can pass the Camera object to that kernel. - // * Each path ray must carry at minimum a (ray, color) pair, - // * where color starts as the multiplicative identity, white = (1, 1, 1). - // * This has already been done for you. - // * For each depth: - // * Compute an intersection in the scene for each path ray. - // A very naive version of this has been implemented for you, but feel - // free to add more primitives and/or a better algorithm. - // Currently, intersection distance is recorded as a parametric distance, - // t, or a "distance along the ray." t = -1.0 indicates no intersection. - // * Color is attenuated (multiplied) by reflections off of any object - // * TODO: Stream compact away all of the terminated paths. - // You may use either your implementation or `thrust::remove_if` or its - // cousins. - // * Note that you can't really use a 2D kernel launch any more - switch - // to 1D. - // * TODO: Shade the rays that intersected something or didn't bottom out. - // That is, color the ray by performing a color computation according - // to the shader, then generate a new ray to continue the ray path. - // We recommend just updating the ray's PathSegment in place. - // Note that this step may come before or after stream compaction, - // since some shaders you write may also cause a path to terminate. - // * Finally, add this iteration's results to the image. This has been done - // for you. - - // TODO: perform one iteration of path tracing - - generateRayFromCamera <<>>(cam, iter, traceDepth, dev_paths); + if (integrator_type == 1 || integrator_type == 2 || integrator_type == 3)//direct light mis integrator and full light integrator, adding results instead of multiplying results + { + generateRayFromCamera << > > (cam, iter, traceDepth, dev_paths, glm::vec3(0, 0, 0)); + } + else + { + generateRayFromCamera << > > (cam, iter, traceDepth, dev_paths, glm::vec3(1, 1, 1)); + } checkCUDAError("generate camera ray"); int depth = 0; - PathSegment* dev_path_end = dev_paths + pixelcount; - int num_paths = dev_path_end - dev_paths; + + int num_paths = pixelcount; + + int geomSize = hst_scene->geoms.size(); + + int lightSize = hst_scene->lights.size(); // --- PathSegment Tracing Stage --- // Shoot ray into scene, bounce between objects, push shading chunks - bool iterationComplete = false; + bool iterationComplete = false; + while (!iterationComplete) { + depth++; - // clean shading chunks - cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); + dim3 gridSize1d = (num_paths + blockSize1d - 1) / blockSize1d; - // tracing - dim3 numblocksPathSegmentTracing = (num_paths + blockSize1d - 1) / blockSize1d; - computeIntersections <<>> ( - depth - , num_paths - , dev_paths - , dev_geoms - , hst_scene->geoms.size() - , dev_intersections - ); - checkCUDAError("trace one bounce"); - cudaDeviceSynchronize(); - depth++; + if (depth == 1 && enable_cache_first_path && paths_cached) + { + // load first paths + cudaMemcpy(dev_paths, dev_paths_cache, pixelcount * sizeof(PathSegment), cudaMemcpyDeviceToDevice); + cudaMemcpy(dev_intersections, dev_intersections_cache, pixelcount * sizeof(ShadeableIntersection), cudaMemcpyDeviceToDevice); + } + else + { + // clean shading chunks + cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); + // compute intersections + ComputeIntersection(gridSize1d, blockSize1d, depth, num_paths, dev_paths, dev_geoms, dev_triangles, dev_materials, geomSize, dev_intersections, dev_intersections_material_id); + // batch materials + if (enable_material_batching == 1) + { + //using an extra int array and iterator of a tuple, supposed to use radix sort + BatchMaterialOne(num_paths, dev_paths, dev_intersections, dev_intersections_material_id); + } + else if (enable_material_batching == 2) + { + //using an struct comparison, supposed to use merge sort + BatchMaterialTwo(num_paths, dev_paths, dev_intersections); + } + + // cache first paths + if (depth == 1 && enable_cache_first_path && !paths_cached) + { + cudaMemcpy(dev_paths_cache, dev_paths, pixelcount * sizeof(PathSegment), cudaMemcpyDeviceToDevice); + cudaMemcpy(dev_intersections_cache, dev_intersections, pixelcount * sizeof(ShadeableIntersection), cudaMemcpyDeviceToDevice); + paths_cached = true; + } + } + // shade paths + if (integrator_type == 0) + { + //naive integrator + ShadePathNaive(gridSize1d, blockSize1d, iter, num_paths, dev_intersections, dev_paths, dev_materials); + } + else if (integrator_type == 1) + { + //direct light integrator + ShadePathDirectLight(gridSize1d, blockSize1d, iter, num_paths, dev_intersections, dev_paths, dev_materials, dev_lights, lightSize, dev_geoms, geomSize, dev_triangles); + } + else if (integrator_type == 2) + { + //direct light mis integrator + ShadePathDirectLightMIS(gridSize1d, blockSize1d, iter, num_paths, dev_intersections, dev_paths, dev_materials, dev_lights, lightSize, dev_geoms, geomSize, dev_triangles); + } + else if (integrator_type == 3) + { + //full light integrator + ShadePathFullLight(gridSize1d, blockSize1d, iter, num_paths, dev_intersections, dev_paths, dev_materials, dev_lights, lightSize, dev_geoms, geomSize, dev_triangles, traceDepth); + } - // TODO: - // --- Shading Stage --- - // Shade path segments based on intersections and generate new rays by - // evaluating the BSDF. - // Start off with just a big kernel that handles all the different - // materials you have in the scenefile. - // TODO: compare between directly shading the path segments and shading - // path segments that have been reshuffled to be contiguous in memory. + // compact paths + if (enable_stream_compact) + { + CompactPath(gridSize1d, blockSize1d, &num_paths, dev_image, dev_paths_exist, dev_paths_indices, &dev_paths, &dev_paths_temp); + } - shadeFakeMaterial<<>> ( - iter, - num_paths, - dev_intersections, - dev_paths, - dev_materials - ); - iterationComplete = true; // TODO: should be based off stream compaction results. + // terminate this iteration + if (depth > traceDepth || num_paths <= 0) iterationComplete = true; } - // Assemble this iteration and apply it to the image - dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; - finalGather<<>>(num_paths, dev_image, dev_paths); + // Assemble this iteration and apply it to the image + if (!enable_stream_compact) + { + dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; + finalGather << > > (pixelcount, dev_image, dev_paths); + } - /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// - // Send results to OpenGL buffer for rendering - sendImageToPBO<<>>(pbo, cam.resolution, iter, dev_image); + // Send results to OpenGL buffer for rendering + sendImageToPBO << > > (pbo, cam.resolution, iter, dev_image); - // Retrieve image from GPU - cudaMemcpy(hst_scene->state.image.data(), dev_image, - pixelcount * sizeof(glm::vec3), cudaMemcpyDeviceToHost); + // Retrieve image from GPU + cudaMemcpy(hst_scene->state.image.data(), dev_image, pixelcount * sizeof(glm::vec3), cudaMemcpyDeviceToHost); - checkCUDAError("pathtrace"); + checkCUDAError("pathtrace"); } diff --git a/src/pathtrace.h b/src/pathtrace.h index 1241227..05be2a9 100644 --- a/src/pathtrace.h +++ b/src/pathtrace.h @@ -5,4 +5,10 @@ void pathtraceInit(Scene *scene); void pathtraceFree(); -void pathtrace(uchar4 *pbo, int frame, int iteration); +void pathtrace(uchar4 *pbo, + int frame, + int iteration, + bool enable_stream_compact = false, + int enable_material_batching = 0, + bool enable_cache_first_path = false, + int integrator_type = 0);//0-niave, 1-direct-light, 2-direct-light-mis, 3-full light diff --git a/src/scene.cpp b/src/scene.cpp index cbae043..34eddfd 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -3,8 +3,11 @@ #include #include #include +#include "tiny_obj_loader.h" +#include "BVHKDTree.h" +#include -Scene::Scene(string filename) { +Scene::Scene(const string& filename) { cout << "Reading scene from " << filename << " ..." << endl; cout << " " << endl; char* fname = (char*)filename.c_str(); @@ -22,7 +25,7 @@ Scene::Scene(string filename) { loadMaterial(tokens[1]); cout << " " << endl; } else if (strcmp(tokens[0].c_str(), "OBJECT") == 0) { - loadGeom(tokens[1]); + loadGeom(filename, tokens[1]); cout << " " << endl; } else if (strcmp(tokens[0].c_str(), "CAMERA") == 0) { loadCamera(); @@ -30,9 +33,102 @@ Scene::Scene(string filename) { } } } + + //My code here + cout << "total tris: " << triangles.size() << endl; + //debugNodeIntersectionTest(100, scene->triangles.data(), scene->geoms[6].root); + +} + +int Scene::loadMesh(const string& sceneName, const string& fileName, vector& mesh_triangles) +{ + //get the directory for the obj file + int slash_forward = sceneName.find_last_of("/"); + int slash_backward = sceneName.find_last_of("\\"); + int slash = sceneName.size(); + if (slash_forward != slash && slash_backward != slash) + { + slash = max(slash_forward, slash_backward); + } + else if(slash_forward != slash) + { + slash = slash_forward; + } + else if (slash_backward != slash) + { + slash = slash_backward; + } + else + { + slash = -1; + } + + string filePath = sceneName.substr(0, slash + 1); + filePath.append(fileName); + + vector shapes; + vector materials; + string errors = tinyobj::LoadObj(shapes, materials, filePath.c_str()); + + if (errors.size() == 0) + { + //Read the information from the vector of shape_ts + for (unsigned int i = 0; i < shapes.size(); i++) + { + std::vector &positions = shapes[i].mesh.positions; + std::vector &normals = shapes[i].mesh.normals; + std::vector &uvs = shapes[i].mesh.texcoords; + std::vector &indices = shapes[i].mesh.indices; + for (unsigned int j = 0; j < indices.size(); j += 3) + { + glm::vec3 p1(positions[indices[j] * 3], positions[indices[j] * 3 + 1], positions[indices[j] * 3 + 2]); + glm::vec3 p2(positions[indices[j + 1] * 3], positions[indices[j + 1] * 3 + 1], positions[indices[j + 1] * 3 + 2]); + glm::vec3 p3(positions[indices[j + 2] * 3], positions[indices[j + 2] * 3 + 1], positions[indices[j + 2] * 3 + 2]); + + Triangle t(p1, p2, p3); + + t.max = glm::max(p1, glm::max(p2, p3)); + t.min = glm::min(p1, glm::min(p2, p3)); + t.center = (t.max + t.min) / 2.f; + //assuming anti-clk-wise + // p2 + // / \ + // p3---p1 + t.planeNormal = glm::normalize(glm::cross(p3 - p2, p1 - p2)); + + if (normals.size() > 0) + { + glm::vec3 n1(normals[indices[j] * 3], normals[indices[j] * 3 + 1], normals[indices[j] * 3 + 2]); + glm::vec3 n2(normals[indices[j + 1] * 3], normals[indices[j + 1] * 3 + 1], normals[indices[j + 1] * 3 + 2]); + glm::vec3 n3(normals[indices[j + 2] * 3], normals[indices[j + 2] * 3 + 1], normals[indices[j + 2] * 3 + 2]); + t.n[0] = n1; + t.n[1] = n2; + t.n[2] = n3; + } + if (uvs.size() > 0) + { + glm::vec2 t1(uvs[indices[j] * 2], uvs[indices[j] * 2 + 1]); + glm::vec2 t2(uvs[indices[j + 1] * 2], uvs[indices[j + 1] * 2 + 1]); + glm::vec2 t3(uvs[indices[j + 2] * 2], uvs[indices[j + 2] * 2 + 1]); + t.t[0] = t1; + t.t[1] = t2; + t.t[2] = t3; + } + mesh_triangles.push_back(t); + } + } + } + else + { + //An error loading the OBJ occurred! + cout << filePath << ", error: " << errors << std::endl; + } + + //assume mesh_triangles is empty before invoking this function + return mesh_triangles.size(); } -int Scene::loadGeom(string objectid) { +int Scene::loadGeom(const string& sceneName, const string& objectid) { int id = atoi(objectid.c_str()); if (id != geoms.size()) { cout << "ERROR: OBJECT ID does not match expected number of geoms" << endl; @@ -45,13 +141,38 @@ int Scene::loadGeom(string objectid) { //load object type utilityCore::safeGetline(fp_in, line); if (!line.empty() && fp_in.good()) { - if (strcmp(line.c_str(), "sphere") == 0) { + + vector tokens = utilityCore::tokenizeString(line); + + if (strcmp(tokens[0].c_str(), "sphere") == 0) { cout << "Creating new sphere..." << endl; newGeom.type = SPHERE; - } else if (strcmp(line.c_str(), "cube") == 0) { + } else if (strcmp(tokens[0].c_str(), "cube") == 0) { cout << "Creating new cube..." << endl; newGeom.type = CUBE; - } + } + else if (strcmp(tokens[0].c_str(), "square") == 0) { + cout << "Creating new square..." << endl; + newGeom.type = SQUARE; + } + else if (strcmp(tokens[0].c_str(), "mesh") == 0) { + string fileName = tokens[1]; + cout << "Creating new mesh from " << fileName << "..." << endl; + newGeom.type = MESH; + vector mesh_triangles; + if (loadMesh(sceneName, fileName, mesh_triangles) > 0) + { + int size = triangles.size(); + //build tree & flaten tree into array + newGeom.root = BVHKDTree::buildTree(mesh_triangles, 0, size, 0, mesh_triangles.size() - 1); + newGeom.triangleStartIndex = size; + newGeom.triangleCount = mesh_triangles.size(); + //copy array to scene triangle vector + triangles.insert(triangles.end(), mesh_triangles.begin(), mesh_triangles.end()); + //debug + //printTriangles(triangles); + } + } } //link material @@ -79,12 +200,20 @@ int Scene::loadGeom(string objectid) { utilityCore::safeGetline(fp_in, line); } - newGeom.transform = utilityCore::buildTransformationMatrix( - newGeom.translation, newGeom.rotation, newGeom.scale); + newGeom.transform = utilityCore::buildTransformationMatrix(newGeom.translation, newGeom.rotation, newGeom.scale); newGeom.inverseTransform = glm::inverse(newGeom.transform); newGeom.invTranspose = glm::inverseTranspose(newGeom.transform); + + //My code here + newGeom.id = id; geoms.push_back(newGeom); + + //My code here + if (materials[newGeom.materialid].type == 1)//if emmissive + { + lights.push_back(newGeom); + } return 1; } } @@ -150,7 +279,7 @@ int Scene::loadCamera() { return 1; } -int Scene::loadMaterial(string materialid) { +int Scene::loadMaterial(const string& materialid) { int id = atoi(materialid.c_str()); if (id != materials.size()) { cout << "ERROR: MATERIAL ID does not match expected number of materials" << endl; @@ -160,29 +289,72 @@ int Scene::loadMaterial(string materialid) { Material newMaterial; //load static properties - for (int i = 0; i < 7; i++) { + for (int i = 0; i < 7; i++) {//My code here. Added a lot of stuff so changed to 10 string line; utilityCore::safeGetline(fp_in, line); vector tokens = utilityCore::tokenizeString(line); - if (strcmp(tokens[0].c_str(), "RGB") == 0) { - glm::vec3 color( atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str()) ); - newMaterial.color = color; - } else if (strcmp(tokens[0].c_str(), "SPECEX") == 0) { - newMaterial.specular.exponent = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "SPECRGB") == 0) { + if (strcmp(tokens[0].c_str(), "RGB") == 0) + { + glm::vec3 color(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + newMaterial.color = color; + } + else if (strcmp(tokens[0].c_str(), "SPECRRGB") == 0) + { glm::vec3 specColor(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - newMaterial.specular.color = specColor; - } else if (strcmp(tokens[0].c_str(), "REFL") == 0) { - newMaterial.hasReflective = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "REFR") == 0) { - newMaterial.hasRefractive = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "REFRIOR") == 0) { - newMaterial.indexOfRefraction = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "EMITTANCE") == 0) { - newMaterial.emittance = atof(tokens[1].c_str()); + newMaterial.specularReflective.color = specColor; + } + else if (strcmp(tokens[0].c_str(), "SPECTRGB") == 0) + { + glm::vec3 specColor(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + newMaterial.specularTransmissive.color = specColor; + } + else if (strcmp(tokens[0].c_str(), "IOR") == 0) + { + newMaterial.specularReflective.indexOfRefraction = atof(tokens[1].c_str()); + newMaterial.specularTransmissive.indexOfRefraction = atof(tokens[1].c_str()); + } + else if (strcmp(tokens[0].c_str(), "EMITRGB") == 0) + { + glm::vec3 emitColor(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + newMaterial.emissive.color = emitColor; + } + else if (strcmp(tokens[0].c_str(), "EMITTANCE") == 0) + { + newMaterial.emissive.emittance = atof(tokens[1].c_str()); + } + else if (strcmp(tokens[0].c_str(), "TYPE") == 0) + { + newMaterial.type = atoi(tokens[1].c_str()); } } materials.push_back(newMaterial); return 1; } } + +void Scene::printTriangles(const vector &triangles) +{ + for (int i = 0; i < triangles.size(); i++) + { + cout << i << + ": left= " << triangles[i].leftIndex << + "; right= " << triangles[i].rightIndex << + "; min= " << triangles[i].min.x << "," << triangles[i].min.y << "," << triangles[i].min.z << + "; max= " << triangles[i].max.x << "," << triangles[i].max.y << "," << triangles[i].max.z << + endl; + for (int j = 0; j < 3; j++) + { + cout << "p" << j << ": " << triangles[i].v[j].x << "," << triangles[i].v[j].y << "," << triangles[i].v[j].z << "; " << endl; + } + for (int j = 0; j < 3; j++) + { + cout << "n" << j << ": " << triangles[i].n[j].x << "," << triangles[i].n[j].y << "," << triangles[i].n[j].z << "; " << endl; + } + for (int j = 0; j < 3; j++) + { + cout << "t" << j << ": " << triangles[i].t[j].x << "," << triangles[i].t[j].y << "; " << endl; + } + + cout << "plane normal: " << triangles[i].planeNormal.x << "," << triangles[i].planeNormal.y << "," << triangles[i].planeNormal.z << endl; + } +} diff --git a/src/scene.h b/src/scene.h index f29a917..5772aef 100644 --- a/src/scene.h +++ b/src/scene.h @@ -13,14 +13,22 @@ using namespace std; class Scene { private: ifstream fp_in; - int loadMaterial(string materialid); - int loadGeom(string objectid); + int loadMaterial(const string& materialid); + int loadGeom(const string& sceneName, const string& objectid); int loadCamera(); + //My code here + int loadMesh(const string& sceneName, const string& fileName, vector& mesh_triangles); + void printTriangles(const vector &triangles); + public: - Scene(string filename); - ~Scene(); + Scene(const string& filename); std::vector geoms; std::vector materials; RenderState state; + + //My code here + std::vector triangles; + std::vector lights; }; + diff --git a/src/sceneStructs.h b/src/sceneStructs.h index b38b820..a4f267b 100644 --- a/src/sceneStructs.h +++ b/src/sceneStructs.h @@ -6,71 +6,143 @@ #include "glm/glm.hpp" #define BACKGROUND_COLOR (glm::vec3(0.0f)) +#define MAX_RECORD_DEPTH 8 enum GeomType { - SPHERE, - CUBE, + SQUARE,//facing positive z + SPHERE, + CUBE, + MESH }; struct Ray { - glm::vec3 origin; - glm::vec3 direction; + glm::vec3 origin; + glm::vec3 direction; }; struct Geom { - enum GeomType type; - int materialid; - glm::vec3 translation; - glm::vec3 rotation; - glm::vec3 scale; - glm::mat4 transform; - glm::mat4 inverseTransform; - glm::mat4 invTranspose; + enum GeomType type; + int materialid; + glm::vec3 translation; + glm::vec3 rotation; + glm::vec3 scale; + glm::mat4 transform; + glm::mat4 inverseTransform; + glm::mat4 invTranspose; + int triangleStartIndex;//for debug, will be deleted + int triangleCount;//for debug, will be deleted + int root; + int id;//for shadow feeler +}; + +struct Triangle { + Triangle(glm::vec3 p0, glm::vec3 p1, glm::vec3 p2) + { + v[0] = p0; + v[1] = p1; + v[2] = p2; + leftIndex = -1; + rightIndex = -1; + } + glm::vec3 v[3]; + glm::vec2 t[3]; + glm::vec3 n[3]; + glm::vec3 min;//for BVH + glm::vec3 max;//for BVH + glm::vec3 center;//for kdtree + glm::vec3 planeNormal; + int leftIndex;//left child + int rightIndex;//right child }; struct Material { - glm::vec3 color; - struct { - float exponent; - glm::vec3 color; - } specular; - float hasReflective; - float hasRefractive; - float indexOfRefraction; - float emittance; + glm::vec3 color; + struct { + glm::vec3 color; + float indexOfRefraction; + } specularReflective; + struct { + glm::vec3 color; + float indexOfRefraction; + } specularTransmissive; + struct { + glm::vec3 color; + float emittance; + } emissive; + uint8_t type;//0-diffuse, 1-emmisive, 2-specular reflective, 3-specular transmissive }; struct Camera { - glm::ivec2 resolution; - glm::vec3 position; - glm::vec3 lookAt; - glm::vec3 view; - glm::vec3 up; - glm::vec3 right; - glm::vec2 fov; - glm::vec2 pixelLength; + glm::ivec2 resolution; + glm::vec3 position; + glm::vec3 lookAt; + glm::vec3 view; + glm::vec3 up; + glm::vec3 right; + glm::vec2 fov; + glm::vec2 pixelLength; }; struct RenderState { - Camera camera; - unsigned int iterations; - int traceDepth; - std::vector image; - std::string imageName; + Camera camera; + unsigned int iterations; + int traceDepth; + std::vector image; + std::string imageName; }; struct PathSegment { + //PathSegment() + //{ + // ray.origin = glm::vec3(0, 0, 0); + // ray.direction = glm::vec3(0, 0, 0); + // color = glm::vec3(0, 0, 0); + // pixelIndex = 0; + // remainingBounces = 0; + // for (int i = 0; i < MAX_RECORD_DEPTH; i++) + // { + // geomIds[i] = 0; + // outsides[i] = false; + // } + // hitLight = false; + //} + //////// Ray ray; - glm::vec3 color; + glm::vec3 color;//when using multi-importance sampling, initiate this to (0, 0, 0) int pixelIndex; int remainingBounces; + //My code here + int geomIds[MAX_RECORD_DEPTH];//for debug + bool outsides[MAX_RECORD_DEPTH];//for debug + bool hitLight;//for naive integrator + glm::vec3 throughput;//for multi-importance sampling + bool hitSpecular;//for full light integrator }; // Use with a corresponding PathSegment to do: // 1) color contribution computation // 2) BSDF evaluation: generate a new ray struct ShadeableIntersection { - float t; - glm::vec3 surfaceNormal; - int materialId; + ShadeableIntersection() + { + t = -1; + surfaceNormal = glm::vec3(0, 0, 0); + materialId = -1; + geomId = -1; + point = glm::vec3(0, 0, 0); + uv = glm::vec2(0, 0); + surfaceTangent = glm::vec3(0, 0, 0); + surfaceBitangent = glm::vec3(0, 0, 0); + outside = false; + } + float t; + glm::vec3 surfaceNormal; + int materialId; + //My code here. + int geomId; + glm::vec3 point; + glm::vec2 uv; + glm::vec3 surfaceTangent; + glm::vec3 surfaceBitangent; + bool outside; }; diff --git a/src/tiny_obj_loader.cc b/src/tiny_obj_loader.cc new file mode 100644 index 0000000..ca5222a --- /dev/null +++ b/src/tiny_obj_loader.cc @@ -0,0 +1,868 @@ +// +// Copyright 2012-2015, Syoyo Fujita. +// +// Licensed under 2-clause BSD liecense. +// + +// +// version 0.9.9: Replace atof() with custom parser. +// version 0.9.8: Fix multi-materials(per-face material ID). +// version 0.9.7: Support multi-materials(per-face material ID) per +// object/group. +// version 0.9.6: Support Ni(index of refraction) mtl parameter. +// Parse transmittance material parameter correctly. +// version 0.9.5: Parse multiple group name. +// Add support of specifying the base path to load material file. +// version 0.9.4: Initial suupport of group tag(g) +// version 0.9.3: Fix parsing triple 'x/y/z' +// version 0.9.2: Add more .mtl load support +// version 0.9.1: Add initial .mtl load support +// version 0.9.0: Initial +// + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tiny_obj_loader.h" + +namespace tinyobj { + +struct vertex_index { + int v_idx, vt_idx, vn_idx; + vertex_index(){}; + vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx){}; + vertex_index(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx){}; +}; +// for std::map +static inline bool operator<(const vertex_index &a, const vertex_index &b) { + if (a.v_idx != b.v_idx) + return (a.v_idx < b.v_idx); + if (a.vn_idx != b.vn_idx) + return (a.vn_idx < b.vn_idx); + if (a.vt_idx != b.vt_idx) + return (a.vt_idx < b.vt_idx); + + return false; +} + +struct obj_shape { + std::vector v; + std::vector vn; + std::vector vt; +}; + +static inline bool isSpace(const char c) { return (c == ' ') || (c == '\t'); } + +static inline bool isNewLine(const char c) { + return (c == '\r') || (c == '\n') || (c == '\0'); +} + +// Make index zero-base, and also support relative index. +static inline int fixIndex(int idx, int n) { + if (idx > 0) return idx - 1; + if (idx == 0) return 0; + return n + idx; // negative value = relative +} + +static inline std::string parseString(const char *&token) { + std::string s; + token += strspn(token, " \t"); + size_t e = strcspn(token, " \t\r"); + s = std::string(token, &token[e]); + token += e; + return s; +} + +static inline int parseInt(const char *&token) { + token += strspn(token, " \t"); + int i = atoi(token); + token += strcspn(token, " \t\r"); + return i; +} + + +// Tries to parse a floating point number located at s. +// +// s_end should be a location in the string where reading should absolutely +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// Valid strings are for example: +// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 +// +// If the parsing is a success, result is set to the parsed value and true +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// The following situations triggers a failure: +// - s >= s_end. +// - parse failure. +// +static bool tryParseDouble(const char *s, const char *s_end, double *result) +{ + if (s >= s_end) + { + return false; + } + + double mantissa = 0.0; + // This exponent is base 2 rather than 10. + // However the exponent we parse is supposed to be one of ten, + // thus we must take care to convert the exponent/and or the + // mantissa to a * 2^E, where a is the mantissa and E is the + // exponent. + // To get the final double we will use ldexp, it requires the + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // How many characters were read in a loop. + int read = 0; + // Tells whether a loop terminated due to reaching s_end. + bool end_not_reached = false; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') + { + sign = *curr; + curr++; + } + else if (isdigit(*curr)) { /* Pass through. */ } + else + { + goto fail; + } + + // Read the integer part. + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; read++; + } + + // We must make sure we actually got something. + if (read == 0) + goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) + goto assemble; + + // Read the decimal part. + if (*curr == '.') + { + curr++; + read = 1; + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + // NOTE: Don't use powf here, it will absolutely murder precision. + mantissa += static_cast(*curr - 0x30) * pow(10, -read); + read++; curr++; + } + } + else if (*curr == 'e' || *curr == 'E') {} + else + { + goto assemble; + } + + if (!end_not_reached) + goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') + { + curr++; + // Figure out if a sign is present and if it is. + if ((end_not_reached = (curr != s_end)) && (*curr == '+' || *curr == '-')) + { + exp_sign = *curr; + curr++; + } + else if (isdigit(*curr)) { /* Pass through. */ } + else + { + // Empty E is not allowed. + goto fail; + } + + read = 0; + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; read++; + } + exponent *= (exp_sign == '+'? 1 : -1); + if (read == 0) + goto fail; + } + +assemble: + *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5, exponent), exponent); + return true; +fail: + return false; +} +static inline float parseFloat(const char *&token) { + token += strspn(token, " \t"); +#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER + float f = (float)atof(token); + token += strcspn(token, " \t\r"); +#else + const char *end = token + strcspn(token, " \t\r"); + double val = 0.0; + tryParseDouble(token, end, &val); + float f = static_cast(val); + token = end; +#endif + return f; +} + + +static inline void parseFloat2(float &x, float &y, const char *&token) { + x = parseFloat(token); + y = parseFloat(token); +} + +static inline void parseFloat3(float &x, float &y, float &z, + const char *&token) { + x = parseFloat(token); + y = parseFloat(token); + z = parseFloat(token); +} + +// Parse triples: i, i/j/k, i//k, i/j +static vertex_index parseTriple(const char *&token, int vsize, int vnsize, + int vtsize) { + vertex_index vi(-1); + + vi.v_idx = fixIndex(atoi(token), vsize); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return vi; + } + token++; + + // i//k + if (token[0] == '/') { + token++; + vi.vn_idx = fixIndex(atoi(token), vnsize); + token += strcspn(token, "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = fixIndex(atoi(token), vtsize); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return vi; + } + + // i/j/k + token++; // skip '/' + vi.vn_idx = fixIndex(atoi(token), vnsize); + token += strcspn(token, "/ \t\r"); + return vi; +} + +static unsigned int +updateVertex(std::map &vertexCache, + std::vector &positions, std::vector &normals, + std::vector &texcoords, + const std::vector &in_positions, + const std::vector &in_normals, + const std::vector &in_texcoords, const vertex_index &i) { + const std::map::iterator it = vertexCache.find(i); + + if (it != vertexCache.end()) { + // found cache + return it->second; + } + + assert(in_positions.size() > (unsigned int)(3 * i.v_idx + 2)); + + positions.push_back(in_positions[3 * i.v_idx + 0]); + positions.push_back(in_positions[3 * i.v_idx + 1]); + positions.push_back(in_positions[3 * i.v_idx + 2]); + + if (i.vn_idx >= 0) { + normals.push_back(in_normals[3 * i.vn_idx + 0]); + normals.push_back(in_normals[3 * i.vn_idx + 1]); + normals.push_back(in_normals[3 * i.vn_idx + 2]); + } + + if (i.vt_idx >= 0) { + texcoords.push_back(in_texcoords[2 * i.vt_idx + 0]); + texcoords.push_back(in_texcoords[2 * i.vt_idx + 1]); + } + + unsigned int idx = static_cast(positions.size() / 3 - 1); + vertexCache[i] = idx; + + return idx; +} + +void InitMaterial(material_t &material) { + material.name = ""; + material.ambient_texname = ""; + material.diffuse_texname = ""; + material.specular_texname = ""; + material.normal_texname = ""; + for (int i = 0; i < 3; i++) { + material.ambient[i] = 0.f; + material.diffuse[i] = 0.f; + material.specular[i] = 0.f; + material.transmittance[i] = 0.f; + material.emission[i] = 0.f; + } + material.illum = 0; + material.dissolve = 1.f; + material.shininess = 1.f; + material.ior = 1.f; + material.unknown_parameter.clear(); +} + +static bool exportFaceGroupToShape( + shape_t &shape, std::map vertexCache, + const std::vector &in_positions, + const std::vector &in_normals, + const std::vector &in_texcoords, + const std::vector > &faceGroup, + const int material_id, const std::string &name, bool clearCache) { + if (faceGroup.empty()) { + return false; + } + + // Flatten vertices and indices + for (size_t i = 0; i < faceGroup.size(); i++) { + const std::vector &face = faceGroup[i]; + + vertex_index i0 = face[0]; + vertex_index i1(-1); + vertex_index i2 = face[1]; + + size_t npolys = face.size(); + + // Polygon -> triangle fan conversion + for (size_t k = 2; k < npolys; k++) { + i1 = i2; + i2 = face[k]; + + unsigned int v0 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); + unsigned int v1 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); + unsigned int v2 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); + + shape.mesh.indices.push_back(v0); + shape.mesh.indices.push_back(v1); + shape.mesh.indices.push_back(v2); + + shape.mesh.material_ids.push_back(material_id); + } + } + + shape.name = name; + + if (clearCache) + vertexCache.clear(); + + return true; +} + +std::string LoadMtl(std::map &material_map, + std::vector &materials, + std::istream &inStream) { + std::stringstream err; + + material_t material; + + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); + + std::string linebuf(&buf[0]); + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') + continue; // empty line + + if (token[0] == '#') + continue; // comment line + + // new mtl + if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { + // flush previous material. + if (!material.name.empty()) { + material_map.insert( + std::pair(material.name, static_cast(materials.size()))); + materials.push_back(material); + } + + // initial temporary material + InitMaterial(material); + + // set new mtl name + char namebuf[4096]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else + sscanf(token, "%s", namebuf); +#endif + material.name = namebuf; + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } + + // diffuse + if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + continue; + } + + // specular + if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } + + // transmittance + if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } + + // ior(index of refraction) + if (token[0] == 'N' && token[1] == 'i' && isSpace((token[2]))) { + token += 2; + material.ior = parseFloat(token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } + + // shininess + if (token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { + token += 2; + material.shininess = parseFloat(token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && isSpace(token[5])) { + token += 6; + material.illum = parseInt(token); + continue; + } + + // dissolve + if ((token[0] == 'd' && isSpace(token[1]))) { + token += 1; + material.dissolve = parseFloat(token); + continue; + } + if (token[0] == 'T' && token[1] == 'r' && isSpace(token[2])) { + token += 2; + material.dissolve = parseFloat(token); + continue; + } + + // ambient texture + if ((0 == strncmp(token, "map_Ka", 6)) && isSpace(token[6])) { + token += 7; + material.ambient_texname = token; + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && isSpace(token[6])) { + token += 7; + material.diffuse_texname = token; + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { + token += 7; + material.specular_texname = token; + continue; + } + + // normal texture + if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { + token += 7; + material.normal_texname = token; + continue; + } + + // unknown parameter + const char *_space = strchr(token, ' '); + if (!_space) { + _space = strchr(token, '\t'); + } + if (_space) { + std::ptrdiff_t len = _space - token; + std::string key(token, len); + std::string value = _space + 1; + material.unknown_parameter.insert( + std::pair(key, value)); + } + } + // flush last material. + material_map.insert( + std::pair(material.name, static_cast(materials.size()))); + materials.push_back(material); + + return err.str(); +} + +std::string MaterialFileReader::operator()(const std::string &matId, + std::vector &materials, + std::map &matMap) { + std::string filepath; + + if (!m_mtlBasePath.empty()) { + filepath = std::string(m_mtlBasePath) + matId; + } else { + filepath = matId; + } + + std::ifstream matIStream(filepath.c_str()); + return LoadMtl(matMap, materials, matIStream); +} + +std::string LoadObj(std::vector &shapes, + std::vector &materials, // [output] + const char *filename, const char *mtl_basepath) { + + shapes.clear(); + + std::stringstream err; + + std::ifstream ifs(filename); + if (!ifs) { + err << "Cannot open file [" << filename << "]" << std::endl; + return err.str(); + } + + std::string basePath; + if (mtl_basepath) { + basePath = mtl_basepath; + } + MaterialFileReader matFileReader(basePath); + + return LoadObj(shapes, materials, ifs, matFileReader); +} + +std::string LoadObj(std::vector &shapes, + std::vector &materials, // [output] + std::istream &inStream, MaterialReader &readMatFn) { + std::stringstream err; + + std::vector v; + std::vector vn; + std::vector vt; + std::vector > faceGroup; + std::string name; + + // material + std::map material_map; + std::map vertexCache; + int material = -1; + + shape_t shape; + + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); + + std::string linebuf(&buf[0]); + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') + continue; // empty line + + if (token[0] == '#') + continue; // comment line + + // vertex + if (token[0] == 'v' && isSpace((token[1]))) { + token += 2; + float x, y, z; + parseFloat3(x, y, z, token); + v.push_back(x); + v.push_back(y); + v.push_back(z); + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) { + token += 3; + float x, y, z; + parseFloat3(x, y, z, token); + vn.push_back(x); + vn.push_back(y); + vn.push_back(z); + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) { + token += 3; + float x, y; + parseFloat2(x, y, token); + vt.push_back(x); + vt.push_back(y); + continue; + } + + // face + if (token[0] == 'f' && isSpace((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + std::vector face; + while (!isNewLine(token[0])) { + vertex_index vi = + parseTriple(token, static_cast(v.size() / 3), static_cast(vn.size() / 3), static_cast(vt.size() / 2)); + face.push_back(vi); + size_t n = strspn(token, " \t\r"); + token += n; + } + + faceGroup.push_back(face); + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { + + char namebuf[4096]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else + sscanf(token, "%s", namebuf); +#endif + + // Create face group per material. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + faceGroup.clear(); + } + + if (material_map.find(namebuf) != material_map.end()) { + material = material_map[namebuf]; + } else { + // { error!! material not found } + material = -1; + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { + char namebuf[4096]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else + sscanf(token, "%s", namebuf); +#endif + + std::string err_mtl = readMatFn(namebuf, materials, material_map); + if (!err_mtl.empty()) { + faceGroup.clear(); // for safety + return err_mtl; + } + + continue; + } + + // group name + if (token[0] == 'g' && isSpace((token[1]))) { + + // flush previous face group. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } + + shape = shape_t(); + + // material = -1; + faceGroup.clear(); + + std::vector names; + while (!isNewLine(token[0])) { + std::string str = parseString(token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + assert(names.size() > 0); + + // names[0] must be 'g', so skip the 0th element. + if (names.size() > 1) { + name = names[1]; + } else { + name = ""; + } + + continue; + } + + // object name + if (token[0] == 'o' && isSpace((token[1]))) { + + // flush previous face group. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } + + // material = -1; + faceGroup.clear(); + shape = shape_t(); + + // @todo { multiple object name? } + char namebuf[4096]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else + sscanf(token, "%s", namebuf); +#endif + name = std::string(namebuf); + + continue; + } + + // Ignore unknown command. + } + + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, + material, name, true); + if (ret) { + shapes.push_back(shape); + } + faceGroup.clear(); // for safety + + return err.str(); +} +} diff --git a/src/tiny_obj_loader.h b/src/tiny_obj_loader.h new file mode 100644 index 0000000..efc99ce --- /dev/null +++ b/src/tiny_obj_loader.h @@ -0,0 +1,94 @@ +// +// Copyright 2012-2015, Syoyo Fujita. +// +// Licensed under 2-clause BSD liecense. +// +#ifndef _TINY_OBJ_LOADER_H +#define _TINY_OBJ_LOADER_H + +#include +#include +#include + +namespace tinyobj { + +typedef struct { + std::string name; + + float ambient[3]; + float diffuse[3]; + float specular[3]; + float transmittance[3]; + float emission[3]; + float shininess; + float ior; // index of refraction + float dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; + + std::string ambient_texname; + std::string diffuse_texname; + std::string specular_texname; + std::string normal_texname; + std::map unknown_parameter; +} material_t; + +typedef struct { + std::vector positions; + std::vector normals; + std::vector texcoords; + std::vector indices; + std::vector material_ids; // per-mesh material ID +} mesh_t; + +typedef struct { + std::string name; + mesh_t mesh; +} shape_t; + +class MaterialReader { +public: + MaterialReader() {} + virtual ~MaterialReader() {} + + virtual std::string operator()(const std::string &matId, + std::vector &materials, + std::map &matMap) = 0; +}; + +class MaterialFileReader : public MaterialReader { +public: + MaterialFileReader(const std::string &mtl_basepath) + : m_mtlBasePath(mtl_basepath) {} + virtual ~MaterialFileReader() {} + virtual std::string operator()(const std::string &matId, + std::vector &materials, + std::map &matMap); + +private: + std::string m_mtlBasePath; +}; + +/// Loads .obj from a file. +/// 'shapes' will be filled with parsed shape data +/// The function returns error string. +/// Returns empty string when loading .obj success. +/// 'mtl_basepath' is optional, and used for base path for .mtl file. +std::string LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + const char *filename, const char *mtl_basepath = nullptr); + +/// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve +/// std::istream for materials. +/// Returns empty string when loading .obj success. +std::string LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::istream &inStream, MaterialReader &readMatFn); + +/// Loads materials into std::map +/// Returns an empty string if successful +std::string LoadMtl(std::map &material_map, + std::vector &materials, std::istream &inStream); +} + +#endif // _TINY_OBJ_LOADER_H diff --git a/src/utilities.h b/src/utilities.h index abb4f27..f137090 100644 --- a/src/utilities.h +++ b/src/utilities.h @@ -12,7 +12,9 @@ #define PI 3.1415926535897932384626422832795028841971f #define TWO_PI 6.2831853071795864769252867665590057683943f #define SQRT_OF_ONE_THIRD 0.5773502691896257645091487805019574556476f -#define EPSILON 0.00001f +#define EPSILON 0.000001f +#define MY_OFFSET 0.001f //this will affect transmissive material +#define START_RUASSIAN_ROULETTE_AFTER 3 namespace utilityCore { extern float clamp(float f, float min, float max);