diff --git a/DOF5000.bmp b/DOF5000.bmp new file mode 100644 index 0000000..3c7dd2e Binary files /dev/null and b/DOF5000.bmp differ diff --git a/FirstRefraction.bmp b/FirstRefraction.bmp new file mode 100644 index 0000000..2f8ebca Binary files /dev/null and b/FirstRefraction.bmp differ diff --git a/README.md b/README.md index ae6088d..e1d959d 100644 --- a/README.md +++ b/README.md @@ -5,157 +5,33 @@ Fall 2014 Due Wed, 10/8 (submit without penalty until Sun, 10/12) -## INTRODUCTION -In this project, you will implement a CUDA based pathtracer capable of -generating pathtraced rendered images extremely quickly. Building a pathtracer can be viewed as a generalization of building a raytracer, so for those of you who have taken 460/560, the basic concept should not be very new to you. For those of you that have not taken -CIS460/560, raytracing is a technique for generating images by tracing rays of -light through pixels in an image plane out into a scene and following the way -the rays of light bounce and interact with objects in the scene. More -information can be found here: -http://en.wikipedia.org/wiki/Ray_tracing_(graphics). Pathtracing is a generalization of this technique by considering more than just the contribution of direct lighting to a surface. - -Since in this class we are concerned with working in generating actual images -and less so with mundane tasks like file I/O, this project includes basecode -for loading a scene description file format, described below, and various other -things that generally make up the render "harness" that takes care of -everything up to the rendering itself. The core renderer is left for you to -implement. Finally, note that while this basecode is meant to serve as a -strong starting point for a CUDA pathtracer, you are not required to use this -basecode if you wish, and you may also change any part of the basecode -specification as you please, so long as the final rendered result is correct. - -## CONTENTS -The Project3 root directory contains the following subdirectories: - -* src/ contains the source code for the project. Both the Windows Visual Studio - solution and the OSX and Linux makefiles reference this folder for all - source; the base source code compiles on Linux, OSX and Windows without - modification. If you are building on OSX, be sure to uncomment lines 4 & 5 of - the CMakeLists.txt in order to make sure CMake builds against clang. -* data/scenes/ contains an example scene description file. -* renders/ contains an example render of the given example scene file. -* windows/ contains a Windows Visual Studio 2010 project and all dependencies - needed for building and running on Windows 7. If you would like to create a - Visual Studio 2012 or 2013 projects, there are static libraries that you can - use for GLFW that are in external/bin/GLFW (Visual Studio 2012 uses msvc110, - and Visual Studio 2013 uses msvc120) -* external/ contains all the header, static libraries and built binaries for - 3rd party libraries (i.e. glm, GLEW, GLFW) that we use for windowing and OpenGL - extensions ## RUNNING THE CODE -The main function requires a scene description file (that is provided in data/scenes). -The main function reads in the scene file by an argument as such : -'scene=[sceneFileName]' - -If you are using Visual Studio, you can set this in the Debugging > Command Arguments section -in the Project properties. +* Make sure to look at sampleScene.txt and pay attention to DOF and APERATURE. ## REQUIREMENTS -In this project, you are given code for: - -* Loading, reading, and storing the scene scene description format -* Example functions that can run on both the CPU and GPU for generating random - numbers, spherical intersection testing, and surface point sampling on cubes -* A class for handling image operations and saving images -* Working code for CUDA-GL interop - -You will need to implement the following features: +I have so far implemented the following additional features: -* Raycasting from a camera into a scene through a pixel grid -* Diffuse surfaces -* Perfect specular reflective surfaces -* Cube intersection testing -* Sphere surface point sampling -* Stream compaction optimization - -You are also required to implement at least 2 of the following features: - -* Texture mapping -* Bump mapping * Depth of field * Refraction, i.e. glass -* OBJ Mesh loading and rendering -* Interactive camera -* Motion blur -* Subsurface scattering - -The 'extra features' list is not comprehensive. If you have a particular feature -you would like to implement (e.g. acceleration structures, etc.) please contact us -first! - -For each 'extra feature' you must provide the following analysis : -* overview write up of the feature -* performance impact of the feature -* if you did something to accelerate the feature, why did you do what you did -* compare your GPU version to a CPU version of this feature (you do NOT need to - implement a CPU version) -* how can this feature be further optimized (again, not necessary to implement it, but - should give a roadmap of how to further optimize and why you believe this is the next - step) -## BASE CODE TOUR -You will be working in three files: raytraceKernel.cu, intersections.h, and -interactions.h. Within these files, areas that you need to complete are marked -with a TODO comment. Areas that are useful to and serve as hints for optional -features are marked with TODO (Optional). Functions that are useful for -reference are marked with the comment LOOK. +## ANALYSIS DEPTH OF FIELD +![DOF5000.bmp](https://raw.githubusercontent.com/RTCassidy1/Project3-Pathtracer/master/DOF5000.bmp) +I incorporated Depth of Field by adding two parameters to the camera. +* the DOF parameter is the distance from the eye to the focal point. (it would be more aptly named focal length, note to self for future refactoring) +* the APERATURE parameter is the size of the aperature +The way the feature works is that when casting the first ray, after selecting within my jittered pixel I place the FocalPoint along this ray at DOF distance. Then I randomly jitter my pixel position within the range of the aperature (so a larger aperature = smaller depth of field = more blur. +* Adding this feature did add a few more calculations into the initial raycast which slowed performance down slightly. I was overly cautious in normalizing my direction vectors so it's possible I could remove a few of these normalizations for additional speedup. I also probably don't need to jitter within the pixel anymore, but kept it there so I still maintain AntiAliasing when aperature is 0. +* I didn't do anything particularly to speed this feature up. In RayTracing people often have to supersample the pixel to achieve this, but since we're already taking 1000s of samples per pixle I merely had to jitter around within the size of the aperature. -* raytraceKernel.cu contains the core raytracing CUDA kernel. You will need to - complete: - * cudaRaytraceCore() handles kernel launches and memory management; this - function already contains example code for launching kernels, - transferring geometry and cameras from the host to the device, and transferring - image buffers from the host to the device and back. You will have to complete - this function to support passing materials and lights to CUDA. - * raycastFromCameraKernel() is a function that you need to implement. This - function once correctly implemented should handle camera raycasting. - * raytraceRay() is the core raytracing CUDA kernel; all of your pathtracing - logic should be implemented in this CUDA kernel. raytraceRay() should - take in a camera, image buffer, geometry, materials, and lights, and should - trace a ray through the scene and write the resultant color to a pixel in the - image buffer. +## ANALYSIS REFRACTION +![FirstRefraction.bmp](https://raw.githubusercontent.com/RTCassidy1/Project3-Pathtracer/master/FirstRefraction.bmp) +My BSDF is pretty simple, I would like to go through and make it more modular at a future time when I have a chance to refactor. Instead of using Reflective and Refractive markers as flags, I used them as floats each representing the percentage of photons that hit is that will reflect or refract(transmit). If a photon doesn't reflect or refract then it is treated as diffuse. +* my BSDF accepts two random numbers from 0-1 as parameters. I use these to determine if I will check the reflectance or refractance threshold first. I then use the number to determine which way to treat the material. If my random number is less than the threshhold for reflectance (or refractance) I treat it as a reflection (refraction). If it is above the threshold it falls through to the next test. If it fails everything else it's treated as diffuse. -* intersections.h contains functions for geometry intersection testing and - point generation. You will need to complete: - * boxIntersectionTest(), which takes in a box and a ray and performs an - intersection test. This function should work in the same way as - sphereIntersectionTest(). - * getRandomPointOnSphere(), which takes in a sphere and returns a random - point on the surface of the sphere with an even probability distribution. - This function should work in the same way as getRandomPointOnCube(). You can - (although do not necessarily have to) use this to generate points on a sphere - to use a point lights, or can use this for area lighting. - -* interactions.h contains functions for ray-object interactions that define how - rays behave upon hitting materials and objects. You will need to complete: - * getRandomDirectionInSphere(), which generates a random direction in a - sphere with a uniform probability. This function works in a fashion - similar to that of calculateRandomDirectionInHemisphere(), which generates a - random cosine-weighted direction in a hemisphere. - * calculateBSDF(), which takes in an incoming ray, normal, material, and - other information, and returns an outgoing ray. You can either implement - this function for ray-surface interactions, or you can replace it with your own - function(s). - -You will also want to familiarize yourself with: - -* sceneStructs.h, which contains definitions for how geometry, materials, - lights, cameras, and animation frames are stored in the renderer. -* utilities.h, which serves as a kitchen-sink of useful functions - -## NOTES ON GLM -This project uses GLM, the GL Math library, for linear algebra. You need to -know two important points on how GLM is used in this project: - -* In this project, indices in GLM vectors (such as vec3, vec4), are accessed - via swizzling. So, instead of v[0], v.x is used, and instead of v[1], v.y is - used, and so on and so forth. -* GLM Matrix operations work fine on NVIDIA Fermi cards and later, but - pre-Fermi cards do not play nice with GLM matrices. As such, in this project, - GLM matrices are replaced with a custom matrix struct, called a cudaMat4, found - in cudaMat4.h. A custom function for multiplying glm::vec4s and cudaMat4s is - provided as multiplyMV() in intersections.h. +## ANALYSIS STREAM COMPACTION +I also implemented stream compaction in an effort to speed up my renders. Unfortunately it has so far offered little to no performance increase with depth 5 or 10. I think this is because my implementation is not efficient enough and has too much memory access overhead. I used thrust to scan an array for retired threads, but then implemented my own function to compact the rayStates. I think with a little more research of Thrust I can implement my rayState array as a thrust vector and use built in functions to prune it on each depth iteration. +* I also planned to look at shared memory, but haven't yet had the chance. There are a lot of parallel streams doing ray-intersection tests with the same geometry, so I speculate there could be an increase in efficiency by moving the geometery (and possibly materials) into shared memory. This will have an overhead to actually move them, and the shear number of threads may actually be hiding any latency in the accesses, but I haven't had a chance to look at it yet. ## SCENE FORMAT This project uses a custom scene description format. @@ -170,10 +46,8 @@ Materials are defined in the following fashion: * RGB (float r) (float g) (float b) //diffuse color * SPECX (float specx) //specular exponent * SPECRGB (float r) (float g) (float b) //specular color -* REFL (bool refl) //reflectivity flag, 0 for - no, 1 for yes -* REFR (bool refr) //refractivity flag, 0 for - no, 1 for yes +* REFL (bool refl) //how reflective is it? 0 means diffuse +* REFR (float refr) //how transparent is it? 0 means opaque * REFRIOR (float ior) //index of refraction for Fresnel effects * SCATTER (float scatter) //scatter flag, 0 for @@ -203,6 +77,8 @@ Cameras are defined in the following fashion: * VIEW (float x) (float y) (float z) //camera's view direction * UP (float x) (float y) (float z) //camera's up vector +* DOF (float x) //the Distance to the focal point +* APERATURE (float x) //the size of the aperature (should be large) Objects are defined in the following fashion: * OBJECT (object ID) //object header @@ -234,29 +110,6 @@ SCALE .01 10 10 Check the Google group for some sample .obj files of varying complexity. -## THIRD PARTY CODE POLICY -* Use of any third-party code must be approved by asking on our Google Group. - If it is approved, all students are welcome to use it. Generally, we approve - use of third-party code that is not a core part of the project. For example, - for the ray tracer, we would approve using a third-party library for loading - models, but would not approve copying and pasting a CUDA function for doing - refraction. -* Third-party code must be credited in README.md. -* Using third-party code without its approval, including using another - student's code, is an academic integrity violation, and will result in you - receiving an F for the semester. - -## SELF-GRADING -* On the submission date, email your grade, on a scale of 0 to 100, to Harmony, - harmoli+cis565@seas.upenn.com, with a one paragraph explanation. Be concise and - realistic. Recall that we reserve 30 points as a sanity check to adjust your - grade. Your actual grade will be (0.7 * your grade) + (0.3 * our grade). We - hope to only use this in extreme cases when your grade does not realistically - reflect your work - it is either too high or too low. In most cases, we plan - to give you the exact grade you suggest. -* Projects are not weighted evenly, e.g., Project 0 doesn't count as much as - the path tracer. We will determine the weighting at the end of the semester - based on the size of each project. ## SUBMISSION Please change the README to reflect the answers to the questions we have posed diff --git a/data/scenes/sampleScene.txt b/data/scenes/sampleScene.txt index 6a9f5cc..f873fef 100644 --- a/data/scenes/sampleScene.txt +++ b/data/scenes/sampleScene.txt @@ -14,7 +14,7 @@ MATERIAL 1 //red diffuse RGB .63 .06 .04 SPECEX 0 SPECRGB 1 1 1 -REFL 0 +REFL .2 REFR 0 REFRIOR 0 SCATTER 0 @@ -35,11 +35,11 @@ RSCTCOEFF 0 EMITTANCE 0 MATERIAL 3 //red glossy -RGB .63 .06 .04 +RGB .63 .16 .04 SPECEX 0 SPECRGB 1 1 1 -REFL 0 -REFR 0 +REFL .1 +REFR .9 REFRIOR 2 SCATTER 0 ABSCOEFF 0 0 0 @@ -50,7 +50,7 @@ MATERIAL 4 //white glossy RGB 1 1 1 SPECEX 0 SPECRGB 1 1 1 -REFL 0 +REFL .3 REFR 0 REFRIOR 2 SCATTER 0 @@ -74,7 +74,7 @@ MATERIAL 6 //green glossy RGB .15 .48 .09 SPECEX 0 SPECRGB 1 1 1 -REFL 0 +REFL .1 REFR 0 REFRIOR 2.6 SCATTER 0 @@ -95,7 +95,19 @@ RSCTCOEFF 0 EMITTANCE 1 MATERIAL 8 //light -RGB 1 1 1 +RGB 1 1 .5 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 5 + +MATERIAL 9 //light +RGB .5 1 1 SPECEX 0 SPECRGB 0 0 0 REFL 0 @@ -115,6 +127,8 @@ frame 0 EYE 0 4.5 12 VIEW 0 0 -1 UP 0 1 0 +DOF 12 +APERATURE 1500 OBJECT 0 cube @@ -160,7 +174,7 @@ OBJECT 5 sphere material 4 frame 0 -TRANS 0 2 0 +TRANS 0 5 0 ROTAT 0 180 0 SCALE 3 3 3 @@ -168,7 +182,7 @@ OBJECT 6 sphere material 3 frame 0 -TRANS 2 5 2 +TRANS 2 1.5.5 -3 ROTAT 0 180 0 SCALE 2.5 2.5 2.5 @@ -176,7 +190,7 @@ OBJECT 7 sphere material 6 frame 0 -TRANS -2 5 -2 +TRANS -2 1.5 -3 ROTAT 0 180 0 SCALE 3 3 3 @@ -187,4 +201,12 @@ material 8 frame 0 TRANS 0 10 0 ROTAT 0 0 90 -SCALE .3 3 3 \ No newline at end of file +SCALE .3 6 6 + +OBJECT 9 +cube +material 9 +frame 0 +TRANS 0 4.5 12 +ROTAT 0 0 0 +SCALE 4 4 .3 \ No newline at end of file diff --git a/src/interactions.h b/src/interactions.h index 7bf6fab..c9c9d88 100644 --- a/src/interactions.h +++ b/src/interactions.h @@ -86,21 +86,121 @@ __host__ __device__ glm::vec3 calculateRandomDirectionInHemisphere(glm::vec3 nor } -// TODO: IMPLEMENT THIS FUNCTION + // Now that you know how cosine weighted direction generation works, try implementing // non-cosine (uniform) weighted random direction generation. // This should be much easier than if you had to implement calculateRandomDirectionInHemisphere. __host__ __device__ glm::vec3 getRandomDirectionInSphere(float xi1, float xi2) { - return glm::vec3(0,0,0); + float randomSeed = xi1 * xi2; + //from method found at http://mathworld.wolfram.com/SpherePointPicking.html + thrust::default_random_engine rng(hash(randomSeed)); + thrust::uniform_real_distribution u01(-1,1); + //thrust::uniform_real_distribution u02(-1,1); + float x_1 = 2; + float x_2 = 2; + float x_1_squared = 4; + float x_2_squared = 4; + while((x_1_squared + x_2_squared) >= 1){ //reject where sum of squares >= 1 + x_1 = (float)u01(rng); + x_2 = (float)u01(rng); + x_1_squared = x_1 * x_1; + x_2_squared = x_2 * x_2; + } + float x, y, z; + x = 2 * x_1 + sqrt(1 - x_1_squared - x_2_squared); + y = 2 * x_2 + sqrt(1 - x_1_squared - x_2_squared); + z = 1 - 2 * (x_1_squared + x_2_squared); + + return glm::vec3(x,y,z); +} + +__host__ __device__ int calculateReflective(ray& thisRay, glm::vec3 intersect, glm::vec3 normal, + glm::vec3& color, material mat, float seed1, float seed2){ + ray newRay; + //Perfect reflective + newRay.direction = glm::reflect(thisRay.direction, normal); + newRay.direction = glm::normalize(newRay.direction); + newRay.origin = intersect + .001f * newRay.direction;//nudge in direction + thisRay = newRay; + return 1; +} + +__host__ __device__ int calculateRefractive(ray& thisRay, glm::vec3 intersect, glm::vec3 normal, + glm::vec3& color, material mat, float seed1, float seed2){ +//consulted Bram de Grave paper 2006 Reflections and Refractions in Ray Tracing for help with algorithm. + ray newRay; + float theta, phi; + float indexOfRefraction = mat.indexOfRefraction; + + //refraction angle + theta = glm::dot(normal, thisRay.direction); + if (theta > 0){ + //flip normal, i'm inside the object + normal = - normal; + }else{ + //flip theta and invert IOR + theta = -theta; + indexOfRefraction = 1/indexOfRefraction; + } + phi = indexOfRefraction * indexOfRefraction * (1 - theta * theta); + //is there total internal reflection? + if (phi > 1){ + return calculateReflective(thisRay, intersect, normal, color, mat, seed1, seed2); //switch to reflection + } + float sinPhi = sqrt(1 - phi); + newRay.direction = indexOfRefraction * thisRay.direction + (indexOfRefraction * theta - sinPhi) * normal; + newRay.direction = glm::normalize(newRay.direction); + newRay.origin = intersect + .001f * newRay.direction; //nudge in direction + thisRay = newRay; + return 2; +} + +__host__ __device__ int calculateDiffuse(ray& thisRay, glm::vec3 intersect, glm::vec3 normal, + glm::vec3& color, material mat, float seed1, float seed2){ + ray newRay; + //Diffuse + newRay.direction = calculateRandomDirectionInHemisphere(normal, seed1, seed2); + newRay.direction = glm::normalize(newRay.direction); + newRay.origin = intersect + .001f * newRay.direction; //nudge in direction + //get Cosine of new ray and normal + float cos = glm::dot(newRay.direction, normal); + //update COLOR + color = color * mat.color * cos; + thisRay = newRay; + return 0; } // TODO (PARTIALLY OPTIONAL): IMPLEMENT THIS FUNCTION + /////////////////////////////////// + ////////////////////////////////// + // TODO: IMPLEMENT THIS FUNCTION/ + //////////////////////////////// + /////////////////////////////// // Returns 0 if diffuse scatter, 1 if reflected, 2 if transmitted. -__host__ __device__ int calculateBSDF(ray& r, glm::vec3 intersect, glm::vec3 normal, glm::vec3 emittedColor, +/*__host__ __device__ int calculateBSDF(ray& r, glm::vec3 intersect, glm::vec3 normal, glm::vec3 emittedColor, AbsorptionAndScatteringProperties& currentAbsorptionAndScattering, - glm::vec3& color, glm::vec3& unabsorbedColor, material m){ - - return 1; + glm::vec3& color, glm::vec3& unabsorbedColor, material m){ */ +__host__ __device__ int calculateBSDF(ray& thisRay, glm::vec3 intersect, glm::vec3 normal, + glm::vec3& color, material mat, float seed1, float seed2){ + if((seed1 + seed2) > 1){ + //check reflectance first + if(seed2 < mat.hasReflective){ + return calculateReflective(thisRay, intersect, normal, color, mat, seed1, seed2); + }else if (seed2 < mat.hasRefractive){ + return calculateRefractive(thisRay, intersect, normal, color, mat, seed1, seed2); + }else{ + return calculateDiffuse(thisRay, intersect, normal, color, mat, seed1, seed2); + } + }else{ + //check refractive first + if(seed1 < mat.hasRefractive){ + return calculateRefractive(thisRay, intersect, normal, color, mat, seed1, seed2); + }else if (seed1 < mat.hasReflective){ + return calculateReflective(thisRay, intersect, normal, color, mat, seed1, seed2); + }else{ + return calculateDiffuse(thisRay, intersect, normal, color, mat, seed1, seed2); + } + } }; #endif diff --git a/src/intersections.h b/src/intersections.h index c9eafb6..c1c4ca6 100644 --- a/src/intersections.h +++ b/src/intersections.h @@ -12,16 +12,18 @@ #include "sceneStructs.h" #include "cudaMat4.h" #include "utilities.h" +//#include "interactions.h" // Some forward declarations __host__ __device__ glm::vec3 getPointOnRay(ray r, float t); __host__ __device__ glm::vec3 multiplyMV(cudaMat4 m, glm::vec4 v); __host__ __device__ glm::vec3 getSignOfRay(ray r); __host__ __device__ glm::vec3 getInverseDirectionOfRay(ray r); -__host__ __device__ float boxIntersectionTest(staticGeom sphere, ray r, glm::vec3& intersectionPoint, glm::vec3& normal); +__host__ __device__ float boxIntersectionTest(glm::vec3 boxMin, glm::vec3 boxMax, staticGeom box, ray r, glm::vec3& intersectionPoint, glm::vec3& normal); __host__ __device__ float sphereIntersectionTest(staticGeom sphere, ray r, glm::vec3& intersectionPoint, glm::vec3& normal); __host__ __device__ glm::vec3 getRandomPointOnCube(staticGeom cube, float randomSeed); + // Handy dandy little hashing function that provides seeds for random number generation __host__ __device__ unsigned int hash(unsigned int a){ a = (a+0x7ed55d16) + (a<<12); @@ -69,13 +71,103 @@ __host__ __device__ glm::vec3 getSignOfRay(ray r){ return glm::vec3((int)(inv_direction.x < 0), (int)(inv_direction.y < 0), (int)(inv_direction.z < 0)); } -// TODO: IMPLEMENT THIS FUNCTION -// Cube intersection test, return -1 if no intersection, otherwise, distance to intersection -__host__ __device__ float boxIntersectionTest(staticGeom box, ray r, glm::vec3& intersectionPoint, glm::vec3& normal){ + /////////////////////////////////// + ////////////////////////////////// + // TODO: IMPLEMENT THIS FUNCTION/ + //////////////////////////////// + /////////////////////////////// + // Cube intersection test, return -1 if no intersection, otherwise, distance to intersection +__host__ __device__ float boxIntersectionTest(staticGeom box, ray r, glm::vec3& intersectionPoint, glm::vec3& normal){ +//Transform into object space + glm::vec3 ro = multiplyMV(box.inverseTransform, glm::vec4(r.origin,1.0f)); + glm::vec3 rd = glm::normalize(multiplyMV(box.inverseTransform,glm::vec4(r.direction,0.0f))); + + ray rt; + rt.origin = ro; + rt.direction = rd; + + glm::vec3 sign = getSignOfRay(rt); + glm::vec3 inverse = getInverseDirectionOfRay(rt); + + float tmin, tmax, tymin, tymax, tzmin, tzmax; + + //set tmin & tmax + if((int)sign.x == 0){ + tmin = (-0.5 - rt.origin.x) * inverse.x; + tmax = ( 0.5 - rt.origin.x) * inverse.x; + }else{ + tmax = (-0.5 - rt.origin.x) * inverse.x; + tmin = ( 0.5 - rt.origin.x) * inverse.x; + } + //set tymin & tymax + if((int)sign.y == 0){ + tymin = (-0.5 - rt.origin.y) * inverse.y; + tymax = ( 0.5 - rt.origin.y) * inverse.y; + }else{ + tymax = (-0.5 - rt.origin.y) * inverse.y; + tymin = ( 0.5 - rt.origin.y) * inverse.y; + } + + if( (tmin > tymax) || (tymin > tmax) ){ + return -1; + } + if(tymin > tmin){ + tmin = tymin; + } + if (tymax < tmax){ + tmax = tymax; + } + //set tzmin & tzmax + if((int) sign.z == 0){ + tzmin = (-0.5 - rt.origin.z) * inverse.z; + tzmax = ( 0.5 - rt.origin.z) * inverse.z; + }else{ + tzmax = (-0.5 - rt.origin.z) * inverse.z; + tzmin = ( 0.5 - rt.origin.z) * inverse.z; + } + + if( (tmin > tzmax) || (tzmin > tmax) ){ return -1; + } + if(tzmin > tmin){ + tmin = tzmin; + } + if(tzmax < tmax){ + tmax = tzmax; + } + if(tmin < 0){ + return -1; //can't intersect behind myself. + } + + //okay now I have distance, have to find out where I hit and set normal & intersection point(for bounce) + + //glm::vec3 iPoint = rt.origin + rt.direction * tmin; //intersection point in object space + glm::vec3 iPoint = getPointOnRay(rt, tmin); + glm::vec3 iNorm;//normal in vector space + if (abs(iPoint.x - 0.5 ) < .001){ + iNorm = glm::vec3(1,0,0); + }else if(abs(iPoint.y - 0.5 ) < .001){ + iNorm = glm::vec3(0,1,0); + }else if(abs(iPoint.z - 0.5 ) < .001){ + iNorm = glm::vec3(0,0,1); + }else if(abs(iPoint.x + 0.5 ) < .001){ + iNorm = glm::vec3(-1,0,0); + }else if(abs(iPoint.y + 0.5 ) < .001){ + iNorm = glm::vec3(0,-1,0); + }else { //if(abs(iPoint.z + 0.5 ) < .001){ + iNorm = glm::vec3(0,0,-1); + } + + //now transform back to global space + intersectionPoint = multiplyMV(box.transform, glm::vec4(iPoint, 1.0)); + normal = glm::normalize(multiplyMV(box.transform, glm::vec4(iNorm, 0.0))); + return glm::length(intersectionPoint - r.origin); + } + + // LOOK: Here's an intersection test example from a sphere. Now you just need to figure out cube and, optionally, triangle. // Sphere intersection test, return -1 if no intersection, otherwise, distance to intersection __host__ __device__ float sphereIntersectionTest(staticGeom sphere, ray r, glm::vec3& intersectionPoint, glm::vec3& normal){ @@ -174,11 +266,18 @@ __host__ __device__ glm::vec3 getRandomPointOnCube(staticGeom cube, float random } -// TODO: IMPLEMENT THIS FUNCTION + + + /////////////////////////////////// + ////////////////////////////////// + // TODO: IMPLEMENT THIS FUNCTION/ + //////////////////////////////// + /////////////////////////////// // Generates a random point on a given sphere __host__ __device__ glm::vec3 getRandomPointOnSphere(staticGeom sphere, float randomSeed){ - - return glm::vec3(0,0,0); + glm::vec3 point = glm::vec3(0,0,0);//getRandomDirectionInSphere(randomSeed); + point = multiplyMV(sphere.transform, glm::vec4(point,1.0f)); + return point; } #endif diff --git a/src/main.cpp b/src/main.cpp index b002500..2b92464 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,7 +40,7 @@ int main(int argc, char** argv){ } // Set up camera stuff from loaded pathtracer settings - iterations = 0; + iterations = 1; renderCam = &renderScene->renderCam; width = renderCam->resolution[0]; height = renderCam->resolution[1]; diff --git a/src/raytraceKernel.cu b/src/raytraceKernel.cu index 9c7bc7d..ee68837 100644 --- a/src/raytraceKernel.cu +++ b/src/raytraceKernel.cu @@ -9,6 +9,12 @@ #include #include +#include +#include +#include +#include + + #include "sceneStructs.h" #include "glm/glm.hpp" #include "utilities.h" @@ -16,6 +22,9 @@ #include "intersections.h" #include "interactions.h" + + + void checkCUDAError(const char *msg) { cudaError_t err = cudaGetLastError(); if( cudaSuccess != err) { @@ -35,12 +44,48 @@ __host__ __device__ glm::vec3 generateRandomNumberFromThread(glm::vec2 resolutio return glm::vec3((float) u01(rng), (float) u01(rng), (float) u01(rng)); } -// TODO: IMPLEMENT THIS FUNCTION + + +/////////////////////////////////// +////////////////////////////////// +// TODO: IMPLEMENT THIS FUNCTION/ +//////////////////////////////// +/////////////////////////////// // Function that does the initial raycast from the camera -__host__ __device__ ray raycastFromCameraKernel(glm::vec2 resolution, float time, int x, int y, glm::vec3 eye, glm::vec3 view, glm::vec3 up, glm::vec2 fov){ +__host__ __device__ ray raycastFromCameraKernel(glm::vec2 resolution, float time, int x, int y, glm::vec3 eye, glm::vec3 view, glm::vec3 up, glm::vec2 fov, float DOF, float aperature){ + int index = x + (y * resolution.x); + + + glm::vec3 alpha, beta, midPix, horizScale, vertScale, pixel; + alpha = glm::cross(view, up); + beta = glm::cross(alpha, view); + midPix = eye + view; + + vertScale = glm::normalize(beta) * glm::length(view) * tan(glm::radians( - fov.y)); //had to flip this (it was upside down) + horizScale = glm::normalize(alpha) * glm::length(view) * tan(glm::radians(fov.x)); + + //jitter the pixel + thrust::default_random_engine rng(hash(index*time)); + thrust::uniform_real_distribution u01(-0.5,0.5); + thrust::uniform_real_distribution u02(-0.01,0.01); + + + pixel = midPix + horizScale * (float)((2.0 * (x + (float)u01(rng))/resolution.x) - 1.0) + vertScale * (float)((2.0 * (y + (float)u01(rng))/resolution.y) - 1.0); ray r; - r.origin = glm::vec3(0,0,0); - r.direction = glm::vec3(0,0,-1); + + //COMMENT OUT FOR DOF + r.origin = eye; + r.direction = glm::normalize(pixel - eye); + + /* //UNCOMMENT FOR DOF + r.origin = pixel; + float aperatureOffsetX = (float)u01(rng) * aperature;//for DOF + float aperatureOffsetY = (float)u01(rng) * aperature;//for DOF + glm::vec3 focalDirection = glm::normalize(pixel - eye); + glm::vec3 focalPoint = eye + (focalDirection * DOF);//for depth of field + r.origin = r.origin + horizScale * (aperatureOffsetX/resolution.x) + vertScale * (aperatureOffsetY/resolution.y); + r.direction = glm::normalize(focalPoint - r.origin); + */ return r; } @@ -88,26 +133,128 @@ __global__ void sendImageToPBO(uchar4* PBOpos, glm::vec2 resolution, glm::vec3* } } -// TODO: IMPLEMENT THIS FUNCTION -// Core raytracer kernel -__global__ void raytraceRay(glm::vec2 resolution, float time, cameraData cam, int rayDepth, glm::vec3* colors, - staticGeom* geoms, int numberOfGeoms){ - +//Initialize rays +__global__ void initializeRay(glm::vec2 resolution, float time, cameraData cam, rayState* rayList){ int x = (blockIdx.x * blockDim.x) + threadIdx.x; int y = (blockIdx.y * blockDim.y) + threadIdx.y; int index = x + (y * resolution.x); if((x<=resolution.x && y<=resolution.y)){ + ray thisRay = raycastFromCameraKernel(resolution, time, x, y, cam.position, cam.view, cam.up, cam.fov, cam.DOF, cam.APERATURE); + rayList[index].RAY = thisRay; + rayList[index].isValid = 1; + rayList[index].color = glm::vec3(1,1,1); + rayList[index].photoIDX = index; + } +} - colors[index] = generateRandomNumberFromThread(resolution, time, x, y); - } + + +/////////////////////////////////// +////////////////////////////////// +// TODO: IMPLEMENT THIS FUNCTION/ +// raytraceRay() should take in a camera, image buffer, geometry, materials, and lights, +// and should trace a ray through the scene and write the resultant color to a pixel in the image buffer. +//////////////////////////////// +/////////////////////////////// +// Core raytracer kernel +__global__ void raytraceRay(glm::vec2 resolution, float time, cameraData cam, int maxDepth, glm::vec3* colors, + staticGeom* geoms, int numberOfGeoms, material* materials, int numberOfMaterials, + rayState* rayList, int currDepth, int* validRays, int length){ + //need to update for string compaction + //int x = (blockIdx.x * blockDim.x) + threadIdx.x; + //int y = (blockIdx.y * blockDim.y) + threadIdx.y; + //int index = x + (y * resolution.x); + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if(index < length){ + //if((x<=resolution.x && y<=resolution.y)){ + if(rayList[index].isValid == 0){ + return; + } + if(currDepth >= maxDepth){//exceeded max depth + //this contribution is black + colors[rayList[index].photoIDX] = (colors[rayList[index].photoIDX] * (time - 1.0f)/time) + (glm::vec3(0,0,0) * 1.0f/time); + rayList[index].isValid = 0; + validRays[index] = 0; + return; + } + //get variables + ray thisRay = rayList[index].RAY; + glm::vec3 COLOR = rayList[index].color; + + //intersection checks: + float distToIntersect = FLT_MAX;//infinite distance + float tmpDist; + glm::vec3 tmpIntersectPoint, tmpIntersectNormal, intersectPoint, intersectNormal; + material mat; + + for(int i = 0; i < numberOfGeoms; i++){ + if (geoms[i].type == SPHERE){ + tmpDist = sphereIntersectionTest(geoms[i], thisRay, tmpIntersectPoint, tmpIntersectNormal); + }else if (geoms[i].type == CUBE){ + tmpDist = boxIntersectionTest( geoms[i], thisRay, tmpIntersectPoint, tmpIntersectNormal); + }//insert triangles here for meshes + if (tmpDist != -1 && tmpDist < distToIntersect){ //hit is new closest + distToIntersect = tmpDist; + intersectNormal = tmpIntersectNormal; + intersectPoint = tmpIntersectPoint; + mat = materials[geoms[i].materialid]; + } + } + //Did I intersect anything? + if(distToIntersect == FLT_MAX){//miss + //this contribution is black + colors[rayList[index].photoIDX] = (colors[rayList[index].photoIDX] * (time - 1.0f)/time) + (glm::vec3(0,0,0) * 1.0f/time); + rayList[index].isValid = 0; + validRays[index] = 0; + } + //is this a light source? + if(mat.emittance > 0.001){ + COLOR = COLOR * (mat.color * mat.emittance); + colors[rayList[index].photoIDX] = (colors[rayList[index].photoIDX] * (time - 1.0f)/time) + (COLOR * 1.0f/time); + rayList[index].isValid = 0; + validRays[index] = 0; + return; + } + + //update variables + thrust::default_random_engine rng(hash(index * (time + currDepth))); + thrust::uniform_real_distribution u01(0,1); + calculateBSDF(thisRay, intersectPoint, intersectNormal, COLOR, mat, (float) u01(rng) ,(float) u01(rng)); + //update struct + rayList[index].RAY = thisRay; + rayList[index].color = COLOR; + } } -// TODO: FINISH THIS FUNCTION +__global__ void compactRays(int* scanRays, rayState* rayList, int* validRays, int length){ + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + if(index >= length){ + return; + } + validRays[index] = 0; + if(index == 0){//first + return; + } + + if(scanRays[index - 1] < scanRays[index]){ + rayState newRay = rayList[index]; + __syncthreads(); + rayList[scanRays[index]] = newRay; + validRays[scanRays[index]] = 1; + } +} + + +/////////////////////////////////// +////////////////////////////////// +// TODO: Finish THIS FUNCTION /// You will have to complete this function to support passing materials and lights to CUDA +//////////////////////////////// +/////////////////////////////// // Wrapper for the __global__ call that sets up the kernel calls and does a ton of memory management void cudaRaytraceCore(uchar4* PBOpos, camera* renderCam, int frame, int iterations, material* materials, int numberOfMaterials, geom* geoms, int numberOfGeoms){ - int traceDepth = 1; //determines how many bounces the raytracer traces + int traceDepth = 10; //determines how many bounces the raytracer traces // set up crucial magic int tileSize = 8; @@ -116,8 +263,8 @@ void cudaRaytraceCore(uchar4* PBOpos, camera* renderCam, int frame, int iteratio // send image to GPU glm::vec3* cudaimage = NULL; - cudaMalloc((void**)&cudaimage, (int)renderCam->resolution.x*(int)renderCam->resolution.y*sizeof(glm::vec3)); - cudaMemcpy( cudaimage, renderCam->image, (int)renderCam->resolution.x*(int)renderCam->resolution.y*sizeof(glm::vec3), cudaMemcpyHostToDevice); + cudaMalloc((void**)&cudaimage, (int)renderCam->resolution.x * (int)renderCam->resolution.y * sizeof(glm::vec3)); + cudaMemcpy( cudaimage, renderCam->image, (int)renderCam->resolution.x * (int)renderCam->resolution.y * sizeof(glm::vec3), cudaMemcpyHostToDevice); // package geometry and materials and sent to GPU staticGeom* geomList = new staticGeom[numberOfGeoms]; @@ -134,8 +281,8 @@ void cudaRaytraceCore(uchar4* PBOpos, camera* renderCam, int frame, int iteratio } staticGeom* cudageoms = NULL; - cudaMalloc((void**)&cudageoms, numberOfGeoms*sizeof(staticGeom)); - cudaMemcpy( cudageoms, geomList, numberOfGeoms*sizeof(staticGeom), cudaMemcpyHostToDevice); + cudaMalloc((void**)&cudageoms, numberOfGeoms * sizeof(staticGeom)); + cudaMemcpy( cudageoms, geomList, numberOfGeoms * sizeof(staticGeom), cudaMemcpyHostToDevice); // package camera cameraData cam; @@ -144,10 +291,45 @@ void cudaRaytraceCore(uchar4* PBOpos, camera* renderCam, int frame, int iteratio cam.view = renderCam->views[frame]; cam.up = renderCam->ups[frame]; cam.fov = renderCam->fov; + cam.DOF = renderCam->DOF[frame];//new + cam.APERATURE = renderCam->APERATURE[frame];//new + + // package materials + material* materialList = NULL; + cudaMalloc((void**) &materialList, numberOfMaterials * sizeof(material)); + cudaMemcpy( materialList, materials, numberOfMaterials * sizeof(material), cudaMemcpyHostToDevice); + + //allocate Rays + rayState* rayList = NULL; + cudaMalloc((void**)&rayList, (int)renderCam->resolution.x * (int)renderCam->resolution.y * sizeof(rayState)); + + + // kernel launches - raytraceRay<<>>(renderCam->resolution, (float)iterations, cam, traceDepth, cudaimage, cudageoms, numberOfGeoms); + //Get initial rays + initializeRay<<>>(renderCam->resolution, (float)iterations, cam, rayList); + thrust::device_vector validRays((int)renderCam->resolution.x * (int)renderCam->resolution.y, 1); + int* thrustArray = thrust::raw_pointer_cast( &validRays[0] ); + int length = thrust::count(validRays.begin(), validRays.end(), 1);//count valid rays + thrust::device_vector scanRay((int)renderCam->resolution.x * (int)renderCam->resolution.y, 0); + int* scanPointer = thrust::raw_pointer_cast( &scanRay[0] ); + + + //depth trace with compaction + for(int i = 0; i <= traceDepth; i++){ + //do one step + raytraceRay<<<(int)ceil((float)length/64.0f), 64>>>(renderCam->resolution, (float)iterations, cam, traceDepth, cudaimage, cudageoms, numberOfGeoms, materialList, numberOfMaterials, rayList, i, thrustArray, length); + //build scan + thrust::exclusive_scan(validRays.begin(), validRays.end(), &scanRay[0]); + scanPointer = thrust::raw_pointer_cast( &scanRay[0] ); + //compact rays + compactRays<<<(int)ceil((float)length/64.0f), 64 >>>(scanPointer, rayList, thrustArray, length); + //update length + length = thrust::count(validRays.begin(), validRays.end(), 1);//count valid rays + } + //update visual sendImageToPBO<<>>(PBOpos, renderCam->resolution, cudaimage); // retrieve image from GPU @@ -156,6 +338,8 @@ void cudaRaytraceCore(uchar4* PBOpos, camera* renderCam, int frame, int iteratio // free up stuff, or else we'll leak memory like a madman cudaFree( cudaimage ); cudaFree( cudageoms ); + cudaFree(materialList); //added + cudaFree(rayList); //added delete geomList; // make certain the kernel has completed diff --git a/src/scene.cpp b/src/scene.cpp index 4cbe216..82e5024 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -162,6 +162,8 @@ int scene::loadCamera(){ vector positions; vector views; vector ups; + vector DOFs; + vector APERATUREs; while (!line.empty() && fp_in.good()){ //check frame number @@ -172,7 +174,7 @@ int scene::loadCamera(){ } //load camera properties - for(int i=0; i<3; i++){ + for(int i=0; i<5; i++){ //glm::vec3 translation; glm::vec3 rotation; glm::vec3 scale; utilityCore::safeGetline(fp_in,line); tokens = utilityCore::tokenizeString(line); @@ -182,6 +184,10 @@ int scene::loadCamera(){ views.push_back(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){ ups.push_back(glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str()))); + }else if(strcmp(tokens[0].c_str(), "DOF")==0){ + DOFs.push_back(atof(tokens[1].c_str())); + }else if(strcmp(tokens[0].c_str(), "APERATURE")==0){ + APERATUREs.push_back(atof(tokens[1].c_str())); } } @@ -194,10 +200,14 @@ int scene::loadCamera(){ newCamera.positions = new glm::vec3[frameCount]; newCamera.views = new glm::vec3[frameCount]; newCamera.ups = new glm::vec3[frameCount]; + newCamera.DOF = new float[frameCount]; + newCamera.APERATURE = new float[frameCount]; for(int i = 0; i < frameCount; i++){ newCamera.positions[i] = positions[i]; newCamera.views[i] = views[i]; newCamera.ups[i] = ups[i]; + newCamera.DOF[i] = DOFs[i]; + newCamera.APERATURE[i] = APERATUREs[i]; } //calculate fov based on resolution diff --git a/src/sceneStructs.h b/src/sceneStructs.h index 5e0c853..dee0eb6 100644 --- a/src/sceneStructs.h +++ b/src/sceneStructs.h @@ -45,6 +45,8 @@ struct cameraData { glm::vec3 view; glm::vec3 up; glm::vec2 fov; + float DOF;//new + float APERATURE;//new }; struct camera { @@ -58,6 +60,8 @@ struct camera { glm::vec3* image; ray* rayList; std::string imageName; + float* DOF;//new + float* APERATURE; //new }; struct material{ @@ -73,4 +77,11 @@ struct material{ float emittance; }; +struct rayState{ + ray RAY; + int isValid; + glm::vec3 color; + int photoIDX; +}; + #endif //CUDASTRUCTS_H diff --git a/src/utilities.cpp b/src/utilities.cpp index a8e5d90..c0d2cba 100755 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -4,7 +4,7 @@ // File: utilities.cpp // A collection/kitchen sink of generally useful functions -#define GLM_FORCE_RADIANS +//#define GLM_FORCE_RADIANS #include #include @@ -72,7 +72,11 @@ void utilityCore::printCudaMat4(cudaMat4 m){ glm::mat4 utilityCore::buildTransformationMatrix(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale){ glm::mat4 translationMat = glm::translate(glm::mat4(), translation); - glm::mat4 rotationMat = glm::rotate(glm::mat4(), rotation.x, glm::vec3(1,0,0)); + + //glm::mat4 rotationMat = glm::rotate(glm::mat4(), (float)(rotation.x / 180 * PI), glm::vec3(1,0,0)); + //rotationMat = rotationMat*glm::rotate(glm::mat4(), (float)(rotation.y / 180 * PI), glm::vec3(0,1,0)); + //rotationMat = rotationMat*glm::rotate(glm::mat4(), (float)(rotation.z / 180 * PI), glm::vec3(0,0,1)); + glm::mat4 rotationMat = glm::rotate(glm::mat4(), rotation.x, glm::vec3(1,0,0)); rotationMat = rotationMat*glm::rotate(glm::mat4(), rotation.y, glm::vec3(0,1,0)); rotationMat = rotationMat*glm::rotate(glm::mat4(), rotation.z, glm::vec3(0,0,1)); glm::mat4 scaleMat = glm::scale(glm::mat4(), scale); diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj b/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj index c45dd79..f05f54d 100644 --- a/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj +++ b/windows/Project3-Pathtracer/Project3-Pathtracer/Project3-Pathtracer.vcxproj @@ -28,7 +28,7 @@ - + @@ -95,6 +95,6 @@ - + \ No newline at end of file