diff --git a/CMakeLists.txt b/CMakeLists.txt index c473e2c..df0b486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ set(headers src/sceneStructs.h src/preview.h src/utilities.h + src/tiny_obj_loader.h src/ImGui/imconfig.h src/ImGui/imgui.h diff --git a/README.md b/README.md index 110697c..5cfa487 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,435 @@ 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) +* Nick Moon + * [LinkedIn](https://www.linkedin.com/in/nick-moon1/), [personal website](https://nicholasmoon.github.io/) +* Tested on: Windows 10, AMD Ryzen 9 5900HS @ 3.0GHz 32GB, NVIDIA RTX 3060 Laptop 6GB (Personal Laptop) -### (TODO: Your README) +**USED 4 LATE DAYS** -*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. +**This is an offline Physically-Based Path Tracer accelerated on the GPU with CUDA. Path Tracing is a +technique of simulating the actual way light bounces around a scene from light sources towards an eye or +camera. Physically-Based implies energy conservation and plausibly accurate light simulation via solving +the Light Transport Equation** \ +\ +**I used this project to: learn path tracing +optimization techniques by utilizing the parallel nature of the GPU; review, reimplement, and extend +my work from CIS 561: Physically-Based Rendering; use this as an opportunity to learn and implement the +Bounding Volume Hierarchy acceleration structure, keeping in mind the limitations of GPU programming.** + +**The result of the Multiple Importance Sampling (MIS) and Bounding Volume Hierarchy (BVH) I implemented is a PBR path tracer capable +of rendering scenes with millions of triangles and multiple area light sources with at least one sample +per second.** + +## RESULTS + +![](img/renders/objs_clipped.png) +*7000 spp, ~300,000 triangles, max ray depth 16, rendered in ~1 hour* + +![](img/renders/teapot_clipped.png) + +*8000 spp, ~20,000 triangles, max ray depth 16, rendered in ~50 minutes* + +![](img/renders/dragons2_clipped.png) + +*11000 spp, ~2,500,000 triangles, max ray depth 16, rendered in ~2 hour* + +### All models obtained from either Alec Jacobson's or Morgan McGuire's common 3D model repositories. See references at the bottom of readme + +## IMPLEMENTATION + +### Physically-Based Rendering + +Physically based rendering involves solving the Light Transport Equation, which describes how light is transmitted +from one direction into another at a particular surface. It is shown here below: + +![](img/figures/LTE.png) + +Lo is the direction in which a surface is viewed. Le is the light emitted from that surface. The integral indicates +a summing of all directions in the hemisphere surrounding the surface normal (all wi's). The f term represents the amount of light +transmitted from a certain incoming direction wi towards the viewing direction wo. The Li term represents the incoming +light from the direction wi. The V term is visiblity i.e. whether that surface is visible from the viewing direction. +The dot term represents the lambertian term, i.e. that less contribution of incoming +light is reflected the more perpendicular to the surface normal that the incoming direction is. + +### Bidirectional Scattering Distribution Functions (BSDFs) + +Bidirectional Scattering Distribution Functions describe the scattering of energy on a surface as a result +of the surface's material properties. Components of a BSDF can include the +Bidirectional Reflectance Distribution Function and the Bidirectional Transmittance Distribution Function. +These describe the reflective and transmissive properties respectively. Sampling a BSDF involves generating +the **wi**, **pdf**, and **f** terms from the LTE in the above section. The **pdf** is needed, because we will never +be able to sample all possible ray directions in the hemisphere directed along the surface normal, so we need to account +for how much of the total domain of directions this wi takes up with respect to the particular BSDF. + +#### Diffuse BRDF + +![](img/renders/diffuse.PNG) + +The Diffuse BRDF scatters light in all directions in the hemisphere oriented along the surface normal. + +#### Specular BRDF + +![](img/renders/spec_brdf.PNG) + +The Specular BRDF reflects rays in only a single direction: the incoming ray reflected about the surface normal. +This yields a perfect mirror-like reflectance model. + +#### Specular BTDF + +![](img/renders/spec_btdf.PNG) + +The Specular BTDF reflects rays in only a single direction: the incoming ray refracted about the surface normal +based on the specified index of refraction according to Snell's Law. At some points of interesection, this may yield +total internal reflection, at which point the ray should be reflected outwards similar to the Specular BRDF. However, +this specific case is handled in full in the Specular Glass BxDF below. + +#### Specular Glass BxDF + +![](img/renders/spec_glass.PNG) + +Specular Glass combines both the Specular BRDF and the Specular BTDF into one BSDF. The mix of reflection vs. refraction +is modulated by the Fresnel term for dielectrics, which specifies that more reflection occurs where the surface normal +is more perpendicular to the look direction, whilst +more refraction occurs in cases where the surface normal is more parallel with the look direction. + +#### Specular Plastic BxDF + +![](img/renders/spec_plastic.PNG) + +Specular Plastic is a BxDF mix between the diffuse BRDF and the specular BRDF. It functions nearly +identically to glass in terms of mixing these two BSDFs, and yields a partially rough and partially +mirror-like surface. + +### Multiple Importance Sampling (MIS) + +Multiple Importance Sampling, as well as Direct Light Sampling, are methods of optimizing the way rays are cast throughout +a scene. Direct Light Sampling, for every vertex along a path, sends a ray towards a light source in an effort to +directly sample the source for its incoming light. This ensures that every ray path at least tries to hit a light +source, instead of randomly being sent into a new direction based on the surface's BSDF. + +The problem with this approach is that it does not work as well for nearly specular surfaces. Although I did not implement +microfacet BSDFs in this assignment, the idea is that directly sampling a light source for incoming light information +in a nearly specular surface doesn't account for the fact that a nearly specular surface only allows incoming light +to reflect off itself if it is within its "glossy lobe", which encompasses directions near the perfect "specular" reflection +direction where some amount of reflection can still occur. It would be better in this case to actually use the BSDF +to generate the new ray direction, because this will ensure that, if a light source is hit, that is from a direction +within this lobe. Multiple Importance Sampling seeks to bridge these two sampling techniques, Direct Light Sampling and +BSDF Sampling, and does both samples for every vertex in a ray path. The contribution from these two sampling rays are +then weighted based on how much that ray directiona aligns with the BSDF ray direction domain. This is a concept originally +proposed by Eric Veach, who also proposed the *Power Heuristic* as a good method of combining the two samples. That is +what is used in this path tracer. + +This is the result of using a ray depth of 1, which is essentially just direct lighting. Note the glass teardrop is black +because there is no refraction or reflection with ray depth 1. +![](img/renders/depth_1.PNG) + +At ray depth 8, all the effects of global illumination are now visable. +![](img/renders/depth_8.PNG) + +This render shows the result of 1 sample of the scene rendered. In just 1 sample, we get a pretty good idea of what +the scene should look like: +![](img/renders/iter_1.PNG) + +And with 100 iterations, it looks nearly converged. This scene with roughly 5000 triangles and 100 iterations +at 1080p with ray depth 8 took only 25 seconds to render +![](img/renders/iter_100.PNG) + +### Depth of Field + +![](img/renders/depth_of_field.PNG) + +Depth of Field models the way a lens works in the real world. In the real world, a lens is normally disk shaped, +and this causes the rays to focus on a certain distance in space depending on the radius of the lens and the +focal distance. Note that the curvature or thickness of the lens is not included in this **thin lens** approximation. +Thus, in order to achieve this effect, we simply need to randomly choose a point in a disk to set as the ray origin +for each of the rays we generate in a sample. This yields the blurring effect seen above. + +### Stochastic Anti-Aliasing + +Naively generating rays causes the primary ray from the camera for every pixel to deterministically hit +the same object every sample. The result of this is the appearance of "jaggies" as seen below: + +![](img/figures/no_aa_fig.PNG) + +By providing a small, random jitter in the x and y directions of a pixels grid cell and +modifying the ray direction using this jittered value, the rays shot from a single pixel grid cell can +thus hit different, neighboring objects each sample yielding anti-aliasing for "free": + +![](img/figures/aa_fig.PNG) + +### Tone Mapping and Gamma Correction + +The image that results from rendering this scene with a basic RGB path tracer is the following image: + +![](img/renders/no_hdr_no_gamma.PNG) + +This image looks overly dark in the areas further from the light, and is too bright near the top of the +teardrop mesh. It is hard to tell there is even any global illumination going on! This is because +this render has not been tone mapped nor gamma corrected. + +This is the result of adding Reinhard HDR tone mapping to the render: + +![](img/renders/hdr_no_gamma.PNG) + +The intense color near the tip of the teardrop has been eased out, providing a much more natural looking +diffuse gradient from top to bottom. This is achieved by dividing the final color by the final color plus 1. +The effect of this is that very high intensity light values are always less than 1, but the amount there are less than +one is not linear. Small light values will pretty much remain the same, while larger light values will be shrunk much +more. + + +This is the result of adding gamma correction to the render: + +![](img/renders/no_hdr_gamma.PNG) + +The color near the edge of the screen has now been boosted to more closely match human color perception, +and now those areas are now all clearly illuminated by a mix of global illumination and the far away light source. +However, without the Reinhard operator, the tip of the teardrop is still to bright. Gamma correction is implemented by +taking the final color and taking it to the power of 1 / 2.2. + +Finally, combining both operations together yields: + +![](img/renders/diffuse.PNG) + +This render has been properly tone mapped and gamma corrected, and now looks more cohesive, natural, and +follows physically-based rendering principles. See the links in the references for more detail in how these operations +work. + +### OBJ Loading with TinyOBJ + +Using the TinyOBJ library, a. .obj file can be specified in the scene description and then read in via +the library into arrays of triangle positions, normals, and texture coordinates. These are then grouped together +in a struct, and an array of these structs is sent to the GPU to be rendered. The wire frame render of the +teardrop model looks like this: + +![](img/renders/wireframe.PNG) + +P.S. In order to render this wireframe version while still in a physically-based framework and in an +easy and quick to implement way, I changed my triangle intersection test. In the final check for if +the barycentric coordinates of the hit point are within the triangle, I simply added an upper limit to +the distance from an edge a point is that will still count as an intersection. + +By using the normals read in from the obj file, the normal at the hit point of the ray-triangle intersection +test can be interpolated using the barycentric coordinates, yielding smooth shading, like that below: + +![](img/renders/diffuse.PNG) + + +### Optimizations Features + +#### Bounding Volume Hierarchy (BVH) + +During path tracing, a ray needs to intersect with the geometry in the scene to determine things like +the surface normal, the distance from the origin, and the BSDF at the surface. To do this, a naive +ray tracing engine has to perform a ray-primitive intersection test for every primitive in the scene. +While this is acceptable for scenes with a small amount of, for example, cubes and spheres, this quickly falls +apart for scenes with meshes, especially ones made up of thousands and more triangles. + +The Bounding Volume Hierarchy is a binary-tree based primitive acceleration structure that can be used +to optimize this ray-scene intersection test. Instead of intersecting with every triangle, instead a ray +interesects with nodes in the BVH, represented spatially as an Axis Aligned Bounding Box (AABB). An intersection +test will only be performed on a node if the intersection test with its parent was found to be a hit. The +leaf nodes store the actual primitives, so the triangle intersection test, which is more expensive than +the AABB, is only done once a leaf node AABB is hit. + +In terms of how to split a node in the BVH, I opted for splitting along the max extent of the centroids of the +triangles contained within the node. This is similar to the naive approach that PBRT takes. Thus, +all triangles with a centroid less than the average centroid value in the max extent axis is put in the +left node, and the rest in the right. If I had more time, +I would have tried the Surface Area Heuristic instead, as it constructs more well formed trees. + +I then have to convert the CPU side nodes to GPU side nodes. This means storing the tree in an array. +I used some optimizations explained by PBRT, which includes +storing the tree in depth first order. What this means is that, for all nodes, their left child is always in the +next index of the array from it, while the second child is further down the array. This means an index to the first +child does not need to be stored in the nodes, and it also means that nodes will always be continuous in memory if +traversing down the left child path. + +For traversing, recursion cannot be used, because it is on the GPU. So, an iterative approach must be +taken, which also means I must use a stack. While the stack is not empty, the front of the stack is popped and +the popped node is the current one. Its AABB is intersected with, and if it is not hit, then continue on to the next +thing in the stack. Otherwise, if the node is a leaf node, then intersect the ray with the node's triangle, and update +the ray t value. If the stack is empty, this means we are done, and have fully traversed the tree. Otherwise we go get +the next thing on the stack. If the node is not a leaf node, then we add the right child to the stack to process, and +set the next node to process to be the left child, as this is more memory coherent. At the end of traversal we have +our hit triangle. + +This optimization should hopefully provide **O(logn)** runtime, as opposed to the **O(n)** of the naive linear scan. + + + +#### Russian Roulette Ray Termination + +Russian Roulette Ray Termination is an optimization that seeks to remove ray paths that have no additional +information to contribute to the scene by bouncing more times. For my renderer, I enable a ray termination +check if the depth is greater than 3. This is because, for a scene made of mostly diffuse surfaces, +4-5 bounces is usually enough to get the majority of the global illumination information for a path (assuming +MIS/direct light sampling is also used). Thus, on depth 4 and greater, a random number is generated for each +ray path. If the max channel of the throughput of the ray (the value which gets attenuated when hitting +diffuse surfaces) is less than this random number, then the ray is terminated. However, if the max channel +is greater, then the ray path continues bouncing (for at least one more bounce if applicable). Additionally, +the throughput of a ray path that passes this check is divided by it max channel. This is to counter +the "early" termination of the rays which did not pass this check on earlier (or later) samples, thus +preserving the overall light intensity at each pixel. The image would be slightly darkened without this, +as energy would no longer be conserved. + +#### Stream Compaction Ray Termination + +The following explanation is from my HW 02: Stream Compaction README: + +"Stream compaction is an array algorithm that, given an input array of ints ```idata``` of size ```n```, +returns an output array ```odata``` of some size ```[0,n]``` such that ```odata``` contains only the +values ```x``` in ```idata``` that satisfy some criteria function ```f(x)```. This is essentially +used to compact an array into a smaller size by getting rid of unneeded elements as determined by +the criteria function ```f(x)```. Values for which ```f(x)``` return ```true``` are kept, while +values for which ```f(x)``` return false are removed." + +For the purposes of path tracing, we have an array of ray paths, with the remaining bounces of a path +being the amount of bounces a ray has left to take in the scene. We obviously don't want to be doing +unecessary intersections and shading for rays that no longer contribute color to the final +image. We thus define the ```f(x)``` from Stream Compaction to be that a ray path's remaining bounces +is not equal to 0. By using the Thrust library's Stream Compaction function, we can thus move unneeded rays +to the back of the array of paths, and only call the intersection and shading kernels on the rays that +will actually contribute to the rendering. + +#### Material Sorting + +Another optimization that can be made is by recognizing that all the material shading is currently done in +a single "uber" kernel. This means that individual threads in a warp might be calculating the f, pdf, and wi +terms for the hit surface using different BSDFs. These BSDFs could potentially be very intsense and vary with +high spatial frequency across the scene. This means a lot of potential warp divergence, which could dramatically +slow down the shading kernel. Instead, if we sort the ray path's and intersections by the material type +returned by the intersection kernel, we could then insure that most ray paths with similar material +types are laid out sequentially in memory. This will yield less divergence, and thus faster runtime. Thrust +Radix Sort is used for this sorting process. Note that the material IDs are what are being sorted here, +not necessarily the BSDF or material type, so unfortunately materials that are the same in every aspect save +albedo will still count as seperate entries to be sorted. + +#### First Bounce Caching + +First bounce caching is an optimization used in settings where features like depth of field or anti-aliasing +are not needed (which I assume are pretty rare.) Because the direction and origin of the intitial camera +ray are deterministic in this scenario, then this means this ray will always hit the same primitive. Thus, +instead of computing this intersection every sample, we instead cache this first intersection into a seperate +device array on the first sample, and then load it into the main intersection device array for every sample +beyond the first. Especially for scenarios where the max ray depth is on the lower end, this should +improve the runtime. + +## Performance Analysis + +### Stream Compaction and Russian Roulette Ray Termination + +**All the below figures are in a scene with 32 max depth (unless otherwise noted) +, 1920x1080 render target, 250000 tris, and 1 light. Runtimes +of an iteration are averaged over 50 samples.** + +![](img/figures/streamcompactclosedruntime.png) + +![](img/figures/streamcompactopenruntime.png) + +As can be seen from the two charts above, adding stream compaction actually increased the runtime. I believe this was +mostly because, in all my kernels, I always made sure to check that the returning bounces amount was 0, and to return +if so. This essentially negated a lot of the perceived benefit of the ray termination. Although not as many threads were +being launched by kernel calls, the performance impact this gave was negated by the cost in running the stream compact +algorithm. Like expected, the performance penalty of stream compaction was indeed smaller with the open scene, +where more rays terminate. However, it is still slightly worse than without, again because I still have those checks +in all my kernels. + +![](img/figures/RRclosedruntime.png) + +![](img/figures/RRopenruntime.png) + +Next, as can be seen from the two charts above, adding Russian Roulette Ray Termination **greatly** increases performance. +This is because, even in closed scenes, which normally would only terminate rays that hit the light, there are still +many rays per iteration that become terminated due to the feature. The performance gain from this feature is also better +when a higher max depth is used. + +![](img/figures/streamcompactrays.png) + +![](img/figures/streamcompactraysopen.png) + +Finally, the above two charts showcase the number of active rays at each depth for a single sample. +As can be seen, without Russian Roulette in a closed scene, the number of rays that terminate per depth is very very +small. with Russian Roulette, the number of rays terminated almost matches that for the open scene. Except for the +first three depth, as this is before the feature takes effect. For the open scene, +the number of rays remaining with and without Russian Roulette is very similar. This is because most rays in the scene +are being terminated by hitting the void. If a ray is already terminated by hitting the void, then Russian Roulette +will not even be used on those rays. Overall, it appears that Russian Roulette Ray termination is a very useful and +cheap method of ray termiantion, whereas stream compaction is too expensive to justify its inclusion. + + +### Material Sorting + +![](img/figures/matsort4.png) + +![](img/figures/matsort8.png) + +The above two charts display the runtime impact of material sorting with different numbers of materials. As can be seen, +while the runtime with material sorting got a bit faster with double the amount of materials, the runtime without +material sorting in both cases still is incredibly faster. This is probably because the impact of material sorting +can only really be felt with thousands of materials in the scene and much more expensive BSDF calculations. Both of +these are beyond the scope of this project, but would be an exciting test to run in the future. + +### First Bounce Caching + +![](img/figures/firstbounce.png) + +The above chart shows the performance impact of adding a first bounce cache to the rendering pipeline (without AA or DOF). +As can be seen above, the performance impact is actually not that much. This is because of the other features I have +implemented, specifically the BVH and MIS. MIS means that, per depth, 3 intersection tests are made. So, caching +only the first intersection test of the first depth is not much in the grand scheme of things. The BVH means that the +intersection test is much faster, which decreases the total amount of impact that caching one of these tests can have +overall. As the ray depth increases, the performance impact from caching also decreased, as now the cached bounce is +only, for the example of max ray depth 8, 1 of 24 total intersection tests made per sample, as opposed to 1 of 3 total for +max ray depth 1. + +### Anti Aliasing and Depth of Field + +![](img/figures/aadof.png) + +The chart above shows the performance penalties for Anti Aliasing and Depth of Field. Both of these features have very +small impact on the overall runtime of the program relative to the first bounce caching optimization, especially +because I handle most calculations and random number generation CPU side to make things easier for the kernel. + +### Bounding Volume Hierarchy (BVH) + +This chart shows the average runtime across 50 pixel samples: + +![](img/figures/bvhruntime.png) + +As can be seen from the chart above, the runtime of the linear triangle intersection search is indeed O(n) (remember logarithmic axes). And, +the runtime with the BVH appears to be logarithmic. Note that there was some variance in the objects used, and mesh +topology, and scale in the scene definitely play a role in making the runtime vary per object tested. For anything past +1000 triangles, the linear search was less than 1 frame a second, and became untennable to actually test the runtime for. + + +## Bloopers + +![](img/bloops/lotsadragons.PNG) +Accidentally made the walls specular + +**For more bloopers, see img/bloops :)** + +## References + +Adam Mally's CIS 560 and 561 Slides and Lectures + +Physically Based Rendering: https://pbrt.org/ + +TinyOBJ (used for obj loading): https://github.com/tinyobjloader/tinyobjloader + +AABB traversal help: https://tavianator.com/2011/ray_box.html + +Jacco Bikker Blog on BVHs: https://jacco.ompf2.com/2022/04/13/how-to-build-a-bvh-part-1-basics/ + +Alec Jacobson Common 3D Test Models: https://github.com/alecjacobson/common-3d-test-models + +Morgan McGuire, Computer Graphics Archive, July 2017 (https://casual-effects.com/data) + +Eric Veach. Multiple Importance Sampling: https://graphics.stanford.edu/courses/cs348b-03/papers/veach-chapter9.pdf + +expf Reinhard Tone Mapping post: https://expf.wordpress.com/2010/05/04/reinhards_tone_mapping_operator/ + +LearnOpenGL Gamma Correction: https://learnopengl.com/Advanced-Lighting/Gamma-Correction diff --git a/img/bloops/bad_diffuse0.PNG b/img/bloops/bad_diffuse0.PNG new file mode 100644 index 0000000..347bd42 Binary files /dev/null and b/img/bloops/bad_diffuse0.PNG differ diff --git a/img/bloops/bad_diffuse1.PNG b/img/bloops/bad_diffuse1.PNG new file mode 100644 index 0000000..af737be Binary files /dev/null and b/img/bloops/bad_diffuse1.PNG differ diff --git a/img/bloops/bad_shared_mem.PNG b/img/bloops/bad_shared_mem.PNG new file mode 100644 index 0000000..ff372c2 Binary files /dev/null and b/img/bloops/bad_shared_mem.PNG differ diff --git a/img/bloops/bloop.PNG b/img/bloops/bloop.PNG new file mode 100644 index 0000000..f098e22 Binary files /dev/null and b/img/bloops/bloop.PNG differ diff --git a/img/bloops/bloopmat.PNG b/img/bloops/bloopmat.PNG new file mode 100644 index 0000000..708d4c1 Binary files /dev/null and b/img/bloops/bloopmat.PNG differ diff --git a/img/bloops/bloopmat2.PNG b/img/bloops/bloopmat2.PNG new file mode 100644 index 0000000..acb46dd Binary files /dev/null and b/img/bloops/bloopmat2.PNG differ diff --git a/img/bloops/bloopy.PNG b/img/bloops/bloopy.PNG new file mode 100644 index 0000000..b3e7ee3 Binary files /dev/null and b/img/bloops/bloopy.PNG differ diff --git a/img/bloops/blue.PNG b/img/bloops/blue.PNG new file mode 100644 index 0000000..790eaf8 Binary files /dev/null and b/img/bloops/blue.PNG differ diff --git a/img/bloops/boopep.PNG b/img/bloops/boopep.PNG new file mode 100644 index 0000000..1620082 Binary files /dev/null and b/img/bloops/boopep.PNG differ diff --git a/img/bloops/dfg.PNG b/img/bloops/dfg.PNG new file mode 100644 index 0000000..03266ad Binary files /dev/null and b/img/bloops/dfg.PNG differ diff --git a/img/bloops/diffuse_almost_works.PNG b/img/bloops/diffuse_almost_works.PNG new file mode 100644 index 0000000..84f741b Binary files /dev/null and b/img/bloops/diffuse_almost_works.PNG differ diff --git a/img/bloops/diffuse_almost_works_still.PNG b/img/bloops/diffuse_almost_works_still.PNG new file mode 100644 index 0000000..ea82007 Binary files /dev/null and b/img/bloops/diffuse_almost_works_still.PNG differ diff --git a/img/bloops/fresnel.PNG b/img/bloops/fresnel.PNG new file mode 100644 index 0000000..0dfd7df Binary files /dev/null and b/img/bloops/fresnel.PNG differ diff --git a/img/bloops/io2.PNG b/img/bloops/io2.PNG new file mode 100644 index 0000000..52678c7 Binary files /dev/null and b/img/bloops/io2.PNG differ diff --git a/img/bloops/io3.PNG b/img/bloops/io3.PNG new file mode 100644 index 0000000..b4f63b8 Binary files /dev/null and b/img/bloops/io3.PNG differ diff --git a/img/bloops/lotsadragons.PNG b/img/bloops/lotsadragons.PNG new file mode 100644 index 0000000..6a3d982 Binary files /dev/null and b/img/bloops/lotsadragons.PNG differ diff --git a/img/bloops/notquire.PNG b/img/bloops/notquire.PNG new file mode 100644 index 0000000..5a49241 Binary files /dev/null and b/img/bloops/notquire.PNG differ diff --git a/img/bloops/oi.PNG b/img/bloops/oi.PNG new file mode 100644 index 0000000..25f1765 Binary files /dev/null and b/img/bloops/oi.PNG differ diff --git a/img/bloops/refractiondoesntwork.PNG b/img/bloops/refractiondoesntwork.PNG new file mode 100644 index 0000000..8609858 Binary files /dev/null and b/img/bloops/refractiondoesntwork.PNG differ diff --git a/img/bloops/thinlens.PNG b/img/bloops/thinlens.PNG new file mode 100644 index 0000000..d2e3a3e Binary files /dev/null and b/img/bloops/thinlens.PNG differ diff --git a/img/figures/LTE.png b/img/figures/LTE.png new file mode 100644 index 0000000..dabf761 Binary files /dev/null and b/img/figures/LTE.png differ diff --git a/img/figures/RRclosedruntime.png b/img/figures/RRclosedruntime.png new file mode 100644 index 0000000..8b6df2b Binary files /dev/null and b/img/figures/RRclosedruntime.png differ diff --git a/img/figures/RRopenruntime.png b/img/figures/RRopenruntime.png new file mode 100644 index 0000000..0434dc2 Binary files /dev/null and b/img/figures/RRopenruntime.png differ diff --git a/img/figures/aa.png b/img/figures/aa.png new file mode 100644 index 0000000..95dc636 Binary files /dev/null and b/img/figures/aa.png differ diff --git a/img/figures/aa_fig.PNG b/img/figures/aa_fig.PNG new file mode 100644 index 0000000..91160ea Binary files /dev/null and b/img/figures/aa_fig.PNG differ diff --git a/img/figures/aadof.png b/img/figures/aadof.png new file mode 100644 index 0000000..0c2176c Binary files /dev/null and b/img/figures/aadof.png differ diff --git a/img/figures/bvhruntime.png b/img/figures/bvhruntime.png new file mode 100644 index 0000000..75a55c2 Binary files /dev/null and b/img/figures/bvhruntime.png differ diff --git a/img/figures/firstbounce.png b/img/figures/firstbounce.png new file mode 100644 index 0000000..6c37d04 Binary files /dev/null and b/img/figures/firstbounce.png differ diff --git a/img/figures/matsort4.png b/img/figures/matsort4.png new file mode 100644 index 0000000..b7cfebe Binary files /dev/null and b/img/figures/matsort4.png differ diff --git a/img/figures/matsort8.png b/img/figures/matsort8.png new file mode 100644 index 0000000..88bbe92 Binary files /dev/null and b/img/figures/matsort8.png differ diff --git a/img/figures/no_aa.png b/img/figures/no_aa.png new file mode 100644 index 0000000..acb7ef1 Binary files /dev/null and b/img/figures/no_aa.png differ diff --git a/img/figures/no_aa_fig.PNG b/img/figures/no_aa_fig.PNG new file mode 100644 index 0000000..714b411 Binary files /dev/null and b/img/figures/no_aa_fig.PNG differ diff --git a/img/figures/streamcompactclosedruntime.png b/img/figures/streamcompactclosedruntime.png new file mode 100644 index 0000000..8d7949e Binary files /dev/null and b/img/figures/streamcompactclosedruntime.png differ diff --git a/img/figures/streamcompactopenruntime.png b/img/figures/streamcompactopenruntime.png new file mode 100644 index 0000000..40ce0e7 Binary files /dev/null and b/img/figures/streamcompactopenruntime.png differ diff --git a/img/figures/streamcompactrays.png b/img/figures/streamcompactrays.png new file mode 100644 index 0000000..239fd84 Binary files /dev/null and b/img/figures/streamcompactrays.png differ diff --git a/img/figures/streamcompactraysopen.png b/img/figures/streamcompactraysopen.png new file mode 100644 index 0000000..369e564 Binary files /dev/null and b/img/figures/streamcompactraysopen.png differ diff --git a/img/renders/bsdf_weight.PNG b/img/renders/bsdf_weight.PNG new file mode 100644 index 0000000..59e2421 Binary files /dev/null and b/img/renders/bsdf_weight.PNG differ diff --git a/img/renders/cornell_561.PNG b/img/renders/cornell_561.PNG new file mode 100644 index 0000000..5f997e4 Binary files /dev/null and b/img/renders/cornell_561.PNG differ diff --git a/img/renders/depth_1.PNG b/img/renders/depth_1.PNG new file mode 100644 index 0000000..3b4a7d3 Binary files /dev/null and b/img/renders/depth_1.PNG differ diff --git a/img/renders/depth_2.PNG b/img/renders/depth_2.PNG new file mode 100644 index 0000000..b92f319 Binary files /dev/null and b/img/renders/depth_2.PNG differ diff --git a/img/renders/depth_3.PNG b/img/renders/depth_3.PNG new file mode 100644 index 0000000..73a3b71 Binary files /dev/null and b/img/renders/depth_3.PNG differ diff --git a/img/renders/depth_4.PNG b/img/renders/depth_4.PNG new file mode 100644 index 0000000..cb0d287 Binary files /dev/null and b/img/renders/depth_4.PNG differ diff --git a/img/renders/depth_5.PNG b/img/renders/depth_5.PNG new file mode 100644 index 0000000..7686bf2 Binary files /dev/null and b/img/renders/depth_5.PNG differ diff --git a/img/renders/depth_8.PNG b/img/renders/depth_8.PNG new file mode 100644 index 0000000..cfb4c61 Binary files /dev/null and b/img/renders/depth_8.PNG differ diff --git a/img/renders/depth_of_field.PNG b/img/renders/depth_of_field.PNG new file mode 100644 index 0000000..f4e17fe Binary files /dev/null and b/img/renders/depth_of_field.PNG differ diff --git a/img/renders/diffuse.PNG b/img/renders/diffuse.PNG new file mode 100644 index 0000000..10aed26 Binary files /dev/null and b/img/renders/diffuse.PNG differ diff --git a/img/renders/dragon.PNG b/img/renders/dragon.PNG new file mode 100644 index 0000000..4deebb7 Binary files /dev/null and b/img/renders/dragon.PNG differ diff --git a/img/renders/dragons.PNG b/img/renders/dragons.PNG new file mode 100644 index 0000000..61a7e2f Binary files /dev/null and b/img/renders/dragons.PNG differ diff --git a/img/renders/dragons2.PNG b/img/renders/dragons2.PNG new file mode 100644 index 0000000..52133db Binary files /dev/null and b/img/renders/dragons2.PNG differ diff --git a/img/renders/dragons2_clipped.png b/img/renders/dragons2_clipped.png new file mode 100644 index 0000000..c869dc3 Binary files /dev/null and b/img/renders/dragons2_clipped.png differ diff --git a/img/renders/hdr_no_gamma.PNG b/img/renders/hdr_no_gamma.PNG new file mode 100644 index 0000000..654133b Binary files /dev/null and b/img/renders/hdr_no_gamma.PNG differ diff --git a/img/renders/iter_1.PNG b/img/renders/iter_1.PNG new file mode 100644 index 0000000..3204f50 Binary files /dev/null and b/img/renders/iter_1.PNG differ diff --git a/img/renders/iter_10.PNG b/img/renders/iter_10.PNG new file mode 100644 index 0000000..e587a56 Binary files /dev/null and b/img/renders/iter_10.PNG differ diff --git a/img/renders/iter_100.PNG b/img/renders/iter_100.PNG new file mode 100644 index 0000000..f66ab69 Binary files /dev/null and b/img/renders/iter_100.PNG differ diff --git a/img/renders/iter_100_bsdf_mis.PNG b/img/renders/iter_100_bsdf_mis.PNG new file mode 100644 index 0000000..641d9e1 Binary files /dev/null and b/img/renders/iter_100_bsdf_mis.PNG differ diff --git a/img/renders/iter_100_light_mis.PNG b/img/renders/iter_100_light_mis.PNG new file mode 100644 index 0000000..c53c825 Binary files /dev/null and b/img/renders/iter_100_light_mis.PNG differ diff --git a/img/renders/iter_100_mis.PNG b/img/renders/iter_100_mis.PNG new file mode 100644 index 0000000..5beaf94 Binary files /dev/null and b/img/renders/iter_100_mis.PNG differ diff --git a/img/renders/iter_5.PNG b/img/renders/iter_5.PNG new file mode 100644 index 0000000..04da77b Binary files /dev/null and b/img/renders/iter_5.PNG differ diff --git a/img/renders/iter_50.PNG b/img/renders/iter_50.PNG new file mode 100644 index 0000000..a930ee6 Binary files /dev/null and b/img/renders/iter_50.PNG differ diff --git a/img/renders/light_weight.PNG b/img/renders/light_weight.PNG new file mode 100644 index 0000000..c9fbde8 Binary files /dev/null and b/img/renders/light_weight.PNG differ diff --git a/img/renders/no_aa.PNG b/img/renders/no_aa.PNG new file mode 100644 index 0000000..92a6f2d Binary files /dev/null and b/img/renders/no_aa.PNG differ diff --git a/img/renders/no_hdr_gamma.PNG b/img/renders/no_hdr_gamma.PNG new file mode 100644 index 0000000..1e3cd2d Binary files /dev/null and b/img/renders/no_hdr_gamma.PNG differ diff --git a/img/renders/no_hdr_no_gamma.PNG b/img/renders/no_hdr_no_gamma.PNG new file mode 100644 index 0000000..3e4da3c Binary files /dev/null and b/img/renders/no_hdr_no_gamma.PNG differ diff --git a/img/renders/objs.PNG b/img/renders/objs.PNG new file mode 100644 index 0000000..efdb365 Binary files /dev/null and b/img/renders/objs.PNG differ diff --git a/img/renders/objs_clipped.png b/img/renders/objs_clipped.png new file mode 100644 index 0000000..7d76d9f Binary files /dev/null and b/img/renders/objs_clipped.png differ diff --git a/img/renders/plastic.PNG b/img/renders/plastic.PNG new file mode 100644 index 0000000..d923fe8 Binary files /dev/null and b/img/renders/plastic.PNG differ diff --git a/img/renders/spec_brdf.PNG b/img/renders/spec_brdf.PNG new file mode 100644 index 0000000..e9abf8a Binary files /dev/null and b/img/renders/spec_brdf.PNG differ diff --git a/img/renders/spec_btdf.PNG b/img/renders/spec_btdf.PNG new file mode 100644 index 0000000..19c20ec Binary files /dev/null and b/img/renders/spec_btdf.PNG differ diff --git a/img/renders/spec_glass.PNG b/img/renders/spec_glass.PNG new file mode 100644 index 0000000..0e8b1d1 Binary files /dev/null and b/img/renders/spec_glass.PNG differ diff --git a/img/renders/spec_plastic.PNG b/img/renders/spec_plastic.PNG new file mode 100644 index 0000000..18bed10 Binary files /dev/null and b/img/renders/spec_plastic.PNG differ diff --git a/img/renders/teapot.PNG b/img/renders/teapot.PNG new file mode 100644 index 0000000..0f6492c Binary files /dev/null and b/img/renders/teapot.PNG differ diff --git a/img/renders/teapot_clipped.png b/img/renders/teapot_clipped.png new file mode 100644 index 0000000..0158294 Binary files /dev/null and b/img/renders/teapot_clipped.png differ diff --git a/img/renders/teapots.PNG b/img/renders/teapots.PNG new file mode 100644 index 0000000..2c24b6f Binary files /dev/null and b/img/renders/teapots.PNG differ diff --git a/img/renders/two_bunnies.PNG b/img/renders/two_bunnies.PNG new file mode 100644 index 0000000..8346e0f Binary files /dev/null and b/img/renders/two_bunnies.PNG differ diff --git a/img/renders/wireframe.PNG b/img/renders/wireframe.PNG new file mode 100644 index 0000000..f969280 Binary files /dev/null and b/img/renders/wireframe.PNG differ diff --git a/scenes/BSDFs.txt b/scenes/BSDFs.txt new file mode 100644 index 0000000..e2e5b06 --- /dev/null +++ b/scenes/BSDFs.txt @@ -0,0 +1,182 @@ +// Emissive material (light) +MATERIAL 0 +R_COLOR 20 30 40 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 1 + +// Diffuse white +MATERIAL 1 +R_COLOR 0.24 0.25 0.42 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +R_COLOR 0.14 0.12 0.1 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +R_COLOR 0.9 0.3 0.1 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +R_COLOR 0.72 0.71 0.75 +T_COLOR 0.35 0.95 0.95 +DIFFUSE_BRDF +IOR 1.8 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +R_COLOR 0.72 0.71 0.75 +T_COLOR 0.95 0.95 0.95 +SPEC_BRDF +IOR 1.55 +EMITTANCE 0 + +// Camera +CAMERA +RES 1920 1080 +FOVY 19.5 +ITERATIONS 100 +DEPTH 8 +FILE cornell +FOCAL_DISTANCE 14 +LENS_RADIUS 0.0 +EYE 0.0 -4.5 14 +LOOKAT 0 0 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +squareplane +material 0 +TRANS 0 5.45 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +squareplane +material 1 +TRANS 0 -2.5 0 +ROTAT 270 0 0 +SCALE 20 10 10 + +// Ceiling +OBJECT 2 +squareplane +material 1 +TRANS 0 7.5 0 +ROTAT 90 0 0 +SCALE 20 10 10 + +// Back wall +OBJECT 3 +squareplane +material 2 +TRANS 0 2.5 -5 +ROTAT 0 0 0 +SCALE 20 10 10 + +// Left wall +OBJECT 4 +squareplane +material 2 +TRANS -10 2.5 0 +ROTAT 0 90 0 +SCALE 10 10 10 + +// Right wall +OBJECT 5 +squareplane +material 2 +TRANS 10 2.5 0 +ROTAT 0 270 0 +SCALE 10 10 10 + +// front wall +OBJECT 6 +squareplane +material 2 +TRANS 0 2.5 5 +ROTAT 180 0 0 +SCALE 20 10 10 + +// OBJTEST +OBJECT 7 +mesh +../scenes/BSDFs/coney.obj +material 4 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// OBJTEST +OBJECT 8 +cube +material 3 +TRANS 6 -1 -3 +ROTAT 0 0 0 +SCALE 3 3 3 + +// OBJTEST +OBJECT 9 +cube +material 3 +TRANS -6 -1 -3 +ROTAT 0 0 0 +SCALE 3 3 3 + +// OBJTEST +OBJECT 10 +cube +material 3 +TRANS 6 -1.5 -0.25 +ROTAT 0 0 0 +SCALE 2 2 2 + +// OBJTEST +OBJECT 11 +cube +material 3 +TRANS -6 -1.5 -0.25 +ROTAT 0 0 0 +SCALE 2 2 2 + +// OBJTEST +OBJECT 12 +cube +material 3 +TRANS 6 -2.0 1.5 +ROTAT 0 0 0 +SCALE 1 1 1 + +// OBJTEST +OBJECT 13 +cube +material 3 +TRANS -6 -2.0 1.5 +ROTAT 0 0 0 +SCALE 1 1 1 + +// OBJTEST +OBJECT 14 +cube +material 3 +TRANS 0 0 -5 +ROTAT 0 0 0 +SCALE 7 5 0.5 \ No newline at end of file diff --git a/scenes/cornell.txt b/scenes/cornell.txt index 83ff820..ca22866 100644 --- a/scenes/cornell.txt +++ b/scenes/cornell.txt @@ -1,16 +1,16 @@ // Emissive material (light) MATERIAL 0 -RGB 1 1 1 +RGB 40 40 40 SPECEX 0 SPECRGB 0 0 0 REFL 0 REFR 0 REFRIOR 0 -EMITTANCE 5 +EMITTANCE 1 // Diffuse white MATERIAL 1 -RGB .98 .98 .98 +RGB 0.85 0.81 0.78 SPECEX 0 SPECRGB 0 0 0 REFL 0 @@ -20,7 +20,7 @@ EMITTANCE 0 // Diffuse red MATERIAL 2 -RGB .85 .35 .35 +RGB 0.63 0.065 0.05 SPECEX 0 SPECRGB 0 0 0 REFL 0 @@ -30,7 +30,7 @@ EMITTANCE 0 // Diffuse green MATERIAL 3 -RGB .35 .85 .35 +RGB 0.14 0.45 0.091 SPECEX 0 SPECRGB 0 0 0 REFL 0 @@ -40,77 +40,101 @@ EMITTANCE 0 // Specular white MATERIAL 4 -RGB .98 .98 .98 +RGB 0.85 0.81 0.78 SPECEX 0 -SPECRGB .98 .98 .98 -REFL 1 +SPECRGB 0 0 0 +REFL 0 REFR 0 REFRIOR 0 EMITTANCE 0 // Camera CAMERA -RES 800 800 -FOVY 45 +RES 1024 1024 +FOVY 19.5 ITERATIONS 5000 -DEPTH 8 +DEPTH 5 FILE cornell -EYE 0.0 5 10.5 -LOOKAT 0 5 0 +EYE 0.0 2.5 18 +LOOKAT 0 2.5 0 UP 0 1 0 // Ceiling light OBJECT 0 -cube +squareplane material 0 -TRANS 0 10 0 -ROTAT 0 0 0 -SCALE 3 .3 3 +TRANS 0 7.45 0 +ROTAT 90 0 0 +SCALE 3 3 1 // Floor OBJECT 1 -cube +squareplane material 1 -TRANS 0 0 0 -ROTAT 0 0 0 -SCALE 10 .01 10 +TRANS 0 -2.5 0 +ROTAT 270 0 0 +SCALE 10 10 10 // Ceiling OBJECT 2 -cube +squareplane material 1 -TRANS 0 10 0 -ROTAT 0 0 90 -SCALE .01 10 10 +TRANS 0 7.5 0 +ROTAT 90 0 0 +SCALE 10 10 10 // Back wall OBJECT 3 -cube +squareplane material 1 -TRANS 0 5 -5 -ROTAT 0 90 0 -SCALE .01 10 10 +TRANS 0 2.5 -5 +ROTAT 0 0 0 +SCALE 10 10 10 // Left wall OBJECT 4 -cube +squareplane material 2 -TRANS -5 5 0 -ROTAT 0 0 0 -SCALE .01 10 10 +TRANS -5 2.5 0 +ROTAT 0 90 0 +SCALE 10 10 10 // Right wall OBJECT 5 -cube +squareplane material 3 -TRANS 5 5 0 -ROTAT 0 0 0 -SCALE .01 10 10 +TRANS 5 2.5 0 +ROTAT 0 270 0 +SCALE 10 10 10 -// Sphere +// front wall OBJECT 6 -sphere +squareplane +material 1 +TRANS 0 2.5 5 +ROTAT 180 0 0 +SCALE 10 10 10 + +// long cube +OBJECT 7 +cube +material 4 +TRANS -2 0 -3 +ROTAT 0 27.5 0 +SCALE 3 6 3 + +// short cube +OBJECT 8 +cube +material 4 +TRANS 2 -1 -0.75 +ROTAT 0 -17.5 0 +SCALE 3 3 3 + + +// OBJTEST +OBJ_OBJECT 9 ../scenes/bunny.obj material 4 TRANS -1 4 -1 ROTAT 0 0 0 diff --git a/scenes/cornell_bunny.txt b/scenes/cornell_bunny.txt new file mode 100644 index 0000000..06ee22a --- /dev/null +++ b/scenes/cornell_bunny.txt @@ -0,0 +1,188 @@ +// Emissive material (light) +MATERIAL 0 +R_COLOR 1 1 1 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 1 + +// Diffuse white +MATERIAL 1 +R_COLOR 0.85 0.81 0.78 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +R_COLOR 0.63 0.265 0.05 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +R_COLOR 0.14 0.45 0.391 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +R_COLOR 0.4 0.25 0.74 +T_COLOR 0.21 0.45 0.8 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +R_COLOR 0.67 0.85 0.2 +T_COLOR 1 0.4 0.1 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Emissive material (light) +MATERIAL 6 +R_COLOR 40 40 40 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 1 + +// Camera +CAMERA +RES 1024 1024 +FOVY 19.5 +ITERATIONS 5000 +DEPTH 4 +FILE cornell +FOCAL_DISTANCE 5.0 +LENS_RADIUS 0 +EYE 0.0 2.5 18 +LOOKAT 0 2.5 0 +UP 0 1 0 + +// Ceiling light +OBJECT 0 +squareplane +material 0 +TRANS 0 7.45 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +squareplane +material 1 +TRANS 0 -2.5 0 +ROTAT 270 0 0 +SCALE 10 10 10 + +// Ceiling +OBJECT 2 +squareplane +material 1 +TRANS 0 7.5 0 +ROTAT 90 0 0 +SCALE 10 10 10 + +// Back wall +OBJECT 3 +squareplane +material 1 +TRANS 0 2.5 -5 +ROTAT 0 0 0 +SCALE 10 10 10 + +// Left wall +OBJECT 4 +squareplane +material 2 +TRANS -5 2.5 0 +ROTAT 0 90 0 +SCALE 10 10 10 + +// Right wall +OBJECT 5 +squareplane +material 3 +TRANS 5 2.5 0 +ROTAT 0 270 0 +SCALE 10 10 10 + +// front wall +OBJECT 6 +squareplane +material 1 +TRANS 0 2.5 5 +ROTAT 180 0 0 +SCALE 10 10 10 + +// OBJTEST +OBJECT 7 +sphere +material 4 +TRANS -2 0 0 +ROTAT 0 0 0 +SCALE 3 3 3 + +// OBJTEST +OBJECT 8 +sphere +material 5 +TRANS 2 0 0 +ROTAT 0 0 0 +SCALE 3 3 3 + +// Ceiling light +OBJECT 9 +squareplane +material 6 +TRANS -4 4.45 0 +ROTAT 90 0 0 +SCALE 1 1 1 + +// Ceiling light +OBJECT 10 +squareplane +material 6 +TRANS 4 4.45 0 +ROTAT 90 0 0 +SCALE 1 1 1 + +// Ceiling light +OBJECT 11 +squareplane +material 6 +TRANS -4 4.45 -4 +ROTAT 90 0 0 +SCALE 1 1 1 + +// Ceiling light +OBJECT 12 +squareplane +material 6 +TRANS 4 4.45 -4 +ROTAT 90 0 0 +SCALE 1 1 1 + +// Ceiling light +OBJECT 13 +squareplane +material 6 +TRANS -4 4.45 4 +ROTAT 90 0 0 +SCALE 1 1 1 + +// Ceiling light +OBJECT 14 +squareplane +material 6 +TRANS 4 4.45 4 +ROTAT 90 0 0 +SCALE 1 1 1 \ No newline at end of file diff --git a/scenes/cornell_tex.txt b/scenes/cornell_tex.txt new file mode 100644 index 0000000..7fe23a8 --- /dev/null +++ b/scenes/cornell_tex.txt @@ -0,0 +1,133 @@ +// Emissive material (light) +MATERIAL 0 +R_COLOR 40 40 40 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 1 + +// Diffuse white +MATERIAL 1 +R_COLOR 0.85 0.81 0.78 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +R_COLOR 0.63 0.265 0.05 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +R_COLOR 0.14 0.45 0.391 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +R_COLOR 0.4 0.25 0.74 +T_COLOR 0.85 0.85 0.85 +DIFFUSE_BRDF +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +R_COLOR 0.67 0.85 0.2 +T_COLOR 1 0.4 0.1 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Emissive material (light) +MATERIAL 6 +R_COLOR 40 40 40 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 1 + +// Camera +CAMERA +RES 1024 1024 +FOVY 19.5 +ITERATIONS 5000 +DEPTH 8 +FILE cornell +FOCAL_DISTANCE 5.0 +LENS_RADIUS 0 +EYE 0.0 2.5 18 +LOOKAT 0 2.5 0 +UP 0 1 0 + +// Ceiling light +OBJECT 0 +squareplane +material 0 +TRANS 0 7.45 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +squareplane +material 1 +TRANS 0 -2.5 0 +ROTAT 270 0 0 +SCALE 10 10 10 + +// Ceiling +OBJECT 2 +squareplane +material 1 +TRANS 0 7.5 0 +ROTAT 90 0 0 +SCALE 10 10 10 + +// Back wall +OBJECT 3 +squareplane +material 1 +TRANS 0 2.5 -5 +ROTAT 0 0 0 +SCALE 10 10 10 + +// Left wall +OBJECT 4 +squareplane +material 2 +TRANS -5 2.5 0 +ROTAT 0 90 0 +SCALE 10 10 10 + +// Right wall +OBJECT 5 +squareplane +material 3 +TRANS 5 2.5 0 +ROTAT 0 270 0 +SCALE 10 10 10 + +// front wall +OBJECT 6 +squareplane +material 1 +TRANS 0 2.5 5 +ROTAT 180 0 0 +SCALE 10 10 10 + +// OBJTEST +OBJECT 7 +mesh +../scenes/bunny.obj +material 4 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 \ No newline at end of file diff --git a/scenes/dragons.txt b/scenes/dragons.txt new file mode 100644 index 0000000..bc72dd0 --- /dev/null +++ b/scenes/dragons.txt @@ -0,0 +1,199 @@ +// Emissive material (light) +MATERIAL 0 +R_COLOR 5 10 20 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 5 + +// Diffuse white +MATERIAL 1 +R_COLOR 0.29 0.27 0.28 +T_COLOR 0.85 0.85 0.85 +DIFFUSE_BRDF +IOR 1.55 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +R_COLOR 0.63 0.265 0.05 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +R_COLOR 0.14 0.45 0.391 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// diffuse +MATERIAL 4 +R_COLOR 0.4 0.25 0.74 +T_COLOR 0.85 0.85 0.85 +DIFFUSE_BRDF +IOR 1.55 +EMITTANCE 0 + +// glass +MATERIAL 5 +R_COLOR 0.61 0.95 0.64 +T_COLOR 0.95 0.91 0.65 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// plastic +MATERIAL 6 +R_COLOR 0.84 0.31 0.21 +T_COLOR 0.85 0.85 0.85 +SPEC_PLASTIC +IOR 1.55 +EMITTANCE 0 + +// Emissive material (light) +MATERIAL 7 +R_COLOR 20 10 5 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 7 + +// Camera +CAMERA +RES 1920 1080 +FOVY 19.5 +ITERATIONS 20000 +DEPTH 16 +FILE cornell +FOCAL_DISTANCE 17.9 +LENS_RADIUS 1.5 +EYE 0.0 2.5 18.0 +LOOKAT 0 2.5 0 +UP 0 1 0 + +// Ceiling light +OBJECT 0 +squareplane +material 7 +TRANS 0 3.45 -3.5 +ROTAT 60 0 0 +SCALE 2 2 1 + +// Floor +OBJECT 1 +squareplane +material 1 +TRANS 0 -2.5 0 +ROTAT 270 0 0 +SCALE 40 10 10 + +// Ceiling +OBJECT 2 +squareplane +material 1 +TRANS 0 7.5 0 +ROTAT 90 0 0 +SCALE 40 10 10 + +// Back wall +OBJECT 3 +squareplane +material 1 +TRANS 0 2.5 -5 +ROTAT 0 0 0 +SCALE 40 10 10 + +// Left wall +OBJECT 4 +squareplane +material 2 +TRANS -11.5 2.5 0 +ROTAT 0 90 0 +SCALE 10 10 10 + +// Right wall +OBJECT 5 +squareplane +material 3 +TRANS 11.5 2.5 0 +ROTAT 0 270 0 +SCALE 10 10 10 + +// front wall +OBJECT 6 +squareplane +material 1 +TRANS 0 2.5 5 +ROTAT 180 0 0 +SCALE 40 10 10 + +// OBJTEST +OBJECT 7 +mesh +../scenes/dragon.obj +material 5 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// OBJTEST +OBJECT 8 +mesh +../scenes/dragon_left.obj +material 4 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// OBJTEST +OBJECT 9 +mesh +../scenes/dragon_right.obj +material 6 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// Ceiling light +OBJECT 10 +squareplane +material 0 +TRANS -8 3.45 -3.5 +ROTAT 60 0 0 +SCALE 2 2 1 + +// Ceiling light +OBJECT 11 +squareplane +material 0 +TRANS 8 3.45 -3.5 +ROTAT 60 0 0 +SCALE 2 2 1 + +// Ceiling light +OBJECT 12 +squareplane +material 7 +TRANS 0 3.45 3.5 +ROTAT 120 0 0 +SCALE 2 2 1 + +// Ceiling light +OBJECT 13 +squareplane +material 0 +TRANS -8 3.75 3.5 +ROTAT 120 0 0 +SCALE 2 2 1 + +// Ceiling light +OBJECT 14 +squareplane +material 0 +TRANS 8 3.45 3.5 +ROTAT 120 0 0 +SCALE 2 2 1 \ No newline at end of file diff --git a/scenes/performance.txt b/scenes/performance.txt new file mode 100644 index 0000000..a543430 --- /dev/null +++ b/scenes/performance.txt @@ -0,0 +1,234 @@ +// Emissive material (light) +MATERIAL 0 +R_COLOR 40 40 40 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 1 + +// Diffuse white +MATERIAL 1 +R_COLOR 0.74 0.65 0.62 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +R_COLOR 0.54 0.91 0.61 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +R_COLOR 0.89 0.67 0.81 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +R_COLOR 0.96 0.85 0.57 +T_COLOR 0.56 0.65 0.97 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +R_COLOR 0.3 0.8 0.9 +T_COLOR 0.46 0.55 0.97 +DIFFUSE_BDRF +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 6 +R_COLOR 0.85 0.9 0.95 +T_COLOR 0.46 0.55 0.97 +SPEC_BRDF +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 7 +R_COLOR 0.95 0.71 0.33 +T_COLOR 0.95 0.95 0.95 +SPEC_PLASTIC +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 8 +R_COLOR 0.81 0.91 0.66 +T_COLOR 0.95 0.75 0.65 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 9 +R_COLOR 0.95 0.71 0.33 +T_COLOR 0.54 0.91 0.61 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 10 +R_COLOR 0.95 0.71 0.33 +T_COLOR 0.89 0.67 0.81 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Camera +CAMERA +RES 1920 1080 +FOVY 19.5 +ITERATIONS 5000 +DEPTH 16 +FILE cornell +FOCAL_DISTANCE 17.9 +LENS_RADIUS 0.0 +EYE 0.0 5.0 17 +LOOKAT 0 2.5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +squareplane +material 0 +TRANS 0 7.45 -3 +ROTAT 90 0 0 +SCALE 6 3 1 + +// Floor +OBJECT 1 +squareplane +material 1 +TRANS 0 -2.5 0 +ROTAT 270 0 0 +SCALE 20 10 10 + +// Ceiling +OBJECT 2 +squareplane +material 1 +TRANS 0 7.5 0 +ROTAT 90 0 0 +SCALE 20 10 10 + +// Back wall +OBJECT 3 +squareplane +material 1 +TRANS 0 2.5 -5 +ROTAT 0 0 0 +SCALE 20 10 10 + +// Left wall +OBJECT 4 +squareplane +material 2 +TRANS -10 2.5 0 +ROTAT 0 90 0 +SCALE 10 10 10 + +// Right wall +OBJECT 5 +squareplane +material 3 +TRANS 10 2.5 0 +ROTAT 0 270 0 +SCALE 10 10 10 + +// front wall +OBJECT 6 +squareplane +material 1 +TRANS 0 2.5 5 +ROTAT 180 0 0 +SCALE 20 10 10 + +// OBJTEST +OBJECT 7 +mesh +../scenes/performance/happy.obj +material 6 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// OBJTEST +OBJECT 8 +mesh +../scenes/performance/horse.obj +material 5 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// OBJTEST +OBJECT 9 +mesh +../scenes/performance/spot.obj +material 7 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// OBJTEST +OBJECT 10 +mesh +../scenes/performance/suzanne.obj +material 8 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// OBJTEST +OBJECT 11 +mesh +../scenes/performance/lucy.obj +material 4 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// long cube +OBJECT 12 +cube +material 3 +TRANS -8 0 -3 +ROTAT 0 27.5 0 +SCALE 3 6 3 + +// short cube +OBJECT 13 +cube +material 2 +TRANS 8 -1 -0.75 +ROTAT 0 -17.5 0 +SCALE 3 3 3 + +// short cube +OBJECT 14 +sphere +material 10 +TRANS -8 4.5 -3 +ROTAT 0 -17.5 0 +SCALE 3 3 3 + +// short cube +OBJECT 15 +sphere +material 9 +TRANS 8 2 -0.75 +ROTAT 0 -17.5 0 +SCALE 3 3 3 \ No newline at end of file diff --git a/scenes/performance_test.txt b/scenes/performance_test.txt new file mode 100644 index 0000000..5ce10bf --- /dev/null +++ b/scenes/performance_test.txt @@ -0,0 +1,191 @@ +// Emissive material (light) +MATERIAL 0 +R_COLOR 40 20 10 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 2 + +// Diffuse white +MATERIAL 1 +R_COLOR 0.24 0.25 0.22 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +R_COLOR 0.34 0.45 0.66 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +R_COLOR 0.24 0.25 0.22 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +R_COLOR 0.46 0.98 0.65 +T_COLOR 0.77 0.91 0.75 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +R_COLOR 0.3 0.8 0.9 +T_COLOR 0.46 0.55 0.97 +DIFFUSE_BDRF +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 6 +R_COLOR 0.85 0.9 0.95 +T_COLOR 0.46 0.55 0.97 +SPEC_BRDF +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 7 +R_COLOR 0.95 0.71 0.33 +T_COLOR 0.95 0.95 0.95 +SPEC_PLASTIC +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 8 +R_COLOR 0.81 0.91 0.66 +T_COLOR 0.95 0.75 0.65 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 9 +R_COLOR 0.95 0.71 0.33 +T_COLOR 0.54 0.91 0.61 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 10 +R_COLOR 0.95 0.71 0.33 +T_COLOR 0.89 0.67 0.81 +SPEC_GLASS +IOR 1.55 +EMITTANCE 0 + +// Camera +CAMERA +RES 1920 1080 +FOVY 19.5 +ITERATIONS 20000 +DEPTH 16 +FILE cornell +FOCAL_DISTANCE 17.9 +LENS_RADIUS 0.0 +EYE 0.0 -4.5 14 +LOOKAT 0 0 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +squareplane +material 0 +TRANS -4.85 0 -2 +ROTAT 0 60 0 +SCALE 3 2 1 + +// Floor +OBJECT 1 +squareplane +material 1 +TRANS 0 -2.5 0 +ROTAT 270 0 0 +SCALE 20 10 10 + +// Ceiling +OBJECT 2 +squareplane +material 1 +TRANS 0 7.5 0 +ROTAT 90 0 0 +SCALE 20 10 10 + +// Back wall +OBJECT 3 +squareplane +material 1 +TRANS 0 2.5 -5 +ROTAT 0 0 0 +SCALE 20 10 10 + +// Left wall +OBJECT 4 +squareplane +material 2 +TRANS -10 2.5 0 +ROTAT 0 90 0 +SCALE 10 10 10 + +// Right wall +OBJECT 5 +squareplane +material 2 +TRANS 10 2.5 0 +ROTAT 0 270 0 +SCALE 10 10 10 + +// front wall +OBJECT 6 +squareplane +material 1 +TRANS 0 2.5 5 +ROTAT 180 0 0 +SCALE 20 10 10 + +// OBJTEST +OBJECT 7 +mesh +../scenes/performance_test/teapot.obj +material 4 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// OBJTEST +OBJECT 8 +cube +material 2 +TRANS 7 -1.9 -3 +ROTAT 0 0 0 +SCALE 3 1.5 3 + +// OBJTEST +OBJECT 9 +mesh +../scenes/performance_test/teapot_2.obj +material 4 +TRANS 0 2 0 +ROTAT 0 0 0 +SCALE 6 6 6 + +// OBJTEST +OBJECT 10 +sphere +material 2 +TRANS -6 -1 2 +ROTAT 0 0 0 +SCALE 3 3 3 \ No newline at end of file diff --git a/scenes/plastic_scene.txt b/scenes/plastic_scene.txt new file mode 100644 index 0000000..d5ee4e4 --- /dev/null +++ b/scenes/plastic_scene.txt @@ -0,0 +1,197 @@ +// Emissive material (light) +MATERIAL 0 +R_COLOR 40 40 40 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 1 + +// Diffuse white +MATERIAL 1 +R_COLOR 0.85 0.81 0.78 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +R_COLOR 0.63 0.065 0.05 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +R_COLOR 0.14 0.45 0.091 +T_COLOR 0 0 0 +DIFFUSE_BRDF +IOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +R_COLOR 0.1 0.6 1 +T_COLOR 0.95 0.95 0.95 +SPEC_PLASTIC +IOR 1.65 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +R_COLOR 1 0.4 0.1 +T_COLOR 0.95 0.95 0.95 +SPEC_PLASTIC +IOR 1.65 +EMITTANCE 0 + +// Specular white +MATERIAL 6 +R_COLOR 0.1 0.6 1 +T_COLOR 0 0 0 +SPEC_BRDF +IOR 1.55 +EMITTANCE 0 + +// Specular white +MATERIAL 7 +R_COLOR 1 0.4 0.1 +T_COLOR 0 0 0 +SPEC_BRDF +IOR 1.55 +EMITTANCE 0 + +// Camera +CAMERA +RES 1024 1024 +FOVY 19.5 +ITERATIONS 5000 +DEPTH 8 +FILE cornell +FOCAL_DISTANCE 16.0 +LENS_RADIUS 0.0 +EYE 0.0 2.5 18 +LOOKAT 0 2.5 0 +UP 0 1 0 + +// Ceiling light +OBJECT 0 +squareplane +material 0 +TRANS 0 7.45 0 +ROTAT 90 0 0 +SCALE 3 3 1 + +// Floor +OBJECT 1 +squareplane +material 1 +TRANS 0 -2.5 0 +ROTAT 270 0 0 +SCALE 10 10 10 + +// Ceiling +OBJECT 2 +squareplane +material 1 +TRANS 0 7.5 0 +ROTAT 90 0 0 +SCALE 10 10 10 + +// Back wall +OBJECT 3 +squareplane +material 1 +TRANS 0 2.5 -5 +ROTAT 0 0 0 +SCALE 10 10 10 + +// Left wall +OBJECT 4 +squareplane +material 2 +TRANS -5 2.5 0 +ROTAT 0 90 0 +SCALE 10 10 10 + +// Right wall +OBJECT 5 +squareplane +material 3 +TRANS 5 2.5 0 +ROTAT 0 270 0 +SCALE 10 10 10 + +// front wall +OBJECT 6 +squareplane +material 1 +TRANS 0 2.5 5 +ROTAT 180 0 0 +SCALE 10 10 10 + +// sphere +OBJECT 7 +sphere +material 4 +TRANS 3 -1.0 3 +ROTAT 0 0 0 +SCALE 3 3 3 + +// sphere +OBJECT 8 +sphere +material 4 +TRANS 3 1.5 3 +ROTAT 0 0 0 +SCALE 2 2 2 + +// sphere +OBJECT 9 +sphere +material 4 +TRANS 3 3 3 +ROTAT 0 0 0 +SCALE 1 1 1 + +// sphere +OBJECT 10 +sphere +material 5 +TRANS -3 -1.0 -3 +ROTAT 0 0 0 +SCALE 3 3 3 + +// sphere +OBJECT 11 +sphere +material 5 +TRANS -3 1.5 -3 +ROTAT 0 0 0 +SCALE 2 2 2 + +// sphere +OBJECT 12 +sphere +material 5 +TRANS -3 3 -3 +ROTAT 0 0 0 +SCALE 1 1 1 + +// sphere +OBJECT 13 +sphere +material 6 +TRANS -0.5 -1.75 2 +ROTAT 0 0 0 +SCALE 1.5 1.5 1.5 + +// sphere +OBJECT 14 +sphere +material 7 +TRANS 0.5 -1.75 -2 +ROTAT 0 0 0 +SCALE 1.5 1.5 1.5 + diff --git a/src/interactions.h b/src/interactions.h index f969e45..3bdd637 100644 --- a/src/interactions.h +++ b/src/interactions.h @@ -1,6 +1,10 @@ #pragma once #include "intersections.h" +#include + + +//#define USE_SCHLICK_APPROX // CHECKITOUT /** @@ -9,8 +13,7 @@ */ __host__ __device__ glm::vec3 calculateRandomDirectionInHemisphere( - glm::vec3 normal, thrust::default_random_engine &rng) { - thrust::uniform_real_distribution u01(0, 1); + glm::vec3 normal, thrust::default_random_engine &rng, thrust::uniform_real_distribution& u01) { float up = sqrt(u01(rng)); // cos(theta) float over = sqrt(1 - up * up); // sin(theta) @@ -41,39 +44,202 @@ glm::vec3 calculateRandomDirectionInHemisphere( + sin(around) * over * perpendicularDirection2; } -/** - * Scatter a ray with some probabilities according to the material properties. - * For example, a diffuse surface scatters in a cosine-weighted hemisphere. - * A perfect specular surface scatters in the reflected ray direction. - * In order to apply multiple effects to one surface, probabilistically choose - * between them. - * - * The visual effect you want is to straight-up add the diffuse and specular - * components. You can do this in a few ways. This logic also applies to - * combining other types of materias (such as refractive). - * - * - Always take an even (50/50) split between a each effect (a diffuse bounce - * and a specular bounce), but divide the resulting color of either branch - * by its probability (0.5), to counteract the chance (0.5) of the branch - * being taken. - * - This way is inefficient, but serves as a good starting point - it - * converges slowly, especially for pure-diffuse or pure-specular. - * - Pick the split based on the intensity of each material color, and divide - * branch result by that branch's probability (whatever probability you use). - * - * This method applies its changes to the Ray parameter `ray` in place. - * It also modifies the color `color` of the ray in place. - * - * You may need to change the parameter list for your purposes! - */ +// Using information from PBRT +// https://www.pbr-book.org/3ed-2018/Reflection_Models/Specular_Reflection_and_Transmission +// and https://en.wikipedia.org/wiki/Schlick%27s_approximation +// and https://en.wikipedia.org/wiki/Fresnel_equations + +__host__ __device__ glm::vec3 fresnelDielectric(float cos_theta_i, float etaT) { + + // assume scene medium is air + float etaI = 1.0f; + + cos_theta_i = glm::clamp(cos_theta_i, -1.0f, 1.0f); + + // flip the indices of refraction if exiting the medium + if (cos_theta_i > 0.0f) { + float tmp = etaI; + etaI = etaT; + etaT = tmp; + + } + cos_theta_i = glm::abs(cos_theta_i); + +#ifdef USE_SCHLICK_APPROX + // schlicks approximation + + float r = (etaI - etaT) / (etaI + etaT); + float R_0 = r * r; + float Fr = R_0 + (1.0f - R_0) * glm::pow((1.0f - cos_theta_i), 5.0f); + return glm::vec3(Fr); +#else + // physically based fresnel term + + // need to solve for cos(thetaT) + // use sin^2 = 1 - cos^2 + float sinThetaI = glm::sqrt(1.0f - cos_theta_i * cos_theta_i); + + // snells law sin(thetaT) = n1/n2 * sin(thetaI) + float sinThetaT = etaI / etaT * sinThetaI; + + // total internal reflection + if (sinThetaT >= 0.999f) { + return glm::vec3(1.0f); + } + + float cos_theta_t = glm::sqrt(1.0f - sinThetaT * sinThetaT); + + float eta_t_cos_theta_i = etaT * cos_theta_i; + float eta_i_cos_theta_i = etaI * cos_theta_i; + float eta_t_cos_theta_t = etaT * cos_theta_t; + float eta_i_cos_theta_t = etaI * cos_theta_t; + + // light polarization equations + float Rparl = ((eta_t_cos_theta_i) - (eta_i_cos_theta_t)) / ((eta_t_cos_theta_i) + (eta_i_cos_theta_t)); + float Rperp = ((eta_i_cos_theta_i) - (eta_t_cos_theta_t)) / ((eta_i_cos_theta_i) + (eta_t_cos_theta_t)); + return glm::vec3((Rparl * Rparl + Rperp * Rperp) / 2.0f); +#endif +} + __host__ __device__ void scatterRay( - PathSegment & pathSegment, + PathSegment& r, glm::vec3 intersect, glm::vec3 normal, const Material &m, thrust::default_random_engine &rng) { - // TODO: implement this. - // A basic implementation of pure-diffuse shading will just call the - // calculateRandomDirectionInHemisphere defined above. + + glm::vec3 wi = glm::vec3(0.0f); + glm::vec3 f = glm::vec3(0.0f); + float pdf = 0.0f; + float absDot = 0.0f; + + thrust::uniform_real_distribution u01(0, 1); + + // Physically based BSDF sampling influenced by PBRT + // https://www.pbr-book.org/3ed-2018/Reflection_Models/Specular_Reflection_and_Transmission + // https://www.pbr-book.org/3ed-2018/Reflection_Models/Lambertian_Reflection + + if (m.type == SPEC_BRDF) { + wi = glm::reflect(r.ray.direction, normal); + absDot = glm::abs(glm::dot(normal, wi)); + pdf = 1.0f; + if (absDot >= -0.0001f && absDot <= -0.0001f) { + f = m.R; + } + else { + f = m.R / absDot; + } + } + else if (m.type == SPEC_BTDF) { + // spec refl + float eta = m.ior; + if (glm::dot(normal, r.ray.direction) < 0.0001f) { + // outside + eta = 1.0f / eta; + wi = glm::refract(r.ray.direction, normal, eta); + } + else { + // inside + wi = glm::refract(r.ray.direction, -normal, eta); + } + absDot = glm::abs(glm::dot(normal, wi)); + pdf = 1.0f; + if (glm::length(wi) <= 0.0001f) { + // total internal reflection + f = glm::vec3(0.0f); + } + else if (absDot >= -0.0001f && absDot <= -0.0001f) { + f = m.T; + } + else { + f = m.T / absDot; + } + } + else if (m.type == SPEC_GLASS) { + // spec glass + float eta = m.ior; + if (u01(rng) < 0.5f) { + // spec refl + wi = glm::reflect(r.ray.direction, normal); + absDot = glm::abs(glm::dot(normal, wi)); + pdf = 1.0f; + if (absDot == 0.0f) { + f = m.R; + } + else { + f = m.R / absDot; + } + f *= fresnelDielectric(glm::dot(normal, r.ray.direction), m.ior); + } + else { + // spec refr + if (glm::dot(normal, r.ray.direction) < 0.0f) { + // outside + eta = 1.0f / eta; + wi = glm::refract(r.ray.direction, normal, eta); + } + else { + // inside + wi = glm::refract(r.ray.direction, -normal, eta); + } + absDot = glm::abs(glm::dot(normal, wi)); + pdf = 1.0f; + if (glm::length(wi) <= 0.0001f) { + // total internal reflection + f = glm::vec3(0.0f); + } + if (absDot == 0.0f) { + f = m.T; + } + else { + f = m.T / absDot; + } + f *= glm::vec3(1.0f) - fresnelDielectric(glm::dot(normal, r.ray.direction), m.ior); + } + f *= 2.0f; + } + else if (m.type == SPEC_PLASTIC) { + // spec plastic + if (u01(rng) < 0.5f) { + // diffuse + wi = glm::normalize(calculateRandomDirectionInHemisphere(normal, rng, u01)); + absDot = glm::abs(glm::dot(normal, wi)); + pdf = absDot * 0.31831f; + f = m.R * 0.31831f; + f *= glm::vec3(1.0f) - fresnelDielectric(glm::dot(normal, r.ray.direction), m.ior); + } + else { + // spec refl + wi = glm::reflect(r.ray.direction, normal); + absDot = glm::abs(glm::dot(normal, wi)); + pdf = 1.0f; + if (absDot == 0.0f) { + f = m.T; + } + else { + f = m.T / absDot; + } + f *= fresnelDielectric(glm::dot(normal, r.ray.direction), m.ior); + } + f *= 2.0f; + } + else if (m.type == MIRCROFACET_BRDF) { + + } + else { + // diffuse + wi = glm::normalize(calculateRandomDirectionInHemisphere(normal, rng, u01)); + absDot = glm::abs(glm::dot(normal, wi)); + pdf = absDot * 0.31831f; + f = m.R * 0.31831f; + } + + r.rayThroughput *= f * absDot / pdf; + + // Change ray direction + r.ray.direction = wi; + r.ray.direction_inv = glm::vec3(1.0f / wi.x, 1.0f / wi.y, 1.0f / wi.z); + r.ray.origin = intersect + (wi * 0.001f); } + diff --git a/src/intersections.h b/src/intersections.h index b150407..963b1ab 100644 --- a/src/intersections.h +++ b/src/intersections.h @@ -6,6 +6,9 @@ #include "sceneStructs.h" #include "utilities.h" +#define MIN_INTERSECT_DIST 0.0001f +#define MAX_INTERSECT_DIST 10000.0f + /** * Handy-dandy hash function that provides seeds for random number generation. */ @@ -25,7 +28,7 @@ __host__ __device__ inline unsigned int utilhash(unsigned int a) { * Falls slightly short so that it doesn't intersect the object it's hitting. */ __host__ __device__ glm::vec3 getPointOnRay(Ray r, float t) { - return r.origin + (t - .0001f) * glm::normalize(r.direction); + return r.origin + t * glm::normalize(r.direction); } /** @@ -35,6 +38,24 @@ __host__ __device__ glm::vec3 multiplyMV(glm::mat4 m, glm::vec4 v) { return glm::vec3(m * v); } +__host__ __device__ float squareplaneIntersectionTest(Geom& squareplane, Ray& r, glm::vec3& normal) { + Ray q; + q.origin = multiplyMV(squareplane.inverseTransform, glm::vec4(r.origin, 1.0f)); + q.direction = glm::normalize(multiplyMV(squareplane.inverseTransform, glm::vec4(r.direction, 0.0f))); + + float t = glm::dot(glm::vec3(0.0f, 0.0f, 1.0f), (glm::vec3(0.5f, 0.5f, 0.0f) - q.origin)) / glm::dot(glm::vec3(0.0f, 0.0f, 1.0f), q.direction); + glm::vec3 objspaceIntersection = getPointOnRay(q, t); + + if (t > 0.0001f && objspaceIntersection.x >= -0.5001f && objspaceIntersection.x <= 0.5001f && objspaceIntersection.y >= -0.5001f && objspaceIntersection.y <= 0.5001f) { + glm::vec3 intersectionPoint = multiplyMV(squareplane.transform, glm::vec4(objspaceIntersection, 1.0f)); + normal = glm::normalize(multiplyMV(squareplane.invTranspose, glm::vec4(0.0f, 0.0f, 1.0f, 0.0f))); + return glm::length(r.origin - intersectionPoint); + } + + return MAX_INTERSECT_DIST; +} + + // CHECKITOUT /** * Test intersection between a ray and a transformed cube. Untransformed, @@ -45,8 +66,7 @@ __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(Geom &box, Ray &r, glm::vec3 &normal) { 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))); @@ -63,8 +83,8 @@ __host__ __device__ float boxIntersectionTest(Geom box, Ray r, float ta = glm::min(t1, t2); float tb = glm::max(t1, t2); glm::vec3 n; - n[xyz] = t2 < t1 ? +1 : -1; - if (ta > 0 && ta > tmin) { + n[xyz] = t2 < t1 ? +1.0f : -1.0f; + if (ta > 0.0f && ta > tmin) { tmin = ta; tmin_n = n; } @@ -75,18 +95,16 @@ __host__ __device__ float boxIntersectionTest(Geom box, Ray r, } } - if (tmax >= tmin && tmax > 0) { - outside = true; - if (tmin <= 0) { + if (tmax >= tmin && tmax > 0.0f) { + if (tmin <= 0.0f) { tmin = tmax; tmin_n = tmax_n; - outside = false; } - intersectionPoint = multiplyMV(box.transform, glm::vec4(getPointOnRay(q, tmin), 1.0f)); + glm::vec3 intersectionPoint = multiplyMV(box.transform, glm::vec4(getPointOnRay(q, tmin), 1.0f)); normal = glm::normalize(multiplyMV(box.invTranspose, glm::vec4(tmin_n, 0.0f))); return glm::length(r.origin - intersectionPoint); } - return -1; + return MAX_INTERSECT_DIST; } // CHECKITOUT @@ -99,9 +117,8 @@ __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) { - float radius = .5; +__host__ __device__ float sphereIntersectionTest(Geom &sphere, Ray &r, glm::vec3 &normal) { + float radius = 0.5f; glm::vec3 ro = multiplyMV(sphere.inverseTransform, glm::vec4(r.origin, 1.0f)); glm::vec3 rd = glm::normalize(multiplyMV(sphere.inverseTransform, glm::vec4(r.direction, 0.0f))); @@ -111,9 +128,9 @@ __host__ __device__ float sphereIntersectionTest(Geom sphere, Ray r, rt.direction = rd; float vDotDirection = glm::dot(rt.origin, rt.direction); - float radicand = vDotDirection * vDotDirection - (glm::dot(rt.origin, rt.origin) - powf(radius, 2)); - if (radicand < 0) { - return -1; + float radicand = vDotDirection * vDotDirection - (glm::dot(rt.origin, rt.origin) - powf(radius, 2.0f)); + if (radicand < 0.0f) { + return MAX_INTERSECT_DIST; } float squareRoot = sqrt(radicand); @@ -121,24 +138,51 @@ __host__ __device__ float sphereIntersectionTest(Geom sphere, Ray r, float t1 = firstTerm + squareRoot; float t2 = firstTerm - squareRoot; - float t = 0; - if (t1 < 0 && t2 < 0) { - return -1; - } else if (t1 > 0 && t2 > 0) { + float t = 0.0f; + if (t1 < 0.0f && t2 < 0.0f) { + return MAX_INTERSECT_DIST; + } else if (t1 > 0.0f && t2 > 0.0f) { t = min(t1, t2); - outside = true; } else { t = max(t1, t2); - outside = false; } 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 = -normal; - } + glm::vec3 intersectionPoint = multiplyMV(sphere.transform, glm::vec4(objspaceIntersection, 1.0f)); + normal = glm::normalize(multiplyMV(sphere.invTranspose, glm::vec4(objspaceIntersection, 0.0f))); return glm::length(r.origin - intersectionPoint); } + + +// CHECKITOUT +/** + * Test intersection between a ray and a triangle. + * + * @param intersectionPoint Output parameter for point of intersection. + * @param normal Output parameter for surface normal. + * @param outside Output param for whether the ray came from outside. + * @return Ray parameter `t` value. -1 if no intersection. + */ +__host__ __device__ float triIntersectionTest(Tri &tri, Ray &r, glm::vec3& normal) { + //glm::vec3 bary_coords; + //t = glm::intersectRayTriangle(pathSegment.ray.origin, pathSegment.ray.direction, tri.p0, tri.p1, tri.p2, bary_coords); + //1. Ray-plane intersection + float t = glm::dot(tri.plane_normal, (tri.p0 - r.origin)) / glm::dot(tri.plane_normal, r.direction); + if (t < 0.0f) return t; + + glm::vec3 P = r.origin + t * r.direction; + //2. Barycentric test + float S = 0.5f * glm::length(glm::cross(tri.p0 - tri.p1, tri.p0 - tri.p2)); + float s1 = 0.5f * glm::length(glm::cross(P - tri.p1, P - tri.p2)) / S; + float s2 = 0.5f * glm::length(glm::cross(P - tri.p2, P - tri.p0)) / S; + float s3 = 0.5f * glm::length(glm::cross(P - tri.p0, P - tri.p1)) / S; + float sum = s1 + s2 + s3; + + if (s1 >= 0 && s1 <= 1 && s2 >= 0 && s2 <= 1 && s3 >= 0 && s3 <= 1 && sum == 1.0f) { + normal = tri.plane_normal; + return t; + } + return -1.0f; +} diff --git a/src/main.cpp b/src/main.cpp index 96127b6..050e61a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,11 @@ +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + #include "main.h" #include "preview.h" #include + static std::string startTimeString; // For camera controls @@ -24,6 +28,9 @@ GuiDataContainer* guiData; RenderState* renderState; int iteration; +int cur_x; +int cur_y; + int width; int height; @@ -49,6 +56,8 @@ int main(int argc, char** argv) { // Set up camera stuff from loaded path tracer settings iteration = 0; + cur_x = 0; + cur_y = 0; renderState = &scene->state; Camera& cam = renderState->camera; width = cam.resolution.x; @@ -129,7 +138,6 @@ void runCuda() { // 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); @@ -147,12 +155,12 @@ void runCuda() { // unmap buffer object cudaGLUnmapBufferObject(pbo); } - else { + /*else { saveImage(); pathtraceFree(); cudaDeviceReset(); exit(EXIT_SUCCESS); - } + }*/ } void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { @@ -189,13 +197,13 @@ 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; + phi -= ((xpos - lastX) / width) * 2.5f; + theta -= ((ypos - lastY) / height) * 2.5f; theta = std::fmax(0.001f, std::fmin(theta, PI)); camchanged = true; } else if (rightMousePressed) { - zoom += (ypos - lastY) / height; + zoom += ((ypos - lastY) / height) * 7.5f; zoom = std::fmax(0.1f, zoom); camchanged = true; } diff --git a/src/main.h b/src/main.h index fdb7d5d..1fbcf3c 100644 --- a/src/main.h +++ b/src/main.h @@ -1,8 +1,12 @@ #pragma once + + #include #include + + #include #include #include @@ -20,6 +24,8 @@ #include "utilities.h" #include "scene.h" + + using namespace std; //------------------------------- diff --git a/src/pathtrace.cu b/src/pathtrace.cu index fd2a464..6413ad9 100644 --- a/src/pathtrace.cu +++ b/src/pathtrace.cu @@ -1,9 +1,11 @@ #include -#include #include #include #include #include +#include +#include +#include #include "sceneStructs.h" #include "scene.h" @@ -15,6 +17,24 @@ #include "interactions.h" #define ERRORCHECK 1 +//#define CACHE_FIRST_BOUNCE +//#define SORT_BY_MATERIAL +//#define STREAM_COMPACT +#define ANTI_ALIASING + +#define BLOCK_SIZE_1D 128 +#define BLOCK_SIZE_2D 16 + +#define MIN_INTERSECT_DIST 0.0001f +#define MAX_INTERSECT_DIST 10000.0f + +#define ENABLE_RECTS +#define ENABLE_SPHERES +#define ENABLE_TRIS +#define ENABLE_SQUAREPLANES + +#define ENABLE_BVH_ACCEL + #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) @@ -38,44 +58,46 @@ void checkCUDAErrorFn(const char* msg, const char* file, int line) { #endif } + + __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); } -//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; - - 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); - // 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; static GuiDataContainer* guiData = NULL; static glm::vec3* dev_image = NULL; static Geom* dev_geoms = NULL; +static Tri* dev_tris = NULL; +static Light* dev_lights = NULL; static Material* dev_materials = NULL; static PathSegment* dev_paths = NULL; static ShadeableIntersection* dev_intersections = NULL; -// TODO: static variables for device memory, any extra info you need, etc -// ... +static BVHNode_GPU* dev_bvh_nodes = NULL; + +static MISLightRay* dev_direct_light_rays = NULL; +static MISLightIntersection* dev_direct_light_isects = NULL; + +static MISLightRay* dev_bsdf_light_rays = NULL; +static MISLightIntersection* dev_bsdf_light_isects = NULL; + + + +thrust::device_ptr thrust_dv_paths; +thrust::device_ptr thrust_dv_paths_end; +thrust::device_ptr thrust_dv_isects; + +#ifdef CACHE_FIRST_BOUNCE +static ShadeableIntersection* dev_first_bounce_cache = NULL; +thrust::device_ptr thrust_dv_first_bounce_cache; +#endif + + +static glm::vec3* dev_sample_colors = NULL; void InitDataContainer(GuiDataContainer* imGuiData) { @@ -96,13 +118,39 @@ void pathtraceInit(Scene* scene) { cudaMalloc(&dev_geoms, scene->geoms.size() * sizeof(Geom)); cudaMemcpy(dev_geoms, scene->geoms.data(), scene->geoms.size() * sizeof(Geom), cudaMemcpyHostToDevice); + cudaMalloc(&dev_tris, scene->num_tris * sizeof(Tri)); + cudaMemcpy(dev_tris, scene->mesh_tris_sorted.data(), scene->num_tris * sizeof(Tri), cudaMemcpyHostToDevice); + + cudaMalloc(&dev_bvh_nodes, scene->bvh_nodes_gpu.size() * sizeof(BVHNode_GPU)); + cudaMemcpy(dev_bvh_nodes, scene->bvh_nodes_gpu.data(), scene->bvh_nodes_gpu.size() * sizeof(BVHNode_GPU), cudaMemcpyHostToDevice); + + cudaMalloc(&dev_lights, scene->lights.size() * sizeof(Light)); + cudaMemcpy(dev_lights, scene->lights.data(), scene->lights.size() * sizeof(Light), 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)); + + // FOR LIGHT SAMPLED MIS RAY + cudaMalloc(&dev_direct_light_rays, pixelcount * sizeof(MISLightRay)); + + cudaMalloc(&dev_direct_light_isects, pixelcount * sizeof(MISLightIntersection)); + cudaMemset(dev_direct_light_isects, 0, pixelcount * sizeof(MISLightIntersection)); + + // FOR BSDF SAMPLED MIS RAY + cudaMalloc(&dev_bsdf_light_rays, pixelcount * sizeof(MISLightRay)); + + cudaMalloc(&dev_bsdf_light_isects, pixelcount * sizeof(MISLightIntersection)); + cudaMemset(dev_bsdf_light_isects, 0, pixelcount * sizeof(MISLightIntersection)); + // TODO: initialize any extra device memeory you need +#ifdef CACHE_FIRST_BOUNCE + cudaMalloc(&dev_first_bounce_cache, pixelcount * sizeof(ShadeableIntersection)); + cudaMemset(dev_first_bounce_cache, 0, pixelcount * sizeof(ShadeableIntersection)); +#endif checkCUDAError("pathtraceInit"); } @@ -111,32 +159,123 @@ void pathtraceFree() { cudaFree(dev_image); // no-op if dev_image is null cudaFree(dev_paths); cudaFree(dev_geoms); + cudaFree(dev_tris); + cudaFree(dev_bvh_nodes); cudaFree(dev_materials); cudaFree(dev_intersections); // TODO: clean up any extra device memory you created + cudaFree(dev_lights); + cudaFree(dev_direct_light_rays); + cudaFree(dev_direct_light_isects); + cudaFree(dev_bsdf_light_rays); + cudaFree(dev_bsdf_light_isects); + + +#ifdef CACHE_FIRST_BOUNCE + cudaFree(dev_first_bounce_cache); +#endif checkCUDAError("pathtraceFree"); } -/** -* Generate PathSegments with rays from the camera through the screen into the -* scene, which is the first bounce of rays. -* -* Antialiasing - add rays for sub-pixel sampling -* 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) +#ifdef ANTI_ALIASING +// AA +__global__ void generateRayFromThinLensCamera(Camera cam, int iter, int traceDepth, float jitterX, float jitterY, glm::vec3 thinLensCamOrigin, glm::vec3 newRef, + PathSegment* pathSegments) +{ + __shared__ PathSegment mat[BLOCK_SIZE_2D][BLOCK_SIZE_2D]; + + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + int index = x + (y * cam.resolution.x); + + if (x < cam.resolution.x && y < cam.resolution.y) { + mat[threadIdx.x][threadIdx.y] = pathSegments[index]; + PathSegment& segment = mat[threadIdx.x][threadIdx.y]; + + segment.ray.origin = thinLensCamOrigin; + segment.rayThroughput = glm::vec3(1.0f, 1.0f, 1.0f); + segment.accumulatedIrradiance = glm::vec3(0.0f, 0.0f, 0.0f); + segment.prev_hit_was_specular = false; + + float jittered_x = ((float)x) + jitterX; + float jittered_y = ((float)y) + jitterY; + + // TODO: implement antialiasing by jittering the ray + segment.ray.direction = glm::normalize( + glm::normalize(newRef - thinLensCamOrigin) - cam.right * cam.pixelLength.x * (jittered_x - (float)cam.resolution.x * 0.5f) + - cam.up * cam.pixelLength.y * (jittered_y - (float)cam.resolution.y * 0.5f) + ); + + segment.ray.direction_inv = 1.0f / glm::vec3(segment.ray.direction); + segment.ray.ray_dir_sign[0] = segment.ray.direction_inv.x < 0.0f; + segment.ray.ray_dir_sign[1] = segment.ray.direction_inv.y < 0.0f; + segment.ray.ray_dir_sign[2] = segment.ray.direction_inv.z < 0.0f; + + segment.pixelIndex = index; + segment.remainingBounces = traceDepth; + + pathSegments[index] = mat[threadIdx.x][threadIdx.y]; + } +} + +__global__ void generateRayFromCamera(Camera cam, int iter, int traceDepth, float jitterX, float jitterY, + PathSegment* pathSegments) +{ + __shared__ PathSegment mat[BLOCK_SIZE_2D][BLOCK_SIZE_2D]; + + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + int index = x + (y * cam.resolution.x); + + if (x < cam.resolution.x && y < cam.resolution.y) { + mat[threadIdx.x][threadIdx.y] = pathSegments[index]; + PathSegment& segment = mat[threadIdx.x][threadIdx.y]; + + segment.ray.origin = cam.position; + segment.rayThroughput = glm::vec3(1.0f, 1.0f, 1.0f); + segment.accumulatedIrradiance = glm::vec3(0.0f, 0.0f, 0.0f); + segment.prev_hit_was_specular = false; + + float jittered_x = ((float)x) + jitterX; + float jittered_y = ((float)y) + jitterY; + + // TODO: implement antialiasing by jittering the ray + segment.ray.direction = glm::normalize( + cam.view - cam.right * cam.pixelLength.x * (jittered_x - (float)cam.resolution.x * 0.5f) + - cam.up * cam.pixelLength.y * (jittered_y - (float)cam.resolution.y * 0.5f) + ); + + segment.ray.direction_inv = 1.0f / glm::vec3(segment.ray.direction); + segment.ray.ray_dir_sign[0] = segment.ray.direction_inv.x < 0.0f; + segment.ray.ray_dir_sign[1] = segment.ray.direction_inv.y < 0.0f; + segment.ray.ray_dir_sign[2] = segment.ray.direction_inv.z < 0.0f; + + segment.pixelIndex = index; + segment.remainingBounces = traceDepth; + + pathSegments[index] = mat[threadIdx.x][threadIdx.y]; + } +} + +#else +// NO AA +__global__ void generateRayFromCamera(Camera cam, int traceDepth, PathSegment* pathSegments) { + + __shared__ PathSegment mat[BLOCK_SIZE_2D][BLOCK_SIZE_2D]; int x = (blockIdx.x * blockDim.x) + threadIdx.x; int y = (blockIdx.y * blockDim.y) + threadIdx.y; + int index = x + (y * cam.resolution.x); if (x < cam.resolution.x && y < cam.resolution.y) { - int index = x + (y * cam.resolution.x); - PathSegment& segment = pathSegments[index]; + mat[threadIdx.x][threadIdx.y] = pathSegments[index]; + PathSegment& segment = mat[threadIdx.x][threadIdx.y]; segment.ray.origin = cam.position; - segment.color = glm::vec3(1.0f, 1.0f, 1.0f); + segment.rayThroughput = glm::vec3(1.0f, 1.0f, 1.0f); + segment.accumulatedIrradiance = glm::vec3(0.0f, 0.0f, 0.0f); + segment.prev_hit_was_specular = false; // TODO: implement antialiasing by jittering the ray segment.ray.direction = glm::normalize(cam.view @@ -144,94 +283,870 @@ __global__ void generateRayFromCamera(Camera cam, int iter, int traceDepth, Path - cam.up * cam.pixelLength.y * ((float)y - (float)cam.resolution.y * 0.5f) ); + segment.ray.direction_inv = 1.0f / glm::vec3(segment.ray.direction); + segment.ray.ray_dir_sign[0] = segment.ray.direction_inv.x < 0.0f; + segment.ray.ray_dir_sign[1] = segment.ray.direction_inv.y < 0.0f; + segment.ray.ray_dir_sign[2] = segment.ray.direction_inv.z < 0.0f; + segment.pixelIndex = index; segment.remainingBounces = traceDepth; + + pathSegments[index] = mat[threadIdx.x][threadIdx.y]; } } +#endif -// 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 , int geoms_size + , Tri* tris + , int tris_size , ShadeableIntersection* intersections + , BVHNode_GPU* bvh_nodes ) { int path_index = blockIdx.x * blockDim.x + threadIdx.x; if (path_index < num_paths) { - PathSegment pathSegment = pathSegments[path_index]; +#ifndef STREAM_COMPACT + if (pathSegments[path_index].remainingBounces == 0) { + return; + } +#endif + Ray r = pathSegments[path_index].ray; + + ShadeableIntersection isect; + isect.t = MAX_INTERSECT_DIST; float t; - glm::vec3 intersect_point; - glm::vec3 normal; - float t_min = FLT_MAX; - int hit_geom_index = -1; - bool outside = true; - glm::vec3 tmp_intersect; + glm::vec3 tmp_normal; + int obj_ID = -1; + +#ifdef ENABLE_TRIS + if (tris_size != 0) { + +#ifdef ENABLE_BVH_ACCEL + int stack_pointer = 0; + int cur_node_index = 0; + int node_stack[32]; + BVHNode_GPU cur_node; + glm::vec3 P; + glm::vec3 s; + float t1; + float t2; + float tmin; + float tmax; + while (true) { + cur_node = bvh_nodes[cur_node_index]; - // naive parse through global geoms + // (ray-aabb test node) + t1 = (cur_node.AABB_min.x - r.origin.x) * r.direction_inv.x; + t2 = (cur_node.AABB_max.x - r.origin.x) * r.direction_inv.x; - for (int i = 0; i < geoms_size; i++) + tmin = glm::min(t1, t2); + tmax = glm::max(t1, t2); + + t1 = (cur_node.AABB_min.y - r.origin.y) * r.direction_inv.y; + t2 = (cur_node.AABB_max.y - r.origin.y) * r.direction_inv.y; + + tmin = glm::max(tmin, glm::min(t1, t2)); + tmax = glm::min(tmax, glm::max(t1, t2)); + + t1 = (cur_node.AABB_min.z - r.origin.z) * r.direction_inv.z; + t2 = (cur_node.AABB_max.z - r.origin.z) * r.direction_inv.z; + + tmin = glm::max(tmin, glm::min(t1, t2)); + tmax = glm::min(tmax, glm::max(t1, t2)); + + if (tmax >= tmin) { + // we intersected AABB + if (cur_node.tri_index != -1) { + // this is leaf node + // triangle intersection test + Tri tri = tris[cur_node.tri_index]; + + t = glm::dot(tri.plane_normal, (tri.p0 - r.origin)) / glm::dot(tri.plane_normal, r.direction); + if (t >= -0.0001f) { + P = r.origin + t * r.direction; + + // barycentric coords + s = glm::vec3(glm::length(glm::cross(P - tri.p1, P - tri.p2)), + glm::length(glm::cross(P - tri.p2, P - tri.p0)), + glm::length(glm::cross(P - tri.p0, P - tri.p1))) / tri.S; + + if (s.x >= -0.0001f && s.x <= 1.0001f && s.y >= -0.0001f && s.y <= 1.0001f && + s.z >= -0.0001f && s.z <= 1.0001f && (s.x + s.y + s.z <= 1.0001f) && (s.x + s.y + s.z >= -0.0001f) && isect.t > t) { + isect.t = t; + isect.materialId = tri.mat_ID; + isect.surfaceNormal = glm::normalize(s.x * tri.n0 + s.y * tri.n1 + s.z * tri.n2); + } + } + // if last node in tree, we are done + if (stack_pointer == 0) { + break; + } + // otherwise need to check rest of the things in the stack + stack_pointer--; + cur_node_index = node_stack[stack_pointer]; + } + else { + node_stack[stack_pointer] = cur_node.offset_to_second_child; + stack_pointer++; + cur_node_index++; + } + } + else { + // didn't intersect AABB, remove from stack + if (stack_pointer == 0) { + break; + } + stack_pointer--; + cur_node_index = node_stack[stack_pointer]; + } + } + +#else + for (int j = 0; j < tris_size; ++j) + { + // triangle intersection test + Tri tri = tris[j]; + + t = glm::dot(tri.plane_normal, (tri.p0 - r.origin)) / glm::dot(tri.plane_normal, r.direction); + if (t < 0.0f) continue; + + glm::vec3 P = r.origin + t * r.direction; + + // barycentric coords + glm::vec3 s = glm::vec3(glm::length(glm::cross(P - tri.p1, P - tri.p2)), + glm::length(glm::cross(P - tri.p2, P - tri.p0)), + glm::length(glm::cross(P - tri.p0, P - tri.p1))) / tri.S; + + if (s.x >= -0.0001f && s.x <= 1.0001f && s.y >= -0.0001f && s.y <= 1.0001f && + s.z >= -0.0001f && s.z <= 1.0001f && (s.x + s.y + s.z <= 1.0001f) && (s.x + s.y + s.z >= -0.0001f) && isect.t > t) { + isect.t = t; + isect.materialId = tri.mat_ID; + isect.surfaceNormal = glm::normalize(s.x * tri.n0 + s.y * tri.n1 + s.z * tri.n2); + } + } +#endif + + } +#endif + + + for (int i = 0; i < geoms_size; ++i) { Geom& geom = geoms[i]; - if (geom.type == CUBE) - { - t = boxIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); + + + if (geom.type == SPHERE) { +#ifdef ENABLE_SPHERES + t = sphereIntersectionTest(geom, r, tmp_normal); +#endif + } + else if (geom.type == SQUAREPLANE) { +#ifdef ENABLE_SQUAREPLANES + t = squareplaneIntersectionTest(geom, r, tmp_normal); +#endif + } + else { +#ifdef ENABLE_RECTS + t = boxIntersectionTest(geom, r, tmp_normal); +#endif + } + + if (depth == 0 && glm::dot(tmp_normal, r.direction) > 0.0) { + continue; } - else if (geom.type == SPHERE) + else if (isect.t > t) { + isect.t = t; + isect.materialId = geom.materialid; + isect.surfaceNormal = tmp_normal; + } + + } + + if (isect.t >= MAX_INTERSECT_DIST) { + // hits nothing + pathSegments[path_index].remainingBounces = 0; + } + else { + intersections[path_index] = isect; + } + } +} + +__global__ void genMISRaysKernel( + int iter + , int num_paths + , int max_depth + , ShadeableIntersection* shadeableIntersections + , PathSegment* pathSegments + , Material* materials + , MISLightRay* direct_light_rays + , MISLightRay* bsdf_light_rays + , Light* lights + , int num_lights + , Geom* geoms + , MISLightIntersection* direct_light_isects + , MISLightIntersection* bsdf_light_isects +) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_paths) + { + if (pathSegments[idx].remainingBounces == 0) { + return; + } + + ShadeableIntersection intersection = shadeableIntersections[idx]; + Material material = materials[intersection.materialId]; + + if (material.emittance > 0.0f) { + if (pathSegments[idx].remainingBounces == max_depth || pathSegments[idx].prev_hit_was_specular) { + // only color lights on first hit + pathSegments[idx].accumulatedIrradiance += (material.R * material.emittance) * pathSegments[idx].rayThroughput; + } + pathSegments[idx].remainingBounces = 0; + return; + } + + pathSegments[idx].prev_hit_was_specular = material.type == SPEC_BRDF || material.type == SPEC_BTDF || material.type == SPEC_GLASS || material.type == SPEC_PLASTIC; + + if (pathSegments[idx].prev_hit_was_specular) { + return; + } + + glm::vec3 intersect_point = pathSegments[idx].ray.origin + intersection.t * pathSegments[idx].ray.direction; + + thrust::default_random_engine rng = makeSeededRandomEngine(iter + glm::abs(intersect_point.x) + idx, iter + glm::abs(intersect_point.y) + pathSegments[idx].remainingBounces, pathSegments[idx].remainingBounces + glm::abs(intersect_point.z)); + + thrust::uniform_real_distribution u01(0, 1); + + // choose light to directly sample + direct_light_rays[idx].light_ID = bsdf_light_rays[idx].light_ID = lights[glm::min((int)(glm::floor(u01(rng) * (float)num_lights)), num_lights - 1)].geom_ID; + + Geom& light = geoms[direct_light_rays[idx].light_ID]; + + Material& light_material = materials[light.materialid]; + + //////////////////////////////////////////////////// + // LIGHT SAMPLED + //////////////////////////////////////////////////// + + // generate light sampled wi + glm::vec3 wi = glm::vec3(0.0f); + float absDot = 0.0f; + glm::vec3 f = glm::vec3(0.0f); + float pdf_L = 0.0f; + float pdf_B = 0.0f; + + if (light.type == SQUAREPLANE) { + glm::vec2 p_obj_space = glm::vec2(u01(rng) - 0.5f, u01(rng) - 0.5f); + glm::vec3 p_world_space = glm::vec3(light.transform * glm::vec4(p_obj_space.x, p_obj_space.y, 0.0f, 1.0f)); + wi = glm::normalize(glm::vec3(p_world_space - intersect_point)); + absDot = glm::dot(wi, glm::normalize(glm::vec3(light.invTranspose * glm::vec4(0.0f, 0.0f, 1.0f, 0.0f)))); + + if (absDot < 0.0001f) { + absDot = glm::abs(absDot); + // pdf of square plane light = distanceSq / (absDot * lightArea) + float dist = glm::length(p_world_space - intersect_point); + if (absDot > 0.0001f) { + pdf_L = (dist * dist) / (absDot * light.scale.x * light.scale.y); + } + } + else { + pdf_L = 0.0f; + } + } + + direct_light_rays[idx].ray.origin = intersect_point + (wi * 0.001f); + direct_light_rays[idx].ray.direction = wi; + direct_light_rays[idx].ray.direction_inv = 1.0f / wi; + direct_light_rays[idx].ray.ray_dir_sign[0] = wi.x < 0.0f; + direct_light_rays[idx].ray.ray_dir_sign[1] = wi.y < 0.0f; + direct_light_rays[idx].ray.ray_dir_sign[2] = wi.z < 0.0f; + + + absDot = glm::abs(glm::dot(intersection.surfaceNormal, wi)); + // generate f, pdf, absdot from light sampled wi + if (material.type == SPEC_BRDF) { + // spec refl + direct_light_rays[idx].f = glm::vec3(0.0f); + } + else if (material.type == SPEC_BTDF) { + // spec refr + direct_light_rays[idx].f = glm::vec3(0.0f); + } + else if (material.type == SPEC_GLASS) { + // spec glass + direct_light_rays[idx].f = glm::vec3(0.0f); + } + else if (material.type == SPEC_PLASTIC) { + pdf_B = absDot * 0.31831f / 2.0f; + f = material.R * 0.31831f; + } + else { + pdf_B = absDot * 0.31831f; + f = material.R * 0.31831f; // INV_PI + + } + direct_light_rays[idx].f = f; + direct_light_rays[idx].pdf = pdf_B; + + // LTE = f * Li * absDot / pdf + if (pdf_L <= 0.0001f) { + direct_light_isects[idx].LTE = glm::vec3(0.0f, 0.0f, 0.0f); + } + else { + direct_light_isects[idx].LTE = light_material.emittance * light_material.R * f * absDot / pdf_L; + + } + + // MIS Power Heuristic + if (pdf_L <= 0.0001f && pdf_B <= 0.0001f) { + direct_light_isects[idx].w = 0.0f; + } + else { + direct_light_isects[idx].w = (pdf_L * pdf_L) / ((pdf_L * pdf_L) + (pdf_B * pdf_B)); + } + + + //////////////////////////////////////////////////// + // BSDF SAMPLED + //////////////////////////////////////////////////// + + if (material.type == SPEC_BRDF) { + // spec refl + wi = glm::reflect(pathSegments[idx].ray.direction, intersection.surfaceNormal); + absDot = glm::abs(glm::dot(intersection.surfaceNormal, wi)); + pdf_B = 1.0f; + if (absDot == 0.0f) { + f = material.R; + } + else { + f = material.R / absDot; + } + } + else if (material.type == SPEC_BTDF) { + // spec refr + float eta = material.ior; + if (glm::dot(intersection.surfaceNormal, pathSegments[idx].ray.direction) < 0.0f) { + // outside + eta = 1.0f / eta; + wi = glm::refract(pathSegments[idx].ray.direction, intersection.surfaceNormal, eta); + } + else { + // inside + wi = glm::refract(pathSegments[idx].ray.direction, -intersection.surfaceNormal, eta); + } + absDot = glm::abs(glm::dot(intersection.surfaceNormal, wi)); + pdf_B = 1.0f; + if (glm::length(wi) <= 0.0001f) { + // total internal reflection + f = glm::vec3(0.0f); + } + if (absDot == 0.0f) { + f = material.T; + } + else { + f = material.T / absDot; + } + } + else if (material.type == SPEC_GLASS) { + // spec glass + float eta = material.ior; + if (u01(rng) < 0.5f) { + // spec refl + wi = glm::reflect(pathSegments[idx].ray.direction, intersection.surfaceNormal); + absDot = glm::abs(glm::dot(intersection.surfaceNormal, wi)); + pdf_B = 1.0f; + if (absDot == 0.0f) { + f = material.R; + } + else { + f = material.R / absDot; + } + f *= fresnelDielectric(glm::dot(intersection.surfaceNormal, pathSegments[idx].ray.direction), material.ior); + } + else { + // spec refr + if (glm::dot(intersection.surfaceNormal, pathSegments[idx].ray.direction) < 0.0f) { + // outside + eta = 1.0f / eta; + wi = glm::refract(pathSegments[idx].ray.direction, intersection.surfaceNormal, eta); + } + else { + // inside + wi = glm::refract(pathSegments[idx].ray.direction, -intersection.surfaceNormal, eta); + } + absDot = glm::abs(glm::dot(intersection.surfaceNormal, wi)); + pdf_B = 1.0f; + if (glm::length(wi) <= 0.0001f) { + // total internal reflection + f = glm::vec3(0.0f); + } + else if (absDot == 0.0f) { + f = material.T; + } + else { + f = material.T / absDot; + } + f *= glm::vec3(1.0f) - fresnelDielectric(glm::dot(intersection.surfaceNormal, pathSegments[idx].ray.direction), material.ior); + } + f *= 2.0f; + } + else if (material.type == SPEC_PLASTIC) { + // spec glass + if (u01(rng) < 0.5f) { + // diffuse + wi = glm::normalize(calculateRandomDirectionInHemisphere(intersection.surfaceNormal, rng, u01)); + absDot = glm::abs(glm::dot(intersection.surfaceNormal, wi)); + pdf_B = absDot * 0.31831f; + f = material.R * 0.31831f; // INV_PI + f *= glm::vec3(1.0f) - fresnelDielectric(glm::dot(intersection.surfaceNormal, pathSegments[idx].ray.direction), material.ior); + } + else { + // spec refl + wi = glm::reflect(pathSegments[idx].ray.direction, intersection.surfaceNormal); + absDot = glm::abs(glm::dot(intersection.surfaceNormal, wi)); + pdf_B = 1.0f; + if (absDot == 0.0f) { + f = material.T; + } + else { + f = material.T / absDot; + } + f *= fresnelDielectric(glm::dot(intersection.surfaceNormal, pathSegments[idx].ray.direction), material.ior); + } + f *= 2.0f; + } + else { + // diffuse + wi = glm::normalize(calculateRandomDirectionInHemisphere(intersection.surfaceNormal, rng, u01)); + absDot = glm::abs(glm::dot(intersection.surfaceNormal, wi)); + pdf_B = absDot * 0.31831f; + f = material.R * 0.31831f; // INV_PI + } + + + // Change ray direction + bsdf_light_rays[idx].ray.origin = intersect_point + (wi * 0.001f); + bsdf_light_rays[idx].ray.direction = wi; + bsdf_light_rays[idx].ray.direction_inv = 1.0f / wi; + bsdf_light_rays[idx].ray.ray_dir_sign[0] = wi.x < 0.0f; + bsdf_light_rays[idx].ray.ray_dir_sign[1] = wi.y < 0.0f; + bsdf_light_rays[idx].ray.ray_dir_sign[2] = wi.z < 0.0f; + bsdf_light_rays[idx].f = f; + + + // LTE = f * Li * absDot / pdf + absDot = glm::abs(glm::dot(intersection.surfaceNormal, bsdf_light_rays[idx].ray.direction)); + bsdf_light_rays[idx].pdf = pdf_B; + + if (pdf_B <= 0.0001f) { + bsdf_light_isects[idx].LTE = glm::vec3(0.0f, 0.0f, 0.0f); + } + else { + bsdf_light_isects[idx].LTE = light_material.emittance * light_material.R * bsdf_light_rays[idx].f * absDot / pdf_B; + } + + } +} + +__global__ void computeDirectLightIsects( + int depth + , int num_paths + , PathSegment* pathSegments + , MISLightRay* direct_light_rays + , Geom* geoms + , int geoms_size + , Tri* tris + , int tris_size + , MISLightIntersection* direct_light_intersections + , BVHNode_GPU* bvh_nodes +) +{ + int path_index = blockIdx.x * blockDim.x + threadIdx.x; + + if (path_index < num_paths) + { + + if (pathSegments[path_index].remainingBounces == 0) { + return; + } + else if (pathSegments[path_index].prev_hit_was_specular) { + return; + } + + MISLightRay r = direct_light_rays[path_index]; + + float t_min = MAX_INTERSECT_DIST; + int obj_ID = -1; + + + float t; + + glm::vec3 tmp_normal; + +#ifdef ENABLE_TRIS + if (tris_size != 0) { +#ifdef ENABLE_BVH_ACCEL + int stack_pointer = 0; + int cur_node_index = 0; + int node_stack[32]; + BVHNode_GPU cur_node; + glm::vec3 P; + glm::vec3 s; + float t1; + float t2; + float tmin; + float tmax; + while (true) { + cur_node = bvh_nodes[cur_node_index]; + + // (ray-aabb test node) + t1 = (cur_node.AABB_min.x - r.ray.origin.x) * r.ray.direction_inv.x; + t2 = (cur_node.AABB_max.x - r.ray.origin.x) * r.ray.direction_inv.x; + + tmin = glm::min(t1, t2); + tmax = glm::max(t1, t2); + + t1 = (cur_node.AABB_min.y - r.ray.origin.y) * r.ray.direction_inv.y; + t2 = (cur_node.AABB_max.y - r.ray.origin.y) * r.ray.direction_inv.y; + + tmin = glm::max(tmin, glm::min(t1, t2)); + tmax = glm::min(tmax, glm::max(t1, t2)); + + t1 = (cur_node.AABB_min.z - r.ray.origin.z) * r.ray.direction_inv.z; + t2 = (cur_node.AABB_max.z - r.ray.origin.z) * r.ray.direction_inv.z; + + tmin = glm::max(tmin, glm::min(t1, t2)); + tmax = glm::min(tmax, glm::max(t1, t2)); + + if (tmax >= tmin) { + // we intersected AABB + if (cur_node.tri_index != -1) { + // this is leaf node + // triangle intersection test + Tri tri = tris[cur_node.tri_index]; + + t = glm::dot(tri.plane_normal, (tri.p0 - r.ray.origin)) / glm::dot(tri.plane_normal, r.ray.direction); + if (t >= -0.0001f) { + P = r.ray.origin + t * r.ray.direction; + + // barycentric coords + s = glm::vec3(glm::length(glm::cross(P - tri.p1, P - tri.p2)), + glm::length(glm::cross(P - tri.p2, P - tri.p0)), + glm::length(glm::cross(P - tri.p0, P - tri.p1))) / tri.S; + + if (s.x >= -0.0001f && s.x <= 1.0001f && s.y >= -0.0001f && s.y <= 1.0001f && + s.z >= -0.0001f && s.z <= 1.0001f && (s.x + s.y + s.z <= 1.0001f) && (s.x + s.y + s.z >= -0.0001f) && t_min > t) { + t_min = t; + } + } + // if last node in tree, we are done + if (stack_pointer == 0) { + break; + } + // otherwise need to check rest of the things in the stack + stack_pointer--; + cur_node_index = node_stack[stack_pointer]; + } + else { + node_stack[stack_pointer] = cur_node.offset_to_second_child; + stack_pointer++; + cur_node_index++; + } + } + else { + // didn't intersect AABB, remove from stack + if (stack_pointer == 0) { + break; + } + stack_pointer--; + cur_node_index = node_stack[stack_pointer]; + } + } + +#else + for (int j = 0; j < tris_size; ++j) { - t = sphereIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); + // triangle intersection test + Tri tri = tris[j]; + + t = glm::dot(tri.plane_normal, (tri.p0 - r.ray.origin)) / glm::dot(tri.plane_normal, r.ray.direction); + if (t < 0.0f) continue; + + glm::vec3 P = r.ray.origin + t * r.ray.direction; + + // barycentric coords + glm::vec3 s = glm::vec3(glm::length(glm::cross(P - tri.p1, P - tri.p2)), + glm::length(glm::cross(P - tri.p2, P - tri.p0)), + glm::length(glm::cross(P - tri.p0, P - tri.p1))) / tri.S; + + if (s.x >= -0.0001f && s.x <= 1.0001f && s.y >= -0.0001f && s.y <= 1.0001f && + s.z >= -0.0001f && s.z <= 1.0001f && (s.x + s.y + s.z <= 1.0001f) && (s.x + s.y + s.z >= -0.0001f) && t_min > t) { + t_min = t; + } + } +#endif + } +#endif + + for (int i = 0; i < geoms_size; ++i) + { + Geom& geom = geoms[i]; + + + + if (geom.type == SPHERE) { +#ifdef ENABLE_SPHERES + t = sphereIntersectionTest(geom, r.ray, tmp_normal); +#endif + } + else if (geom.type == SQUAREPLANE) { +#ifdef ENABLE_SQUAREPLANES + t = squareplaneIntersectionTest(geom, r.ray, tmp_normal); +#endif + } + else { +#ifdef ENABLE_RECTS + t = boxIntersectionTest(geom, r.ray, tmp_normal); +#endif } - // 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) + if (t_min > t) { t_min = t; - hit_geom_index = i; - intersect_point = tmp_intersect; - normal = tmp_normal; + obj_ID = i; } } - if (hit_geom_index == -1) - { - intersections[path_index].t = -1.0f; + if (obj_ID != r.light_ID) { + direct_light_intersections[path_index].LTE = glm::vec3(0.0f, 0.0f, 0.0f); + direct_light_intersections[path_index].w = 0.0f; } - else + + // LTE = f * Li * absDot / pdf + // Already have f, Li, absDot, and pdf from when we generated ray + // MIS Power Heuristic already calulated in raygen + } +} + +__global__ void computeBSDFLightIsects( + int depth + , int num_paths + , PathSegment* pathSegments + , MISLightRay* bsdf_light_rays + , Geom* geoms + , int geoms_size + , Tri* tris + , int tris_size + , MISLightIntersection* bsdf_light_intersections + , BVHNode_GPU* bvh_nodes +) +{ + int path_index = blockIdx.x * blockDim.x + threadIdx.x; + + if (path_index < num_paths) + { + + if (pathSegments[path_index].remainingBounces == 0) { + return; + } + else if (pathSegments[path_index].prev_hit_was_specular) { + return; + } + + MISLightRay r = bsdf_light_rays[path_index]; + + float t_min = MAX_INTERSECT_DIST; + int obj_ID = -1; + float pdf_L_B = 0.0f; + float t; + glm::vec3 hit_normal; + + glm::vec3 tmp_normal; + +#ifdef ENABLE_TRIS + if (tris_size != 0) { +#ifdef ENABLE_BVH_ACCEL + int stack_pointer = 0; + int cur_node_index = 0; + int node_stack[32]; + BVHNode_GPU cur_node; + glm::vec3 P; + glm::vec3 s; + float t1; + float t2; + float tmin; + float tmax; + while (true) { + cur_node = bvh_nodes[cur_node_index]; + + // (ray-aabb test node) + t1 = (cur_node.AABB_min.x - r.ray.origin.x) * r.ray.direction_inv.x; + t2 = (cur_node.AABB_max.x - r.ray.origin.x) * r.ray.direction_inv.x; + + tmin = glm::min(t1, t2); + tmax = glm::max(t1, t2); + + t1 = (cur_node.AABB_min.y - r.ray.origin.y) * r.ray.direction_inv.y; + t2 = (cur_node.AABB_max.y - r.ray.origin.y) * r.ray.direction_inv.y; + + tmin = glm::max(tmin, glm::min(t1, t2)); + tmax = glm::min(tmax, glm::max(t1, t2)); + + t1 = (cur_node.AABB_min.z - r.ray.origin.z) * r.ray.direction_inv.z; + t2 = (cur_node.AABB_max.z - r.ray.origin.z) * r.ray.direction_inv.z; + + tmin = glm::max(tmin, glm::min(t1, t2)); + tmax = glm::min(tmax, glm::max(t1, t2)); + + if (tmax >= tmin) { + // we intersected AABB + if (cur_node.tri_index != -1) { + // this is leaf node + // triangle intersection test + Tri tri = tris[cur_node.tri_index]; + + t = glm::dot(tri.plane_normal, (tri.p0 - r.ray.origin)) / glm::dot(tri.plane_normal, r.ray.direction); + if (t >= -0.0001f) { + P = r.ray.origin + t * r.ray.direction; + + // barycentric coords + s = glm::vec3(glm::length(glm::cross(P - tri.p1, P - tri.p2)), + glm::length(glm::cross(P - tri.p2, P - tri.p0)), + glm::length(glm::cross(P - tri.p0, P - tri.p1))) / tri.S; + + if (s.x >= -0.0001f && s.x <= 1.0001f && s.y >= -0.0001f && s.y <= 1.0001f && + s.z >= -0.0001f && s.z <= 1.0001f && (s.x + s.y + s.z <= 1.0001f) && (s.x + s.y + s.z >= -0.0001f) && t_min > t) { + t_min = t; + hit_normal = glm::normalize(s.x * tri.n0 + s.y * tri.n1 + s.z * tri.n2); + } + } + // if last node in tree, we are done + if (stack_pointer == 0) { + break; + } + // otherwise need to check rest of the things in the stack + stack_pointer--; + cur_node_index = node_stack[stack_pointer]; + } + else { + node_stack[stack_pointer] = cur_node.offset_to_second_child; + stack_pointer++; + cur_node_index++; + } + } + else { + // didn't intersect AABB, remove from stack + if (stack_pointer == 0) { + break; + } + stack_pointer--; + cur_node_index = node_stack[stack_pointer]; + } + } + +#else + for (int j = 0; j < tris_size; ++j) + { + // triangle intersection test + Tri tri = tris[j]; + + t= glm::dot(tri.plane_normal, (tri.p0 - r.ray.origin)) / glm::dot(tri.plane_normal, r.ray.direction); + if (t < 0.0f) continue; + + glm::vec3 P = r.ray.origin + t * r.ray.direction; + + // barycentric coords + glm::vec3 s = glm::vec3(glm::length(glm::cross(P - tri.p1, P - tri.p2)), + glm::length(glm::cross(P - tri.p2, P - tri.p0)), + glm::length(glm::cross(P - tri.p0, P - tri.p1))) / tri.S; + + if (s.x >= -0.0001f && s.x <= 1.0001f && s.y >= -0.0001f && s.y <= 1.0001f && + s.z >= -0.0001f && s.z <= 1.0001f && (s.x + s.y + s.z <= 1.0001f) && (s.x + s.y + s.z >= -0.0001f) && t_min > t) { + t_min = t; + hit_normal = glm::normalize(s.x * tri.n0 + s.y * tri.n1 + s.z * tri.n2); + } + } +#endif + } +#endif + + + for (int i = 0; i < geoms_size; ++i) { - //The ray hits something - intersections[path_index].t = t_min; - intersections[path_index].materialId = geoms[hit_geom_index].materialid; - intersections[path_index].surfaceNormal = normal; + Geom& geom = geoms[i]; + + + + if (geom.type == SPHERE) { +#ifdef ENABLE_SPHERES + t = sphereIntersectionTest(geom, r.ray, tmp_normal); +#endif + } + else if (geom.type == SQUAREPLANE) { +#ifdef ENABLE_SQUAREPLANES + t = squareplaneIntersectionTest(geom, r.ray, tmp_normal); +#endif + } + else { +#ifdef ENABLE_RECTS + t = boxIntersectionTest(geom, r.ray, tmp_normal); +#endif + } + + if (t_min > t) + { + hit_normal = tmp_normal; + t_min = t; + obj_ID = i; + } + } + + float absDot = glm::dot(hit_normal, r.ray.direction); + + if (obj_ID == r.light_ID && absDot < 0.0f) { + + absDot = glm::abs(absDot); + pdf_L_B = (t_min * t_min) / (absDot * geoms[obj_ID].scale.x * geoms[obj_ID].scale.y); + + // LTE = f * Li * absDot / pdf + // Already have f, Li, and pdf from when we generated ray + bsdf_light_intersections[path_index].LTE *= absDot; + + // MIS Power Heuristic + if (pdf_L_B == 0.0f && r.pdf == 0.0f) { + bsdf_light_intersections[path_index].w = 0.0f; + } + else { + bsdf_light_intersections[path_index].w = (r.pdf * r.pdf) / ((r.pdf * r.pdf) + (pdf_L_B * pdf_L_B)); + } + } + else { + bsdf_light_intersections[path_index].LTE = glm::vec3(0.0f, 0.0f, 0.0f); + bsdf_light_intersections[path_index].w = 0.0f; } } } -// 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( +__global__ void shadeMaterialUberKernel( int iter , int num_paths , ShadeableIntersection* shadeableIntersections + , MISLightIntersection* direct_light_isects + , MISLightIntersection* bsdf_light_isects + , int num_lights , PathSegment* pathSegments , Material* materials ) @@ -239,36 +1154,54 @@ __global__ void shadeFakeMaterial( int idx = blockIdx.x * blockDim.x + threadIdx.x; if (idx < num_paths) { + if (pathSegments[idx].remainingBounces == 0) { + return; + } 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. + MISLightIntersection direct_light_intersection = direct_light_isects[idx]; + MISLightIntersection bsdf_light_intersection = bsdf_light_isects[idx]; + + thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, pathSegments[idx].remainingBounces); + + Material material = materials[intersection.materialId]; + + glm::vec3 intersect_point = pathSegments[idx].ray.origin + intersection.t * pathSegments[idx].ray.direction; + + // Combine direct light and bsdf light samples with Power Heuristic + if (!pathSegments[idx].prev_hit_was_specular) { + pathSegments[idx].accumulatedIrradiance += pathSegments[idx].rayThroughput * (float)num_lights * + (direct_light_intersection.w * direct_light_intersection.LTE + + bsdf_light_intersection.w * bsdf_light_intersection.LTE); + } + + + // GI LTE + scatterRay(pathSegments[idx], intersect_point, + intersection.surfaceNormal, + material, + rng); + pathSegments[idx].remainingBounces--; + } +} + +__global__ void russianRouletteKernel(int iter, int num_paths, PathSegment* pathSegments) +{ + int idx = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (idx < num_paths) + { + if (pathSegments[idx].remainingBounces == 0) { + return; + } + thrust::default_random_engine rng = makeSeededRandomEngine(iter + idx, idx, pathSegments[idx].remainingBounces + idx); + thrust::uniform_real_distribution u01(0.0f, 1.0f); + float random_num = u01(rng); + float max_channel = glm::max(glm::max(pathSegments[idx].rayThroughput.r, pathSegments[idx].rayThroughput.g), pathSegments[idx].rayThroughput.b); + if (max_channel < random_num) { + pathSegments[idx].remainingBounces = 0; } else { - pathSegments[idx].color = glm::vec3(0.0f); + pathSegments[idx].rayThroughput /= max_channel; } } } @@ -281,61 +1214,223 @@ __global__ void finalGather(int nPaths, glm::vec3* image, PathSegment* iteration if (index < nPaths) { PathSegment iterationPath = iterationPaths[index]; - image[iterationPath.pixelIndex] += iterationPath.color; + image[iterationPath.pixelIndex] += iterationPath.accumulatedIrradiance; + } +} + +//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; + + if (x < resolution.x && y < resolution.y) { + int index = x + (y * resolution.x); + glm::vec3 pix = image[index]; + + pix /= iter; + + // reinhard (HDR) + pix /= (pix + glm::vec3(1.0f)); + + // gamma correction + pix = glm::pow(pix, glm::vec3(0.454545f)); + + glm::ivec3 color; + color.x = glm::clamp((int)(pix.x * 255.0), 0, 255); + color.y = glm::clamp((int)(pix.y * 255.0), 0, 255); + color.z = glm::clamp((int)(pix.z * 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; + } +} + + +struct is_done +{ + __host__ __device__ + bool operator()(const PathSegment &path) + { + return path.remainingBounces != 0; + } +}; + +struct material_sort +{ + __host__ __device__ + bool operator()(const ShadeableIntersection& isect_0, const ShadeableIntersection& isect_1) + { + return isect_0.materialId < isect_1.materialId; } +}; + +#ifdef CACHE_FIRST_BOUNCE +void cacheFirstBounce(int iter, int cur_paths, dim3 &numblocksPathSegmentTracing, + const int blockSize1d, PerformanceTimer &perf_timer) { + + // clean shading chunks + perf_timer.startGpuTimer(); + cudaMemset(dev_first_bounce_cache, 0, cur_paths * sizeof(ShadeableIntersection)); + perf_timer.endGpuTimer(); + //std::cout << "cudaMemset: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + + // tracing + perf_timer.startGpuTimer(); + computeIntersections << > > ( + 0 + , cur_paths + , dev_paths + , dev_geoms + , hst_scene->geoms.size() + , dev_tris + , hst_scene->num_tris + , dev_first_bounce_cache + , dev_bvh_nodes + ); + checkCUDAError("trace cached intersections"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "computeIntersections: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + } -/** - * Wrapper for the __global__ call that sets up the kernel calls and does a ton - * of memory management - */ + + +void useCachedFirstBounce(int iter, int traceDepth, int &cur_paths, int &depth, bool &iterationComplete, + PathSegment* &dev_path_end, dim3& numblocksPathSegmentTracing, + const int blockSize1d, PerformanceTimer& perf_timer ) { + + perf_timer.startGpuTimer(); + cudaMemcpy(dev_intersections, dev_first_bounce_cache, + cur_paths * sizeof(ShadeableIntersection), cudaMemcpyDeviceToDevice); + perf_timer.endGpuTimer(); + //std::cout << "copy cache to intersections: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + depth++; + +#ifdef SORT_BY_MATERIAL + perf_timer.startGpuTimer(); + thrust::stable_sort_by_key(thrust::device, thrust_dv_isects, thrust_dv_isects + cur_paths, thrust_dv_paths, material_sort()); + perf_timer.endGpuTimer(); + //std::cout << "sort by material: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; +#endif + + perf_timer.startGpuTimer(); + genMISRaysKernel << > > ( + iter, + cur_paths, + traceDepth, + dev_intersections, + dev_paths, + dev_materials, + dev_direct_light_rays, + dev_bsdf_light_rays, + dev_lights, + hst_scene->lights.size(), + dev_geoms, + dev_direct_light_isects, + dev_bsdf_light_isects + ); + checkCUDAError("gen MIS rays (light sampled and bsdf sampled)"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "genMISRaysKernel: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + + + perf_timer.startGpuTimer(); + computeDirectLightIsects << > > ( + depth + , cur_paths + , dev_paths + , dev_direct_light_rays + , dev_geoms + , hst_scene->geoms.size() + , dev_tris + , hst_scene->num_tris + , dev_direct_light_isects + , dev_bvh_nodes + ); + checkCUDAError("get direct lighting intersections"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "computeIntersections: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + + perf_timer.startGpuTimer(); + computeBSDFLightIsects << > > ( + depth + , cur_paths + , dev_paths + , dev_bsdf_light_rays + , dev_geoms + , hst_scene->geoms.size() + , dev_tris + , hst_scene->num_tris + , dev_bsdf_light_isects + , dev_bvh_nodes + ); + checkCUDAError("get bsdf lighting intersections"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "computeIntersections: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + + perf_timer.startGpuTimer(); + shadeMaterialUberKernel << > > ( + iter, + cur_paths, + dev_intersections, + dev_direct_light_isects, + dev_bsdf_light_isects, + hst_scene->lights.size(), + dev_paths, + dev_materials + ); + checkCUDAError("shade one bounce"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "shadeMaterialUberKernel: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + +#ifdef STREAM_COMPACT + perf_timer.startGpuTimer(); + thrust_dv_paths_end = thrust::stable_partition(thrust::device, thrust_dv_paths, thrust_dv_paths + cur_paths, is_done()); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "stream compaction: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + dev_path_end = thrust_dv_paths_end.get(); + cur_paths = dev_path_end - dev_paths; +#endif + + if (depth == traceDepth || cur_paths == 0) { iterationComplete = true; } + + if (guiData != NULL) + { + guiData->TracedDepth = depth; + } +} + +#endif + void pathtrace(uchar4* pbo, int frame, int iter) { + PerformanceTimer perf_timer; + perf_timer.startCpuTimer(); + + //std::cout << "============================== " << iter << " ==============================" << std::endl; + 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 blockSize2d(BLOCK_SIZE_2D, BLOCK_SIZE_2D); const dim3 blocksPerGrid2d( - (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, - (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); + (cam.resolution.x + BLOCK_SIZE_2D - 1) / BLOCK_SIZE_2D, + (cam.resolution.y + BLOCK_SIZE_2D - 1) / BLOCK_SIZE_2D); + // 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); - checkCUDAError("generate camera ray"); + const int blockSize1d = BLOCK_SIZE_1D; int depth = 0; PathSegment* dev_path_end = dev_paths + pixelcount; @@ -344,43 +1439,226 @@ void pathtrace(uchar4* pbo, int frame, int iter) { // --- PathSegment Tracing Stage --- // Shoot ray into scene, bounce between objects, push shading chunks + thrust_dv_paths = thrust::device_pointer_cast(dev_paths); + thrust_dv_paths_end = thrust::device_pointer_cast(dev_path_end); + thrust_dv_isects = thrust::device_pointer_cast(dev_intersections); + bool iterationComplete = false; + int cur_paths = num_paths; + + dim3 numblocksPathSegmentTracing = (cur_paths + blockSize1d - 1) / blockSize1d; + + perf_timer.endCpuTimer(); + +#ifdef CACHE_FIRST_BOUNCE + thrust_dv_first_bounce_cache = thrust::device_pointer_cast(dev_first_bounce_cache); + + perf_timer.startGpuTimer(); + + generateRayFromCamera << > > (cam, traceDepth, dev_paths); + + checkCUDAError("generate camera ray"); + perf_timer.endGpuTimer(); + + if (iter == 1) { + // handle first bounce (depth == 0) + cacheFirstBounce(iter, cur_paths, numblocksPathSegmentTracing, blockSize1d, perf_timer); + + } + // compute depth = 0 using the cached first bounce intersections + useCachedFirstBounce(iter, traceDepth, cur_paths, depth, iterationComplete, dev_path_end, + numblocksPathSegmentTracing, blockSize1d, perf_timer); +#else + + // gen ray + thrust::default_random_engine rng = makeSeededRandomEngine(iter, iter, iter); + thrust::uniform_real_distribution upixel(0.0, 1.0f); + + float jitterX = upixel(rng); + float jitterY = upixel(rng); + + if (cam.lens_radius > 0.0f) { + // thin lens camera model based on my implementation from CIS 561 + // also based on https://www.semanticscholar.org/paper/A-Low-Distortion-Map-Between-Disk-and-Square-Shirley-Chiu/43226a3916a85025acbb3a58c17f6dc0756b35ac?p2df + glm::mat3 M = glm::mat3(cam.right, cam.up, cam.view); + + float focalT = (cam.focal_distance / glm::length(cam.lookAt - cam.position)); + glm::vec3 newRef = cam.position + focalT * (cam.lookAt - cam.position); + glm::vec2 thinLensSample = glm::vec2(upixel(rng), upixel(rng)); + + // turn square shaped random sample domain into disc shaped + glm::vec3 warped = glm::vec3(0.0f); + glm::vec2 sampleRemap = 2.0f * thinLensSample - glm::vec2(1.0f); + float r, theta = 0.0f; + if (glm::abs(sampleRemap.x) > glm::abs(sampleRemap.y)) { + r = sampleRemap.x; + theta = (PI / 4.0f) * (sampleRemap.y / sampleRemap.x); + } + else { + r = sampleRemap.y; + theta = (PI / 2.0f) - (PI / 4.0f) * (sampleRemap.x / sampleRemap.y); + } + warped = r * glm::vec3(glm::cos(theta), glm::sin(theta), 0.0f); + + glm::vec3 lensPoint = cam.lens_radius * warped; + + glm::vec3 thinLensCamOrigin = cam.position + M * lensPoint; + + + //std::cout << "sample init: " << perf_timer.getCpuElapsedTimeForPreviousOperation() << std::endl; + + perf_timer.startGpuTimer(); + + generateRayFromThinLensCamera << > > (cam, + iter, traceDepth, jitterX, jitterY, thinLensCamOrigin, newRef, dev_paths); + + checkCUDAError("generate camera ray"); + perf_timer.endGpuTimer(); + } + else { + perf_timer.startGpuTimer(); + + generateRayFromCamera << > > (cam, + iter, traceDepth, jitterX, jitterY, dev_paths); + + checkCUDAError("generate camera ray"); + perf_timer.endGpuTimer(); + } + +#endif + while (!iterationComplete) { // clean shading chunks - cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); + //cudaMemset(dev_intersections, 0, cur_paths * sizeof(ShadeableIntersection)); // tracing - dim3 numblocksPathSegmentTracing = (num_paths + blockSize1d - 1) / blockSize1d; + numblocksPathSegmentTracing = (cur_paths + blockSize1d - 1) / blockSize1d; + perf_timer.startGpuTimer(); computeIntersections << > > ( depth - , num_paths + , cur_paths , dev_paths , dev_geoms , hst_scene->geoms.size() + , dev_tris + , hst_scene->num_tris , dev_intersections + , dev_bvh_nodes ); checkCUDAError("trace one bounce"); cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "computeIntersections: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; depth++; - // 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. +#ifdef SORT_BY_MATERIAL + perf_timer.startGpuTimer(); + thrust::stable_sort_by_key(thrust::device, thrust_dv_isects, thrust_dv_isects + cur_paths, thrust_dv_paths, material_sort()); + perf_timer.endGpuTimer(); + //std::cout << "sort by material: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; +#endif + + perf_timer.startGpuTimer(); + genMISRaysKernel << > > ( + iter, + cur_paths, + traceDepth, + dev_intersections, + dev_paths, + dev_materials, + dev_direct_light_rays, + dev_bsdf_light_rays, + dev_lights, + hst_scene->lights.size(), + dev_geoms, + dev_direct_light_isects, + dev_bsdf_light_isects + ); + checkCUDAError("gen MIS rays (light sampled and bsdf sampled)"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "genMISRaysKernel: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + + + perf_timer.startGpuTimer(); + computeDirectLightIsects << > > ( + depth + , cur_paths + , dev_paths + , dev_direct_light_rays + , dev_geoms + , hst_scene->geoms.size() + , dev_tris + , hst_scene->num_tris + , dev_direct_light_isects + , dev_bvh_nodes + ); + checkCUDAError("get direct lighting intersections"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "computeIntersections: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; - shadeFakeMaterial << > > ( + perf_timer.startGpuTimer(); + computeBSDFLightIsects << > > ( + depth + , cur_paths + , dev_paths + , dev_bsdf_light_rays + , dev_geoms + , hst_scene->geoms.size() + , dev_tris + , hst_scene->num_tris + , dev_bsdf_light_isects + , dev_bvh_nodes + ); + checkCUDAError("get bsdf lighting intersections"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "computeIntersections: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + + perf_timer.startGpuTimer(); + shadeMaterialUberKernel << > > ( iter, - num_paths, + cur_paths, dev_intersections, + dev_direct_light_isects, + dev_bsdf_light_isects, + hst_scene->lights.size(), dev_paths, dev_materials ); - iterationComplete = true; // TODO: should be based off stream compaction results. + checkCUDAError("shade one bounce"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "shadeMaterialUberKernel: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + + // RUSSIAN ROULETTE + if (depth >= 4) { + perf_timer.startGpuTimer(); + russianRouletteKernel << > > ( + iter, + cur_paths, + dev_paths + ); + checkCUDAError("shade one bounce"); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "russianRouletteKernel: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + } + + +#ifdef STREAM_COMPACT + perf_timer.startGpuTimer(); + thrust_dv_paths_end = thrust::stable_partition(thrust::device, thrust_dv_paths, thrust_dv_paths + cur_paths, is_done()); + cudaDeviceSynchronize(); + perf_timer.endGpuTimer(); + //std::cout << "stream compaction: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + dev_path_end = thrust_dv_paths_end.get(); + cur_paths = dev_path_end - dev_paths; +#endif + + if (depth == traceDepth || cur_paths == 0) { iterationComplete = true; } if (guiData != NULL) { @@ -388,18 +1666,27 @@ void pathtrace(uchar4* pbo, int frame, int iter) { } } + perf_timer.startGpuTimer(); // Assemble this iteration and apply it to the image dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; finalGather << > > (num_paths, dev_image, dev_paths); + perf_timer.endGpuTimer(); + //std::cout << "finalGather: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + + //if ((iter & 64) >> 6 || iter < 2) { - /////////////////////////////////////////////////////////////////////////// + perf_timer.startGpuTimer(); + // Send results to OpenGL buffer for rendering + sendImageToPBO << > > (pbo, cam.resolution, iter, dev_image); + perf_timer.endGpuTimer(); + //std::cout << "sendImageToPBO: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; - // 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); - - checkCUDAError("pathtrace"); -} + // Retrieve image from GPU + perf_timer.startGpuTimer(); + cudaMemcpy(hst_scene->state.image.data(), dev_image, + pixelcount * sizeof(glm::vec3), cudaMemcpyDeviceToHost); + perf_timer.endGpuTimer(); + //std::cout << "last cudamemcpy: " << perf_timer.getGpuElapsedTimeForPreviousOperation() << std::endl; + checkCUDAError("pathtrace"); + //} +} \ No newline at end of file diff --git a/src/pathtrace.h b/src/pathtrace.h index e767d0e..a6f2b4a 100644 --- a/src/pathtrace.h +++ b/src/pathtrace.h @@ -2,8 +2,108 @@ #include #include "scene.h" +#include +#include +#include void InitDataContainer(GuiDataContainer* guiData); void pathtraceInit(Scene *scene); void pathtraceFree(); void pathtrace(uchar4 *pbo, int frame, int iteration); + +void pathtraceInit_Single(Scene* scene); +void pathtraceFree_Single(); +void pathtrace_Single(uchar4* pbo, int frame, int x, int y); + +/** + * This class is used for timing the performance + * Uncopyable and unmovable + * + * Adapted from WindyDarian(https://github.com/WindyDarian) + */ + +class PerformanceTimer +{ +public: + PerformanceTimer() + { + cudaEventCreate(&event_start); + cudaEventCreate(&event_end); + } + + ~PerformanceTimer() + { + cudaEventDestroy(event_start); + cudaEventDestroy(event_end); + } + + void startCpuTimer() + { + if (cpu_timer_started) { throw std::runtime_error("CPU timer already started"); } + cpu_timer_started = true; + + time_start_cpu = std::chrono::high_resolution_clock::now(); + } + + void endCpuTimer() + { + time_end_cpu = std::chrono::high_resolution_clock::now(); + + if (!cpu_timer_started) { throw std::runtime_error("CPU timer not started"); } + + std::chrono::duration duro = time_end_cpu - time_start_cpu; + prev_elapsed_time_cpu_milliseconds = + static_cast(duro.count()); + + cpu_timer_started = false; + } + + void startGpuTimer() + { + if (gpu_timer_started) { throw std::runtime_error("GPU timer already started"); } + gpu_timer_started = true; + + cudaEventRecord(event_start); + } + + void endGpuTimer() + { + cudaEventRecord(event_end); + cudaEventSynchronize(event_end); + + if (!gpu_timer_started) { throw std::runtime_error("GPU timer not started"); } + + cudaEventElapsedTime(&prev_elapsed_time_gpu_milliseconds, event_start, event_end); + gpu_timer_started = false; + } + + float getCpuElapsedTimeForPreviousOperation() //noexcept //(damn I need VS 2015 + { + return prev_elapsed_time_cpu_milliseconds; + } + + float getGpuElapsedTimeForPreviousOperation() //noexcept + { + return prev_elapsed_time_gpu_milliseconds; + } + + // remove copy and move functions + PerformanceTimer(const PerformanceTimer&) = delete; + PerformanceTimer(PerformanceTimer&&) = delete; + PerformanceTimer& operator=(const PerformanceTimer&) = delete; + PerformanceTimer& operator=(PerformanceTimer&&) = delete; + +private: + cudaEvent_t event_start = nullptr; + cudaEvent_t event_end = nullptr; + + using time_point_t = std::chrono::high_resolution_clock::time_point; + time_point_t time_start_cpu; + time_point_t time_end_cpu; + + bool cpu_timer_started = false; + bool gpu_timer_started = false; + + float prev_elapsed_time_cpu_milliseconds = 0.f; + float prev_elapsed_time_gpu_milliseconds = 0.f; +}; diff --git a/src/preview.cpp b/src/preview.cpp index 460debb..2f7c27e 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -191,10 +191,7 @@ void InitImguiData(GuiDataContainer* guiData) // LOOK: Un-Comment to check ImGui Usage void RenderImGui() { - if(io->WantCaptureMouse) - { - mouseOverImGuiWinow = true; - } + mouseOverImGuiWinow = io->WantCaptureMouse; ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); diff --git a/src/scene.cpp b/src/scene.cpp index 3fb6239..5cd775b 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -3,6 +3,8 @@ #include #include #include +#include "tiny_obj_loader.h" +#include Scene::Scene(string filename) { cout << "Reading scene from " << filename << " ..." << endl; @@ -21,23 +23,46 @@ Scene::Scene(string filename) { if (strcmp(tokens[0].c_str(), "MATERIAL") == 0) { loadMaterial(tokens[1]); cout << " " << endl; - } else if (strcmp(tokens[0].c_str(), "OBJECT") == 0) { + } + else if (strcmp(tokens[0].c_str(), "OBJECT") == 0) { loadGeom(tokens[1]); cout << " " << endl; - } else if (strcmp(tokens[0].c_str(), "CAMERA") == 0) { + } + else if (strcmp(tokens[0].c_str(), "CAMERA") == 0) { loadCamera(); cout << " " << endl; } } } + + if (mesh_tris.size() > 0) { + root_node = buildBVH(0, mesh_tris.size()); + + reformatBVHToGPU(); + + std::cout << "num nodes: " << num_nodes << std::endl; + } + + /*for (int i = 0; i < num_nodes; ++i) { + std::cout << "NODE " << i << std::endl; + std::cout << " AABB_min: " << bvh_nodes_gpu[i].AABB_min.x << " " << bvh_nodes_gpu[i].AABB_min.y << " " << bvh_nodes_gpu[i].AABB_min.z << " " << std::endl; + std::cout << " AABB_max: " << bvh_nodes_gpu[i].AABB_max.x << " " << bvh_nodes_gpu[i].AABB_max.y << " " << bvh_nodes_gpu[i].AABB_max.z << " " << std::endl; + std::cout << " num_tris: " << bvh_nodes_gpu[i].num_tris << std::endl; + std::cout << " tri_index: " << bvh_nodes_gpu[i].tri_index << std::endl; + std::cout << " offset_to_second_child: " << bvh_nodes_gpu[i].offset_to_second_child << std::endl; + std::cout << " split_axis: " << bvh_nodes_gpu[i].axis << std::endl; + }*/ + + } int Scene::loadGeom(string objectid) { int id = atoi(objectid.c_str()); - if (id != geoms.size()) { + if (id != num_geoms) { cout << "ERROR: OBJECT ID does not match expected number of geoms" << endl; return -1; } else { + num_geoms++; cout << "Loading Geom " << id << "..." << endl; Geom newGeom; string line; @@ -52,8 +77,123 @@ int Scene::loadGeom(string objectid) { cout << "Creating new cube..." << endl; newGeom.type = CUBE; } + else if (strcmp(line.c_str(), "squareplane") == 0) { + cout << "Creating new squareplane..." << endl; + newGeom.type = SQUAREPLANE; + } + else if (strcmp(line.c_str(), "mesh") == 0) { + std::cout << "Creating new mesh..." << std::endl; + newGeom.type = MESH; + } + } + + std::cout << num_tris << std::endl; + int starting_tri_size = num_tris; + + if (newGeom.type == MESH) { + + utilityCore::safeGetline(fp_in, line); + if (!line.empty() && fp_in.good()) { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn, err; + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, line.c_str())) { + throw std::runtime_error(warn + err); + } + + // every mesh in the obj + for (const tinyobj::shape_t& shape : shapes) { + + + + // every tri in the mesh + for (int i = 0; i < shape.mesh.indices.size(); i += 3) { + Tri newTri; + + + + glm::vec3 newP = glm::vec3(0.0f); + glm::vec3 newN = glm::vec3(0.0f); + glm::vec2 newT = glm::vec2(0.0f); + + for (int k = 0; k < 3; ++k) { + + if (shape.mesh.indices[i + k].vertex_index != -1) { + newP = glm::vec3(attrib.vertices[3 * shape.mesh.indices[i + k].vertex_index + 0], + attrib.vertices[3 * shape.mesh.indices[i + k].vertex_index + 1], + attrib.vertices[3 * shape.mesh.indices[i + k].vertex_index + 2]); + } + + if (shape.mesh.indices[i + k].texcoord_index != -1) { + newT = glm::vec2( + attrib.texcoords[2 * shape.mesh.indices[i + k].texcoord_index + 0], + 1.0f - attrib.texcoords[2 * shape.mesh.indices[i + k].texcoord_index + 1] + ); + } + + if (shape.mesh.indices[i + k].normal_index != -1) { + newN = glm::vec3( + attrib.normals[3 * shape.mesh.indices[i + k].normal_index + 0], + attrib.normals[3 * shape.mesh.indices[i + k].normal_index + 1], + attrib.normals[3 * shape.mesh.indices[i + k].normal_index + 2] + ); + } + + if (k == 0) { + newTri.p0 = newP; + newTri.n0 = newN; + newTri.t0 = newT; + } + else if (k == 1) { + newTri.p1 = newP; + newTri.n1 = newN; + newTri.t1 = newT; + } + else { + newTri.p2 = newP; + newTri.n2 = newN; + newTri.t2 = newT; + } + } + + + newTri.plane_normal = glm::normalize(glm::cross(newTri.p1 - newTri.p0, newTri.p2 - newTri.p1)); + newTri.S = glm::length(glm::cross(newTri.p1 - newTri.p0, newTri.p2 - newTri.p1)); + + TriBounds newTriBounds; + + newTriBounds.tri_ID = num_tris; + + + float max_x = glm::max(glm::max(newTri.p0.x, newTri.p1.x), newTri.p2.x); + float max_y = glm::max(glm::max(newTri.p0.y, newTri.p1.y), newTri.p2.y); + float max_z = glm::max(glm::max(newTri.p0.z, newTri.p1.z), newTri.p2.z); + newTriBounds.AABB_max = glm::vec3(max_x, max_y, max_z); + + float min_x = glm::min(glm::min(newTri.p0.x, newTri.p1.x), newTri.p2.x); + float min_y = glm::min(glm::min(newTri.p0.y, newTri.p1.y), newTri.p2.y); + float min_z = glm::min(glm::min(newTri.p0.z, newTri.p1.z), newTri.p2.z); + newTriBounds.AABB_min = glm::vec3(min_x, min_y, min_z); + + float mid_x = (newTri.p0.x + newTri.p1.x + newTri.p2.x) / 3.0; + float mid_y = (newTri.p0.y + newTri.p1.y + newTri.p2.y) / 3.0; + float mid_z = (newTri.p0.z + newTri.p1.z + newTri.p2.z) / 3.0; + newTriBounds.AABB_centroid = glm::vec3(mid_x, mid_y, mid_z); + + tri_bounds.push_back(newTriBounds); + + mesh_tris.push_back(newTri); + num_tris++; + } + //std::cout << num_tris << std::endl; + } + } } + int ending_tri_size = mesh_tris.size(); + //link material utilityCore::safeGetline(fp_in, line); if (!line.empty() && fp_in.good()) { @@ -62,6 +202,13 @@ int Scene::loadGeom(string objectid) { cout << "Connecting Geom " << objectid << " to Material " << newGeom.materialid << "..." << endl; } + if (newGeom.type == MESH) { + for (int i = starting_tri_size; i < ending_tri_size; ++i) { + mesh_tris[i].mat_ID = newGeom.materialid; + } + } + + //load transformations utilityCore::safeGetline(fp_in, line); while (!line.empty() && fp_in.good()) { @@ -84,7 +231,17 @@ int Scene::loadGeom(string objectid) { newGeom.inverseTransform = glm::inverse(newGeom.transform); newGeom.invTranspose = glm::inverseTranspose(newGeom.transform); - geoms.push_back(newGeom); + if (newGeom.type != MESH) { + geoms.push_back(newGeom); + if (materials[newGeom.materialid].emittance > 0.0f) { + Light newLight; + newLight.geom_ID = geoms.size() - 1; + lights.push_back(newLight); + } + } + + + return 1; } } @@ -96,7 +253,7 @@ int Scene::loadCamera() { float fovy; //load static properties - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 7; i++) { string line; utilityCore::safeGetline(fp_in, line); vector tokens = utilityCore::tokenizeString(line); @@ -112,6 +269,12 @@ int Scene::loadCamera() { } else if (strcmp(tokens[0].c_str(), "FILE") == 0) { state.imageName = tokens[1]; } + else if (strcmp(tokens[0].c_str(), "FOCAL_DISTANCE") == 0) { + camera.focal_distance = atof(tokens[1].c_str()); + } + else if (strcmp(tokens[0].c_str(), "LENS_RADIUS") == 0) { + camera.lens_radius = atof(tokens[1].c_str()); + } } string line; @@ -160,25 +323,40 @@ int Scene::loadMaterial(string materialid) { Material newMaterial; //load static properties - for (int i = 0; i < 7; i++) { + for (int i = 0; i < 5; i++) { 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) { - 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) { + if (strcmp(tokens[0].c_str(), "R_COLOR") == 0) { + glm::vec3 rColor( atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str()) ); + newMaterial.R = rColor; + } + else if (strcmp(tokens[0].c_str(), "T_COLOR") == 0) { + glm::vec3 tColor(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + newMaterial.T = tColor; + } + else if (strcmp(tokens[0].c_str(), "DIFFUSE_BRDF") == 0) { + newMaterial.type = DIFFUSE_BRDF; + } + else if (strcmp(tokens[0].c_str(), "DIFFUSE_BTDF") == 0) { + newMaterial.type = DIFFUSE_BTDF; + } + else if (strcmp(tokens[0].c_str(), "SPEC_BRDF") == 0) { + newMaterial.type = SPEC_BRDF; + } + else if (strcmp(tokens[0].c_str(), "SPEC_BTDF") == 0) { + newMaterial.type = SPEC_BTDF; + } + else if (strcmp(tokens[0].c_str(), "SPEC_GLASS") == 0) { + newMaterial.type = SPEC_GLASS; + } + else if (strcmp(tokens[0].c_str(), "SPEC_PLASTIC") == 0) { + newMaterial.type = SPEC_PLASTIC; + } + else if (strcmp(tokens[0].c_str(), "IOR") == 0) { + newMaterial.ior = atof(tokens[1].c_str()); + } + else if (strcmp(tokens[0].c_str(), "EMITTANCE") == 0) { newMaterial.emittance = atof(tokens[1].c_str()); } } @@ -186,3 +364,170 @@ int Scene::loadMaterial(string materialid) { return 1; } } + +// PBRT BVH as reference +// https://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies + +BVHNode* Scene::buildBVH(int start_index, int end_index) { + BVHNode* new_node = new BVHNode(); + num_nodes++; + int num_tris_in_node = end_index - start_index; + + // get the AABB bounds for this node (getting min and max of all triangles within) + glm::vec3 max_bounds = glm::vec3(-100000.0); + glm::vec3 min_bounds = glm::vec3(100000.0); + for (int i = start_index; i < end_index; ++i) { + if (max_bounds.x < tri_bounds[i].AABB_max.x) { + max_bounds.x = tri_bounds[i].AABB_max.x; + } + if (max_bounds.y < tri_bounds[i].AABB_max.y) { + max_bounds.y = tri_bounds[i].AABB_max.y; + } + if (max_bounds.z < tri_bounds[i].AABB_max.z) { + max_bounds.z = tri_bounds[i].AABB_max.z; + } + + if (min_bounds.x > tri_bounds[i].AABB_min.x) { + min_bounds.x = tri_bounds[i].AABB_min.x; + } + if (min_bounds.y > tri_bounds[i].AABB_min.y) { + min_bounds.y = tri_bounds[i].AABB_min.y; + } + if (min_bounds.z > tri_bounds[i].AABB_min.z) { + min_bounds.z = tri_bounds[i].AABB_min.z; + } + } + + // leaf node (with 1 tri in it) + if (num_tris_in_node <= 1) { + mesh_tris_sorted.push_back(mesh_tris[tri_bounds[start_index].tri_ID]); + new_node->tri_index = mesh_tris_sorted.size() - 1; + new_node->AABB_max = max_bounds; + new_node->AABB_min = min_bounds; + return new_node; + } + // intermediate node (covering tris start_index through end_index + else { + // get the greatest length between tri centroids in each direction x, y, and z + glm::vec3 centroid_max = glm::vec3(-100000.0); + glm::vec3 centroid_min = glm::vec3(100000.0); + for (int i = start_index; i < end_index; ++i) { + if (centroid_max.x < tri_bounds[i].AABB_centroid.x) { + centroid_max.x = tri_bounds[i].AABB_centroid.x; + } + if (centroid_max.y < tri_bounds[i].AABB_centroid.y) { + centroid_max.y = tri_bounds[i].AABB_centroid.y; + } + if (centroid_max.z < tri_bounds[i].AABB_centroid.z) { + centroid_max.z = tri_bounds[i].AABB_centroid.z; + } + + if (centroid_min.x > tri_bounds[i].AABB_centroid.x) { + centroid_min.x = tri_bounds[i].AABB_centroid.x; + } + if (centroid_min.y > tri_bounds[i].AABB_centroid.y) { + centroid_min.y = tri_bounds[i].AABB_centroid.y; + } + if (centroid_min.z > tri_bounds[i].AABB_centroid.z) { + centroid_min.z = tri_bounds[i].AABB_centroid.z; + } + } + glm::vec3 centroid_extent = centroid_max - centroid_min; + + // choose dimension to split along (dimension with largest extent) + int dimension_to_split = 0; + if (centroid_extent.x >= centroid_extent.y && centroid_extent.x >= centroid_extent.z) { + dimension_to_split = 0; + } + else if (centroid_extent.y >= centroid_extent.x && centroid_extent.y >= centroid_extent.z) { + dimension_to_split = 1; + } + else { + dimension_to_split = 2; + } + + + int mid_point = (start_index + end_index) / 2; + float centroid_midpoint = (centroid_min[dimension_to_split] + centroid_max[dimension_to_split]) / 2; + + if (centroid_min[dimension_to_split] == centroid_max[dimension_to_split]) { + mesh_tris_sorted.push_back(mesh_tris[tri_bounds[start_index].tri_ID]); + new_node->tri_index = mesh_tris_sorted.size() - 1; + new_node->AABB_max = max_bounds; + new_node->AABB_min = min_bounds; + return new_node; + } + + // partition triangles in bounding box, ones with centroids less than the midpoint go before ones with greater than + // using std::partition for partition algorithm + // https://en.cppreference.com/w/cpp/algorithm/partition + TriBounds* pointer_to_partition_point = std::partition(&tri_bounds[start_index], &tri_bounds[end_index - 1] + 1, + [dimension_to_split, centroid_midpoint](const TriBounds& triangle_AABB) { + return triangle_AABB.AABB_centroid[dimension_to_split] < centroid_midpoint; + }); + + // get the pointer relative to the start of the array + mid_point = pointer_to_partition_point - &tri_bounds[0]; + + // create two children nodes each for one side of the partitioned node + new_node->child_nodes[0] = buildBVH(start_index, mid_point); + new_node->child_nodes[1] = buildBVH(mid_point, end_index); + + new_node->split_axis = dimension_to_split; + new_node->tri_index = -1; + + new_node->AABB_max.x = glm::max(new_node->child_nodes[0]->AABB_max.x, new_node->child_nodes[1]->AABB_max.x); + new_node->AABB_max.y = glm::max(new_node->child_nodes[0]->AABB_max.y, new_node->child_nodes[1]->AABB_max.y); + new_node->AABB_max.z = glm::max(new_node->child_nodes[0]->AABB_max.z, new_node->child_nodes[1]->AABB_max.z); + + new_node->AABB_min.x = glm::min(new_node->child_nodes[0]->AABB_min.x, new_node->child_nodes[1]->AABB_min.x); + new_node->AABB_min.y = glm::min(new_node->child_nodes[0]->AABB_min.y, new_node->child_nodes[1]->AABB_min.y); + new_node->AABB_min.z = glm::min(new_node->child_nodes[0]->AABB_min.z, new_node->child_nodes[1]->AABB_min.z); + return new_node; + } +} + +void Scene::reformatBVHToGPU() { + BVHNode *cur_node; + std::stack nodes_to_process; + std::stack index_to_parent; + std::stack second_child_query; + int cur_node_index = 0; + int parent_index = 0; + bool is_second_child = false; + nodes_to_process.push(root_node); + index_to_parent.push(-1); + second_child_query.push(false); + while (!nodes_to_process.empty()) { + BVHNode_GPU new_gpu_node; + + cur_node = nodes_to_process.top(); + nodes_to_process.pop(); + parent_index = index_to_parent.top(); + index_to_parent.pop(); + is_second_child = second_child_query.top(); + second_child_query.pop(); + + if (is_second_child && parent_index != -1) { + bvh_nodes_gpu[parent_index].offset_to_second_child = bvh_nodes_gpu.size(); + } + new_gpu_node.AABB_min = cur_node->AABB_min; + new_gpu_node.AABB_max = cur_node->AABB_max; + if (cur_node->tri_index != -1) { + // leaf node + new_gpu_node.tri_index = cur_node->tri_index; + } + else { + // intermediate node + new_gpu_node.axis = cur_node->split_axis; + new_gpu_node.tri_index = -1; + nodes_to_process.push(cur_node->child_nodes[1]); + index_to_parent.push(bvh_nodes_gpu.size()); + second_child_query.push(true); + nodes_to_process.push(cur_node->child_nodes[0]); + index_to_parent.push(-1); + second_child_query.push(false); + } + bvh_nodes_gpu.push_back(new_gpu_node); + } +} \ No newline at end of file diff --git a/src/scene.h b/src/scene.h index f29a917..b3aa9d4 100644 --- a/src/scene.h +++ b/src/scene.h @@ -16,11 +16,31 @@ class Scene { int loadMaterial(string materialid); int loadGeom(string objectid); int loadCamera(); + + public: + Scene(string filename); ~Scene(); + BVHNode* buildBVH(int start_index, int end_index); + void reformatBVHToGPU(); + + int num_tris = 0; + std::vector geoms; + int num_geoms = 0; + //std::vector meshes; + std::vector lights; std::vector materials; + + std::vector mesh_tris; + std::vector mesh_tris_sorted; + + BVHNode* root_node; + int num_nodes = 0; + + std::vector bvh_nodes_gpu; + std::vector tri_bounds; RenderState state; }; diff --git a/src/sceneStructs.h b/src/sceneStructs.h index da4dbf3..2c27f28 100644 --- a/src/sceneStructs.h +++ b/src/sceneStructs.h @@ -10,11 +10,68 @@ enum GeomType { SPHERE, CUBE, + SQUAREPLANE, + MESH, + TRI, +}; + +enum BSDF { + DIFFUSE_BRDF, + DIFFUSE_BTDF, + SPEC_BRDF, + SPEC_BTDF, + SPEC_GLASS, + SPEC_PLASTIC, + MIRCROFACET_BRDF, }; struct Ray { glm::vec3 origin; glm::vec3 direction; + glm::vec3 direction_inv; + int ray_dir_sign[3]; // + == 0, - == 1 +}; + +struct TriBounds { + glm::vec3 AABB_min; + glm::vec3 AABB_max; + glm::vec3 AABB_centroid; + int tri_ID; +}; + +struct BVHNode { + glm::vec3 AABB_min; + glm::vec3 AABB_max; + BVHNode* child_nodes[2]; + int split_axis; + int tri_index; +}; + +struct BVHNode_GPU { + glm::vec3 AABB_min; + glm::vec3 AABB_max; + int tri_index; + int offset_to_second_child; + int axis; +}; + +struct Tri { + // positions + glm::vec3 p0; + glm::vec3 p1; + glm::vec3 p2; + // normals + glm::vec3 n0; + glm::vec3 n1; + glm::vec3 n2; + // uvs + glm::vec2 t0; + glm::vec2 t1; + glm::vec2 t2; + // plane normal + glm::vec3 plane_normal; + float S; + int mat_ID; }; struct Geom { @@ -28,15 +85,15 @@ struct Geom { glm::mat4 invTranspose; }; +struct Light { + int geom_ID; +}; + struct Material { - glm::vec3 color; - struct { - float exponent; - glm::vec3 color; - } specular; - float hasReflective; - float hasRefractive; - float indexOfRefraction; + glm::vec3 R; + glm::vec3 T; + BSDF type; + float ior; float emittance; }; @@ -49,6 +106,8 @@ struct Camera { glm::vec3 right; glm::vec2 fov; glm::vec2 pixelLength; + float focal_distance; + float lens_radius; }; struct RenderState { @@ -61,9 +120,23 @@ struct RenderState { struct PathSegment { Ray ray; - glm::vec3 color; + glm::vec3 accumulatedIrradiance; + glm::vec3 rayThroughput; int pixelIndex; int remainingBounces; + bool prev_hit_was_specular; +}; + +struct MISLightRay { + Ray ray; + glm::vec3 f; + float pdf; + int light_ID; +}; + +struct MISLightIntersection { + glm::vec3 LTE; + float w; }; // Use with a corresponding PathSegment to do: diff --git a/src/tiny_obj_loader.h b/src/tiny_obj_loader.h new file mode 100644 index 0000000..7d0c384 --- /dev/null +++ b/src/tiny_obj_loader.h @@ -0,0 +1,3333 @@ +/* +The MIT License (MIT) + +Copyright (c) 2012-Present, Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// +// version 2.0.0 : Add new object oriented API. 1.x API is still provided. +// * Support line primitive. +// * Support points primitive. +// * Support multiple search path for .mtl(v1 API). +// * Support vertex weight `vw`(as an tinyobj extension) +// * Support escaped whitespece in mtllib +// * Add robust triangulation using Mapbox earcut(TINYOBJLOADER_USE_MAPBOX_EARCUT). +// version 1.4.0 : Modifed ParseTextureNameAndOption API +// version 1.3.1 : Make ParseTextureNameAndOption API public +// version 1.3.0 : Separate warning and error message(breaking API of LoadObj) +// version 1.2.3 : Added color space extension('-colorspace') to tex opts. +// version 1.2.2 : Parse multiple group names. +// version 1.2.1 : Added initial support for line('l') primitive(PR #178) +// version 1.2.0 : Hardened implementation(#175) +// version 1.1.1 : Support smoothing groups(#162) +// version 1.1.0 : Support parsing vertex color(#144) +// version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138) +// version 1.0.7 : Support multiple tex options(#126) +// version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124) +// version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43) +// version 1.0.4 : Support multiple filenames for 'mtllib'(#112) +// version 1.0.3 : Support parsing texture options(#85) +// version 1.0.2 : Improve parsing speed by about a factor of 2 for large +// files(#105) +// version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104) +// version 1.0.0 : Change data structure. Change license from BSD to MIT. +// + +// +// Use this in *one* .cc +// #define TINYOBJLOADER_IMPLEMENTATION +// #include "tiny_obj_loader.h" +// + +#ifndef TINY_OBJ_LOADER_H_ +#define TINY_OBJ_LOADER_H_ + +#include +#include +#include + +namespace tinyobj { + +// TODO(syoyo): Better C++11 detection for older compiler +#if __cplusplus > 199711L +#define TINYOBJ_OVERRIDE override +#else +#define TINYOBJ_OVERRIDE +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif + +#pragma clang diagnostic ignored "-Wpadded" + +#endif + +// https://en.wikipedia.org/wiki/Wavefront_.obj_file says ... +// +// -blendu on | off # set horizontal texture blending +// (default on) +// -blendv on | off # set vertical texture blending +// (default on) +// -boost real_value # boost mip-map sharpness +// -mm base_value gain_value # modify texture map values (default +// 0 1) +// # base_value = brightness, +// gain_value = contrast +// -o u [v [w]] # Origin offset (default +// 0 0 0) +// -s u [v [w]] # Scale (default +// 1 1 1) +// -t u [v [w]] # Turbulence (default +// 0 0 0) +// -texres resolution # texture resolution to create +// -clamp on | off # only render texels in the clamped +// 0-1 range (default off) +// # When unclamped, textures are +// repeated across a surface, +// # when clamped, only texels which +// fall within the 0-1 +// # range are rendered. +// -bm mult_value # bump multiplier (for bump maps +// only) +// +// -imfchan r | g | b | m | l | z # specifies which channel of the file +// is used to +// # create a scalar or bump texture. +// r:red, g:green, +// # b:blue, m:matte, l:luminance, +// z:z-depth.. +// # (the default for bump is 'l' and +// for decal is 'm') +// bump -imfchan r bumpmap.tga # says to use the red channel of +// bumpmap.tga as the bumpmap +// +// For reflection maps... +// +// -type sphere # specifies a sphere for a "refl" +// reflection map +// -type cube_top | cube_bottom | # when using a cube map, the texture +// file for each +// cube_front | cube_back | # side of the cube is specified +// separately +// cube_left | cube_right +// +// TinyObjLoader extension. +// +// -colorspace SPACE # Color space of the texture. e.g. +// 'sRGB` or 'linear' +// + +#ifdef TINYOBJLOADER_USE_DOUBLE +//#pragma message "using double" +typedef double real_t; +#else +//#pragma message "using float" +typedef float real_t; +#endif + +typedef enum { + TEXTURE_TYPE_NONE, // default + TEXTURE_TYPE_SPHERE, + TEXTURE_TYPE_CUBE_TOP, + TEXTURE_TYPE_CUBE_BOTTOM, + TEXTURE_TYPE_CUBE_FRONT, + TEXTURE_TYPE_CUBE_BACK, + TEXTURE_TYPE_CUBE_LEFT, + TEXTURE_TYPE_CUBE_RIGHT +} texture_type_t; + +struct texture_option_t { + texture_type_t type; // -type (default TEXTURE_TYPE_NONE) + real_t sharpness; // -boost (default 1.0?) + real_t brightness; // base_value in -mm option (default 0) + real_t contrast; // gain_value in -mm option (default 1) + real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0) + real_t scale[3]; // -s u [v [w]] (default 1 1 1) + real_t turbulence[3]; // -t u [v [w]] (default 0 0 0) + int texture_resolution; // -texres resolution (No default value in the spec. + // We'll use -1) + bool clamp; // -clamp (default false) + char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') + bool blendu; // -blendu (default on) + bool blendv; // -blendv (default on) + real_t bump_multiplier; // -bm (for bump maps only, default 1.0) + + // extension + std::string colorspace; // Explicitly specify color space of stored texel + // value. Usually `sRGB` or `linear` (default empty). +}; + +struct material_t { + std::string name; + + real_t ambient[3]; + real_t diffuse[3]; + real_t specular[3]; + real_t transmittance[3]; + real_t emission[3]; + real_t shininess; + real_t ior; // index of refraction + real_t dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; + + int dummy; // Suppress padding warning. + + std::string ambient_texname; // map_Ka + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, map_Bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d + std::string reflection_texname; // refl + + texture_option_t ambient_texopt; + texture_option_t diffuse_texopt; + texture_option_t specular_texopt; + texture_option_t specular_highlight_texopt; + texture_option_t bump_texopt; + texture_option_t displacement_texopt; + texture_option_t alpha_texopt; + texture_option_t reflection_texopt; + + // PBR extension + // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + real_t roughness; // [0, 1] default 0 + real_t metallic; // [0, 1] default 0 + real_t sheen; // [0, 1] default 0 + real_t clearcoat_thickness; // [0, 1] default 0 + real_t clearcoat_roughness; // [0, 1] default 0 + real_t anisotropy; // aniso. [0, 1] default 0 + real_t anisotropy_rotation; // anisor. [0, 1] default 0 + real_t pad0; + std::string roughness_texname; // map_Pr + std::string metallic_texname; // map_Pm + std::string sheen_texname; // map_Ps + std::string emissive_texname; // map_Ke + std::string normal_texname; // norm. For normal mapping. + + texture_option_t roughness_texopt; + texture_option_t metallic_texopt; + texture_option_t sheen_texopt; + texture_option_t emissive_texopt; + texture_option_t normal_texopt; + + int pad2; + + std::map unknown_parameter; + +#ifdef TINY_OBJ_LOADER_PYTHON_BINDING + // For pybind11 + std::array GetDiffuse() { + std::array values; + values[0] = double(diffuse[0]); + values[1] = double(diffuse[1]); + values[2] = double(diffuse[2]); + + return values; + } + + std::array GetSpecular() { + std::array values; + values[0] = double(specular[0]); + values[1] = double(specular[1]); + values[2] = double(specular[2]); + + return values; + } + + std::array GetTransmittance() { + std::array values; + values[0] = double(transmittance[0]); + values[1] = double(transmittance[1]); + values[2] = double(transmittance[2]); + + return values; + } + + std::array GetEmission() { + std::array values; + values[0] = double(emission[0]); + values[1] = double(emission[1]); + values[2] = double(emission[2]); + + return values; + } + + std::array GetAmbient() { + std::array values; + values[0] = double(ambient[0]); + values[1] = double(ambient[1]); + values[2] = double(ambient[2]); + + return values; + } + + void SetDiffuse(std::array &a) { + diffuse[0] = real_t(a[0]); + diffuse[1] = real_t(a[1]); + diffuse[2] = real_t(a[2]); + } + + void SetAmbient(std::array &a) { + ambient[0] = real_t(a[0]); + ambient[1] = real_t(a[1]); + ambient[2] = real_t(a[2]); + } + + void SetSpecular(std::array &a) { + specular[0] = real_t(a[0]); + specular[1] = real_t(a[1]); + specular[2] = real_t(a[2]); + } + + void SetTransmittance(std::array &a) { + transmittance[0] = real_t(a[0]); + transmittance[1] = real_t(a[1]); + transmittance[2] = real_t(a[2]); + } + + std::string GetCustomParameter(const std::string &key) { + std::map::const_iterator it = + unknown_parameter.find(key); + + if (it != unknown_parameter.end()) { + return it->second; + } + return std::string(); + } + +#endif +}; + +struct tag_t { + std::string name; + + std::vector intValues; + std::vector floatValues; + std::vector stringValues; +}; + +struct joint_and_weight_t { + int joint_id; + real_t weight; +}; + +struct skin_weight_t { + int vertex_id; // Corresponding vertex index in `attrib_t::vertices`. + // Compared to `index_t`, this index must be positive and + // start with 0(does not allow relative indexing) + std::vector weightValues; +}; + +// Index struct to support different indices for vtx/normal/texcoord. +// -1 means not used. +struct index_t { + int vertex_index; + int normal_index; + int texcoord_index; +}; + +struct mesh_t { + std::vector indices; + std::vector + num_face_vertices; // The number of vertices per + // face. 3 = triangle, 4 = quad, + // ... Up to 255 vertices per face. + std::vector material_ids; // per-face material ID + std::vector smoothing_group_ids; // per-face smoothing group + // ID(0 = off. positive value + // = group id) + std::vector tags; // SubD tag +}; + +// struct path_t { +// std::vector indices; // pairs of indices for lines +//}; + +struct lines_t { + // Linear flattened indices. + std::vector indices; // indices for vertices(poly lines) + std::vector num_line_vertices; // The number of vertices per line. +}; + +struct points_t { + std::vector indices; // indices for points +}; + +struct shape_t { + std::string name; + mesh_t mesh; + lines_t lines; + points_t points; +}; + +// Vertex attributes +struct attrib_t { + std::vector vertices; // 'v'(xyz) + + // For backward compatibility, we store vertex weight in separate array. + std::vector vertex_weights; // 'v'(w) + std::vector normals; // 'vn' + std::vector texcoords; // 'vt'(uv) + + // For backward compatibility, we store texture coordinate 'w' in separate + // array. + std::vector texcoord_ws; // 'vt'(w) + std::vector colors; // extension: vertex colors + + // + // TinyObj extension. + // + + // NOTE(syoyo): array index is based on the appearance order. + // To get a corresponding skin weight for a specific vertex id `vid`, + // Need to reconstruct a look up table: `skin_weight_t::vertex_id` == `vid` + // (e.g. using std::map, std::unordered_map) + std::vector skin_weights; + + attrib_t() {} + + // + // For pybind11 + // + const std::vector &GetVertices() const { return vertices; } + + const std::vector &GetVertexWeights() const { return vertex_weights; } +}; + +struct callback_t { + // W is optional and set to 1 if there is no `w` item in `v` line + void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w); + void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z); + + // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in + // `vt` line. + void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z); + + // called per 'f' line. num_indices is the number of face indices(e.g. 3 for + // triangle, 4 for quad) + // 0 will be passed for undefined index in index_t members. + void (*index_cb)(void *user_data, index_t *indices, int num_indices); + // `name` material name, `material_id` = the array index of material_t[]. -1 + // if + // a material not found in .mtl + void (*usemtl_cb)(void *user_data, const char *name, int material_id); + // `materials` = parsed material data. + void (*mtllib_cb)(void *user_data, const material_t *materials, + int num_materials); + // There may be multiple group names + void (*group_cb)(void *user_data, const char **names, int num_names); + void (*object_cb)(void *user_data, const char *name); + + callback_t() + : vertex_cb(NULL), + normal_cb(NULL), + texcoord_cb(NULL), + index_cb(NULL), + usemtl_cb(NULL), + mtllib_cb(NULL), + group_cb(NULL), + object_cb(NULL) {} +}; + +class MaterialReader { + public: + MaterialReader() {} + virtual ~MaterialReader(); + + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *warn, + std::string *err) = 0; +}; + +/// +/// Read .mtl from a file. +/// +class MaterialFileReader : public MaterialReader { + public: + // Path could contain separator(';' in Windows, ':' in Posix) + explicit MaterialFileReader(const std::string &mtl_basedir) + : m_mtlBaseDir(mtl_basedir) {} + virtual ~MaterialFileReader() TINYOBJ_OVERRIDE {} + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *warn, + std::string *err) TINYOBJ_OVERRIDE; + + private: + std::string m_mtlBaseDir; +}; + +/// +/// Read .mtl from a stream. +/// +class MaterialStreamReader : public MaterialReader { + public: + explicit MaterialStreamReader(std::istream &inStream) + : m_inStream(inStream) {} + virtual ~MaterialStreamReader() TINYOBJ_OVERRIDE {} + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *warn, + std::string *err) TINYOBJ_OVERRIDE; + + private: + std::istream &m_inStream; +}; + +// v2 API +struct ObjReaderConfig { + bool triangulate; // triangulate polygon? + + // Currently not used. + // "simple" or empty: Create triangle fan + // "earcut": Use the algorithm based on Ear clipping + std::string triangulation_method; + + /// Parse vertex color. + /// If vertex color is not present, its filled with default value. + /// false = no vertex color + /// This will increase memory of parsed .obj + bool vertex_color; + + /// + /// Search path to .mtl file. + /// Default = "" = search from the same directory of .obj file. + /// Valid only when loading .obj from a file. + /// + std::string mtl_search_path; + + ObjReaderConfig() + : triangulate(true), triangulation_method("simple"), vertex_color(true) {} +}; + +/// +/// Wavefront .obj reader class(v2 API) +/// +class ObjReader { + public: + ObjReader() : valid_(false) {} + + /// + /// Load .obj and .mtl from a file. + /// + /// @param[in] filename wavefront .obj filename + /// @param[in] config Reader configuration + /// + bool ParseFromFile(const std::string &filename, + const ObjReaderConfig &config = ObjReaderConfig()); + + /// + /// Parse .obj from a text string. + /// Need to supply .mtl text string by `mtl_text`. + /// This function ignores `mtllib` line in .obj text. + /// + /// @param[in] obj_text wavefront .obj filename + /// @param[in] mtl_text wavefront .mtl filename + /// @param[in] config Reader configuration + /// + bool ParseFromString(const std::string &obj_text, const std::string &mtl_text, + const ObjReaderConfig &config = ObjReaderConfig()); + + /// + /// .obj was loaded or parsed correctly. + /// + bool Valid() const { return valid_; } + + const attrib_t &GetAttrib() const { return attrib_; } + + const std::vector &GetShapes() const { return shapes_; } + + const std::vector &GetMaterials() const { return materials_; } + + /// + /// Warning message(may be filled after `Load` or `Parse`) + /// + const std::string &Warning() const { return warning_; } + + /// + /// Error message(filled when `Load` or `Parse` failed) + /// + const std::string &Error() const { return error_; } + + private: + bool valid_; + + attrib_t attrib_; + std::vector shapes_; + std::vector materials_; + + std::string warning_; + std::string error_; +}; + +/// ==>>========= Legacy v1 API ============================================= + +/// Loads .obj from a file. +/// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data +/// 'shapes' will be filled with parsed shape data +/// Returns true when loading .obj become success. +/// Returns warning message into `warn`, and error message into `err` +/// 'mtl_basedir' is optional, and used for base directory for .mtl file. +/// In default(`NULL'), .mtl file is searched from an application's working +/// directory. +/// 'triangulate' is optional, and used whether triangulate polygon face in .obj +/// or not. +/// Option 'default_vcols_fallback' specifies whether vertex colors should +/// always be defined, even if no colors are given (fallback to white). +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, const char *filename, + const char *mtl_basedir = NULL, bool triangulate = true, + bool default_vcols_fallback = true); + +/// Loads .obj from a file with custom user callback. +/// .mtl is loaded as usual and parsed material_t data will be passed to +/// `callback.mtllib_cb`. +/// Returns true when loading .obj/.mtl become success. +/// Returns warning message into `warn`, and error message into `err` +/// See `examples/callback_api/` for how to use this function. +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data = NULL, + MaterialReader *readMatFn = NULL, + std::string *warn = NULL, std::string *err = NULL); + +/// Loads object from a std::istream, uses `readMatFn` to retrieve +/// std::istream for materials. +/// Returns true when loading .obj become success. +/// Returns warning and error message into `err` +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn = NULL, bool triangulate = true, + bool default_vcols_fallback = true); + +/// Loads materials into std::map +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream, + std::string *warning, std::string *err); + +/// +/// Parse texture name and texture option for custom texture parameter through +/// material::unknown_parameter +/// +/// @param[out] texname Parsed texture name +/// @param[out] texopt Parsed texopt +/// @param[in] linebuf Input string +/// +bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, + const char *linebuf); + +/// =<<========== Legacy v1 API ============================================= + +} // namespace tinyobj + +#endif // TINY_OBJ_LOADER_H_ + +#ifdef TINYOBJLOADER_IMPLEMENTATION +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + +#ifdef TINYOBJLOADER_DONOT_INCLUDE_MAPBOX_EARCUT +// Assume earcut.hpp is included outside of tiny_obj_loader.h +#else + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include +#include "mapbox/earcut.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#endif // TINYOBJLOADER_USE_MAPBOX_EARCUT + +namespace tinyobj { + +MaterialReader::~MaterialReader() {} + +struct vertex_index_t { + int v_idx, vt_idx, vn_idx; + vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} + vertex_index_t(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} +}; + +// Internal data structure for face representation +// index + smoothing group. +struct face_t { + unsigned int + smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. + int pad_; + std::vector vertex_indices; // face vertex indices. + + face_t() : smoothing_group_id(0), pad_(0) {} +}; + +// Internal data structure for line representation +struct __line_t { + // l v1/vt1 v2/vt2 ... + // In the specification, line primitrive does not have normal index, but + // TinyObjLoader allow it + std::vector vertex_indices; +}; + +// Internal data structure for points representation +struct __points_t { + // p v1 v2 ... + // In the specification, point primitrive does not have normal index and + // texture coord index, but TinyObjLoader allow it. + std::vector vertex_indices; +}; + +struct tag_sizes { + tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} + int num_ints; + int num_reals; + int num_strings; +}; + +struct obj_shape { + std::vector v; + std::vector vn; + std::vector vt; +}; + +// +// Manages group of primitives(face, line, points, ...) +struct PrimGroup { + std::vector faceGroup; + std::vector<__line_t> lineGroup; + std::vector<__points_t> pointsGroup; + + void clear() { + faceGroup.clear(); + lineGroup.clear(); + pointsGroup.clear(); + } + + bool IsEmpty() const { + return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty(); + } + + // TODO(syoyo): bspline, surface, ... +}; + +// See +// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf +static std::istream &safeGetline(std::istream &is, std::string &t) { + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf *sb = is.rdbuf(); + + if (se) { + for (;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if (sb->sgetc() == '\n') sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if (t.empty()) is.setstate(std::ios::eofbit); + return is; + default: + t += static_cast(c); + } + } + } + + return is; +} + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) \ + (static_cast((x) - '0') < static_cast(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +// Make index zero-base, and also support relative index. +static inline bool fixIndex(int idx, int n, int *ret) { + if (!ret) { + return false; + } + + if (idx > 0) { + (*ret) = idx - 1; + return true; + } + + if (idx == 0) { + // zero is not allowed according to the spec. + return false; + } + + if (idx < 0) { + (*ret) = n + idx; // negative value = relative + return true; + } + + return false; // never reach here. +} + +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; + bool leading_decimal_dots = false; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + if ((curr != s_end) && (*curr == '.')) { + // accept. Somethig like `.7e+2`, `-.5234` + leading_decimal_dots = true; + } + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else if (*curr == '.') { + // accept. Somethig like `.7e+2`, `-.5234` + leading_decimal_dots = true; + } else { + goto fail; + } + + // Read the integer part. + end_not_reached = (curr != s_end); + if (!leading_decimal_dots) { + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + // 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; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + static const double pow_lut[] = { + 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, + }; + const int lut_entries = sizeof pow_lut / sizeof pow_lut[0]; + + // NOTE: Don't use powf here, it will absolutely murder precision. + mantissa += static_cast(*curr - 0x30) * + (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read)); + read++; + curr++; + end_not_reached = (curr != s_end); + } + } 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. + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + // Empty E is not allowed. + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + // To avoid annoying MSVC's min/max macro definiton, + // Use hardcoded int max value + if (exponent > (2147483647/10)) { // 2147483647 = std::numeric_limits::max() + // Integer overflow + goto fail; + } + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) goto fail; + } + +assemble: + *result = (sign == '+' ? 1 : -1) * + (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) + : mantissa); + return true; +fail: + return false; +} + +static inline real_t parseReal(const char **token, double default_value = 0.0) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val = default_value; + tryParseDouble((*token), end, &val); + real_t f = static_cast(val); + (*token) = end; + return f; +} + +static inline bool parseReal(const char **token, real_t *out) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val; + bool ret = tryParseDouble((*token), end, &val); + if (ret) { + real_t f = static_cast(val); + (*out) = f; + } + (*token) = end; + return ret; +} + +static inline void parseReal2(real_t *x, real_t *y, const char **token, + const double default_x = 0.0, + const double default_y = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); +} + +static inline void parseReal3(real_t *x, real_t *y, real_t *z, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); +} + +static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0, + const double default_w = 1.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + (*w) = parseReal(token, default_w); +} + +// Extension: parse vertex with colors(6 items) +static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z, + real_t *r, real_t *g, real_t *b, + const char **token, + const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + + const bool found_color = + parseReal(token, r) && parseReal(token, g) && parseReal(token, b); + + if (!found_color) { + (*r) = (*g) = (*b) = 1.0; + } + + return found_color; +} + +static inline bool parseOnOff(const char **token, bool default_value = true) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + + bool ret = default_value; + if ((0 == strncmp((*token), "on", 2))) { + ret = true; + } else if ((0 == strncmp((*token), "off", 3))) { + ret = false; + } + + (*token) = end; + return ret; +} + +static inline texture_type_t parseTextureType( + const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + texture_type_t ty = default_value; + + if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) { + ty = TEXTURE_TYPE_CUBE_TOP; + } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) { + ty = TEXTURE_TYPE_CUBE_BOTTOM; + } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) { + ty = TEXTURE_TYPE_CUBE_LEFT; + } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) { + ty = TEXTURE_TYPE_CUBE_RIGHT; + } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) { + ty = TEXTURE_TYPE_CUBE_FRONT; + } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) { + ty = TEXTURE_TYPE_CUBE_BACK; + } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) { + ty = TEXTURE_TYPE_SPHERE; + } + + (*token) = end; + return ty; +} + +static tag_sizes parseTagTriple(const char **token) { + tag_sizes ts; + + (*token) += strspn((*token), " \t"); + ts.num_ints = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + + (*token)++; // Skip '/' + + (*token) += strspn((*token), " \t"); + ts.num_reals = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + (*token)++; // Skip '/' + + ts.num_strings = parseInt(token); + + return ts; +} + +// Parse triples with index offsets: i, i/j/k, i//k, i/j +static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize, + vertex_index_t *ret) { + if (!ret) { + return false; + } + + vertex_index_t vi(-1); + + if (!fixIndex(atoi((*token)), vsize, &(vi.v_idx))) { + return false; + } + + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + (*ret) = vi; + return true; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); + (*ret) = vi; + return true; + } + + // i/j/k or i/j + if (!fixIndex(atoi((*token)), vtsize, &(vi.vt_idx))) { + return false; + } + + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + (*ret) = vi; + return true; + } + + // i/j/k + (*token)++; // skip '/' + if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); + + (*ret) = vi; + + return true; +} + +// Parse raw triples: i, i/j/k, i//k, i/j +static vertex_index_t parseRawTriple(const char **token) { + vertex_index_t vi(static_cast(0)); // 0 is an invalid index in OBJ + + vi.v_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; +} + +bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, + const char *linebuf) { + // @todo { write more robust lexer and parser. } + bool found_texname = false; + std::string texture_name; + + const char *token = linebuf; // Assume line ends with NULL + + while (!IS_NEW_LINE((*token))) { + token += strspn(token, " \t"); // skip space + if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendu = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendv = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->clamp = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->sharpness = parseReal(&token, 1.0); + } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) { + token += 4; + texopt->bump_multiplier = parseReal(&token, 1.0); + } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), + &(texopt->origin_offset[2]), &token); + } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), + &token, 1.0, 1.0, 1.0); + } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), + &(texopt->turbulence[2]), &token); + } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) { + token += 5; + texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE); + } else if ((0 == strncmp(token, "-texres", 7)) && IS_SPACE((token[7]))) { + token += 7; + // TODO(syoyo): Check if arg is int type. + texopt->texture_resolution = parseInt(&token); + } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) { + token += 9; + token += strspn(token, " \t"); + const char *end = token + strcspn(token, " \t\r"); + if ((end - token) == 1) { // Assume one char for -imfchan + texopt->imfchan = (*token); + } + token = end; + } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) { + token += 4; + parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); + } else if ((0 == strncmp(token, "-colorspace", 11)) && + IS_SPACE((token[11]))) { + token += 12; + texopt->colorspace = parseString(&token); + } else { +// Assume texture filename +#if 0 + size_t len = strcspn(token, " \t\r"); // untile next space + texture_name = std::string(token, token + len); + token += len; + + token += strspn(token, " \t"); // skip space +#else + // Read filename until line end to parse filename containing whitespace + // TODO(syoyo): Support parsing texture option flag after the filename. + texture_name = std::string(token); + token += texture_name.length(); +#endif + + found_texname = true; + } + } + + if (found_texname) { + (*texname) = texture_name; + return true; + } else { + return false; + } +} + +static void InitTexOpt(texture_option_t *texopt, const bool is_bump) { + if (is_bump) { + texopt->imfchan = 'l'; + } else { + texopt->imfchan = 'm'; + } + texopt->bump_multiplier = static_cast(1.0); + texopt->clamp = false; + texopt->blendu = true; + texopt->blendv = true; + texopt->sharpness = static_cast(1.0); + texopt->brightness = static_cast(0.0); + texopt->contrast = static_cast(1.0); + texopt->origin_offset[0] = static_cast(0.0); + texopt->origin_offset[1] = static_cast(0.0); + texopt->origin_offset[2] = static_cast(0.0); + texopt->scale[0] = static_cast(1.0); + texopt->scale[1] = static_cast(1.0); + texopt->scale[2] = static_cast(1.0); + texopt->turbulence[0] = static_cast(0.0); + texopt->turbulence[1] = static_cast(0.0); + texopt->turbulence[2] = static_cast(0.0); + texopt->texture_resolution = -1; + texopt->type = TEXTURE_TYPE_NONE; +} + +static void InitMaterial(material_t *material) { + InitTexOpt(&material->ambient_texopt, /* is_bump */ false); + InitTexOpt(&material->diffuse_texopt, /* is_bump */ false); + InitTexOpt(&material->specular_texopt, /* is_bump */ false); + InitTexOpt(&material->specular_highlight_texopt, /* is_bump */ false); + InitTexOpt(&material->bump_texopt, /* is_bump */ true); + InitTexOpt(&material->displacement_texopt, /* is_bump */ false); + InitTexOpt(&material->alpha_texopt, /* is_bump */ false); + InitTexOpt(&material->reflection_texopt, /* is_bump */ false); + InitTexOpt(&material->roughness_texopt, /* is_bump */ false); + InitTexOpt(&material->metallic_texopt, /* is_bump */ false); + InitTexOpt(&material->sheen_texopt, /* is_bump */ false); + InitTexOpt(&material->emissive_texopt, /* is_bump */ false); + InitTexOpt(&material->normal_texopt, + /* is_bump */ false); // @fixme { is_bump will be true? } + material->name = ""; + material->ambient_texname = ""; + material->diffuse_texname = ""; + material->specular_texname = ""; + material->specular_highlight_texname = ""; + material->bump_texname = ""; + material->displacement_texname = ""; + material->reflection_texname = ""; + material->alpha_texname = ""; + for (int i = 0; i < 3; i++) { + material->ambient[i] = static_cast(0.0); + material->diffuse[i] = static_cast(0.0); + material->specular[i] = static_cast(0.0); + material->transmittance[i] = static_cast(0.0); + material->emission[i] = static_cast(0.0); + } + material->illum = 0; + material->dissolve = static_cast(1.0); + material->shininess = static_cast(1.0); + material->ior = static_cast(1.0); + + material->roughness = static_cast(0.0); + material->metallic = static_cast(0.0); + material->sheen = static_cast(0.0); + material->clearcoat_thickness = static_cast(0.0); + material->clearcoat_roughness = static_cast(0.0); + material->anisotropy_rotation = static_cast(0.0); + material->anisotropy = static_cast(0.0); + material->roughness_texname = ""; + material->metallic_texname = ""; + material->sheen_texname = ""; + material->emissive_texname = ""; + material->normal_texname = ""; + + material->unknown_parameter.clear(); +} + +// code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html +template +static int pnpoly(int nvert, T *vertx, T *verty, T testx, T testy) { + int i, j, c = 0; + for (i = 0, j = nvert - 1; i < nvert; j = i++) { + if (((verty[i] > testy) != (verty[j] > testy)) && + (testx < + (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + + vertx[i])) + c = !c; + } + return c; +} + +// TODO(syoyo): refactor function. +static bool exportGroupsToShape(shape_t *shape, const PrimGroup &prim_group, + const std::vector &tags, + const int material_id, const std::string &name, + bool triangulate, const std::vector &v, + std::string *warn) { + if (prim_group.IsEmpty()) { + return false; + } + + shape->name = name; + + // polygon + if (!prim_group.faceGroup.empty()) { + // Flatten vertices and indices + for (size_t i = 0; i < prim_group.faceGroup.size(); i++) { + const face_t &face = prim_group.faceGroup[i]; + + size_t npolys = face.vertex_indices.size(); + + if (npolys < 3) { + // Face must have 3+ vertices. + if (warn) { + (*warn) += "Degenerated face found\n."; + } + continue; + } + + if (triangulate) { + if (npolys == 4) { + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i1 = face.vertex_indices[1]; + vertex_index_t i2 = face.vertex_indices[2]; + vertex_index_t i3 = face.vertex_indices[3]; + + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + size_t vi2 = size_t(i2.v_idx); + size_t vi3 = size_t(i3.v_idx); + + if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || + ((3 * vi2 + 2) >= v.size()) || ((3 * vi3 + 2) >= v.size())) { + // Invalid triangle. + // FIXME(syoyo): Is it ok to simply skip this invalid triangle? + if (warn) { + (*warn) += "Face with invalid vertex index found.\n"; + } + continue; + } + + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + real_t v1x = v[vi1 * 3 + 0]; + real_t v1y = v[vi1 * 3 + 1]; + real_t v1z = v[vi1 * 3 + 2]; + real_t v2x = v[vi2 * 3 + 0]; + real_t v2y = v[vi2 * 3 + 1]; + real_t v2z = v[vi2 * 3 + 2]; + real_t v3x = v[vi3 * 3 + 0]; + real_t v3y = v[vi3 * 3 + 1]; + real_t v3z = v[vi3 * 3 + 2]; + + // There are two candidates to split the quad into two triangles. + // + // Choose the shortest edge. + // TODO: Is it better to determine the edge to split by calculating + // the area of each triangle? + // + // +---+ + // |\ | + // | \ | + // | \| + // +---+ + // + // +---+ + // | /| + // | / | + // |/ | + // +---+ + + real_t e02x = v2x - v0x; + real_t e02y = v2y - v0y; + real_t e02z = v2z - v0z; + real_t e13x = v3x - v1x; + real_t e13y = v3y - v1y; + real_t e13z = v3z - v1z; + + real_t sqr02 = e02x * e02x + e02y * e02y + e02z * e02z; + real_t sqr13 = e13x * e13x + e13y * e13y + e13z * e13z; + + index_t idx0, idx1, idx2, idx3; + + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; + idx3.vertex_index = i3.v_idx; + idx3.normal_index = i3.vn_idx; + idx3.texcoord_index = i3.vt_idx; + + if (sqr02 < sqr13) { + // [0, 1, 2], [0, 2, 3] + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx2); + shape->mesh.indices.push_back(idx3); + } else { + // [0, 1, 3], [1, 2, 3] + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx3); + + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + shape->mesh.indices.push_back(idx3); + } + + // Two triangle faces + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.num_face_vertices.push_back(3); + + shape->mesh.material_ids.push_back(material_id); + shape->mesh.material_ids.push_back(material_id); + + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + + } else { + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i1(-1); + vertex_index_t i2 = face.vertex_indices[1]; + + // find the two axes to work in + size_t axes[2] = {1, 2}; + for (size_t k = 0; k < npolys; ++k) { + i0 = face.vertex_indices[(k + 0) % npolys]; + i1 = face.vertex_indices[(k + 1) % npolys]; + i2 = face.vertex_indices[(k + 2) % npolys]; + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + size_t vi2 = size_t(i2.v_idx); + + if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || + ((3 * vi2 + 2) >= v.size())) { + // Invalid triangle. + // FIXME(syoyo): Is it ok to simply skip this invalid triangle? + continue; + } + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + real_t v1x = v[vi1 * 3 + 0]; + real_t v1y = v[vi1 * 3 + 1]; + real_t v1z = v[vi1 * 3 + 2]; + real_t v2x = v[vi2 * 3 + 0]; + real_t v2y = v[vi2 * 3 + 1]; + real_t v2z = v[vi2 * 3 + 2]; + real_t e0x = v1x - v0x; + real_t e0y = v1y - v0y; + real_t e0z = v1z - v0z; + real_t e1x = v2x - v1x; + real_t e1y = v2y - v1y; + real_t e1z = v2z - v1z; + real_t cx = std::fabs(e0y * e1z - e0z * e1y); + real_t cy = std::fabs(e0z * e1x - e0x * e1z); + real_t cz = std::fabs(e0x * e1y - e0y * e1x); + const real_t epsilon = std::numeric_limits::epsilon(); + // std::cout << "cx " << cx << ", cy " << cy << ", cz " << cz << + // "\n"; + if (cx > epsilon || cy > epsilon || cz > epsilon) { + // std::cout << "corner\n"; + // found a corner + if (cx > cy && cx > cz) { + // std::cout << "pattern0\n"; + } else { + // std::cout << "axes[0] = 0\n"; + axes[0] = 0; + if (cz > cx && cz > cy) { + // std::cout << "axes[1] = 1\n"; + axes[1] = 1; + } + } + break; + } + } + +#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + using Point = std::array; + + // first polyline define the main polygon. + // following polylines define holes(not used in tinyobj). + std::vector > polygon; + + std::vector polyline; + + // Fill polygon data(facevarying vertices). + for (size_t k = 0; k < npolys; k++) { + i0 = face.vertex_indices[k]; + size_t vi0 = size_t(i0.v_idx); + + assert(((3 * vi0 + 2) < v.size())); + + real_t v0x = v[vi0 * 3 + axes[0]]; + real_t v0y = v[vi0 * 3 + axes[1]]; + + polyline.push_back({v0x, v0y}); + } + + polygon.push_back(polyline); + std::vector indices = mapbox::earcut(polygon); + // => result = 3 * faces, clockwise + + assert(indices.size() % 3 == 0); + + // Reconstruct vertex_index_t + for (size_t k = 0; k < indices.size() / 3; k++) { + { + index_t idx0, idx1, idx2; + idx0.vertex_index = face.vertex_indices[indices[3 * k + 0]].v_idx; + idx0.normal_index = + face.vertex_indices[indices[3 * k + 0]].vn_idx; + idx0.texcoord_index = + face.vertex_indices[indices[3 * k + 0]].vt_idx; + idx1.vertex_index = face.vertex_indices[indices[3 * k + 1]].v_idx; + idx1.normal_index = + face.vertex_indices[indices[3 * k + 1]].vn_idx; + idx1.texcoord_index = + face.vertex_indices[indices[3 * k + 1]].vt_idx; + idx2.vertex_index = face.vertex_indices[indices[3 * k + 2]].v_idx; + idx2.normal_index = + face.vertex_indices[indices[3 * k + 2]].vn_idx; + idx2.texcoord_index = + face.vertex_indices[indices[3 * k + 2]].vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + } + +#else // Built-in ear clipping triangulation + + + face_t remainingFace = face; // copy + size_t guess_vert = 0; + vertex_index_t ind[3]; + real_t vx[3]; + real_t vy[3]; + + // How many iterations can we do without decreasing the remaining + // vertices. + size_t remainingIterations = face.vertex_indices.size(); + size_t previousRemainingVertices = + remainingFace.vertex_indices.size(); + + while (remainingFace.vertex_indices.size() > 3 && + remainingIterations > 0) { + // std::cout << "remainingIterations " << remainingIterations << + // "\n"; + + npolys = remainingFace.vertex_indices.size(); + if (guess_vert >= npolys) { + guess_vert -= npolys; + } + + if (previousRemainingVertices != npolys) { + // The number of remaining vertices decreased. Reset counters. + previousRemainingVertices = npolys; + remainingIterations = npolys; + } else { + // We didn't consume a vertex on previous iteration, reduce the + // available iterations. + remainingIterations--; + } + + for (size_t k = 0; k < 3; k++) { + ind[k] = remainingFace.vertex_indices[(guess_vert + k) % npolys]; + size_t vi = size_t(ind[k].v_idx); + if (((vi * 3 + axes[0]) >= v.size()) || + ((vi * 3 + axes[1]) >= v.size())) { + // ??? + vx[k] = static_cast(0.0); + vy[k] = static_cast(0.0); + } else { + vx[k] = v[vi * 3 + axes[0]]; + vy[k] = v[vi * 3 + axes[1]]; + } + } + + // + // area is calculated per face + // + real_t e0x = vx[1] - vx[0]; + real_t e0y = vy[1] - vy[0]; + real_t e1x = vx[2] - vx[1]; + real_t e1y = vy[2] - vy[1]; + real_t cross = e0x * e1y - e0y * e1x; + // std::cout << "axes = " << axes[0] << ", " << axes[1] << "\n"; + // std::cout << "e0x, e0y, e1x, e1y " << e0x << ", " << e0y << ", " + // << e1x << ", " << e1y << "\n"; + + real_t area = (vx[0] * vy[1] - vy[0] * vx[1]) * static_cast(0.5); + // std::cout << "cross " << cross << ", area " << area << "\n"; + // if an internal angle + if (cross * area < static_cast(0.0)) { + // std::cout << "internal \n"; + guess_vert += 1; + // std::cout << "guess vert : " << guess_vert << "\n"; + continue; + } + + // check all other verts in case they are inside this triangle + bool overlap = false; + for (size_t otherVert = 3; otherVert < npolys; ++otherVert) { + size_t idx = (guess_vert + otherVert) % npolys; + + if (idx >= remainingFace.vertex_indices.size()) { + // std::cout << "???0\n"; + // ??? + continue; + } + + size_t ovi = size_t(remainingFace.vertex_indices[idx].v_idx); + + if (((ovi * 3 + axes[0]) >= v.size()) || + ((ovi * 3 + axes[1]) >= v.size())) { + // std::cout << "???1\n"; + // ??? + continue; + } + real_t tx = v[ovi * 3 + axes[0]]; + real_t ty = v[ovi * 3 + axes[1]]; + if (pnpoly(3, vx, vy, tx, ty)) { + // std::cout << "overlap\n"; + overlap = true; + break; + } + } + + if (overlap) { + // std::cout << "overlap2\n"; + guess_vert += 1; + continue; + } + + // this triangle is an ear + { + index_t idx0, idx1, idx2; + idx0.vertex_index = ind[0].v_idx; + idx0.normal_index = ind[0].vn_idx; + idx0.texcoord_index = ind[0].vt_idx; + idx1.vertex_index = ind[1].v_idx; + idx1.normal_index = ind[1].vn_idx; + idx1.texcoord_index = ind[1].vt_idx; + idx2.vertex_index = ind[2].v_idx; + idx2.normal_index = ind[2].vn_idx; + idx2.texcoord_index = ind[2].vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + + // remove v1 from the list + size_t removed_vert_index = (guess_vert + 1) % npolys; + while (removed_vert_index + 1 < npolys) { + remainingFace.vertex_indices[removed_vert_index] = + remainingFace.vertex_indices[removed_vert_index + 1]; + removed_vert_index += 1; + } + remainingFace.vertex_indices.pop_back(); + } + + // std::cout << "remainingFace.vi.size = " << + // remainingFace.vertex_indices.size() << "\n"; + if (remainingFace.vertex_indices.size() == 3) { + i0 = remainingFace.vertex_indices[0]; + i1 = remainingFace.vertex_indices[1]; + i2 = remainingFace.vertex_indices[2]; + { + index_t idx0, idx1, idx2; + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + } +#endif + } // npolys + } else { + for (size_t k = 0; k < npolys; k++) { + index_t idx; + idx.vertex_index = face.vertex_indices[k].v_idx; + idx.normal_index = face.vertex_indices[k].vn_idx; + idx.texcoord_index = face.vertex_indices[k].vt_idx; + shape->mesh.indices.push_back(idx); + } + + shape->mesh.num_face_vertices.push_back( + static_cast(npolys)); + shape->mesh.material_ids.push_back(material_id); // per face + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); // per face + } + } + + shape->mesh.tags = tags; + } + + // line + if (!prim_group.lineGroup.empty()) { + // Flatten indices + for (size_t i = 0; i < prim_group.lineGroup.size(); i++) { + for (size_t j = 0; j < prim_group.lineGroup[i].vertex_indices.size(); + j++) { + const vertex_index_t &vi = prim_group.lineGroup[i].vertex_indices[j]; + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + shape->lines.indices.push_back(idx); + } + + shape->lines.num_line_vertices.push_back( + int(prim_group.lineGroup[i].vertex_indices.size())); + } + } + + // points + if (!prim_group.pointsGroup.empty()) { + // Flatten & convert indices + for (size_t i = 0; i < prim_group.pointsGroup.size(); i++) { + for (size_t j = 0; j < prim_group.pointsGroup[i].vertex_indices.size(); + j++) { + const vertex_index_t &vi = prim_group.pointsGroup[i].vertex_indices[j]; + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + shape->points.indices.push_back(idx); + } + } + } + + return true; +} + +// Split a string with specified delimiter character and escape character. +// https://rosettacode.org/wiki/Tokenize_a_string_with_escaping#C.2B.2B +static void SplitString(const std::string &s, char delim, char escape, + std::vector &elems) { + std::string token; + + bool escaping = false; + for (size_t i = 0; i < s.size(); ++i) { + char ch = s[i]; + if (escaping) { + escaping = false; + } else if (ch == escape) { + escaping = true; + continue; + } else if (ch == delim) { + if (!token.empty()) { + elems.push_back(token); + } + token.clear(); + continue; + } + token += ch; + } + + elems.push_back(token); +} + +static std::string JoinPath(const std::string &dir, + const std::string &filename) { + if (dir.empty()) { + return filename; + } else { + // check '/' + char lastChar = *dir.rbegin(); + if (lastChar != '/') { + return dir + std::string("/") + filename; + } else { + return dir + filename; + } + } +} + +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream, + std::string *warning, std::string *err) { + (void)err; + + // Create a default material anyway. + material_t material; + InitMaterial(&material); + + // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification. + bool has_d = false; + bool has_tr = false; + + // has_kd is used to set a default diffuse value when map_Kd is present + // and Kd is not. + bool has_kd = false; + + std::stringstream warn_ss; + + size_t line_no = 0; + std::string linebuf; + while (inStream->peek() != -1) { + safeGetline(*inStream, linebuf); + line_no++; + + // Trim trailing whitespace. + if (linebuf.size() > 0) { + linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); + } + + // 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)) && IS_SPACE((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); + + has_d = false; + has_tr = false; + + // set new mtl name + token += 7; + { + std::stringstream sstr; + sstr << token; + material.name = sstr.str(); + } + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&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' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + has_kd = true; + continue; + } + + // specular + if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&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' && IS_SPACE((token[2]))) || + (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { + token += 2; + real_t r, g, b; + parseReal3(&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' && IS_SPACE((token[2]))) { + token += 2; + material.ior = parseReal(&token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { + token += 2; + real_t r, g, b; + parseReal3(&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' && IS_SPACE(token[2])) { + token += 2; + material.shininess = parseReal(&token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { + token += 6; + material.illum = parseInt(&token); + continue; + } + + // dissolve + if ((token[0] == 'd' && IS_SPACE(token[1]))) { + token += 1; + material.dissolve = parseReal(&token); + + if (has_tr) { + warn_ss << "Both `d` and `Tr` parameters defined for \"" + << material.name + << "\". Use the value of `d` for dissolve (line " << line_no + << " in .mtl.)\n"; + } + has_d = true; + continue; + } + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + if (has_d) { + // `d` wins. Ignore `Tr` value. + warn_ss << "Both `d` and `Tr` parameters defined for \"" + << material.name + << "\". Use the value of `d` for dissolve (line " << line_no + << " in .mtl.)\n"; + } else { + // We invert value of Tr(assume Tr is in range [0, 1]) + // NOTE: Interpretation of Tr is application(exporter) dependent. For + // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43) + material.dissolve = static_cast(1.0) - parseReal(&token); + } + has_tr = true; + continue; + } + + // PBR: roughness + if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + material.roughness = parseReal(&token); + continue; + } + + // PBR: metallic + if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { + token += 2; + material.metallic = parseReal(&token); + continue; + } + + // PBR: sheen + if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.sheen = parseReal(&token); + continue; + } + + // PBR: clearcoat thickness + if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { + token += 2; + material.clearcoat_thickness = parseReal(&token); + continue; + } + + // PBR: clearcoat roughness + if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { + token += 4; + material.clearcoat_roughness = parseReal(&token); + continue; + } + + // PBR: anisotropy + if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { + token += 6; + material.anisotropy = parseReal(&token); + continue; + } + + // PBR: anisotropy rotation + if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { + token += 7; + material.anisotropy_rotation = parseReal(&token); + continue; + } + + // ambient texture + if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.ambient_texname), + &(material.ambient_texopt), token); + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.diffuse_texname), + &(material.diffuse_texopt), token); + + // Set a decent diffuse default value if a diffuse texture is specified + // without a matching Kd value. + if (!has_kd) { + material.diffuse[0] = static_cast(0.6); + material.diffuse[1] = static_cast(0.6); + material.diffuse[2] = static_cast(0.6); + } + + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.specular_texname), + &(material.specular_texopt), token); + continue; + } + + // specular highlight texture + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.specular_highlight_texname), + &(material.specular_highlight_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_Bump", 8)) && IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { + token += 6; + material.alpha_texname = token; + ParseTextureNameAndOption(&(material.alpha_texname), + &(material.alpha_texopt), token); + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.displacement_texname), + &(material.displacement_texopt), token); + continue; + } + + // reflection map + if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.reflection_texname), + &(material.reflection_texopt), token); + continue; + } + + // PBR: roughness texture + if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.roughness_texname), + &(material.roughness_texopt), token); + continue; + } + + // PBR: metallic texture + if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.metallic_texname), + &(material.metallic_texopt), token); + continue; + } + + // PBR: sheen texture + if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.sheen_texname), + &(material.sheen_texopt), token); + continue; + } + + // PBR: emissive texture + if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.emissive_texname), + &(material.emissive_texopt), token); + continue; + } + + // PBR: normal map texture + if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.normal_texname), + &(material.normal_texopt), 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, static_cast(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); + + if (warning) { + (*warning) = warn_ss.str(); + } +} + +bool MaterialFileReader::operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *warn, std::string *err) { + if (!m_mtlBaseDir.empty()) { +#ifdef _WIN32 + char sep = ';'; +#else + char sep = ':'; +#endif + + // https://stackoverflow.com/questions/5167625/splitting-a-c-stdstring-using-tokens-e-g + std::vector paths; + std::istringstream f(m_mtlBaseDir); + + std::string s; + while (getline(f, s, sep)) { + paths.push_back(s); + } + + for (size_t i = 0; i < paths.size(); i++) { + std::string filepath = JoinPath(paths[i], matId); + + std::ifstream matIStream(filepath.c_str()); + if (matIStream) { + LoadMtl(matMap, materials, &matIStream, warn, err); + + return true; + } + } + + std::stringstream ss; + ss << "Material file [ " << matId + << " ] not found in a path : " << m_mtlBaseDir << "\n"; + if (warn) { + (*warn) += ss.str(); + } + return false; + + } else { + std::string filepath = matId; + std::ifstream matIStream(filepath.c_str()); + if (matIStream) { + LoadMtl(matMap, materials, &matIStream, warn, err); + + return true; + } + + std::stringstream ss; + ss << "Material file [ " << filepath + << " ] not found in a path : " << m_mtlBaseDir << "\n"; + if (warn) { + (*warn) += ss.str(); + } + + return false; + } +} + +bool MaterialStreamReader::operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *warn, std::string *err) { + (void)err; + (void)matId; + if (!m_inStream) { + std::stringstream ss; + ss << "Material stream in error state. \n"; + if (warn) { + (*warn) += ss.str(); + } + return false; + } + + LoadMtl(matMap, materials, &m_inStream, warn, err); + + return true; +} + +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, const char *filename, const char *mtl_basedir, + bool triangulate, bool default_vcols_fallback) { + attrib->vertices.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + attrib->colors.clear(); + shapes->clear(); + + std::stringstream errss; + + std::ifstream ifs(filename); + if (!ifs) { + errss << "Cannot open file [" << filename << "]\n"; + if (err) { + (*err) = errss.str(); + } + return false; + } + + std::string baseDir = mtl_basedir ? mtl_basedir : ""; + if (!baseDir.empty()) { +#ifndef _WIN32 + const char dirsep = '/'; +#else + const char dirsep = '\\'; +#endif + if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; + } + MaterialFileReader matFileReader(baseDir); + + return LoadObj(attrib, shapes, materials, warn, err, &ifs, &matFileReader, + triangulate, default_vcols_fallback); +} + +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn /*= NULL*/, bool triangulate, + bool default_vcols_fallback) { + std::stringstream errss; + + std::vector v; + std::vector vn; + std::vector vt; + std::vector vc; + std::vector vw; + std::vector tags; + PrimGroup prim_group; + std::string name; + + // material + std::set material_filenames; + std::map material_map; + int material = -1; + + // smoothing group id + unsigned int current_smoothing_id = + 0; // Initial value. 0 means no smoothing. + + int greatest_v_idx = -1; + int greatest_vn_idx = -1; + int greatest_vt_idx = -1; + + shape_t shape; + + bool found_all_colors = true; + + size_t line_num = 0; + std::string linebuf; + while (inStream->peek() != -1) { + safeGetline(*inStream, linebuf); + + line_num++; + + // 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' && IS_SPACE((token[1]))) { + token += 2; + real_t x, y, z; + real_t r, g, b; + + found_all_colors &= parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token); + + v.push_back(x); + v.push_back(y); + v.push_back(z); + + if (found_all_colors || default_vcols_fallback) { + vc.push_back(r); + vc.push_back(g); + vc.push_back(b); + } + + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; + parseReal3(&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' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y; + parseReal2(&x, &y, &token); + vt.push_back(x); + vt.push_back(y); + continue; + } + + // skin weight. tinyobj extension + if (token[0] == 'v' && token[1] == 'w' && IS_SPACE((token[2]))) { + token += 3; + + // vw ... + // example: + // vw 0 0 0.25 1 0.25 2 0.5 + + // TODO(syoyo): Add syntax check + int vid = 0; + vid = parseInt(&token); + + skin_weight_t sw; + + sw.vertex_id = vid; + + while (!IS_NEW_LINE(token[0])) { + real_t j, w; + // joint_id should not be negative, weight may be negative + // TODO(syoyo): # of elements check + parseReal2(&j, &w, &token, -1.0); + + if (j < static_cast(0)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `vw' line. joint_id is negative. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + joint_and_weight_t jw; + + jw.joint_id = int(j); + jw.weight = w; + + sw.weightValues.push_back(jw); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + vw.push_back(sw); + } + + // line + if (token[0] == 'l' && IS_SPACE((token[1]))) { + token += 2; + + __line_t line; + + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `l' line(e.g. zero value for vertex index. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + line.vertex_indices.push_back(vi); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + prim_group.lineGroup.push_back(line); + + continue; + } + + // points + if (token[0] == 'p' && IS_SPACE((token[1]))) { + token += 2; + + __points_t pts; + + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `p' line(e.g. zero value for vertex index. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + pts.vertex_indices.push_back(vi); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + prim_group.pointsGroup.push_back(pts); + + continue; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + face_t face; + + face.smoothing_group_id = current_smoothing_id; + face.vertex_indices.reserve(3); + + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `f' line(e.g. zero value for face index. line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + greatest_v_idx = greatest_v_idx > vi.v_idx ? greatest_v_idx : vi.v_idx; + greatest_vn_idx = + greatest_vn_idx > vi.vn_idx ? greatest_vn_idx : vi.vn_idx; + greatest_vt_idx = + greatest_vt_idx > vi.vt_idx ? greatest_vt_idx : vi.vt_idx; + + face.vertex_indices.push_back(vi); + size_t n = strspn(token, " \t\r"); + token += n; + } + + // replace with emplace_back + std::move on C++11 + prim_group.faceGroup.push_back(face); + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6))) { + token += 6; + std::string namebuf = parseString(&token); + + int newMaterialId = -1; + std::map::const_iterator it = + material_map.find(namebuf); + if (it != material_map.end()) { + newMaterialId = it->second; + } else { + // { error!! material not found } + if (warn) { + (*warn) += "material [ '" + namebuf + "' ] not found in .mtl\n"; + } + } + + if (newMaterialId != material) { + // Create per-face material. Thus we don't add `shape` to `shapes` at + // this time. + // just clear `faceGroup` after `exportGroupsToShape()` call. + exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + prim_group.faceGroup.clear(); + material = newMaterialId; + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (readMatFn) { + token += 7; + + std::vector filenames; + SplitString(std::string(token), ' ', '\\', filenames); + + if (filenames.empty()) { + if (warn) { + std::stringstream ss; + ss << "Looks like empty filename for mtllib. Use default " + "material (line " + << line_num << ".)\n"; + + (*warn) += ss.str(); + } + } else { + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + if (material_filenames.count(filenames[s]) > 0) { + found = true; + continue; + } + + std::string warn_mtl; + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), materials, + &material_map, &warn_mtl, &err_mtl); + if (warn && (!warn_mtl.empty())) { + (*warn) += warn_mtl; + } + + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; + } + + if (ok) { + found = true; + material_filenames.insert(filenames[s]); + break; + } + } + + if (!found) { + if (warn) { + (*warn) += + "Failed to load material file(s). Use default " + "material.\n"; + } + } + } + } + + continue; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + // flush previous face group. + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + (void)ret; // return value not used. + + if (shape.mesh.indices.size() > 0) { + shapes->push_back(shape); + } + + shape = shape_t(); + + // material = -1; + prim_group.clear(); + + std::vector names; + + while (!IS_NEW_LINE(token[0])) { + std::string str = parseString(&token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + // names[0] must be 'g' + + if (names.size() < 2) { + // 'g' with empty names + if (warn) { + std::stringstream ss; + ss << "Empty group name. line: " << line_num << "\n"; + (*warn) += ss.str(); + name = ""; + } + } else { + std::stringstream ss; + ss << names[1]; + + // tinyobjloader does not support multiple groups for a primitive. + // Currently we concatinate multiple group names with a space to get + // single group name. + + for (size_t i = 2; i < names.size(); i++) { + ss << " " << names[i]; + } + + name = ss.str(); + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // flush previous face group. + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + (void)ret; // return value not used. + + if (shape.mesh.indices.size() > 0 || shape.lines.indices.size() > 0 || + shape.points.indices.size() > 0) { + shapes->push_back(shape); + } + + // material = -1; + prim_group.clear(); + shape = shape_t(); + + // @todo { multiple object name? } + token += 2; + std::stringstream ss; + ss << token; + name = ss.str(); + + continue; + } + + if (token[0] == 't' && IS_SPACE(token[1])) { + const int max_tag_nums = 8192; // FIXME(syoyo): Parameterize. + tag_t tag; + + token += 2; + + tag.name = parseString(&token); + + tag_sizes ts = parseTagTriple(&token); + + if (ts.num_ints < 0) { + ts.num_ints = 0; + } + if (ts.num_ints > max_tag_nums) { + ts.num_ints = max_tag_nums; + } + + if (ts.num_reals < 0) { + ts.num_reals = 0; + } + if (ts.num_reals > max_tag_nums) { + ts.num_reals = max_tag_nums; + } + + if (ts.num_strings < 0) { + ts.num_strings = 0; + } + if (ts.num_strings > max_tag_nums) { + ts.num_strings = max_tag_nums; + } + + tag.intValues.resize(static_cast(ts.num_ints)); + + for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { + tag.intValues[i] = parseInt(&token); + } + + tag.floatValues.resize(static_cast(ts.num_reals)); + for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { + tag.floatValues[i] = parseReal(&token); + } + + tag.stringValues.resize(static_cast(ts.num_strings)); + for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { + tag.stringValues[i] = parseString(&token); + } + + tags.push_back(tag); + + continue; + } + + if (token[0] == 's' && IS_SPACE(token[1])) { + // smoothing group id + token += 2; + + // skip space. + token += strspn(token, " \t"); // skip space + + if (token[0] == '\0') { + continue; + } + + if (token[0] == '\r' || token[1] == '\n') { + continue; + } + + if (strlen(token) >= 3 && token[0] == 'o' && token[1] == 'f' && + token[2] == 'f') { + current_smoothing_id = 0; + } else { + // assume number + int smGroupId = parseInt(&token); + if (smGroupId < 0) { + // parse error. force set to 0. + // FIXME(syoyo): Report warning. + current_smoothing_id = 0; + } else { + current_smoothing_id = static_cast(smGroupId); + } + } + + continue; + } // smoothing group id + + // Ignore unknown command. + } + + // not all vertices have colors, no default colors desired? -> clear colors + if (!found_all_colors && !default_vcols_fallback) { + vc.clear(); + } + + if (greatest_v_idx >= static_cast(v.size() / 3)) { + if (warn) { + std::stringstream ss; + ss << "Vertex indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + if (greatest_vn_idx >= static_cast(vn.size() / 3)) { + if (warn) { + std::stringstream ss; + ss << "Vertex normal indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + if (greatest_vt_idx >= static_cast(vt.size() / 2)) { + if (warn) { + std::stringstream ss; + ss << "Vertex texcoord indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + // exportGroupsToShape return false when `usemtl` is called in the last + // line. + // we also add `shape` to `shapes` when `shape.mesh` has already some + // faces(indices) + if (ret || shape.mesh.indices + .size()) { // FIXME(syoyo): Support other prims(e.g. lines) + shapes->push_back(shape); + } + prim_group.clear(); // for safety + + if (err) { + (*err) += errss.str(); + } + + attrib->vertices.swap(v); + attrib->vertex_weights.swap(v); + attrib->normals.swap(vn); + attrib->texcoords.swap(vt); + attrib->texcoord_ws.swap(vt); + attrib->colors.swap(vc); + attrib->skin_weights.swap(vw); + + return true; +} + +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data /*= NULL*/, + MaterialReader *readMatFn /*= NULL*/, + std::string *warn, /* = NULL*/ + std::string *err /*= NULL*/) { + std::stringstream errss; + + // material + std::set material_filenames; + std::map material_map; + int material_id = -1; // -1 = invalid + + std::vector indices; + std::vector materials; + std::vector names; + names.reserve(2); + std::vector names_out; + + std::string linebuf; + while (inStream.peek() != -1) { + safeGetline(inStream, linebuf); + + // 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' && IS_SPACE((token[1]))) { + token += 2; + // TODO(syoyo): Support parsing vertex color extension. + real_t x, y, z, w; // w is optional. default = 1.0 + parseV(&x, &y, &z, &w, &token); + if (callback.vertex_cb) { + callback.vertex_cb(user_data, x, y, z, w); + } + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; + parseReal3(&x, &y, &z, &token); + if (callback.normal_cb) { + callback.normal_cb(user_data, x, y, z); + } + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; // y and z are optional. default = 0.0 + parseReal3(&x, &y, &z, &token); + if (callback.texcoord_cb) { + callback.texcoord_cb(user_data, x, y, z); + } + continue; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + indices.clear(); + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi = parseRawTriple(&token); + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + indices.push_back(idx); + size_t n = strspn(token, " \t\r"); + token += n; + } + + if (callback.index_cb && indices.size() > 0) { + callback.index_cb(user_data, &indices.at(0), + static_cast(indices.size())); + } + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + token += 7; + std::stringstream ss; + ss << token; + std::string namebuf = ss.str(); + + int newMaterialId = -1; + std::map::const_iterator it = + material_map.find(namebuf); + if (it != material_map.end()) { + newMaterialId = it->second; + } else { + // { warn!! material not found } + if (warn && (!callback.usemtl_cb)) { + (*warn) += "material [ " + namebuf + " ] not found in .mtl\n"; + } + } + + if (newMaterialId != material_id) { + material_id = newMaterialId; + } + + if (callback.usemtl_cb) { + callback.usemtl_cb(user_data, namebuf.c_str(), material_id); + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (readMatFn) { + token += 7; + + std::vector filenames; + SplitString(std::string(token), ' ', '\\', filenames); + + if (filenames.empty()) { + if (warn) { + (*warn) += + "Looks like empty filename for mtllib. Use default " + "material. \n"; + } + } else { + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + if (material_filenames.count(filenames[s]) > 0) { + found = true; + continue; + } + + std::string warn_mtl; + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), &materials, + &material_map, &warn_mtl, &err_mtl); + + if (warn && (!warn_mtl.empty())) { + (*warn) += warn_mtl; // This should be warn message. + } + + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; + } + + if (ok) { + found = true; + material_filenames.insert(filenames[s]); + break; + } + } + + if (!found) { + if (warn) { + (*warn) += + "Failed to load material file(s). Use default " + "material.\n"; + } + } else { + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } + } + } + } + + continue; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + names.clear(); + + while (!IS_NEW_LINE(token[0])) { + std::string str = parseString(&token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + assert(names.size() > 0); + + if (callback.group_cb) { + if (names.size() > 1) { + // create const char* array. + names_out.resize(names.size() - 1); + for (size_t j = 0; j < names_out.size(); j++) { + names_out[j] = names[j + 1].c_str(); + } + callback.group_cb(user_data, &names_out.at(0), + static_cast(names_out.size())); + + } else { + callback.group_cb(user_data, NULL, 0); + } + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + token += 2; + + std::stringstream ss; + ss << token; + std::string object_name = ss.str(); + + if (callback.object_cb) { + callback.object_cb(user_data, object_name.c_str()); + } + + continue; + } + +#if 0 // @todo + if (token[0] == 't' && IS_SPACE(token[1])) { + tag_t tag; + + token += 2; + std::stringstream ss; + ss << token; + tag.name = ss.str(); + + token += tag.name.size() + 1; + + tag_sizes ts = parseTagTriple(&token); + + tag.intValues.resize(static_cast(ts.num_ints)); + + for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { + tag.intValues[i] = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.floatValues.resize(static_cast(ts.num_reals)); + for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { + tag.floatValues[i] = parseReal(&token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.stringValues.resize(static_cast(ts.num_strings)); + for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { + std::stringstream ss; + ss << token; + tag.stringValues[i] = ss.str(); + token += tag.stringValues[i].size() + 1; + } + + tags.push_back(tag); + } +#endif + + // Ignore unknown command. + } + + if (err) { + (*err) += errss.str(); + } + + return true; +} + +bool ObjReader::ParseFromFile(const std::string &filename, + const ObjReaderConfig &config) { + std::string mtl_search_path; + + if (config.mtl_search_path.empty()) { + // + // split at last '/'(for unixish system) or '\\'(for windows) to get + // the base directory of .obj file + // + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) { + mtl_search_path = filename.substr(0, pos); + } + } else { + mtl_search_path = config.mtl_search_path; + } + + valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, + filename.c_str(), mtl_search_path.c_str(), + config.triangulate, config.vertex_color); + + return valid_; +} + +bool ObjReader::ParseFromString(const std::string &obj_text, + const std::string &mtl_text, + const ObjReaderConfig &config) { + std::stringbuf obj_buf(obj_text); + std::stringbuf mtl_buf(mtl_text); + + std::istream obj_ifs(&obj_buf); + std::istream mtl_ifs(&mtl_buf); + + MaterialStreamReader mtl_ss(mtl_ifs); + + valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, + &obj_ifs, &mtl_ss, config.triangulate, config.vertex_color); + + return valid_; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} // namespace tinyobj + +#endif