diff --git a/CMakeLists.txt b/CMakeLists.txt index 62c0e599..7ec57edd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,8 @@ set(headers src/sceneStructs.h src/preview.h src/utilities.h + src/tiny_obj_loader.h + src/polygon.h ) set(sources @@ -84,6 +86,8 @@ set(sources src/scene.cpp src/preview.cpp src/utilities.cpp + src/tiny_obj_loader.cc + src/polygon.cpp ) list(SORT headers) diff --git a/README.md b/README.md index 110697ce..e2e2e50e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,201 @@ -CUDA Path Tracer -================ +Project 3 CUSA Path Tracer +====================== -**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 3** +**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) +* Raymond Yang + * [LinkedIn](https://www.linkedin.com/in/raymond-yang-b85b19168) + * Tested on: + * 10/09/2021 + * Windows 10 + * NVIDIA GeForce GTX 1080 Ti. + * Submitted on: 10/09/2021 + * Used 3 Late Days -### (TODO: Your README) +

+ drawing +

-*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. +

+ drawing +

+## Introduction +The objective of this project was to implement a naive core path tracer that took a simplistic approach to rendering scenes. +

+ drawing +

+From a camera, a viewing plane (or image plane) can projected. We simulate the physical characters of light by shooting a ray (photon) from each pixel within our viewing plane towards the scene. The rays would iteratively be bounced from an origin point and a surface. In each iteration, the ray can either miss the scene entirely (entering a void) or can be obstructed by an entity within the scene. The ray can be obstructed either by a light source or non-light source. +

+ drawing +

+For each iteration, if the ray is not obstructed or is obstructed by a light source, the ray is terminated. If the ray is obstructed by a non-light source, it will reflect, refract, and/or diffuse against the obstructing surface. +

+ drawing +

+For each successful obstruction, the color of the obstructing surface is factored into the final color the ray's original corresponding pixel. + + +## Core Features +The [core features](https://github.com/CIS565-Fall-2021/Project3-CUDA-Path-Tracer/blob/main/INSTRUCTION.md#part-1---core-features) include: +* Naive BSDF Path Tracer (Feature Implementation) +* First Iteration Caching (Performance Improvement) +* Ray Stream Compaction (Performance Improvement) +* Material Sorting (Performance Improvement) +All features and performance improvements may be toggled by `#define`s found in `src/sceneStructs.h` +

+ drawing +

+ +### Naive BSDF Path Tracer +Bidirectional scattering distribution function (BSDF) is a combination of bidirectional reflectance distribution function (BRDF) and bidirectional transmittance distribution function (BTDF). Given any material, as defined as a component of reflectance and refractance, rays should demonstrate a combination of reflecting, refracting, and diffusing behavior. In `scenes/`, materials are defined such that: +

+ REFL + REFR <= 1 +

+As a result, the range `[0,1]` can be broken into two components such that: +

+ REFL + REFR + DIFFUSION == 1 +

+ +* Cornell with reflective sphere: +

+ drawing +

+* Cornell with refractive sphere: +

+ drawing +

+* Cornell with diffusive sphere: +

+ drawing +

+* Cornell with all three properties: +

+ drawing +

+ +### First Iteration Caching +Iterations allow a more precise, represetative image of the scene by repeatedly shooting rays into the scene. Without antialiasing, every first bounce (ray from image plane to scene) between all iterations should be identical. As a result, we should be able to cache the results of the first bounce of the first iteration and use this data in subsequent iterations without re-calculating the first bounce. + +### Ray Stream Compaction +Each ray is terminated when it either hits a light source or is not obstructed. Between each depth, where depth is defined as each batch of single bounces, in each iteration, we can cull a number of rays that have should be terminated by performing stream compaction. Consequently, fewer rays (and threads) must be launched in subsequent depths to optimize on memory and computation. + +### Material Sorting +Like stream compaction, each ray stores the type of material it was obstructed by. Between each depth, we sort rays by their material type. The intention is to minimize branch divergence in subsequent depths. Rays where are obstructed by similar surface materials are more likely to demonstrate similar behavior and require relatively comparable computations times. This allows the GPU to terminate entire warps who are processing rays against similar surfaces more quickly and with fewer stalls. + +## Additional Features +The [unique features](https://github.com/CIS565-Fall-2021/Project3-CUDA-Path-Tracer/blob/main/INSTRUCTION.md#part-2---make-your-pathtracer-unique) include: +* Mesh Loading using tinyOBJ (Feature Implementation) + * Bounding Box (Performance Improvement) +* [Anti-Aliasing](https://raytracing.github.io/books/RayTracingInOneWeekend.html#antialiasing) (Feature Implementation) +* [Refraction using Schlick's Approximation](https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics) (Feature Implementation) + +### Mesh Loading using tinyOBJ +This feature allows you to import unique .OBJ mesh into the path tracer. Much of the code was refactored from CIS560's' rasterizer. As an object is loaded, we generate an buffer of tuples of vertex position and normal. OBJ files follow a format such that each three groups of data represent a face of a triangle on the mesh. Once this data is loaded into the GPU, the GPU checks for intersections of rays against these triangle meshes. +

+ drawing +

+ +#### Bounding Box +Each mesh is a complex arrangement of numerous triangular faces with unique vertices and normals. The naive implementation would check every ray projected into the scene against every triangular surface of every mesh. This is clearly computationally expensive and time consuming. The first step to optimize this would be to restrict the volume of each mesh into a bounding box. That is, a mesh will only be checked against a ray for intersection if the ray will enter the bounding box of the mesh. The current implementation is minimally effective in that it is a single volume bounding box around the entire mesh. + +### Anti-Aliasing +Anti-aliasing is a common feature that slightly distorts how a scene is rendered. This prevents far objects from being rendered with sharp edges that would typically result in texture jittering and collisions. The current implementation deviates the origin ray direction that is first projected from the camera into the scene on a random distribution. More precisely, the first ray of each iteration is shot out from a random position within the same pixel. That way, we obtain a better average of the color of the pixel. +

+ https://raytracing.github.io/images/fig-1.07-pixel-samples.jpg +

+#### Anti-Aliasing full images +With anti-aliasing: +

+ drawing +

+Without anti-aliasing: +

+ drawing +

+#### Anti-Aliasing zoomed images +With anti-aliasing: +

+ drawing +

+Without anti-aliasing: +

+ drawing +

+ +### Refraction using Schlick's Approximation +If we looked at a refractive material surface such as a plane of glass or clear plastic from a steep angle, the material ceases to demonstrate refractive properties and would show reflective properties instead. The current implementation mimics this behavior using Schlick's approximation in cases where the incident angle between the surface and the ray is sufficiently shallow, and snell's law in cases where the incident angle between the surface and the ray is sufficiently large. + +With Schlick's Approximation: +

+ drawing +

+Without Schlick's Approximation: +

+ drawing +

+ +## Performance Analysis +There are four features intended to optimize the path tracer: +* First Iteration Caching +* Ray Stream Compaction +* Material Sorting +* Bounding Box + +### Ray Stream Compaction +Benchmark: `scene/cornell.txt` +This section intends to measure the isolated rate of culling rays via stream compaction. More threads culled is correlated with improved performance. All renders begin with 640,000 rays. The values in the charts and number of rays remaining after each iteration. + +Lower is better: +

+ drawing +

+ +#### Open Scene +| Iteration | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|-----------|----------|----------|----------|----------|----------|----------|----------|----------| +| Depth 1 | 618771 | | | | | | | | +| Depth 2 | 618771 | 450015 | | | | | | | +| Depth 3 | 618771 | 450015 | 348852 | | | | | | +| Depth 4 | 618771 | 450015 | 348852 | 279639 | | | | | +| Depth 5 | 618771 | 450015 | 348852 | 279639 | 228985 | | | | +| Depth 6 | 618771 | 450015 | 348852 | 279639 | 228985 | 189313 | | | +| Depth 7 | 618771 | 450015 | 348852 | 279639 | 228985 | 189313 | 157422 | | +| Depth 8 | 618771 | 450015 | 348852 | 279639 | 228985 | 189313 | 157422 | 131689 | + +#### Closed Scene +| Iteration | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +|-----------|----------|----------|----------|----------|----------|----------|----------|----------| +| Depth 1 | 605474 | | | | | | | | +| Depth 2 | 605474 | 587380 | | | | | | | +| Depth 3 | 605474 | 587380 | 570202 | | | | | | +| Depth 4 | 605474 | 587380 | 570202 | 554235 | | | | | +| Depth 5 | 605474 | 587380 | 570202 | 554235 | 538737 | | | | +| Depth 6 | 605474 | 587380 | 570202 | 554235 | 538737 | 523410 | | | +| Depth 7 | 605474 | 587380 | 570202 | 554235 | 538737 | 523410 | 509205 | | +| Depth 8 | 605474 | 587380 | 570202 | 554235 | 538737 | 523410 | 509205 | 495054 | + +### Comparisons +Benchmark: `scene/cornellOBJ.txt` +This section intends to measure the efficacy of each optimization in isolation and finally all together. Effectiveness is measured in runtime. Lower runtime is better. Runtime is determined by the total clocktime for the path tracer to complete 100 iterations under the following condition: +* No optimization +* With first iteration caching +* With ray stream compaction +* With material sorting +* With bounding box +* With first iteration caching, ray stream compaction, material sorting, and bounding box + +| | Time (s) | +|------------------|----------| +| No Optimization | 778 | +| 1st Iter Cache | 624 | +| Ray Compaction | 228 | +| Material Sorting | 770 | +| Bounding Box | 704 | +| All Optimization | 209 | +

+ drawing +

+ +#### Error Analysis +Note that my render times for a simple mesh are extraordinarily high. Therefore, I do not believe my results are even close to representative of correctly implemented optimizations. I believe that I have instantiated, or at least incorrectly launched, my kernels such that OBJ rendering is almost serialized. \ No newline at end of file diff --git a/img/a.png b/img/a.png new file mode 100644 index 00000000..14fab20a Binary files /dev/null and b/img/a.png differ diff --git a/img/b.png b/img/b.png new file mode 100644 index 00000000..6389650d Binary files /dev/null and b/img/b.png differ diff --git a/img/c.png b/img/c.png new file mode 100644 index 00000000..d19276e9 Binary files /dev/null and b/img/c.png differ diff --git a/img/cornellAAN.png b/img/cornellAAN.png new file mode 100644 index 00000000..f30b88b7 Binary files /dev/null and b/img/cornellAAN.png differ diff --git a/img/cornellAANFull.png b/img/cornellAANFull.png new file mode 100644 index 00000000..7803e94f Binary files /dev/null and b/img/cornellAANFull.png differ diff --git a/img/cornellAAY.png b/img/cornellAAY.png new file mode 100644 index 00000000..7af2e154 Binary files /dev/null and b/img/cornellAAY.png differ diff --git a/img/cornellAAYFull.png b/img/cornellAAYFull.png new file mode 100644 index 00000000..bc156bca Binary files /dev/null and b/img/cornellAAYFull.png differ diff --git a/img/cornellBalanced.png b/img/cornellBalanced.png new file mode 100644 index 00000000..c8897243 Binary files /dev/null and b/img/cornellBalanced.png differ diff --git a/img/cornellDiffuse.png b/img/cornellDiffuse.png new file mode 100644 index 00000000..5299dda8 Binary files /dev/null and b/img/cornellDiffuse.png differ diff --git a/img/cornellReflective.png b/img/cornellReflective.png new file mode 100644 index 00000000..86c06510 Binary files /dev/null and b/img/cornellReflective.png differ diff --git a/img/cornellRefractive.png b/img/cornellRefractive.png new file mode 100644 index 00000000..a90e4f8b Binary files /dev/null and b/img/cornellRefractive.png differ diff --git a/img/cornellWahoo.png b/img/cornellWahoo.png new file mode 100644 index 00000000..a9e8c0d8 Binary files /dev/null and b/img/cornellWahoo.png differ diff --git a/img/d.png b/img/d.png new file mode 100644 index 00000000..aee91f34 Binary files /dev/null and b/img/d.png differ diff --git a/img/demo.png b/img/demo.png new file mode 100644 index 00000000..77d19204 Binary files /dev/null and b/img/demo.png differ diff --git a/img/demoGreen.png b/img/demoGreen.png new file mode 100644 index 00000000..0bb5cf48 Binary files /dev/null and b/img/demoGreen.png differ diff --git a/img/glassWallSchlickN.png b/img/glassWallSchlickN.png new file mode 100644 index 00000000..5cd3633c Binary files /dev/null and b/img/glassWallSchlickN.png differ diff --git a/img/glassWallSchlickY.png b/img/glassWallSchlickY.png new file mode 100644 index 00000000..15d9973e Binary files /dev/null and b/img/glassWallSchlickY.png differ diff --git a/img/openvclose.PNG b/img/openvclose.PNG new file mode 100644 index 00000000..35f7746d Binary files /dev/null and b/img/openvclose.PNG differ diff --git a/img/timeComparison.PNG b/img/timeComparison.PNG new file mode 100644 index 00000000..1c5caff0 Binary files /dev/null and b/img/timeComparison.PNG differ diff --git a/md b/md new file mode 100644 index 00000000..e69de29b diff --git a/scenes/cornell.txt b/scenes/cornell.txt index 83ff8202..b93298ce 100644 --- a/scenes/cornell.txt +++ b/scenes/cornell.txt @@ -43,16 +43,16 @@ MATERIAL 4 RGB .98 .98 .98 SPECEX 0 SPECRGB .98 .98 .98 -REFL 1 -REFR 0 -REFRIOR 0 +REFL 0.2 +REFR 0.6 +REFRIOR 1.5 EMITTANCE 0 // Camera CAMERA RES 800 800 FOVY 45 -ITERATIONS 5000 +ITERATIONS 200 DEPTH 8 FILE cornell EYE 0.0 5 10.5 @@ -66,7 +66,7 @@ cube material 0 TRANS 0 10 0 ROTAT 0 0 0 -SCALE 3 .3 3 +SCALE 4 .4 4 // Floor OBJECT 1 @@ -114,4 +114,4 @@ sphere material 4 TRANS -1 4 -1 ROTAT 0 0 0 -SCALE 3 3 3 +SCALE 3 3 3 \ No newline at end of file diff --git a/scenes/cornellAA.txt b/scenes/cornellAA.txt new file mode 100644 index 00000000..270d2b24 --- /dev/null +++ b/scenes/cornellAA.txt @@ -0,0 +1,127 @@ +// Emissive material (light) +MATERIAL 0 +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Diffuse blue +MATERIAL 1 +RGB .35 .35 .85 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse white +MATERIAL 4 +RGB .85 .85 .85 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 1.0 +REFRIOR 1.5 +EMITTANCE 0 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 200 +DEPTH 8 +FILE cornell +EYE 3 -3 6 +LOOKAT -3.48 1.52 -3.48 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 0 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 4 .4 4 + +// Floor +OBJECT 1 +cube +material 4 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 4 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Sphere +OBJECT 6 +sphere +material 5 +TRANS -3.48 1.52 -3.48 +ROTAT 0 0 0 +SCALE 3 3 3 diff --git a/scenes/cornellOBJ.txt b/scenes/cornellOBJ.txt new file mode 100644 index 00000000..7259855b --- /dev/null +++ b/scenes/cornellOBJ.txt @@ -0,0 +1,173 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0.7 0.3 0.3 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 2 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 1 +REFRIOR 1.6 +EMITTANCE 0 + +// Test Color +MATERIAL 6 +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Emissive material (light) +MATERIAL 7 +RGB 0.3 0.7 0.3 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 2 + +// Emissive material (light) +MATERIAL 8 +RGB 0.3 0.3 0.7 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 2 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 100 +DEPTH 8 +FILE cornell +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 8 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 5 .1 5 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 10 0.01 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right Light +OBJECT 6 +cube +material 7 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE 0.1 5 5 + +// Left Light +OBJECT 7 +cube +material 0 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE 0.1 5 5 + +// OBJ wahoo +OBJECT 8 +obj +material 6 +TRANS 0 2.5 0 +ROTAT 0 0 0 +SCALE 1 1 1 \ No newline at end of file diff --git a/scenes/cornellPlus.txt b/scenes/cornellPlus.txt new file mode 100644 index 00000000..075cec06 --- /dev/null +++ b/scenes/cornellPlus.txt @@ -0,0 +1,135 @@ +// Emissive material (light) +MATERIAL 0 +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 1 +REFRIOR 1.6 +EMITTANCE 0 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 5000 +DEPTH 8 +FILE cornell +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 0 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 3 .3 3 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Reflective Sphere +OBJECT 6 +sphere +material 4 +TRANS -2.25 4 -1 +ROTAT 0 0 0 +SCALE 3 3 3 + +// Refractive Sphere +OBJECT 7 +sphere +material 5 +TRANS 2.25 4 -1 +ROTAT 0 0 0 +SCALE 3 3 3 \ No newline at end of file diff --git a/scenes/demo.txt b/scenes/demo.txt new file mode 100644 index 00000000..be06e28e --- /dev/null +++ b/scenes/demo.txt @@ -0,0 +1,528 @@ +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +// Light White +MATERIAL 0 +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 7 + +// Reflective +MATERIAL 1 +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse +MATERIAL 2 +RGB 0.35 0.85 0.85 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse +MATERIAL 3 +RGB 0.85 0.35 0.85 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse +MATERIAL 4 +RGB 0.85 0.85 0.35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse +MATERIAL 5 +RGB 0.35 0.85 0.35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse +MATERIAL 6 +RGB 0.35 0.35 0.85 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Refractive +MATERIAL 7 +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Diffuse +MATERIAL 8 +RGB 0.98 0.98 0.98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse +MATERIAL 9 +RGB 0.43 0.34 0.24 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse +MATERIAL 10 +RGB 0.5 0.5 0.5 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Light White +MATERIAL 11 +RGB 1 0.1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 7 + +// Light White +MATERIAL 12 +RGB 1 0.1 0.1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 7 + +// Light White +MATERIAL 13 +RGB 0.1 0.1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 7 + +// Light White +MATERIAL 14 +RGB 0.1 1 0.1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 7 + +// Diffuse Reflective +MATERIAL 15 +RGB 0.35 0.75 0.35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0.4 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 1500 +DEPTH 12 +FILE cornell +EYE 0 30 70 +LOOKAT 0 30 0 +UP 0 1 0 + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +// Ceiling light +OBJECT 0 +cube +material 11 +TRANS -20 80 0 +ROTAT 0 0 0 +SCALE 10 1 10 + +// Ceiling light +OBJECT 1 +cube +material 11 +TRANS 20 80 0 +ROTAT 0 0 0 +SCALE 10 1 10 + +// Ceiling light +OBJECT 2 +cube +material 0 +TRANS 0 80 0 +ROTAT 0 0 0 +SCALE 10 1 10 + +// Ceiling light +OBJECT 3 +cube +material 12 +TRANS -20 80 -20 +ROTAT 0 0 0 +SCALE 10 1 10 + +// Ceiling light +OBJECT 4 +cube +material 13 +TRANS 20 80 -20 +ROTAT 0 0 0 +SCALE 10 1 10 + +// Ceiling light +OBJECT 5 +cube +material 14 +TRANS 0 80 -20 +ROTAT 0 0 0 +SCALE 10 1 10 + +// Ceiling light +OBJECT 6 +cube +material 13 +TRANS -20 80 20 +ROTAT 0 0 0 +SCALE 10 1 10 + +// Ceiling light +OBJECT 7 +cube +material 12 +TRANS 20 80 20 +ROTAT 0 0 0 +SCALE 10 1 10 + +// Ceiling light +OBJECT 8 +cube +material 14 +TRANS 0 80 20 +ROTAT 0 0 0 +SCALE 10 1 10 + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +// Cube +OBJECT 8 +cube +material 7 +TRANS -12 38.960193405 0 +ROTAT 4 60 53 +SCALE 3.375 3.375 3.375 + +// Cube +OBJECT 9 +cube +material 1 +TRANS -12 32.9939799387 0 +ROTAT 42 25 28 +SCALE 5.0625 5.0625 5.0625 + +// Cube +OBJECT 10 +cube +material 7 +TRANS -12 24.0446597393 0 +ROTAT 19 63 72 +SCALE 7.59375 7.59375 7.59375 + +// Cube +OBJECT 11 +cube +material 7 +TRANS -12 15.0953395399 0 +ROTAT 32 0 58 +SCALE 5.0625 5.0625 5.0625 + +// Cube +OBJECT 12 +cube +material 7 +TRANS -12 9.12912607362 0 +ROTAT 80 14 27 +SCALE 3.375 3.375 3.375 + +// Cube +OBJECT 13 +cube +material 1 +TRANS -12 5.15165042945 0 +ROTAT 67 69 47 +SCALE 2.25 2.25 2.25 + +// Cube +OBJECT 14 +cube +material 7 +TRANS -12 2.5 0 +ROTAT 73 3 57 +SCALE 1.5 1.5 1.5 + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +// Cube +OBJECT 15 +cube +material 1 +TRANS 12 47.6954830935 0 +ROTAT 65 29 48 +SCALE 3.375 3.375 3.375 + +// Cube +OBJECT 16 +cube +material 1 +TRANS 12 41.7292696272 0 +ROTAT 44 16 24 +SCALE 5.0625 5.0625 5.0625 + +// Cube +OBJECT 17 +cube +material 7 +TRANS 12 35.7630561609 0 +ROTAT 34 41 88 +SCALE 3.375 3.375 3.375 + +// Cube +OBJECT 18 +cube +material 7 +TRANS 12 31.7855805167 0 +ROTAT 52 59 49 +SCALE 2.25 2.25 2.25 + +// Cube +OBJECT 19 +cube +material 7 +TRANS 12 29.1339300873 0 +ROTAT 84 70 54 +SCALE 1.5 1.5 1.5 + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +// Cube +OBJECT 20 +cube +material 7 +TRANS 0 43.3278382492 12 +ROTAT 18 21 68 +SCALE 7.529536 7.529536 7.529536 + +// Cube +OBJECT 21 +cube +material 7 +TRANS 0 35.6914949854 12 +ROTAT 71 41 16 +SCALE 5.37824 5.37824 5.37824 + +// Cube +OBJECT 22 +cube +material 7 +TRANS 0 30.2369640827 12 +ROTAT 36 20 1 +SCALE 3.8416 3.8416 3.8416 + +// Cube +OBJECT 23 +cube +material 1 +TRANS 0 26.3408705807 12 +ROTAT 87 76 24 +SCALE 2.744 2.744 2.744 + +// Cube +OBJECT 24 +cube +material 1 +TRANS 0 23.5579466507 12 +ROTAT 34 54 6 +SCALE 1.96 1.96 1.96 + +// Cube +OBJECT 25 +cube +material 7 +TRANS 0 21.5701438436 12 +ROTAT 62 74 44 +SCALE 1.4 1.4 1.4 + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +// Cube +OBJECT 26 +cube +material 7 +TRANS 0 43.3278382492 -12 +ROTAT 55 26 0 +SCALE 7.529536 7.529536 7.529536 + +// Cube +OBJECT 27 +cube +material 1 +TRANS 0 35.6914949854 -12 +ROTAT 29 75 49 +SCALE 5.37824 5.37824 5.37824 + +// Cube +OBJECT 28 +cube +material 7 +TRANS 0 30.2369640827 -12 +ROTAT 13 40 53 +SCALE 3.8416 3.8416 3.8416 + +// Cube +OBJECT 29 +cube +material 7 +TRANS 0 26.3408705807 -12 +ROTAT 82 83 8 +SCALE 2.744 2.744 2.744 + +// Cube +OBJECT 30 +cube +material 7 +TRANS 0 23.5579466507 -12 +ROTAT 39 88 60 +SCALE 1.96 1.96 1.96 + +// Cube +OBJECT 31 +cube +material 1 +TRANS 0 21.5701438436 -12 +ROTAT 4 15 84 +SCALE 1.4 1.4 1.4 + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +// Cube +OBJECT 32 +cube +material 9 +TRANS 0 53.3278382492 0 +ROTAT 0 0 20 +SCALE 27 1.3 3 + +// Cube +OBJECT 33 +cube +material 9 +TRANS 0 54.5278382492 0 +ROTAT 0 0 20 +SCALE 3 1.3 27 + +// Cube +OBJECT 34 +cube +material 8 +TRANS -12 27.9139191246 0 +ROTAT 0 0 0 +SCALE 0.5 45.8278382492 0.5 + +// Cube +OBJECT 35 +cube +material 8 +TRANS 12 45.2308841683 0 +ROTAT 0 0 0 +SCALE 0.5 28.1939081619 0.5 + +// Cube +OBJECT 36 +cube +material 8 +TRANS 0 38.2308841683 12 +ROTAT 0 0 0 +SCALE 0.5 35.1939081619 0.5 + +// Cube +OBJECT 37 +cube +material 8 +TRANS 0 38.2308841683 -12 +ROTAT 0 0 0 +SCALE 0.5 35.1939081619 0.5 + + + +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// + +// World +OBJECT 38 +cube +material 15 +TRANS 0 -10 0 +ROTAT 0 0 0 +SCALE 200 200 200 \ No newline at end of file diff --git a/scenes/glassWall.txt b/scenes/glassWall.txt new file mode 100644 index 00000000..54261d4a --- /dev/null +++ b/scenes/glassWall.txt @@ -0,0 +1,142 @@ +// Emissive material (light) +MATERIAL 0 +RGB 1 1 0 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Emissive material (light) +MATERIAL 1 +RGB 0 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Diffuse white +MATERIAL 2 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 3 +RGB .85 .35 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 4 +RGB .35 .85 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Refractive +MATERIAL 5 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 1 +REFRIOR 1.52 +EMITTANCE 0 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 200 +DEPTH 8 +FILE cornell +EYE 2 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + +// Right light +OBJECT 0 +cube +material 0 +TRANS 5 2 0 +ROTAT 0 0 0 +SCALE .3 3 3 + +// Left light +OBJECT 1 +cube +material 1 +TRANS -5 8 0 +ROTAT 0 0 0 +SCALE .3 3 3 + +// Floor +OBJECT 2 +cube +material 2 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 3 +cube +material 2 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall L +OBJECT 4 +cube +material 3 +TRANS -2.5 5 -5 +ROTAT 0 0 0 +SCALE 5 10 0.01 + +// Back wall R +OBJECT 5 +cube +material 4 +TRANS 2.5 5 -5 +ROTAT 0 0 0 +SCALE 5 10 0.01 + +// Left wall +OBJECT 6 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 7 +cube +material 2 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Glass wall +OBJECT 8 +cube +material 5 +TRANS 0 5 0 +ROTAT 0 0 0 +SCALE .1 10 10 \ No newline at end of file diff --git a/scenes/lightsSpheres.txt b/scenes/lightsSpheres.txt new file mode 100644 index 00000000..dead617a --- /dev/null +++ b/scenes/lightsSpheres.txt @@ -0,0 +1,42 @@ +// Emissive material (light) +MATERIAL 0 +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 5000 +DEPTH 8 +FILE lightsSpheres +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +OBJECT 0 +sphere +material 0 +TRANS 0 -10 0 +ROTAT 0 0 0 +SCALE 1 1 1 + +OBJECT 1 +sphere +material 0 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 1 1 1 + +OBJECT 2 +sphere +material 0 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 1 1 1 diff --git a/src/interactions.h b/src/interactions.h index f969e458..ea8db8f8 100644 --- a/src/interactions.h +++ b/src/interactions.h @@ -1,79 +1,131 @@ -#pragma once - -#include "intersections.h" - -// CHECKITOUT -/** - * Computes a cosine-weighted random direction in a hemisphere. - * Used for diffuse lighting. - */ -__host__ __device__ -glm::vec3 calculateRandomDirectionInHemisphere( - glm::vec3 normal, thrust::default_random_engine &rng) { - thrust::uniform_real_distribution u01(0, 1); - - float up = sqrt(u01(rng)); // cos(theta) - float over = sqrt(1 - up * up); // sin(theta) - float around = u01(rng) * TWO_PI; - - // Find a direction that is not the normal based off of whether or not the - // normal's components are all equal to sqrt(1/3) or whether or not at - // least one component is less than sqrt(1/3). Learned this trick from - // Peter Kutz. - - glm::vec3 directionNotNormal; - if (abs(normal.x) < SQRT_OF_ONE_THIRD) { - directionNotNormal = glm::vec3(1, 0, 0); - } else if (abs(normal.y) < SQRT_OF_ONE_THIRD) { - directionNotNormal = glm::vec3(0, 1, 0); - } else { - directionNotNormal = glm::vec3(0, 0, 1); - } - - // Use not-normal direction to generate two perpendicular directions - glm::vec3 perpendicularDirection1 = - glm::normalize(glm::cross(normal, directionNotNormal)); - glm::vec3 perpendicularDirection2 = - glm::normalize(glm::cross(normal, perpendicularDirection1)); - - return up * normal - + cos(around) * over * perpendicularDirection1 - + 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! - */ -__host__ __device__ -void scatterRay( - PathSegment & pathSegment, - 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. -} +#pragma once + +#include "intersections.h" + +#define EPSILON_SCALE 10.0f + +// CHECKITOUT +/** + * Computes a cosine-weighted random direction in a hemisphere. + * Used for diffuse lighting. + */ +__host__ __device__ +glm::vec3 calculateRandomDirectionInHemisphere( + glm::vec3 normal, thrust::default_random_engine &rng) { + thrust::uniform_real_distribution u01(0, 1); + + float up = sqrt(u01(rng)); // cos(theta) + float over = sqrt(1 - up * up); // sin(theta) + float around = u01(rng) * TWO_PI; + + // Find a direction that is not the normal based off of whether or not the + // normal's components are all equal to sqrt(1/3) or whether or not at + // least one component is less than sqrt(1/3). Learned this trick from + // Peter Kutz. + + glm::vec3 directionNotNormal; + if (abs(normal.x) < SQRT_OF_ONE_THIRD) { + directionNotNormal = glm::vec3(1, 0, 0); + } else if (abs(normal.y) < SQRT_OF_ONE_THIRD) { + directionNotNormal = glm::vec3(0, 1, 0); + } else { + directionNotNormal = glm::vec3(0, 0, 1); + } + + // Use not-normal direction to generate two perpendicular directions + glm::vec3 perpendicularDirection1 = + glm::normalize(glm::cross(normal, directionNotNormal)); + glm::vec3 perpendicularDirection2 = + glm::normalize(glm::cross(normal, perpendicularDirection1)); + + return up * normal + + cos(around) * over * perpendicularDirection1 + + 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! + */ +__host__ __device__ +void scatterRay( + PathSegment & pathSegment, + 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. + + thrust::uniform_real_distribution u01(0, 1); + float prob = u01(rng); + glm::vec3 rayVec = glm::normalize(pathSegment.ray.direction); + glm::vec3 norVec = glm::normalize(normal); + + if (prob < m.hasReflective) { + // Reflection + pathSegment.ray.origin = intersect + (float) EPSILON * EPSILON_SCALE * norVec; + pathSegment.ray.direction = glm::normalize(glm::reflect(rayVec, norVec)); + } + else if (prob < (m.hasReflective + m.hasRefractive)) { + // Refraction + // Reference: https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics + float refractionRatio = (glm::dot(rayVec, norVec) > 0) ? m.indexOfRefraction : (1.0f / m.indexOfRefraction); // inside sphere : outside sphere + + float cosTheta = glm::min(glm::dot(-1.0f * rayVec, norVec), 1.0f); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + bool cannotRefract = refractionRatio * sinTheta > 1.0f; + + // Schlick's Approximation +#if SCHLICK + float r0 = (1 - refractionRatio) / (1 + refractionRatio); + r0 *= r0; + float schlickAppro = r0 + (1 - r0) * pow(1 - cosTheta, 5); + bool schlickBool = schlickAppro > u01(rng); +#endif // SCHLICK + + pathSegment.ray.origin = intersect + (float) EPSILON * EPSILON_SCALE * rayVec; +#if SCHLICK + if (cannotRefract || schlickBool) { + pathSegment.ray.direction = glm::reflect(rayVec, norVec); + } +#else + if (cannotRefract) { + // if refraction is impossible, reflect instead. + pathSegment.ray.direction = glm::reflect(rayVec, norVec); + } +#endif // SCHLICK + else { + // Snell's Law + pathSegment.ray.direction = glm::refract(rayVec, norVec, refractionRatio); + } + } + else { + // Diffusion + pathSegment.ray.origin = intersect + (float) EPSILON * EPSILON_SCALE * norVec; + pathSegment.ray.direction = glm::normalize(calculateRandomDirectionInHemisphere(norVec, rng)); + } + pathSegment.color *= m.color; +} diff --git a/src/intersections.h b/src/intersections.h index b1504071..4df000c9 100644 --- a/src/intersections.h +++ b/src/intersections.h @@ -1,144 +1,183 @@ -#pragma once - -#include -#include - -#include "sceneStructs.h" -#include "utilities.h" - -/** - * Handy-dandy hash function that provides seeds for random number generation. - */ -__host__ __device__ inline unsigned int utilhash(unsigned int a) { - a = (a + 0x7ed55d16) + (a << 12); - a = (a ^ 0xc761c23c) ^ (a >> 19); - a = (a + 0x165667b1) + (a << 5); - a = (a + 0xd3a2646c) ^ (a << 9); - a = (a + 0xfd7046c5) + (a << 3); - a = (a ^ 0xb55a4f09) ^ (a >> 16); - return a; -} - -// CHECKITOUT -/** - * Compute a point at parameter value `t` on ray `r`. - * 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); -} - -/** - * Multiplies a mat4 and a vec4 and returns a vec3 clipped from the vec4. - */ -__host__ __device__ glm::vec3 multiplyMV(glm::mat4 m, glm::vec4 v) { - return glm::vec3(m * v); -} - -// CHECKITOUT -/** - * Test intersection between a ray and a transformed cube. Untransformed, - * the cube ranges from -0.5 to 0.5 in each axis and is centered at the origin. - * - * @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 boxIntersectionTest(Geom box, Ray r, - glm::vec3 &intersectionPoint, glm::vec3 &normal, bool &outside) { - Ray q; - q.origin = multiplyMV(box.inverseTransform, glm::vec4(r.origin , 1.0f)); - q.direction = glm::normalize(multiplyMV(box.inverseTransform, glm::vec4(r.direction, 0.0f))); - - float tmin = -1e38f; - float tmax = 1e38f; - glm::vec3 tmin_n; - glm::vec3 tmax_n; - for (int xyz = 0; xyz < 3; ++xyz) { - float qdxyz = q.direction[xyz]; - /*if (glm::abs(qdxyz) > 0.00001f)*/ { - float t1 = (-0.5f - q.origin[xyz]) / qdxyz; - float t2 = (+0.5f - q.origin[xyz]) / qdxyz; - 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) { - tmin = ta; - tmin_n = n; - } - if (tb < tmax) { - tmax = tb; - tmax_n = n; - } - } - } - - if (tmax >= tmin && tmax > 0) { - outside = true; - if (tmin <= 0) { - tmin = tmax; - tmin_n = tmax_n; - outside = false; - } - intersectionPoint = multiplyMV(box.transform, glm::vec4(getPointOnRay(q, tmin), 1.0f)); - normal = glm::normalize(multiplyMV(box.invTranspose, glm::vec4(tmin_n, 0.0f))); - return glm::length(r.origin - intersectionPoint); - } - return -1; -} - -// CHECKITOUT -/** - * Test intersection between a ray and a transformed sphere. Untransformed, - * the sphere always has radius 0.5 and is centered at the origin. - * - * @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 sphereIntersectionTest(Geom sphere, Ray r, - glm::vec3 &intersectionPoint, glm::vec3 &normal, bool &outside) { - float radius = .5; - - 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))); - - Ray rt; - rt.origin = ro; - 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 squareRoot = sqrt(radicand); - float firstTerm = -vDotDirection; - float t1 = firstTerm + squareRoot; - float t2 = firstTerm - squareRoot; - - float t = 0; - if (t1 < 0 && t2 < 0) { - return -1; - } else if (t1 > 0 && t2 > 0) { - 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; - } - - return glm::length(r.origin - intersectionPoint); -} +#pragma once + +#include +#include + +#include "sceneStructs.h" +#include "utilities.h" + +/** + * Handy-dandy hash function that provides seeds for random number generation. + */ +__host__ __device__ inline unsigned int utilhash(unsigned int a) { + a = (a + 0x7ed55d16) + (a << 12); + a = (a ^ 0xc761c23c) ^ (a >> 19); + a = (a + 0x165667b1) + (a << 5); + a = (a + 0xd3a2646c) ^ (a << 9); + a = (a + 0xfd7046c5) + (a << 3); + a = (a ^ 0xb55a4f09) ^ (a >> 16); + return a; +} + +// CHECKITOUT +/** + * Compute a point at parameter value `t` on ray `r`. + * 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); +} + +/** + * Multiplies a mat4 and a vec4 and returns a vec3 clipped from the vec4. + */ +__host__ __device__ glm::vec3 multiplyMV(glm::mat4 m, glm::vec4 v) { + return glm::vec3(m * v); +} + +// CHECKITOUT +/** + * Test intersection between a ray and a transformed cube. Untransformed, + * the cube ranges from -0.5 to 0.5 in each axis and is centered at the origin. + * + * @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 boxIntersectionTest(Geom box, Ray r, + glm::vec3 &intersectionPoint, glm::vec3 &normal, bool &outside, bool forBB) { + Ray q; + if (forBB) { + q.origin = multiplyMV(box.bbInverseTransform, glm::vec4(r.origin, 1.0f)); + q.direction = glm::normalize(multiplyMV(box.bbInverseTransform, glm::vec4(r.direction, 0.0f))); + } + else { + q.origin = multiplyMV(box.inverseTransform, glm::vec4(r.origin, 1.0f)); + q.direction = glm::normalize(multiplyMV(box.inverseTransform, glm::vec4(r.direction, 0.0f))); + } + + float tmin = -1e38f; + float tmax = 1e38f; + glm::vec3 tmin_n; + glm::vec3 tmax_n; + for (int xyz = 0; xyz < 3; ++xyz) { + float qdxyz = q.direction[xyz]; + float t1 = (-0.5f - q.origin[xyz]) / qdxyz; + float t2 = (+0.5f - q.origin[xyz]) / qdxyz; + 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) { + tmin = ta; + tmin_n = n; + } + if (tb < tmax) { + tmax = tb; + tmax_n = n; + } + } + + if (tmax >= tmin && tmax > 0) { + outside = true; + if (tmin <= 0) { + tmin = tmax; + tmin_n = tmax_n; + outside = false; + } + intersectionPoint = multiplyMV(box.transform, glm::vec4(getPointOnRay(q, tmin), 1.0f)); + normal = glm::normalize(multiplyMV(box.invTranspose, glm::vec4(tmin_n, 0.0f))); + return glm::length(r.origin - intersectionPoint); + } + return -1; +} + +// CHECKITOUT +/** + * Test intersection between a ray and a transformed sphere. Untransformed, + * the sphere always has radius 0.5 and is centered at the origin. + * + * @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 sphereIntersectionTest(Geom sphere, Ray r, + glm::vec3 &intersectionPoint, glm::vec3 &normal, bool &outside) { + float radius = .5; + + 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))); + + Ray rt; + rt.origin = ro; + 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 squareRoot = sqrt(radicand); + float firstTerm = -vDotDirection; + float t1 = firstTerm + squareRoot; + float t2 = firstTerm - squareRoot; + + float t = 0; + if (t1 < 0 && t2 < 0) { + return -1; + } else if (t1 > 0 && t2 > 0) { + 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; + } + + return glm::length(r.origin - intersectionPoint); +} + +__host__ __device__ float OBJIntersectionTest(Geom& objGeom, Ray r, + glm::vec3& intersectionPoint, glm::vec3& normal, bool& outside) { + +#if USE_BOUNDING_BOX + if (boxIntersectionTest(objGeom, r, intersectionPoint, normal, outside, true) < 0) { + return -1.f; + } +#endif // USE_BOUNDING_BOX + + + for (int i = 0; i < objGeom.triCount; i++) + { + glm::vec3 vert0 = glm::vec3(objGeom.transform * objGeom.dev_VecNorArr[6 * i + 0]); + glm::vec3 vert1 = glm::vec3(objGeom.transform * objGeom.dev_VecNorArr[6 * i + 2]); + glm::vec3 vert2 = glm::vec3(objGeom.transform * objGeom.dev_VecNorArr[6 * i + 4]); + + glm::vec3 baryPosition; + bool foundIntersect = glm::intersectRayTriangle(r.origin, r.direction, vert0, vert1, vert2, baryPosition); + + if (foundIntersect) + { + glm::vec4 n1 = objGeom.dev_VecNorArr[6 * i + 1]; + glm::vec4 n2 = objGeom.dev_VecNorArr[6 * i + 3]; + glm::vec4 n3 = objGeom.dev_VecNorArr[6 * i + 5]; + intersectionPoint = getPointOnRay(r, baryPosition[2]); + normal = glm::vec3( + baryPosition[0] * n1 + + baryPosition[1] * n2 + + (1 - baryPosition[0] - baryPosition[1]) * n3); + return glm::length(r.origin - intersectionPoint); + } + } + return -1; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index fe8e85ec..0351e24c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,205 +1,322 @@ -#include "main.h" -#include "preview.h" -#include - -static std::string startTimeString; - -// For camera controls -static bool leftMousePressed = false; -static bool rightMousePressed = false; -static bool middleMousePressed = false; -static double lastX; -static double lastY; - -static bool camchanged = true; -static float dtheta = 0, dphi = 0; -static glm::vec3 cammove; - -float zoom, theta, phi; -glm::vec3 cameraPosition; -glm::vec3 ogLookAt; // for recentering the camera - -Scene *scene; -RenderState *renderState; -int iteration; - -int width; -int height; - -//------------------------------- -//-------------MAIN-------------- -//------------------------------- - -int main(int argc, char** argv) { - startTimeString = currentTimeString(); - - if (argc < 2) { - printf("Usage: %s SCENEFILE.txt\n", argv[0]); - return 1; - } - - const char *sceneFile = argv[1]; - - // Load scene file - scene = new Scene(sceneFile); - - // Set up camera stuff from loaded path tracer settings - iteration = 0; - renderState = &scene->state; - Camera &cam = renderState->camera; - width = cam.resolution.x; - height = cam.resolution.y; - - glm::vec3 view = cam.view; - glm::vec3 up = cam.up; - glm::vec3 right = glm::cross(view, up); - up = glm::cross(right, view); - - cameraPosition = cam.position; - - // compute phi (horizontal) and theta (vertical) relative 3D axis - // so, (0 0 1) is forward, (0 1 0) is up - glm::vec3 viewXZ = glm::vec3(view.x, 0.0f, view.z); - glm::vec3 viewZY = glm::vec3(0.0f, view.y, view.z); - phi = glm::acos(glm::dot(glm::normalize(viewXZ), glm::vec3(0, 0, -1))); - theta = glm::acos(glm::dot(glm::normalize(viewZY), glm::vec3(0, 1, 0))); - ogLookAt = cam.lookAt; - zoom = glm::length(cam.position - ogLookAt); - - // Initialize CUDA and GL components - init(); - - // GLFW main loop - mainLoop(); - - return 0; -} - -void saveImage() { - float samples = iteration; - // output image file - image img(width, height); - - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - int index = x + (y * width); - glm::vec3 pix = renderState->image[index]; - img.setPixel(width - 1 - x, y, glm::vec3(pix) / samples); - } - } - - std::string filename = renderState->imageName; - std::ostringstream ss; - ss << filename << "." << startTimeString << "." << samples << "samp"; - filename = ss.str(); - - // CHECKITOUT - img.savePNG(filename); - //img.saveHDR(filename); // Save a Radiance HDR file -} - -void runCuda() { - if (camchanged) { - iteration = 0; - Camera &cam = renderState->camera; - cameraPosition.x = zoom * sin(phi) * sin(theta); - cameraPosition.y = zoom * cos(theta); - cameraPosition.z = zoom * cos(phi) * sin(theta); - - cam.view = -glm::normalize(cameraPosition); - glm::vec3 v = cam.view; - glm::vec3 u = glm::vec3(0, 1, 0);//glm::normalize(cam.up); - glm::vec3 r = glm::cross(v, u); - cam.up = glm::cross(r, v); - cam.right = r; - - cam.position = cameraPosition; - cameraPosition += cam.lookAt; - cam.position = cameraPosition; - camchanged = false; - } - - // Map OpenGL buffer object for writing from CUDA on a single GPU - // No data is moved (Win & Linux). When mapped to CUDA, OpenGL should not use this buffer - - if (iteration == 0) { - pathtraceFree(); - pathtraceInit(scene); - } - - if (iteration < renderState->iterations) { - uchar4 *pbo_dptr = NULL; - iteration++; - cudaGLMapBufferObject((void**)&pbo_dptr, pbo); - - // execute the kernel - int frame = 0; - pathtrace(pbo_dptr, frame, iteration); - - // unmap buffer object - cudaGLUnmapBufferObject(pbo); - } else { - saveImage(); - pathtraceFree(); - cudaDeviceReset(); - exit(EXIT_SUCCESS); - } -} - -void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (action == GLFW_PRESS) { - switch (key) { - case GLFW_KEY_ESCAPE: - saveImage(); - glfwSetWindowShouldClose(window, GL_TRUE); - break; - case GLFW_KEY_S: - saveImage(); - break; - case GLFW_KEY_SPACE: - camchanged = true; - renderState = &scene->state; - Camera &cam = renderState->camera; - cam.lookAt = ogLookAt; - break; - } - } -} - -void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { - leftMousePressed = (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS); - rightMousePressed = (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS); - middleMousePressed = (button == GLFW_MOUSE_BUTTON_MIDDLE && action == GLFW_PRESS); -} - -void mousePositionCallback(GLFWwindow* window, double xpos, double ypos) { - if (xpos == lastX || ypos == lastY) return; // otherwise, clicking back into window causes re-start - if (leftMousePressed) { - // compute new camera parameters - phi -= (xpos - lastX) / width; - theta -= (ypos - lastY) / height; - theta = std::fmax(0.001f, std::fmin(theta, PI)); - camchanged = true; - } - else if (rightMousePressed) { - zoom += (ypos - lastY) / height; - zoom = std::fmax(0.1f, zoom); - camchanged = true; - } - else if (middleMousePressed) { - renderState = &scene->state; - Camera &cam = renderState->camera; - glm::vec3 forward = cam.view; - forward.y = 0.0f; - forward = glm::normalize(forward); - glm::vec3 right = cam.right; - right.y = 0.0f; - right = glm::normalize(right); - - cam.lookAt -= (float) (xpos - lastX) * right * 0.01f; - cam.lookAt += (float) (ypos - lastY) * forward * 0.01f; - camchanged = true; - } - lastX = xpos; - lastY = ypos; -} +#define TINYOBJLOADER_IMPLEMENTATION + +#include "main.h" +#include "preview.h" +#include +#include "tiny_obj_loader.h" +#include "polygon.h" + +static std::string startTimeString; + +// For camera controls +static bool leftMousePressed = false; +static bool rightMousePressed = false; +static bool middleMousePressed = false; +static double lastX; +static double lastY; + +static bool camchanged = true; +static float dtheta = 0, dphi = 0; +static glm::vec3 cammove; + +float zoom, theta, phi; +glm::vec3 cameraPosition; +glm::vec3 ogLookAt; // for recentering the camera + +Scene *scene; +RenderState *renderState; +int iteration; + +int width; +int height; + +void LoadOBJ(const char* file, Geom& geom) +{ + Polygon p; + std::vector shapes; std::vector materials; + std::string errors = tinyobj::LoadObj(shapes, materials, file); + std::cout << errors << std::endl; + if (errors.size() == 0) + { + int min_idx = 0; + //Read the information from the vectxor of shape_ts + for (int i = 0; i < shapes.size(); i++) + { + std::vector pos, nor; + std::vector uv; + std::vector& positions = shapes[i].mesh.positions; + std::vector& normals = shapes[i].mesh.normals; + for (int j = 0; j < positions.size() / 3; j++) + { + pos.push_back(glm::vec4(positions[j * 3], positions[j * 3 + 1], positions[j * 3 + 2], 1)); + } + for (int j = 0; j < normals.size() / 3; j++) + { + nor.push_back(glm::vec4(normals[j * 3], normals[j * 3 + 1], normals[j * 3 + 2], 0)); + } + for (int j = 0; j < pos.size(); j++) + { + p.AddVertex(Vertex(pos[j], glm::vec3(255, 255, 255), nor[j], glm::vec2(0.f, 0.f))); + } + + std::vector indices = shapes[i].mesh.indices; + for (unsigned int j = 0; j < indices.size(); j += 3) + { + Triangle t; + t.m_indices[0] = indices[j] + min_idx; + t.m_indices[1] = indices[j + 1] + min_idx; + t.m_indices[2] = indices[j + 2] + min_idx; + p.AddTriangle(t); + } + + min_idx += pos.size(); + } + } + else + { + //An error loading the OBJ occurred! + std::cout << errors << std::endl; + return; + } + + geom.triCount = p.m_tris.size(); + geom.host_VecNorArr = (glm::vec4*) malloc(p.m_tris.size() * 6 * sizeof(glm::vec4)); + + for (int i = 0; i < p.m_tris.size(); i++) { + for (int j = 0; j < 3; j++) { + geom.host_VecNorArr[6 * i + 2 * j] = p.m_verts[p.m_tris[i].m_indices[j]].m_pos; + geom.host_VecNorArr[6 * i + 2 * j + 1] = p.m_verts[p.m_tris[i].m_indices[j]].m_normal; + } + } +} + +//------------------------------- +//-------------MAIN-------------- +//------------------------------- + +int main(int argc, char** argv) { + startTimeString = currentTimeString(); + + if (argc < 2) { + printf("Usage: %s SCENEFILE.txt\n", argv[0]); + return 1; + } + + const char *sceneFile = argv[1]; + +#if USE_MESH_LOADING + const char* objFile = NULL; + if (argc > 2 && USE_MESH_LOADING) { + objFile = argv[2]; + } +#endif // USE_MESH_LOADING + + // Load scene file + scene = new Scene(sceneFile); + +#if USE_MESH_LOADING + const char* objPath = "C:/Users/yangr/OneDrive/Desktop/wahoo.obj"; + for (int i = 0; i < scene->geoms.size(); i++) { + if (scene->geoms[i].type == GeomType::OBJ) { + LoadOBJ(objPath, scene->geoms[i]); + +#if USE_BOUNDING_BOX + float minX = std::numeric_limits::max(); + float minY = std::numeric_limits::max(); + float minZ = std::numeric_limits::max(); + + float maxX = std::numeric_limits::min(); + float maxY = std::numeric_limits::min(); + float maxZ = std::numeric_limits::min(); + + for (int i = 0; i < scene->geoms[i].triCount * 6; i += 2) { + minX = (scene->geoms[i].host_VecNorArr[i].x < minX) ? scene->geoms[i].host_VecNorArr[i].x : minX; + minY = (scene->geoms[i].host_VecNorArr[i].y < minY) ? scene->geoms[i].host_VecNorArr[i].y : minY; + minZ = (scene->geoms[i].host_VecNorArr[i].z < minZ) ? scene->geoms[i].host_VecNorArr[i].z : minZ; + + maxX = (scene->geoms[i].host_VecNorArr[i].x > maxX) ? scene->geoms[i].host_VecNorArr[i].x : maxX; + maxY = (scene->geoms[i].host_VecNorArr[i].y > maxY) ? scene->geoms[i].host_VecNorArr[i].y : maxY; + maxZ = (scene->geoms[i].host_VecNorArr[i].z > maxZ) ? scene->geoms[i].host_VecNorArr[i].z : maxZ; + } + + scene->geoms[i].bbScale = glm::vec3(glm::abs((maxX - minX) / 2.0f), glm::abs((maxY - minY) / 2.0f), glm::abs((maxZ - minZ) / 2.0f)); + scene->geoms[i].bbInverseTransform = glm::inverse(utilityCore::buildTransformationMatrix( + scene->geoms[i].translation, scene->geoms[i].rotation, scene->geoms[i].bbScale)); +#endif // USE_BOUNDING_BOX + } + } +#endif // USE_MESH_LOADING + + // Set up camera stuff from loaded path tracer settings + iteration = 0; + renderState = &scene->state; + Camera &cam = renderState->camera; + width = cam.resolution.x; + height = cam.resolution.y; + + glm::vec3 view = cam.view; + glm::vec3 up = cam.up; + glm::vec3 right = glm::cross(view, up); + up = glm::cross(right, view); + + cameraPosition = cam.position; + +#if USE_DOF + cam.aperture = DOF_APERATURE; + cam.focusDist = DOF_FOCUSDIST; +#endif // USE_DOF + + + // compute phi (horizontal) and theta (vertical) relative 3D axis + // so, (0 0 1) is forward, (0 1 0) is up + glm::vec3 viewXZ = glm::vec3(view.x, 0.0f, view.z); + glm::vec3 viewZY = glm::vec3(0.0f, view.y, view.z); + phi = glm::acos(glm::dot(glm::normalize(viewXZ), glm::vec3(0, 0, -1))); + theta = glm::acos(glm::dot(glm::normalize(viewZY), glm::vec3(0, 1, 0))); + ogLookAt = cam.lookAt; + zoom = glm::length(cam.position - ogLookAt); + + // Initialize CUDA and GL components + init(); + + // GLFW main loop + mainLoop(); + + return 0; +} + +void saveImage() { + float samples = iteration; + // output image file + image img(width, height); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int index = x + (y * width); + glm::vec3 pix = renderState->image[index]; + img.setPixel(width - 1 - x, y, glm::vec3(pix) / samples); + } + } + + std::string filename = renderState->imageName; + std::ostringstream ss; + ss << filename << "." << startTimeString << "." << samples << "samp"; + filename = ss.str(); + + // CHECKITOUT + img.savePNG(filename); + //img.saveHDR(filename); // Save a Radiance HDR file +} + +void runCuda() { + if (camchanged) { + iteration = 0; + Camera &cam = renderState->camera; + cameraPosition.x = zoom * sin(phi) * sin(theta); + cameraPosition.y = zoom * cos(theta); + cameraPosition.z = zoom * cos(phi) * sin(theta); + + cam.view = -glm::normalize(cameraPosition); + glm::vec3 v = cam.view; + glm::vec3 u = glm::vec3(0, 1, 0);//glm::normalize(cam.up); + glm::vec3 r = glm::cross(v, u); + cam.up = glm::cross(r, v); + cam.right = r; + + cam.position = cameraPosition; + cameraPosition += cam.lookAt; + cam.position = cameraPosition; + camchanged = false; + } + + // Map OpenGL buffer object for writing from CUDA on a single GPU + // No data is moved (Win & Linux). When mapped to CUDA, OpenGL should not use this buffer + + if (iteration == 0) { + pathtraceFree(); + pathtraceInit(scene); + } + + if (iteration < renderState->iterations) { + uchar4 *pbo_dptr = NULL; + iteration++; + cudaGLMapBufferObject((void**)&pbo_dptr, pbo); + + // execute the kernel + int frame = 0; + pathtrace(pbo_dptr, frame, iteration); + + // unmap buffer object + cudaGLUnmapBufferObject(pbo); + } else { + saveImage(); + pathtraceFree(); + cudaDeviceReset(); +#if USE_MESH_LOADING + for (int i = 0; i < scene->geoms.size(); i++) { + if (scene->geoms[i].type == GeomType::OBJ) { + free(scene->geoms[i].host_VecNorArr); + } + } +#endif // USE_MESH_LOADING + exit(EXIT_SUCCESS); + } +} + +void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { + if (action == GLFW_PRESS) { + switch (key) { + case GLFW_KEY_ESCAPE: + saveImage(); + glfwSetWindowShouldClose(window, GL_TRUE); + break; + case GLFW_KEY_S: + saveImage(); + break; + case GLFW_KEY_SPACE: + camchanged = true; + renderState = &scene->state; + Camera &cam = renderState->camera; + cam.lookAt = ogLookAt; + break; + } + } +} + +void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { + leftMousePressed = (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS); + rightMousePressed = (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS); + middleMousePressed = (button == GLFW_MOUSE_BUTTON_MIDDLE && action == GLFW_PRESS); +} + +void mousePositionCallback(GLFWwindow* window, double xpos, double ypos) { + if (xpos == lastX || ypos == lastY) return; // otherwise, clicking back into window causes re-start + if (leftMousePressed) { + // compute new camera parameters + phi -= (xpos - lastX) / width; + theta -= (ypos - lastY) / height; + theta = std::fmax(0.001f, std::fmin(theta, PI)); + camchanged = true; + } + else if (rightMousePressed) { + zoom += (ypos - lastY) / height; + zoom = std::fmax(0.1f, zoom); + camchanged = true; + } + else if (middleMousePressed) { + renderState = &scene->state; + Camera &cam = renderState->camera; + glm::vec3 forward = cam.view; + forward.y = 0.0f; + forward = glm::normalize(forward); + glm::vec3 right = cam.right; + right.y = 0.0f; + right = glm::normalize(right); + + cam.lookAt -= (float) (xpos - lastX) * right * 0.01f; + cam.lookAt += (float) (ypos - lastY) * forward * 0.01f; + camchanged = true; + } + lastX = xpos; + lastY = ypos; +} diff --git a/src/pathtrace.cu b/src/pathtrace.cu index 056e1467..f696d1f4 100644 --- a/src/pathtrace.cu +++ b/src/pathtrace.cu @@ -1,393 +1,593 @@ -#include -#include -#include -#include -#include -#include - -#include "sceneStructs.h" -#include "scene.h" -#include "glm/glm.hpp" -#include "glm/gtx/norm.hpp" -#include "utilities.h" -#include "pathtrace.h" -#include "intersections.h" -#include "interactions.h" - -#define ERRORCHECK 1 - -#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) -#define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) -void checkCUDAErrorFn(const char *msg, const char *file, int line) { -#if ERRORCHECK - cudaDeviceSynchronize(); - cudaError_t err = cudaGetLastError(); - if (cudaSuccess == err) { - return; - } - - fprintf(stderr, "CUDA error"); - if (file) { - fprintf(stderr, " (%s:%d)", file, line); - } - fprintf(stderr, ": %s: %s\n", msg, cudaGetErrorString(err)); -# ifdef _WIN32 - getchar(); -# endif - exit(EXIT_FAILURE); -#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 glm::vec3 * dev_image = NULL; -static Geom * dev_geoms = 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 -// ... - -void pathtraceInit(Scene *scene) { - hst_scene = scene; - const Camera &cam = hst_scene->state.camera; - const int pixelcount = cam.resolution.x * cam.resolution.y; - - cudaMalloc(&dev_image, pixelcount * sizeof(glm::vec3)); - cudaMemset(dev_image, 0, pixelcount * sizeof(glm::vec3)); - - cudaMalloc(&dev_paths, pixelcount * sizeof(PathSegment)); - - cudaMalloc(&dev_geoms, scene->geoms.size() * sizeof(Geom)); - cudaMemcpy(dev_geoms, scene->geoms.data(), scene->geoms.size() * sizeof(Geom), cudaMemcpyHostToDevice); - - cudaMalloc(&dev_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)); - - // TODO: initialize any extra device memeory you need - - checkCUDAError("pathtraceInit"); -} - -void pathtraceFree() { - cudaFree(dev_image); // no-op if dev_image is null - cudaFree(dev_paths); - cudaFree(dev_geoms); - cudaFree(dev_materials); - cudaFree(dev_intersections); - // TODO: clean up any extra device memory you created - - 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) -{ - int x = (blockIdx.x * blockDim.x) + threadIdx.x; - int y = (blockIdx.y * blockDim.y) + threadIdx.y; - - if (x < cam.resolution.x && y < cam.resolution.y) { - int index = x + (y * cam.resolution.x); - PathSegment & segment = pathSegments[index]; - - segment.ray.origin = cam.position; - segment.color = glm::vec3(1.0f, 1.0f, 1.0f); - - // TODO: implement antialiasing by jittering the ray - segment.ray.direction = glm::normalize(cam.view - - cam.right * cam.pixelLength.x * ((float)x - (float)cam.resolution.x * 0.5f) - - cam.up * cam.pixelLength.y * ((float)y - (float)cam.resolution.y * 0.5f) - ); - - segment.pixelIndex = index; - segment.remainingBounces = traceDepth; - } -} - -// 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 - , ShadeableIntersection * intersections - ) -{ - int path_index = blockIdx.x * blockDim.x + threadIdx.x; - - if (path_index < num_paths) - { - PathSegment pathSegment = pathSegments[path_index]; - - 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; - - // naive parse through global geoms - - 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); - } - else if (geom.type == SPHERE) - { - t = sphereIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); - } - // 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) - { - t_min = t; - hit_geom_index = i; - intersect_point = tmp_intersect; - normal = tmp_normal; - } - } - - if (hit_geom_index == -1) - { - intersections[path_index].t = -1.0f; - } - else - { - //The ray hits something - intersections[path_index].t = t_min; - intersections[path_index].materialId = geoms[hit_geom_index].materialid; - intersections[path_index].surfaceNormal = normal; - } - } -} - -// LOOK: "fake" shader demonstrating what you might do with the info in -// a ShadeableIntersection, as well as how to use thrust's random number -// generator. Observe that since the thrust random number generator basically -// adds "noise" to the iteration, the image should start off noisy and get -// cleaner as more iterations are computed. -// -// Note that this shader does NOT do a BSDF evaluation! -// Your shaders should handle that - this can allow techniques such as -// bump mapping. -__global__ void shadeFakeMaterial ( - int iter - , int num_paths - , ShadeableIntersection * shadeableIntersections - , PathSegment * pathSegments - , Material * materials - ) -{ - int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx < num_paths) - { - ShadeableIntersection intersection = shadeableIntersections[idx]; - if (intersection.t > 0.0f) { // if the intersection exists... - // Set up the RNG - // LOOK: this is how you use thrust's RNG! Please look at - // makeSeededRandomEngine as well. - thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, 0); - thrust::uniform_real_distribution u01(0, 1); - - Material material = materials[intersection.materialId]; - glm::vec3 materialColor = material.color; - - // If the material indicates that the object was a light, "light" the ray - if (material.emittance > 0.0f) { - pathSegments[idx].color *= (materialColor * material.emittance); - } - // Otherwise, do some pseudo-lighting computation. This is actually more - // like what you would expect from shading in a rasterizer like OpenGL. - // TODO: replace this! you should be able to start with basically a one-liner - else { - float lightTerm = glm::dot(intersection.surfaceNormal, glm::vec3(0.0f, 1.0f, 0.0f)); - pathSegments[idx].color *= (materialColor * lightTerm) * 0.3f + ((1.0f - intersection.t * 0.02f) * materialColor) * 0.7f; - pathSegments[idx].color *= u01(rng); // apply some noise because why not - } - // If there was no intersection, color the ray black. - // Lots of renderers use 4 channel color, RGBA, where A = alpha, often - // used for opacity, in which case they can indicate "no opacity". - // This can be useful for post-processing and image compositing. - } else { - pathSegments[idx].color = glm::vec3(0.0f); - } - } -} - -// Add the current iteration's output to the overall image -__global__ void finalGather(int nPaths, glm::vec3 * image, PathSegment * iterationPaths) -{ - int index = (blockIdx.x * blockDim.x) + threadIdx.x; - - if (index < nPaths) - { - PathSegment iterationPath = iterationPaths[index]; - image[iterationPath.pixelIndex] += iterationPath.color; - } -} - -/** - * Wrapper for the __global__ call that sets up the kernel calls and does a ton - * of memory management - */ -void pathtrace(uchar4 *pbo, int frame, int iter) { - const int traceDepth = hst_scene->state.traceDepth; - const Camera &cam = hst_scene->state.camera; - const int pixelcount = cam.resolution.x * cam.resolution.y; - - // 2D block for generating ray from camera - const dim3 blockSize2d(8, 8); - const dim3 blocksPerGrid2d( - (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, - (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); - - // 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"); - - int depth = 0; - PathSegment* dev_path_end = dev_paths + pixelcount; - int num_paths = dev_path_end - dev_paths; - - // --- PathSegment Tracing Stage --- - // Shoot ray into scene, bounce between objects, push shading chunks - - bool iterationComplete = false; - while (!iterationComplete) { - - // clean shading chunks - cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); - - // tracing - dim3 numblocksPathSegmentTracing = (num_paths + blockSize1d - 1) / blockSize1d; - computeIntersections <<>> ( - depth - , num_paths - , dev_paths - , dev_geoms - , hst_scene->geoms.size() - , dev_intersections - ); - checkCUDAError("trace one bounce"); - cudaDeviceSynchronize(); - depth++; - - - // 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. - - shadeFakeMaterial<<>> ( - iter, - num_paths, - dev_intersections, - dev_paths, - dev_materials - ); - iterationComplete = true; // TODO: should be based off stream compaction results. - } - - // Assemble this iteration and apply it to the image - dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; - finalGather<<>>(num_paths, dev_image, dev_paths); - - /////////////////////////////////////////////////////////////////////////// - - // 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"); -} +#include +#include +#include +//#include +//#include +#include +//#include +#include +#include +#include + +#include "sceneStructs.h" +#include "scene.h" +#include "glm/glm.hpp" +#include "glm/gtx/norm.hpp" +#include "utilities.h" +#include "pathtrace.h" +#include "intersections.h" +#include "interactions.h" + +#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) +void checkCUDAErrorFn(const char *msg, const char *file, int line) { +#if ERRORCHECK + cudaDeviceSynchronize(); + cudaError_t err = cudaGetLastError(); + if (cudaSuccess == err) { + return; + } + + fprintf(stderr, "CUDA error"); + if (file) { + fprintf(stderr, " (%s:%d)", file, line); + } + fprintf(stderr, ": %s: %s\n", msg, cudaGetErrorString(err)); +# ifdef _WIN32 + getchar(); +# endif + exit(EXIT_FAILURE); +#endif +} + +// Used for thrust::stable_partition +struct hasRemainingBounces +{ + __host__ __device__ + bool operator()(const int &x) + { + return x > 0; + } +}; + +// Used for thrust::remove_if +struct hasNoRemainingBounces +{ + __host__ __device__ + bool operator()(const PathSegment &x) + { + return x.remainingBounces == 0; + } +}; + +struct compareMaterial +{ + __host__ __device__ bool operator()(const ShadeableIntersection& x, const ShadeableIntersection& y) + { + return x.materialId < y.materialId; + } +}; + +__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 glm::vec3* dev_image = NULL; +static Geom* dev_geoms = NULL; +static Material* dev_materials = NULL; +static PathSegment* dev_paths = NULL; +static ShadeableIntersection* dev_intersections = NULL; +static ShadeableIntersection* dev_intersectionsCache = NULL; +int* dev_stencil = NULL; + +// TODO: static variables for device memory, any extra info you need, etc +// ... + +void pathtraceInit(Scene* scene) { + hst_scene = scene; + const Camera& cam = hst_scene->state.camera; + const int pixelcount = cam.resolution.x * cam.resolution.y; + +#if USE_MESH_LOADING + for (int i = 0; i < scene->geoms.size(); i++) + { + if (scene->geoms[i].type == OBJ) + { + cudaMalloc( + &scene->geoms[i].dev_VecNorArr, + scene->geoms[i].triCount * 6 * sizeof(glm::vec4)); + cudaMemcpy( + scene->geoms[i].dev_VecNorArr, + scene->geoms[i].host_VecNorArr, + scene->geoms[i].triCount * 6 * sizeof(glm::vec4), + cudaMemcpyHostToDevice); + } + } +#endif // USE_MESH_LOADING + + cudaMalloc(&dev_image, pixelcount * sizeof(glm::vec3)); + cudaMemset(dev_image, 0, pixelcount * sizeof(glm::vec3)); + + cudaMalloc(&dev_paths, pixelcount * sizeof(PathSegment)); + + cudaMalloc(&dev_geoms, scene->geoms.size() * sizeof(Geom)); + cudaMemcpy(dev_geoms, scene->geoms.data(), scene->geoms.size() * sizeof(Geom), cudaMemcpyHostToDevice); + + cudaMalloc(&dev_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)); + + // TODO: initialize any extra device memeory you need +#if USE_CACHE + cudaMalloc(&dev_intersectionsCache, pixelcount * sizeof(ShadeableIntersection)); +#endif // USE_CACHE +#if USE_PARTITION + cudaMalloc(&dev_stencil, pixelcount * sizeof(int)); +#endif // USE_PARTITION + + checkCUDAError("pathtraceInit"); +} + +void pathtraceFree() { + cudaFree(dev_image); // no-op if dev_image is null + cudaFree(dev_paths); +//#if USE_MESH_LOADING +// if (hst_scene) { +// for (int i = 0; i < hst_scene->geoms.size(); i++) { +// if (hst_scene->geoms[i].type == GeomType::OBJ) { +// cudaFree(&dev_geoms[i].dev_VecNorArr); +// } +// } +// } +//#endif // USE_MESH_LOADING + cudaFree(dev_geoms); + cudaFree(dev_materials); + cudaFree(dev_intersections); + // TODO: clean up any extra device memory you created +#if USE_CACHE + cudaFree(dev_intersectionsCache); +#endif // USE_CACHE +#if USE_PARTITION + cudaFree(dev_stencil); +#endif // USE_PARTITION + 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) +{ + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + + if (x < cam.resolution.x && y < cam.resolution.y) { + int index = x + (y * cam.resolution.x); + PathSegment & segment = pathSegments[index]; + + segment.ray.origin = cam.position; + segment.color = glm::vec3(1.0f, 1.0f, 1.0f); + + // TODO: implement antialiasing by jittering the ray + thrust::default_random_engine rng = makeSeededRandomEngine(iter, index, 0); + + float probX = 0.0f, probY = 0.0f; +#if ANTIALIASING & !USE_CACHE + thrust::uniform_real_distribution uhh(-0.5f, 0.5f); + probX = uhh(rng); probY = uhh(rng); + segment.ray.direction = glm::normalize( + cam.view - + cam.right * cam.pixelLength.x * ((float)x + probX - (float)cam.resolution.x * 0.5f) - + cam.up * cam.pixelLength.y * ((float)y + probY - (float)cam.resolution.y * 0.5f)); +#endif // ANTIALIASING + + +#if !(USE_DOF | ANTIALIASING) + segment.ray.direction = glm::normalize( + cam.view - + cam.right * cam.pixelLength.x * ((float)x - (float)cam.resolution.x * 0.5f) - + cam.up * cam.pixelLength.y * ((float)y - (float)cam.resolution.y * 0.5f)); +#endif //!(USE_DOF | ANTIALIASING) + + + segment.pixelIndex = index; + segment.remainingBounces = traceDepth; + } +} + +// 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 + , ShadeableIntersection* intersections +) +{ + int path_index = blockIdx.x * blockDim.x + threadIdx.x; + + if (path_index < num_paths) + { + PathSegment pathSegment = pathSegments[path_index]; + + 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; + + // naive parse through global geoms + + 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, false); + } + else if (geom.type == SPHERE) + { + t = sphereIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); + } +#if USE_MESH_LOADING + else if (geom.type == OBJ) + { + t = OBJIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); + } +#endif // USE_MESH_LOADING + // 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) + { + t_min = t; + hit_geom_index = i; + intersect_point = tmp_intersect; + normal = tmp_normal; + } + } + + if (hit_geom_index == -1) + { + intersections[path_index].t = -1.0f; + } + else + { + //The ray hits something + intersections[path_index].t = t_min; + intersections[path_index].materialId = geoms[hit_geom_index].materialid; + intersections[path_index].surfaceNormal = normal; + } + } +} + +// LOOK: "fake" shader demonstrating what you might do with the info in +// a ShadeableIntersection, as well as how to use thrust's random number +// generator. Observe that since the thrust random number generator basically +// adds "noise" to the iteration, the image should start off noisy and get +// cleaner as more iterations are computed. +// +// Note that this shader does NOT do a BSDF evaluation! +// Your shaders should handle that - this can allow techniques such as +// bump mapping. +__global__ void shadeFakeMaterial( + int iter, + int num_paths, + ShadeableIntersection* shadeableIntersections, + PathSegment* pathSegments, + Material* materials) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_paths) + { + ShadeableIntersection intersection = shadeableIntersections[idx]; + if (intersection.t > 0.0f) { // if the intersection exists... + // Set up the RNG + // LOOK: this is how you use thrust's RNG! Please look at + // makeSeededRandomEngine as well. + thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, 0); + thrust::uniform_real_distribution u01(0, 1); + + Material material = materials[intersection.materialId]; + glm::vec3 materialColor = material.color; + + // If the material indicates that the object was a light, "light" the ray + if (material.emittance > 0.0f) { + pathSegments[idx].color *= (materialColor * material.emittance); + } + // Otherwise, do some pseudo-lighting computation. This is actually more + // like what you would expect from shading in a rasterizer like OpenGL. + // TODO: replace this! you should be able to start with basically a one-liner + else { + float lightTerm = glm::dot(intersection.surfaceNormal, glm::vec3(0.0f, 1.0f, 0.0f)); + pathSegments[idx].color *= (materialColor * lightTerm) * 0.3f + ((1.0f - intersection.t * 0.02f) * materialColor) * 0.7f; + pathSegments[idx].color *= u01(rng); // apply some noise because why not + } + // If there was no intersection, color the ray black. + // Lots of renderers use 4 channel color, RGBA, where A = alpha, often + // used for opacity, in which case they can indicate "no opacity". + // This can be useful for post-processing and image compositing. + } else { + pathSegments[idx].color = glm::vec3(0.0f); + } + } +} + +__global__ void shadeMaterialCore( + int depth, + int iter, + int num_paths, + int* stencil, + ShadeableIntersection* shadeableIntersections, + PathSegment* pathSegments, + Material* materials) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= num_paths) { + pathSegments[idx].color = glm::vec3(0.0f); + return; + } + + if (pathSegments[idx].remainingBounces <= 0) { + return; + } + + // 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. + + ShadeableIntersection intersection = shadeableIntersections[idx]; + if (intersection.t <= 0.0f) { + pathSegments[idx].remainingBounces = 0; + pathSegments[idx].color = glm::vec3(0.0f); + return; + } + + 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, depth); + Material material = materials[intersection.materialId]; + + // If the material indicates that the object was a light, "light" the ray + if (material.emittance > 0.0f) { + pathSegments[idx].remainingBounces = 0; + pathSegments[idx].color *= (material.color * 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 { +#if USE_PARTITION + stencil[idx] = 1; +#endif // USE_PARTITION + pathSegments[idx].remainingBounces--; + scatterRay(pathSegments[idx], + pathSegments[idx].ray.origin + pathSegments[idx].ray.direction * intersection.t, + intersection.surfaceNormal, + material, + rng); + } + } +} + +// Add the current iteration's output to the overall image +__global__ void finalGather(int nPaths, glm::vec3 * image, PathSegment * iterationPaths) +{ + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (index < nPaths) + { + PathSegment iterationPath = iterationPaths[index]; + image[iterationPath.pixelIndex] += iterationPath.color; + } +} + +/** + * Wrapper for the __global__ call that sets up the kernel calls and does a ton + * of memory management + */ +void pathtrace(uchar4 *pbo, int frame, int iter) { + const int traceDepth = hst_scene->state.traceDepth; + const Camera &cam = hst_scene->state.camera; + const int pixelcount = cam.resolution.x * cam.resolution.y; + + // 2D block for generating ray from camera + const dim3 blockSize2d(8, 8); + const dim3 blocksPerGrid2d( + (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, + (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); + + // 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"); + + int depth = 0; + PathSegment* dev_path_end = dev_paths + pixelcount; + int num_paths = dev_path_end - dev_paths; + + // --- PathSegment Tracing Stage --- + // Shoot ray into scene, bounce between objects, push shading chunks + +#if ERRORCHECK & PRINT + std::cout << "Iter: " << iter << " num_paths: " << num_paths << "\n"; +#endif // ERRORCHECK & PRINT + + + bool iterationComplete = false; + while (!iterationComplete) { + + + // clean shading chunks + cudaMemset(dev_intersections, 0, num_paths * sizeof(ShadeableIntersection)); +#if USE_PARTITION + cudaMemset(dev_stencil, 0, num_paths * sizeof(int)); +#endif // USE_PARTITION + dim3 numblocksPathSegmentTracing = (num_paths + blockSize1d - 1) / blockSize1d; + +#if USE_CACHE + if (iter == 1 & depth == 0) { + cudaMemset(dev_intersectionsCache, 0, num_paths * sizeof(ShadeableIntersection)); + computeIntersections << > > ( + depth, + num_paths, + dev_paths, + dev_geoms, + hst_scene->geoms.size(), + dev_intersections); + checkCUDAError("computeIntersections failed!"); + cudaMemcpy(dev_intersectionsCache, dev_intersections, num_paths * sizeof(ShadeableIntersection), cudaMemcpyDeviceToDevice); + } + else if (depth == 0) { + cudaMemcpy(dev_intersections, dev_intersectionsCache, num_paths * sizeof(ShadeableIntersection), cudaMemcpyDeviceToDevice); + } + else { + computeIntersections << > > ( + depth, + num_paths, + dev_paths, + dev_geoms, + hst_scene->geoms.size(), + dev_intersections); + checkCUDAError("computeIntersections failed!"); + } +#else + computeIntersections << > > ( + depth, + num_paths, + dev_paths, + dev_geoms, + hst_scene->geoms.size(), + dev_intersections); + checkCUDAError("computeIntersections failed!"); +#endif // USE_CACHE +#if USE_SORT + thrust::sort_by_key(thrust::device, dev_intersections, dev_intersections + num_paths, dev_paths, compareMaterial()); +#endif // USE_SORT + + // 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. + + shadeMaterialCore<<>> ( + depth, + iter, + num_paths, + dev_stencil, + dev_intersections, + dev_paths, + dev_materials); + checkCUDAError("shadeMaterialCore failed!"); + depth++; +#if USE_PARTITION + dev_path_end = thrust::stable_partition(thrust::device, dev_paths, dev_paths + num_paths, dev_stencil, hasRemainingBounces()); +#elif USE_REMOVE_IF + dev_path_end = thrust::remove_if(thrust::device, dev_paths, dev_paths + num_paths, hasNoRemainingBounces()); +#endif // USE_PARTITION + num_paths = dev_path_end - dev_paths; + +#if ERRORCHECK & PRINT + std::cout << "\tdev_path_end: " << dev_path_end << " dev_paths: " << dev_paths << " num_paths: " << num_paths << std::endl; +#endif // ERRORCHECK & PRINT + + iterationComplete = (depth > traceDepth) | (num_paths <= 0); // TODO: should be based off stream compaction results. + } + + // Assemble this iteration and apply it to the image + dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; + finalGather<<>>(pixelcount, dev_image, dev_paths); + + /////////////////////////////////////////////////////////////////////////// + + // Send results to OpenGL buffer for rendering + sendImageToPBO<<>>(pbo, cam.resolution, iter, dev_image); + + // Retrieve image from GPU + cudaMemcpy(hst_scene->state.image.data(), dev_image, + pixelcount * sizeof(glm::vec3), cudaMemcpyDeviceToHost); + + checkCUDAError("pathtrace"); +} diff --git a/src/polygon.cpp b/src/polygon.cpp new file mode 100644 index 00000000..f6198ebd --- /dev/null +++ b/src/polygon.cpp @@ -0,0 +1,72 @@ +#include "polygon.h" +#include + +void Polygon::Triangulate() +{ + + Triangle temp_triangle; + int num_vertices = this->m_verts.size(); + for (int i = 0; i < num_vertices - 2; i++) + { + temp_triangle.m_indices[0] = 0; + temp_triangle.m_indices[1] = i + 1; + temp_triangle.m_indices[2] = i + 2; + this->m_tris.push_back(temp_triangle); + } +} + + +//Use the one above if using textures +// Creates a polygon from the input list of vertex positions and colors +Polygon::Polygon(char* name, const std::vector& pos, const std::vector& col) + : m_tris(), m_verts(), m_name(name) +{ + for (unsigned int i = 0; i < pos.size(); i++) + { + m_verts.push_back(Vertex(pos[i], col[i], glm::vec4(), glm::vec2())); + } + Triangulate(); +} + +Polygon::Polygon() + : m_tris(), m_verts(), m_name("Polygon") +{} + +Polygon::~Polygon() +{ +} + +void Polygon::AddTriangle(const Triangle& t) +{ + m_tris.push_back(t); +} + +void Polygon::AddVertex(const Vertex& v) +{ + m_verts.push_back(v); +} + +void Polygon::ClearTriangles() +{ + m_tris.clear(); +} + +Triangle& Polygon::TriAt(unsigned int i) +{ + return m_tris[i]; +} + +Triangle Polygon::TriAt(unsigned int i) const +{ + return m_tris[i]; +} + +Vertex& Polygon::VertAt(unsigned int i) +{ + return m_verts[i]; +} + +Vertex Polygon::VertAt(unsigned int i) const +{ + return m_verts[i]; +} \ No newline at end of file diff --git a/src/polygon.h b/src/polygon.h new file mode 100644 index 00000000..769a13da --- /dev/null +++ b/src/polygon.h @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include + +// A Vertex is a point in space that defines one corner of a polygon. +// Each Vertex has several attributes that determine how they contribute to the +// appearance of their Polygon, such as coloration. +struct Vertex +{ + glm::vec4 m_pos; // The position of the vertex. In hw02, this is in pixel space. + glm::vec3 m_color; // The color of the vertex. X corresponds to Red, Y corresponds to Green, and Z corresponds to Blue. + glm::vec4 m_normal; // The surface normal of the vertex (not yet used) + glm::vec2 m_uv; // The texture coordinates of the vertex (not yet used) + + Vertex(glm::vec4 p, glm::vec3 c, glm::vec4 n, glm::vec2 u) + : m_pos(p), m_color(c), m_normal(n), m_uv(u) + {} +}; + +struct BoundingBox +{ +public: + float m_ymin, m_ymax, m_xmax, m_xmin; +}; + +// Each Polygon can be decomposed into triangles that fill its area. +struct Triangle +{ + // The indices of the Vertices that make up this triangle. + // The indices correspond to the std::vector of Vertices stored in the Polygon + // which stores this Triangle + unsigned int m_indices[3]; + BoundingBox m_boundBox; +}; + +class Polygon +{ +public: + // TODO: Populate this list of triangles in Triangulate() + std::vector m_tris; + // The list of Vertices that define this polygon. This is already filled by the Polygon constructor. + std::vector m_verts; + // The name of this polygon, primarily to help you debug + char* m_name; + + // Polygon class constructors + Polygon(char* name, const std::vector& pos, const std::vector& col); + //Polygon(const QString& name, int sides, glm::vec3 color, glm::vec4 pos, float rot, glm::vec4 scale); + Polygon(); + ~Polygon(); + + // TODO: Complete the body of Triangulate() in polygon.cpp + // Creates a set of triangles that, when combined, fill the area of this convex polygon. + void Triangulate(); + + // Various getter, setter, and adder functions + void AddVertex(const Vertex&); + void AddTriangle(const Triangle&); + void ClearTriangles(); + + Triangle& TriAt(unsigned int); + Triangle TriAt(unsigned int) const; + + Vertex& VertAt(unsigned int); + Vertex VertAt(unsigned int) const; +}; + +// Returns the color of the pixel in the image at the specified texture coordinates. +// Returns white if the image is a null pointer +//glm::vec3 GetImageColor(const glm::vec2 &uv_coord, const QImage* const image); \ No newline at end of file diff --git a/src/scene.cpp b/src/scene.cpp index 3fb6239a..1370bf31 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -1,188 +1,194 @@ -#include -#include "scene.h" -#include -#include -#include - -Scene::Scene(string filename) { - cout << "Reading scene from " << filename << " ..." << endl; - cout << " " << endl; - char* fname = (char*)filename.c_str(); - fp_in.open(fname); - if (!fp_in.is_open()) { - cout << "Error reading from file - aborting!" << endl; - throw; - } - while (fp_in.good()) { - string line; - utilityCore::safeGetline(fp_in, line); - if (!line.empty()) { - vector tokens = utilityCore::tokenizeString(line); - if (strcmp(tokens[0].c_str(), "MATERIAL") == 0) { - loadMaterial(tokens[1]); - cout << " " << endl; - } else if (strcmp(tokens[0].c_str(), "OBJECT") == 0) { - loadGeom(tokens[1]); - cout << " " << endl; - } else if (strcmp(tokens[0].c_str(), "CAMERA") == 0) { - loadCamera(); - cout << " " << endl; - } - } - } -} - -int Scene::loadGeom(string objectid) { - int id = atoi(objectid.c_str()); - if (id != geoms.size()) { - cout << "ERROR: OBJECT ID does not match expected number of geoms" << endl; - return -1; - } else { - cout << "Loading Geom " << id << "..." << endl; - Geom newGeom; - string line; - - //load object type - utilityCore::safeGetline(fp_in, line); - if (!line.empty() && fp_in.good()) { - if (strcmp(line.c_str(), "sphere") == 0) { - cout << "Creating new sphere..." << endl; - newGeom.type = SPHERE; - } else if (strcmp(line.c_str(), "cube") == 0) { - cout << "Creating new cube..." << endl; - newGeom.type = CUBE; - } - } - - //link material - utilityCore::safeGetline(fp_in, line); - if (!line.empty() && fp_in.good()) { - vector tokens = utilityCore::tokenizeString(line); - newGeom.materialid = atoi(tokens[1].c_str()); - cout << "Connecting Geom " << objectid << " to Material " << newGeom.materialid << "..." << endl; - } - - //load transformations - utilityCore::safeGetline(fp_in, line); - while (!line.empty() && fp_in.good()) { - vector tokens = utilityCore::tokenizeString(line); - - //load tranformations - if (strcmp(tokens[0].c_str(), "TRANS") == 0) { - newGeom.translation = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } else if (strcmp(tokens[0].c_str(), "ROTAT") == 0) { - newGeom.rotation = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } else if (strcmp(tokens[0].c_str(), "SCALE") == 0) { - newGeom.scale = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } - - utilityCore::safeGetline(fp_in, line); - } - - newGeom.transform = utilityCore::buildTransformationMatrix( - newGeom.translation, newGeom.rotation, newGeom.scale); - newGeom.inverseTransform = glm::inverse(newGeom.transform); - newGeom.invTranspose = glm::inverseTranspose(newGeom.transform); - - geoms.push_back(newGeom); - return 1; - } -} - -int Scene::loadCamera() { - cout << "Loading Camera ..." << endl; - RenderState &state = this->state; - Camera &camera = state.camera; - float fovy; - - //load static properties - 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(), "RES") == 0) { - camera.resolution.x = atoi(tokens[1].c_str()); - camera.resolution.y = atoi(tokens[2].c_str()); - } else if (strcmp(tokens[0].c_str(), "FOVY") == 0) { - fovy = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "ITERATIONS") == 0) { - state.iterations = atoi(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "DEPTH") == 0) { - state.traceDepth = atoi(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "FILE") == 0) { - state.imageName = tokens[1]; - } - } - - string line; - utilityCore::safeGetline(fp_in, line); - while (!line.empty() && fp_in.good()) { - vector tokens = utilityCore::tokenizeString(line); - if (strcmp(tokens[0].c_str(), "EYE") == 0) { - camera.position = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } else if (strcmp(tokens[0].c_str(), "LOOKAT") == 0) { - camera.lookAt = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } else if (strcmp(tokens[0].c_str(), "UP") == 0) { - camera.up = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } - - utilityCore::safeGetline(fp_in, line); - } - - //calculate fov based on resolution - float yscaled = tan(fovy * (PI / 180)); - float xscaled = (yscaled * camera.resolution.x) / camera.resolution.y; - float fovx = (atan(xscaled) * 180) / PI; - camera.fov = glm::vec2(fovx, fovy); - - camera.right = glm::normalize(glm::cross(camera.view, camera.up)); - camera.pixelLength = glm::vec2(2 * xscaled / (float)camera.resolution.x, - 2 * yscaled / (float)camera.resolution.y); - - camera.view = glm::normalize(camera.lookAt - camera.position); - - //set up render camera stuff - int arraylen = camera.resolution.x * camera.resolution.y; - state.image.resize(arraylen); - std::fill(state.image.begin(), state.image.end(), glm::vec3()); - - cout << "Loaded camera!" << endl; - return 1; -} - -int Scene::loadMaterial(string materialid) { - int id = atoi(materialid.c_str()); - if (id != materials.size()) { - cout << "ERROR: MATERIAL ID does not match expected number of materials" << endl; - return -1; - } else { - cout << "Loading Material " << id << "..." << endl; - Material newMaterial; - - //load static properties - for (int i = 0; i < 7; 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) { - newMaterial.emittance = atof(tokens[1].c_str()); - } - } - materials.push_back(newMaterial); - return 1; - } -} +#include +#include "scene.h" +#include +#include +#include + +Scene::Scene(string filename) { + cout << "Reading scene from " << filename << " ..." << endl; + cout << " " << endl; + char* fname = (char*)filename.c_str(); + fp_in.open(fname); + if (!fp_in.is_open()) { + cout << "Error reading from file - aborting!" << endl; + throw; + } + while (fp_in.good()) { + string line; + utilityCore::safeGetline(fp_in, line); + if (!line.empty()) { + vector tokens = utilityCore::tokenizeString(line); + if (strcmp(tokens[0].c_str(), "MATERIAL") == 0) { + loadMaterial(tokens[1]); + cout << " " << endl; + } else if (strcmp(tokens[0].c_str(), "OBJECT") == 0) { + loadGeom(tokens[1]); + cout << " " << endl; + } else if (strcmp(tokens[0].c_str(), "CAMERA") == 0) { + loadCamera(); + cout << " " << endl; + } + } + } +} + +int Scene::loadGeom(string objectid) { + int id = atoi(objectid.c_str()); + if (id != geoms.size()) { + cout << "ERROR: OBJECT ID does not match expected number of geoms" << endl; + return -1; + } else { + cout << "Loading Geom " << id << "..." << endl; + Geom newGeom; + string line; + + //load object type + utilityCore::safeGetline(fp_in, line); + if (!line.empty() && fp_in.good()) { + if (strcmp(line.c_str(), "sphere") == 0) { + cout << "Creating new sphere..." << endl; + newGeom.type = SPHERE; + } else if (strcmp(line.c_str(), "cube") == 0) { + cout << "Creating new cube..." << endl; + newGeom.type = CUBE; + } +#if USE_MESH_LOADING + else if (strcmp(line.c_str(), "obj") == 0) { + cout << "Creating new obj..." << endl; + newGeom.type = OBJ; + } +#endif // USE_MESH_LOADING + } + + //link material + utilityCore::safeGetline(fp_in, line); + if (!line.empty() && fp_in.good()) { + vector tokens = utilityCore::tokenizeString(line); + newGeom.materialid = atoi(tokens[1].c_str()); + cout << "Connecting Geom " << objectid << " to Material " << newGeom.materialid << "..." << endl; + } + + //load transformations + utilityCore::safeGetline(fp_in, line); + while (!line.empty() && fp_in.good()) { + vector tokens = utilityCore::tokenizeString(line); + + //load tranformations + if (strcmp(tokens[0].c_str(), "TRANS") == 0) { + newGeom.translation = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + } else if (strcmp(tokens[0].c_str(), "ROTAT") == 0) { + newGeom.rotation = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + } else if (strcmp(tokens[0].c_str(), "SCALE") == 0) { + newGeom.scale = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + } + + utilityCore::safeGetline(fp_in, line); + } + + newGeom.transform = utilityCore::buildTransformationMatrix( + newGeom.translation, newGeom.rotation, newGeom.scale); + newGeom.inverseTransform = glm::inverse(newGeom.transform); + newGeom.invTranspose = glm::inverseTranspose(newGeom.transform); + + geoms.push_back(newGeom); + return 1; + } +} + +int Scene::loadCamera() { + cout << "Loading Camera ..." << endl; + RenderState &state = this->state; + Camera &camera = state.camera; + float fovy; + + //load static properties + 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(), "RES") == 0) { + camera.resolution.x = atoi(tokens[1].c_str()); + camera.resolution.y = atoi(tokens[2].c_str()); + } else if (strcmp(tokens[0].c_str(), "FOVY") == 0) { + fovy = atof(tokens[1].c_str()); + } else if (strcmp(tokens[0].c_str(), "ITERATIONS") == 0) { + state.iterations = atoi(tokens[1].c_str()); + } else if (strcmp(tokens[0].c_str(), "DEPTH") == 0) { + state.traceDepth = atoi(tokens[1].c_str()); + } else if (strcmp(tokens[0].c_str(), "FILE") == 0) { + state.imageName = tokens[1]; + } + } + + string line; + utilityCore::safeGetline(fp_in, line); + while (!line.empty() && fp_in.good()) { + vector tokens = utilityCore::tokenizeString(line); + if (strcmp(tokens[0].c_str(), "EYE") == 0) { + camera.position = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + } else if (strcmp(tokens[0].c_str(), "LOOKAT") == 0) { + camera.lookAt = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + } else if (strcmp(tokens[0].c_str(), "UP") == 0) { + camera.up = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); + } + + utilityCore::safeGetline(fp_in, line); + } + + //calculate fov based on resolution + float yscaled = tan(fovy * (PI / 180)); + float xscaled = (yscaled * camera.resolution.x) / camera.resolution.y; + float fovx = (atan(xscaled) * 180) / PI; + camera.fov = glm::vec2(fovx, fovy); + + camera.right = glm::normalize(glm::cross(camera.view, camera.up)); + camera.pixelLength = glm::vec2(2 * xscaled / (float)camera.resolution.x, + 2 * yscaled / (float)camera.resolution.y); + + camera.view = glm::normalize(camera.lookAt - camera.position); + + //set up render camera stuff + int arraylen = camera.resolution.x * camera.resolution.y; + state.image.resize(arraylen); + std::fill(state.image.begin(), state.image.end(), glm::vec3()); + + cout << "Loaded camera!" << endl; + return 1; +} + +int Scene::loadMaterial(string materialid) { + int id = atoi(materialid.c_str()); + if (id != materials.size()) { + cout << "ERROR: MATERIAL ID does not match expected number of materials" << endl; + return -1; + } else { + cout << "Loading Material " << id << "..." << endl; + Material newMaterial; + + //load static properties + for (int i = 0; i < 7; 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) { + newMaterial.emittance = atof(tokens[1].c_str()); + } + } + materials.push_back(newMaterial); + return 1; + } +} diff --git a/src/sceneStructs.h b/src/sceneStructs.h index da4dbf30..0df7a101 100644 --- a/src/sceneStructs.h +++ b/src/sceneStructs.h @@ -7,9 +7,24 @@ #define BACKGROUND_COLOR (glm::vec3(0.0f)) +#define DOF_APERATURE 1.0f +#define DOF_FOCUSDIST 2.0f +#define USE_DOF 0 +#define USE_BOUNDING_BOX 0 +#define USE_MESH_LOADING 0 +#define ANTIALIASING 1 +#define SCHLICK 1 +#define USE_CACHE 0 +#define USE_SORT 1 +#define USE_PARTITION 1 +#define USE_REMOVE_IF 0 +#define PRINT 0 +#define ERRORCHECK 0 + enum GeomType { SPHERE, CUBE, + OBJ, }; struct Ray { @@ -26,6 +41,14 @@ struct Geom { glm::mat4 transform; glm::mat4 inverseTransform; glm::mat4 invTranspose; + + int triCount = 0; + glm::vec4* host_VecNorArr; + glm::vec4* dev_VecNorArr; + + glm::mat4 bbInverseTransform; + glm::vec3 bbScale; + }; struct Material { @@ -49,6 +72,10 @@ struct Camera { glm::vec3 right; glm::vec2 fov; glm::vec2 pixelLength; + + float aperture; + float focusDist; + }; struct RenderState { diff --git a/src/tiny_obj_loader.cc b/src/tiny_obj_loader.cc new file mode 100644 index 00000000..f5081ffe --- /dev/null +++ b/src/tiny_obj_loader.cc @@ -0,0 +1,868 @@ +// +// Copyright 2012-2015, Syoyo Fujita. +// +// Licensed under 2-clause BSD liecense. +// + +// +// version 0.9.9: Replace atof() with custom parser. +// version 0.9.8: Fix multi-materials(per-face material ID). +// version 0.9.7: Support multi-materials(per-face material ID) per +// object/group. +// version 0.9.6: Support Ni(index of refraction) mtl parameter. +// Parse transmittance material parameter correctly. +// version 0.9.5: Parse multiple group name. +// Add support of specifying the base path to load material file. +// version 0.9.4: Initial suupport of group tag(g) +// version 0.9.3: Fix parsing triple 'x/y/z' +// version 0.9.2: Add more .mtl load support +// version 0.9.1: Add initial .mtl load support +// version 0.9.0: Initial +// + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tiny_obj_loader.h" + +namespace tinyobj { + +struct vertex_index { + int v_idx, vt_idx, vn_idx; + vertex_index(){}; + vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx){}; + vertex_index(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx){}; +}; +// for std::map +static inline bool operator<(const vertex_index &a, const vertex_index &b) { + if (a.v_idx != b.v_idx) + return (a.v_idx < b.v_idx); + if (a.vn_idx != b.vn_idx) + return (a.vn_idx < b.vn_idx); + if (a.vt_idx != b.vt_idx) + return (a.vt_idx < b.vt_idx); + + return false; +} + +struct obj_shape { + std::vector v; + std::vector vn; + std::vector vt; +}; + +static inline bool isSpace(const char c) { return (c == ' ') || (c == '\t'); } + +static inline bool isNewLine(const char c) { + return (c == '\r') || (c == '\n') || (c == '\0'); +} + +// Make index zero-base, and also support relative index. +static inline int fixIndex(int idx, int n) { + if (idx > 0) return idx - 1; + if (idx == 0) return 0; + return n + idx; // negative value = relative +} + +static inline std::string parseString(const char *&token) { + std::string s; + token += strspn(token, " \t"); + size_t e = strcspn(token, " \t\r"); + s = std::string(token, &token[e]); + token += e; + return s; +} + +static inline int parseInt(const char *&token) { + token += strspn(token, " \t"); + int i = atoi(token); + token += strcspn(token, " \t\r"); + return i; +} + + +// Tries to parse a floating point number located at s. +// +// s_end should be a location in the string where reading should absolutely +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// Valid strings are for example: +// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 +// +// If the parsing is a success, result is set to the parsed value and true +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// The following situations triggers a failure: +// - s >= s_end. +// - parse failure. +// +static bool tryParseDouble(const char *s, const char *s_end, double *result) +{ + if (s >= s_end) + { + return false; + } + + double mantissa = 0.0; + // This exponent is base 2 rather than 10. + // However the exponent we parse is supposed to be one of ten, + // thus we must take care to convert the exponent/and or the + // mantissa to a * 2^E, where a is the mantissa and E is the + // exponent. + // To get the final double we will use ldexp, it requires the + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // How many characters were read in a loop. + int read = 0; + // Tells whether a loop terminated due to reaching s_end. + bool end_not_reached = false; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') + { + sign = *curr; + curr++; + } + else if (isdigit(*curr)) { /* Pass through. */ } + else + { + goto fail; + } + + // Read the integer part. + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; read++; + } + + // We must make sure we actually got something. + if (read == 0) + goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) + goto assemble; + + // Read the decimal part. + if (*curr == '.') + { + curr++; + read = 1; + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + // NOTE: Don't use powf here, it will absolutely murder precision. + mantissa += static_cast(*curr - 0x30) * pow(10, -read); + read++; curr++; + } + } + else if (*curr == 'e' || *curr == 'E') {} + else + { + goto assemble; + } + + if (!end_not_reached) + goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') + { + curr++; + // Figure out if a sign is present and if it is. + if ((end_not_reached = (curr != s_end)) && (*curr == '+' || *curr == '-')) + { + exp_sign = *curr; + curr++; + } + else if (isdigit(*curr)) { /* Pass through. */ } + else + { + // Empty E is not allowed. + goto fail; + } + + read = 0; + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; read++; + } + exponent *= (exp_sign == '+'? 1 : -1); + if (read == 0) + goto fail; + } + +assemble: + *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5, exponent), exponent); + return true; +fail: + return false; +} +static inline float parseFloat(const char *&token) { + token += strspn(token, " \t"); +#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER + float f = (float)atof(token); + token += strcspn(token, " \t\r"); +#else + const char *end = token + strcspn(token, " \t\r"); + double val = 0.0; + tryParseDouble(token, end, &val); + float f = static_cast(val); + token = end; +#endif + return f; +} + + +static inline void parseFloat2(float &x, float &y, const char *&token) { + x = parseFloat(token); + y = parseFloat(token); +} + +static inline void parseFloat3(float &x, float &y, float &z, + const char *&token) { + x = parseFloat(token); + y = parseFloat(token); + z = parseFloat(token); +} + +// Parse triples: i, i/j/k, i//k, i/j +static vertex_index parseTriple(const char *&token, int vsize, int vnsize, + int vtsize) { + vertex_index vi(-1); + + vi.v_idx = fixIndex(atoi(token), vsize); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return vi; + } + token++; + + // i//k + if (token[0] == '/') { + token++; + vi.vn_idx = fixIndex(atoi(token), vnsize); + token += strcspn(token, "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = fixIndex(atoi(token), vtsize); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return vi; + } + + // i/j/k + token++; // skip '/' + vi.vn_idx = fixIndex(atoi(token), vnsize); + token += strcspn(token, "/ \t\r"); + return vi; +} + +static unsigned int +updateVertex(std::map &vertexCache, + std::vector &positions, std::vector &normals, + std::vector &texcoords, + const std::vector &in_positions, + const std::vector &in_normals, + const std::vector &in_texcoords, const vertex_index &i) { + const std::map::iterator it = vertexCache.find(i); + + if (it != vertexCache.end()) { + // found cache + return it->second; + } + + assert(in_positions.size() > (unsigned int)(3 * i.v_idx + 2)); + + positions.push_back(in_positions[3 * i.v_idx + 0]); + positions.push_back(in_positions[3 * i.v_idx + 1]); + positions.push_back(in_positions[3 * i.v_idx + 2]); + + if (i.vn_idx >= 0) { + normals.push_back(in_normals[3 * i.vn_idx + 0]); + normals.push_back(in_normals[3 * i.vn_idx + 1]); + normals.push_back(in_normals[3 * i.vn_idx + 2]); + } + + if (i.vt_idx >= 0) { + texcoords.push_back(in_texcoords[2 * i.vt_idx + 0]); + texcoords.push_back(in_texcoords[2 * i.vt_idx + 1]); + } + + unsigned int idx = static_cast(positions.size() / 3 - 1); + vertexCache[i] = idx; + + return idx; +} + +void InitMaterial(material_t &material) { + material.name = ""; + material.ambient_texname = ""; + material.diffuse_texname = ""; + material.specular_texname = ""; + material.normal_texname = ""; + for (int i = 0; i < 3; i++) { + material.ambient[i] = 0.f; + material.diffuse[i] = 0.f; + material.specular[i] = 0.f; + material.transmittance[i] = 0.f; + material.emission[i] = 0.f; + } + material.illum = 0; + material.dissolve = 1.f; + material.shininess = 1.f; + material.ior = 1.f; + material.unknown_parameter.clear(); +} + +static bool exportFaceGroupToShape( + shape_t &shape, std::map vertexCache, + const std::vector &in_positions, + const std::vector &in_normals, + const std::vector &in_texcoords, + const std::vector > &faceGroup, + const int material_id, const std::string &name, bool clearCache) { + if (faceGroup.empty()) { + return false; + } + + // Flatten vertices and indices + for (size_t i = 0; i < faceGroup.size(); i++) { + const std::vector &face = faceGroup[i]; + + vertex_index i0 = face[0]; + vertex_index i1(-1); + vertex_index i2 = face[1]; + + size_t npolys = face.size(); + + // Polygon -> triangle fan conversion + for (size_t k = 2; k < npolys; k++) { + i1 = i2; + i2 = face[k]; + + unsigned int v0 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); + unsigned int v1 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); + unsigned int v2 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); + + shape.mesh.indices.push_back(v0); + shape.mesh.indices.push_back(v1); + shape.mesh.indices.push_back(v2); + + shape.mesh.material_ids.push_back(material_id); + } + } + + shape.name = name; + + if (clearCache) + vertexCache.clear(); + + return true; +} + +std::string LoadMtl(std::map &material_map, + std::vector &materials, + std::istream &inStream) { + std::stringstream err; + + material_t material; + + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); + + std::string linebuf(&buf[0]); + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') + continue; // empty line + + if (token[0] == '#') + continue; // comment line + + // new mtl + if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { + // flush previous material. + if (!material.name.empty()) { + material_map.insert( + std::pair(material.name, static_cast(materials.size()))); + materials.push_back(material); + } + + // initial temporary material + InitMaterial(material); + + // set new mtl name + char namebuf[4096]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else + sscanf(token, "%s", namebuf); +#endif + material.name = namebuf; + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } + + // diffuse + if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + continue; + } + + // specular + if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } + + // transmittance + if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } + + // ior(index of refraction) + if (token[0] == 'N' && token[1] == 'i' && isSpace((token[2]))) { + token += 2; + material.ior = parseFloat(token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } + + // shininess + if (token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { + token += 2; + material.shininess = parseFloat(token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && isSpace(token[5])) { + token += 6; + material.illum = parseInt(token); + continue; + } + + // dissolve + if ((token[0] == 'd' && isSpace(token[1]))) { + token += 1; + material.dissolve = parseFloat(token); + continue; + } + if (token[0] == 'T' && token[1] == 'r' && isSpace(token[2])) { + token += 2; + material.dissolve = parseFloat(token); + continue; + } + + // ambient texture + if ((0 == strncmp(token, "map_Ka", 6)) && isSpace(token[6])) { + token += 7; + material.ambient_texname = token; + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && isSpace(token[6])) { + token += 7; + material.diffuse_texname = token; + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { + token += 7; + material.specular_texname = token; + continue; + } + + // normal texture + if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { + token += 7; + material.normal_texname = token; + continue; + } + + // unknown parameter + const char *_space = strchr(token, ' '); + if (!_space) { + _space = strchr(token, '\t'); + } + if (_space) { + std::ptrdiff_t len = _space - token; + std::string key(token, len); + std::string value = _space + 1; + material.unknown_parameter.insert( + std::pair(key, value)); + } + } + // flush last material. + material_map.insert( + std::pair(material.name, static_cast(materials.size()))); + materials.push_back(material); + + return err.str(); +} + +std::string MaterialFileReader::operator()(const std::string &matId, + std::vector &materials, + std::map &matMap) { + std::string filepath; + + if (!m_mtlBasePath.empty()) { + filepath = std::string(m_mtlBasePath) + matId; + } else { + filepath = matId; + } + + std::ifstream matIStream(filepath.c_str()); + return LoadMtl(matMap, materials, matIStream); +} + +std::string LoadObj(std::vector &shapes, + std::vector &materials, // [output] + const char *filename, const char *mtl_basepath) { + + shapes.clear(); + + std::stringstream err; + + std::ifstream ifs(filename); + if (!ifs) { + err << "Cannot open file [" << filename << "]" << std::endl; + return err.str(); + } + + std::string basePath; + if (mtl_basepath) { + basePath = mtl_basepath; + } + MaterialFileReader matFileReader(basePath); + + return LoadObj(shapes, materials, ifs, matFileReader); +} + +std::string LoadObj(std::vector &shapes, + std::vector &materials, // [output] + std::istream &inStream, MaterialReader &readMatFn) { + std::stringstream err; + + std::vector v; + std::vector vn; + std::vector vt; + std::vector > faceGroup; + std::string name; + + // material + std::map material_map; + std::map vertexCache; + int material = -1; + + shape_t shape; + + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); + + std::string linebuf(&buf[0]); + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') + continue; // empty line + + if (token[0] == '#') + continue; // comment line + + // vertex + if (token[0] == 'v' && isSpace((token[1]))) { + token += 2; + float x, y, z; + parseFloat3(x, y, z, token); + v.push_back(x); + v.push_back(y); + v.push_back(z); + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) { + token += 3; + float x, y, z; + parseFloat3(x, y, z, token); + vn.push_back(x); + vn.push_back(y); + vn.push_back(z); + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) { + token += 3; + float x, y; + parseFloat2(x, y, token); + vt.push_back(x); + vt.push_back(y); + continue; + } + + // face + if (token[0] == 'f' && isSpace((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + std::vector face; + while (!isNewLine(token[0])) { + vertex_index vi = + parseTriple(token, static_cast(v.size() / 3), static_cast(vn.size() / 3), static_cast(vt.size() / 2)); + face.push_back(vi); + size_t n = strspn(token, " \t\r"); + token += n; + } + + faceGroup.push_back(face); + + continue; + } + + // use mtl +// if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { +// +// char namebuf[4096]; +// token += 7; +//#ifdef _MSC_VER +// sscanf_s(token, "%s", namebuf); +//#else +// sscanf(token, "%s", namebuf); +//#endif +// +// // Create face group per material. +// bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, +// faceGroup, material, name, true); +// if (ret) { +// faceGroup.clear(); +// } +// +// if (material_map.find(namebuf) != material_map.end()) { +// material = material_map[namebuf]; +// } else { +// // { error!! material not found } +// material = -1; +// } +// +// continue; + //} + + // load mtl +// if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { +// char namebuf[4096]; +// token += 7; +//#ifdef _MSC_VER +// sscanf_s(token, "%s", namebuf); +//#else +// sscanf(token, "%s", namebuf); +//#endif +// +// std::string err_mtl = readMatFn(namebuf, materials, material_map); +// if (!err_mtl.empty()) { +// faceGroup.clear(); // for safety +// return err_mtl; +// } +// +// continue; +// } + + // group name + if (token[0] == 'g' && isSpace((token[1]))) { + + // flush previous face group. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } + + shape = shape_t(); + + // material = -1; + faceGroup.clear(); + + std::vector names; + while (!isNewLine(token[0])) { + std::string str = parseString(token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + assert(names.size() > 0); + + // names[0] must be 'g', so skip the 0th element. + if (names.size() > 1) { + name = names[1]; + } else { + name = ""; + } + + continue; + } + + // object name + if (token[0] == 'o' && isSpace((token[1]))) { + + // flush previous face group. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } + + // material = -1; + faceGroup.clear(); + shape = shape_t(); + + // @todo { multiple object name? } + char namebuf[4096]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf); +#else + sscanf(token, "%s", namebuf); +#endif + name = std::string(namebuf); + + continue; + } + + // Ignore unknown command. + } + + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, + material, name, true); + if (ret) { + shapes.push_back(shape); + } + faceGroup.clear(); // for safety + + return err.str(); +} +} diff --git a/src/tiny_obj_loader.h b/src/tiny_obj_loader.h new file mode 100644 index 00000000..512f32ba --- /dev/null +++ b/src/tiny_obj_loader.h @@ -0,0 +1,94 @@ +// +// Copyright 2012-2015, Syoyo Fujita. +// +// Licensed under 2-clause BSD liecense. +// +#ifndef _TINY_OBJ_LOADER_H +#define _TINY_OBJ_LOADER_H + +#include +#include +#include + +namespace tinyobj { + +typedef struct { + std::string name; + + float ambient[3]; + float diffuse[3]; + float specular[3]; + float transmittance[3]; + float emission[3]; + float shininess; + float ior; // index of refraction + float dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; + + std::string ambient_texname; + std::string diffuse_texname; + std::string specular_texname; + std::string normal_texname; + std::map unknown_parameter; +} material_t; + +typedef struct { + std::vector positions; + std::vector normals; + std::vector texcoords; + std::vector indices; + std::vector material_ids; // per-mesh material ID +} mesh_t; + +typedef struct { + std::string name; + mesh_t mesh; +} shape_t; + +class MaterialReader { +public: + MaterialReader() {} + virtual ~MaterialReader() {} + + virtual std::string operator()(const std::string &matId, + std::vector &materials, + std::map &matMap) = 0; +}; + +class MaterialFileReader : public MaterialReader { +public: + MaterialFileReader(const std::string &mtl_basepath) + : m_mtlBasePath(mtl_basepath) {} + virtual ~MaterialFileReader() {} + virtual std::string operator()(const std::string &matId, + std::vector &materials, + std::map &matMap); + +private: + std::string m_mtlBasePath; +}; + +/// Loads .obj from a file. +/// 'shapes' will be filled with parsed shape data +/// The function returns error string. +/// Returns empty string when loading .obj success. +/// 'mtl_basepath' is optional, and used for base path for .mtl file. +std::string LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + const char *filename, const char *mtl_basepath = NULL); + +/// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve +/// std::istream for materials. +/// Returns empty string when loading .obj success. +std::string LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::istream &inStream, MaterialReader &readMatFn); + +/// Loads materials into std::map +/// Returns an empty string if successful +std::string LoadMtl(std::map &material_map, + std::vector &materials, std::istream &inStream); +} + +#endif // _TINY_OBJ_LOADER_H