diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md new file mode 100644 index 0000000..ae6088d --- /dev/null +++ b/INSTRUCTIONS.md @@ -0,0 +1,273 @@ +CIS 565 Project3 : CUDA Pathtracer +=================== + +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. + +## 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: + +* 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. + +* 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. + +* 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. + +## SCENE FORMAT +This project uses a custom scene description format. +Scene files are flat text files that describe all geometry, materials, +lights, cameras, render settings, and animation frames inside of the scene. +Items in the format are delimited by new lines, and comments can be added at +the end of each line preceded with a double-slash. + +Materials are defined in the following fashion: + +* MATERIAL (material ID) //material header +* 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 +* REFRIOR (float ior) //index of refraction + for Fresnel effects +* SCATTER (float scatter) //scatter flag, 0 for + no, 1 for yes +* ABSCOEFF (float r) (float b) (float g) //absorption + coefficient for scattering +* RSCTCOEFF (float rsctcoeff) //reduced scattering + coefficient +* EMITTANCE (float emittance) //the emittance of the + material. Anything >0 makes the material a light source. + +Cameras are defined in the following fashion: + +* CAMERA //camera header +* RES (float x) (float y) //resolution +* FOVY (float fovy) //vertical field of + view half-angle. the horizonal angle is calculated from this and the + reslution +* ITERATIONS (float interations) //how many + iterations to refine the image, only relevant for supersampled antialiasing, + depth of field, area lights, and other distributed raytracing applications +* FILE (string filename) //file to output + render to upon completion +* frame (frame number) //start of a frame +* EYE (float x) (float y) (float z) //camera's position in + worldspace +* VIEW (float x) (float y) (float z) //camera's view + direction +* UP (float x) (float y) (float z) //camera's up vector + +Objects are defined in the following fashion: +* OBJECT (object ID) //object header +* (cube OR sphere OR mesh) //type of object, can + be either "cube", "sphere", or "mesh". Note that cubes and spheres are unit + sized and centered at the origin. +* material (material ID) //material to + assign this object +* frame (frame number) //start of a frame +* TRANS (float transx) (float transy) (float transz) //translation +* ROTAT (float rotationx) (float rotationy) (float rotationz) //rotation +* SCALE (float scalex) (float scaley) (float scalez) //scale + +An example scene file setting up two frames inside of a Cornell Box can be +found in the scenes/ directory. + +For meshes, note that the base code will only read in .obj files. For more +information on the .obj specification see http://en.wikipedia.org/wiki/Wavefront_.obj_file. + +An example of a mesh object is as follows: + +OBJECT 0 +mesh tetra.obj +material 0 +frame 0 +TRANS 0 5 -5 +ROTAT 0 90 0 +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 +above. Remember: +* this is a renderer, so include images that you've made! +* be sure to back your claims for optimization with numbers and comparisons +* if you reference any other material, please provide a link to it +* you wil not e graded on how fast your path tracer runs, but getting close to + real-time is always nice +* if you have a fast GPU renderer, it is good to show case this with a video to + show interactivity. If you do so, please include a link. + +Be sure to open a pull request and to send Harmony your grade and why you +believe this is the grade you should get. diff --git a/README.md b/README.md index ae6088d..95abe7a 100644 --- a/README.md +++ b/README.md @@ -1,273 +1,133 @@ CIS 565 Project3 : CUDA Pathtracer =================== +Jiatong He -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. - -## 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: +![alt tag](https://raw.githubusercontent.com/JivingTechnostic/Project3-Pathtracer/master/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth20.0.bmp) +Implemented features: +-------------------- * Raycasting from a camera into a scene through a pixel grid -* Diffuse surfaces +* Simple diffuse surfaces (no specular highlights) * 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. - -* 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. - -* 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. - -## SCENE FORMAT -This project uses a custom scene description format. -Scene files are flat text files that describe all geometry, materials, -lights, cameras, render settings, and animation frames inside of the scene. -Items in the format are delimited by new lines, and comments can be added at -the end of each line preceded with a double-slash. - -Materials are defined in the following fashion: - -* MATERIAL (material ID) //material header -* 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 -* REFRIOR (float ior) //index of refraction - for Fresnel effects -* SCATTER (float scatter) //scatter flag, 0 for - no, 1 for yes -* ABSCOEFF (float r) (float b) (float g) //absorption - coefficient for scattering -* RSCTCOEFF (float rsctcoeff) //reduced scattering - coefficient -* EMITTANCE (float emittance) //the emittance of the - material. Anything >0 makes the material a light source. - -Cameras are defined in the following fashion: - -* CAMERA //camera header -* RES (float x) (float y) //resolution -* FOVY (float fovy) //vertical field of - view half-angle. the horizonal angle is calculated from this and the - reslution -* ITERATIONS (float interations) //how many - iterations to refine the image, only relevant for supersampled antialiasing, - depth of field, area lights, and other distributed raytracing applications -* FILE (string filename) //file to output - render to upon completion -* frame (frame number) //start of a frame -* EYE (float x) (float y) (float z) //camera's position in - worldspace -* VIEW (float x) (float y) (float z) //camera's view - direction -* UP (float x) (float y) (float z) //camera's up vector - -Objects are defined in the following fashion: -* OBJECT (object ID) //object header -* (cube OR sphere OR mesh) //type of object, can - be either "cube", "sphere", or "mesh". Note that cubes and spheres are unit - sized and centered at the origin. -* material (material ID) //material to - assign this object -* frame (frame number) //start of a frame -* TRANS (float transx) (float transy) (float transz) //translation -* ROTAT (float rotationx) (float rotationy) (float rotationz) //rotation -* SCALE (float scalex) (float scaley) (float scalez) //scale - -An example scene file setting up two frames inside of a Cornell Box can be -found in the scenes/ directory. - -For meshes, note that the base code will only read in .obj files. For more -information on the .obj specification see http://en.wikipedia.org/wiki/Wavefront_.obj_file. - -An example of a mesh object is as follows: - -OBJECT 0 -mesh tetra.obj -material 0 -frame 0 -TRANS 0 5 -5 -ROTAT 0 90 0 -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. +* Stream compaction optimization (using Thrust) +* +Interactive camera (pan, zoom, tilt) +* +Depth of field + +Nonworking features (base code is there): +----------------------------------------- +* Refraction (need to modify intersection tests?) -## 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. +> Refraction code has been added to BSDF, but it turns up black. May be due to intersection tests being incompatible with rays originating from inside the -## SUBMISSION -Please change the README to reflect the answers to the questions we have posed -above. Remember: -* this is a renderer, so include images that you've made! -* be sure to back your claims for optimization with numbers and comparisons -* if you reference any other material, please provide a link to it -* you wil not e graded on how fast your path tracer runs, but getting close to - real-time is always nice -* if you have a fast GPU renderer, it is good to show case this with a video to - show interactivity. If you do so, please include a link. +![alt tag](https://raw.githubusercontent.com/JivingTechnostic/Project3-Pathtracer/master/windows/Project3-Pathtracer/Project3-Pathtracer/depth_comparison.bmp) +* A comparison of the same scene with different raytrace depths (4, 7, 20) -Be sure to open a pull request and to send Harmony your grade and why you -believe this is the grade you should get. +Performance +----------- +There are three main contributors to runtime that I would like to focus on: +### 1. Intersection Tests +#### Expectation +I expect that this step takes the longest in the code, and should, ignoring issues with block size/memory, result in linear increases in runtime proportional to the number of geometric bodies in the scene. This is only with spheres and cubes, and will create significantly more overhead once meshes are implemented. I believe this should be highest priority for optimization, preferably first with some acceleration structure such as a kd-tree (detailed in "Future Work"). +#### Results +--postponed-- + +### 2. Raytrace Depth +#### Expectation +This is fairly straightforward, but I wanted to see the slope of the runtime increase due to higher raytrace depth. I would expect that it is slightly less than linear (assuming that the blocks are set up optimally for stream compaction, which they may not be), since the number of threads needed per level decreases every level. +#### Results +--postponed-- + +### 3. Number of Iterations +#### Expectation +Since this is mostly independent of the GPU (it's based a looped call by the host), it should be expected to be linear. I want to see if it slows down or speeds up over time, if at all (time/#iterations). +#### Results +--postponed-- + + +Extra Features +-------------- +### Interactive Camera +![video](https://raw.githubusercontent.com/JivingTechnostic/Project3-Pathtracer/master/pathtracer_camera_demo.mp4) +* Short video demo of the mouse camera controls. Note that there are some bugs in the rendering in this video. + +> see pathtracer_camera_demo.mp4 +> +> Use the mouse to control the camera. +> +> Left click-drag will move the camera. +> +> Right click-drag will tilt the camera. +> +> Middle click-drag will move the camera forward/backward. + +#### Performance + +The mouse control does not actively affect the performance of the raytrace. However, the lack of acceleration in the raytrace means that what the user sees while moving the camera are +only a few iterations. The raytrace continuously refreshes while the camera is moved. Pausing the mouse movement will allow it to continue. I think that while showing a fully-formed image +during a mouse drag is impractical, faster renders will allow the user to see more clearly-formed images before moving again. + +#### Improvement/Optimization + +Since this is external user input, I don't think that optimization is an issue. An improvement over the current system would be to have actual rotation of the camera, which would be more flexible and intuitive. +Currently I am simply adjusting the view vector by some vector coplanar to the view plane rather than rotating it, mainly because glm::rotate gave me really strange errors. + +### Depth of Field +![alt tag](https://raw.githubusercontent.com/JivingTechnostic/Project3-Pathtracer/master/windows/Project3-Pathtracer/Project3-Pathtracer/dof_comparison.bmp) + +> Added a camera field, DEPTH, that defines the focal distance of the camera. Nonpositive DEPTH values should result in no depth of field. +> +> This was implemented by finding the intersection point with the focal plane for each ray cast from the camera, and then jittering the origin of the ray by some random amount, and updating the direction so that it still passes through that point on the focal plane. + +#### Performance +The performance impact of this change is negligible. It is calculated a single time for each ray cast from the camera, and is far surpassed in runtime by the raytrace itself. + +#### Acceleration +None. I kept the implementation very simple in order to prevent it from affecting runtime while still being visually effective. + +#### CPU/GPU comparison +We make resolutionX * resolutionY calculations for each iteration of the path tracing, which is done in parallel on the GPU. The CPU would need to make those resolutionX * resolutionY calculations sequentially. +Because the resolution is typically low enough, the CPU implementation should not be much slower than the GPU implementation. However, the calculations needed are simple, so there's no reason to not use the GPU. + +#### Improvement +The current random "blur" factor seems to prefer a single direction. This should be due to the random seed; assuming a uniform distribution from [0,1] on the random number, it should generate an even split. + +Debugging +--------- +For now, I only have two images to show that I used during debugging: +![normals debug](https://raw.githubusercontent.com/JivingTechnostic/Project3-Pathtracer/master/windows/Project3-Pathtracer/Project3-Pathtracer/debug_normals.bmp) +* Normals of the planes, +x/y/z = r/g/b respectively. + +> The first, and easiest, image to create, other than a basic collision test. I wanted to make sure that the sphere collision was fully functional and the cube collision I implemented was working. As it turned out, it wasn't at first, and for some reason that now escapes me, the cubes were being distorted. + +![first bounce debug](https://raw.githubusercontent.com/JivingTechnostic/Project3-Pathtracer/master/windows/Project3-Pathtracer/Project3-Pathtracer/debug_bounce.bmp) +* Accumulated (left) and single (right) sample of the random rays obtained from the BSDF on the first bounce. + +> I created this image because initially, the back white wall was remaning pure white while the bottom and top were receiving global illumination from the red/green walls. This image suggested that the bounced rays were correct, and I later found the issue in my color equation. + +Challenges +---------- +### RNG +The random number seed is causing me a ton of trouble. I'm not certain I fully understand the effect of the seed... certain seeds will completely ruin the image, while others generate strange artifacts. This is an issue I still have, outlined in the "Problems" section. + +Problems +-------- +### Artifacts +![alt tag](https://raw.githubusercontent.com/JivingTechnostic/Project3-Pathtracer/master/windows/Project3-Pathtracer/Project3-Pathtracer/scene1.0.bmp) + +* (see the squares on the back wall) +I am fairly certain that these artifacts are caused by either float precision (though I think I use epsilon equality everywhere) or the random number generator seed. Putting in a different seed results in vastly different results, some completely wrong. + +### Noise (slow to converge) +![alt tag](https://raw.githubusercontent.com/JivingTechnostic/Project3-Pathtracer/master/windows/Project3-Pathtracer/Project3-Pathtracer/iter_comparison.bmp) +* 20, 200, and 2000 iterations of the raytrace with depth of 7. +This one I'm not sure about. It could be a matter of simply allowing the rays to be more variable, but ultimately the images appear much more spotty than they should be, even after a reasonable number of iterations. Making the light larger might solve the problem, but I'm not sure that's the right solution. + +Future Work +----------- +#### Priority 0 +* Finish refraction +* Fix clipping issues +* Implement meshes +* Implement acceleration data structure (octtree would probably be simplest) diff --git a/data/scenes/sampleScene.txt b/data/scenes/sampleScene.txt index 6a9f5cc..64f0b6d 100644 --- a/data/scenes/sampleScene.txt +++ b/data/scenes/sampleScene.txt @@ -50,7 +50,7 @@ MATERIAL 4 //white glossy RGB 1 1 1 SPECEX 0 SPECRGB 1 1 1 -REFL 0 +REFL 1 REFR 0 REFRIOR 2 SCATTER 0 @@ -110,11 +110,12 @@ CAMERA RES 800 800 FOVY 25 ITERATIONS 5000 -FILE test.bmp +FILE test2.bmp frame 0 EYE 0 4.5 12 VIEW 0 0 -1 UP 0 1 0 +DEPTH -10 OBJECT 0 cube @@ -180,11 +181,18 @@ TRANS -2 5 -2 ROTAT 0 180 0 SCALE 3 3 3 - OBJECT 8 cube material 8 frame 0 TRANS 0 10 0 ROTAT 0 0 90 -SCALE .3 3 3 \ No newline at end of file +SCALE .3 3 3 + +OBJECT 9 +sphere +material 5 +frame 0 +TRANS 0 5 4 +ROTAT 0 0 90 +SCALE 3 3 3 \ No newline at end of file diff --git a/data/scenes/scene1.txt b/data/scenes/scene1.txt new file mode 100644 index 0000000..2f49579 --- /dev/null +++ b/data/scenes/scene1.txt @@ -0,0 +1,254 @@ +MATERIAL 0 //white diffuse +RGB 1 1 1 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 1 //red diffuse +RGB .63 .06 .04 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 2 //green diffuse +RGB .15 .48 .09 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 3 //reflective +RGB 1 1 1 +SPECEX 0 +SPECRGB 1 1 1 +REFL 1 +REFR 0 +REFRIOR 2 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 4 //glass +RGB 0 0 0 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 1 +REFRIOR 2.2 +SCATTER 0 +ABSCOEFF .02 5.1 5.7 +RSCTCOEFF 13 +EMITTANCE 0 + +MATERIAL 5 //light +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 1 + +MATERIAL 6 //strong light +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 15 + +MATERIAL 7 //red light +RGB 1 0 0 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 1 + +MATERIAL 8 //green light +RGB 0 1 0 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 1 + +MATERIAL 9 //blue light +RGB 0 0 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 12 + +MATERIAL 10 //blue diffuse +RGB .06 .06 .40 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +CAMERA +RES 800 800 +FOVY 25 +ITERATIONS 2000 +FILE scene2.bmp +frame 0 +EYE 0 6 6 +VIEW 0 -.6 -.8 +UP 0 1 0 +DEPTH -7.5 + +OBJECT 0 +cube +material 0 +frame 0 +TRANS 0 0 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +OBJECT 1 +cube +material 0 +frame 0 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +OBJECT 2 +cube +material 0 +frame 0 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +OBJECT 3 +cube +material 1 +frame 0 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +OBJECT 4 +cube +material 2 +frame 0 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +OBJECT 5 +cube +material 6 +frame 0 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .3 3 3 + +OBJECT 6 +sphere +material 3 +frame 0 +TRANS 2 5 2 +ROTAT 0 0 0 +SCALE 2.5 2.5 2.5 + +OBJECT 7 +sphere +material 0 +frame 0 +TRANS -2 5 -2 +ROTAT 0 0 0 +SCALE 3 3 3 + +OBJECT 8 +sphere +material 1 +frame 0 +TRANS 1.5 2 .5 +ROTAT 0 0 0 +SCALE 1 1 1 + +OBJECT 9 +sphere +material 0 +frame 0 +TRANS 1 2 -.5 +ROTAT 0 0 0 +SCALE 1 1 1 + +OBJECT 10 +sphere +material 10 +frame 0 +TRANS 0 2 -1 +ROTAT 0 0 0 +SCALE 1 1 1 + +OBJECT 11 +sphere +material 0 +frame 0 +TRANS -1 2 -.5 +ROTAT 0 0 0 +SCALE 1 1 1 + +OBJECT 12 +sphere +material 2 +frame 0 +TRANS -1.5 2 .5 +ROTAT 0 0 0 +SCALE 1 1 1 + +OBJECT 13 +sphere +material 3 +frame 0 +TRANS 0 2 2 +ROTAT 0 0 0 +SCALE 1 1 1 \ No newline at end of file diff --git a/data/scenes/scene2.txt b/data/scenes/scene2.txt new file mode 100644 index 0000000..9c2a233 --- /dev/null +++ b/data/scenes/scene2.txt @@ -0,0 +1,133 @@ +MATERIAL 0 //white diffuse +RGB 1 1 1 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 1 //red diffuse +RGB .63 .06 .04 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 2 //green diffuse +RGB .15 .48 .09 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 3 //red glossy +RGB .63 .06 .04 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 2 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 4 //white glossy +RGB 1 1 1 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 2 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 5 //glass +RGB 0 0 0 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 1 +REFRIOR 2.2 +SCATTER 0 +ABSCOEFF .02 5.1 5.7 +RSCTCOEFF 13 +EMITTANCE 0 + +MATERIAL 6 //green glossy +RGB .15 .48 .09 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 2.6 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 7 //light +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 1 + +MATERIAL 8 //light +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 15 + +CAMERA +RES 800 800 +FOVY 25 +ITERATIONS 5000 +FILE test.bmp +frame 0 +EYE 0 4.5 12 +VIEW 0 0 -1 +UP 0 1 0 + +OBJECT 0 +cube +material 0 +frame 0 +TRANS 0 0 -5 +ROTAT 0 0 90 +SCALE .01 10 10 + +OBJECT 1 +cube +material 1 +frame 0 +TRANS 0 0 -5 +ROTAT 0 0 -90 +SCALE 10 10 10 \ No newline at end of file diff --git a/data/scenes/simpleScene.txt b/data/scenes/simpleScene.txt new file mode 100644 index 0000000..412bb55 --- /dev/null +++ b/data/scenes/simpleScene.txt @@ -0,0 +1,133 @@ +MATERIAL 0 //white diffuse +RGB 1 1 1 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 1 //red diffuse +RGB .63 .06 .04 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 2 //green diffuse +RGB .15 .48 .09 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 3 //red glossy +RGB .63 .06 .04 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 2 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 4 //white glossy +RGB 1 1 1 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 2 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 5 //glass +RGB 0 0 0 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 1 +REFRIOR 2.2 +SCATTER 0 +ABSCOEFF .02 5.1 5.7 +RSCTCOEFF 13 +EMITTANCE 0 + +MATERIAL 6 //green glossy +RGB .15 .48 .09 +SPECEX 0 +SPECRGB 1 1 1 +REFL 0 +REFR 0 +REFRIOR 2.6 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 0 + +MATERIAL 7 //light +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 1 + +MATERIAL 8 //light +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +SCATTER 0 +ABSCOEFF 0 0 0 +RSCTCOEFF 0 +EMITTANCE 15 + +CAMERA +RES 800 800 +FOVY 25 +ITERATIONS 10 +FILE test.bmp +frame 0 +EYE 0 0 12 +VIEW 0 0 -1 +UP 0 1 0 + +OBJECT 0 +cube +material 0 +frame 0 +TRANS 0 0 -5 +ROTAT 0 180 0 +SCALE 10 10 10 + +OBJECT 1 +sphere +material 0 +frame 0 +TRANS 0 5 0 +ROTAT 0 90 0 +SCALE 1 1 1 \ No newline at end of file diff --git a/pathtracer_camera_demo.mp4 b/pathtracer_camera_demo.mp4 new file mode 100644 index 0000000..78540f4 Binary files /dev/null and b/pathtracer_camera_demo.mp4 differ diff --git a/src/interactions.h b/src/interactions.h index 7bf6fab..59ab236 100644 --- a/src/interactions.h +++ b/src/interactions.h @@ -9,13 +9,13 @@ #include "intersections.h" struct Fresnel { - float reflectionCoefficient; - float transmissionCoefficient; + float reflectionCoefficient; + float transmissionCoefficient; }; struct AbsorptionAndScatteringProperties{ - glm::vec3 absorptionCoefficient; - float reducedScatteringCoefficient; + glm::vec3 absorptionCoefficient; + float reducedScatteringCoefficient; }; // Forward declaration @@ -29,61 +29,61 @@ __host__ __device__ glm::vec3 calculateRandomDirectionInHemisphere(glm::vec3 nor // TODO (OPTIONAL): IMPLEMENT THIS FUNCTION __host__ __device__ glm::vec3 calculateTransmission(glm::vec3 absorptionCoefficient, float distance) { - return glm::vec3(0,0,0); + return glm::vec3(0,0,0); } // TODO (OPTIONAL): IMPLEMENT THIS FUNCTION __host__ __device__ bool calculateScatterAndAbsorption(ray& r, float& depth, AbsorptionAndScatteringProperties& currentAbsorptionAndScattering, - glm::vec3& unabsorbedColor, material m, float randomFloatForScatteringDistance, float randomFloat2, float randomFloat3){ - return false; + glm::vec3& unabsorbedColor, material m, float randomFloatForScatteringDistance, float randomFloat2, float randomFloat3){ + return false; } // TODO (OPTIONAL): IMPLEMENT THIS FUNCTION __host__ __device__ glm::vec3 calculateTransmissionDirection(glm::vec3 normal, glm::vec3 incident, float incidentIOR, float transmittedIOR) { - return glm::vec3(0,0,0); + return glm::vec3(0,0,0); } // TODO (OPTIONAL): IMPLEMENT THIS FUNCTION __host__ __device__ glm::vec3 calculateReflectionDirection(glm::vec3 normal, glm::vec3 incident) { - //nothing fancy here - return glm::vec3(0,0,0); + //nothing fancy here + return glm::vec3(0,0,0); } // TODO (OPTIONAL): IMPLEMENT THIS FUNCTION __host__ __device__ Fresnel calculateFresnel(glm::vec3 normal, glm::vec3 incident, float incidentIOR, float transmittedIOR, glm::vec3 reflectionDirection, glm::vec3 transmissionDirection) { - Fresnel fresnel; + Fresnel fresnel; - fresnel.reflectionCoefficient = 1; - fresnel.transmissionCoefficient = 0; - return fresnel; + fresnel.reflectionCoefficient = 1; + fresnel.transmissionCoefficient = 0; + return fresnel; } // LOOK: This function demonstrates cosine weighted random direction generation in a sphere! __host__ __device__ glm::vec3 calculateRandomDirectionInHemisphere(glm::vec3 normal, float xi1, float xi2) { - - // Crucial difference between this and calculateRandomDirectionInSphere: THIS IS COSINE WEIGHTED! - - float up = sqrt(xi1); // cos(theta) - float over = sqrt(1 - up * up); // sin(theta) - float around = xi2 * 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 ); - + + // Crucial difference between this and calculateRandomDirectionInSphere: THIS IS COSINE WEIGHTED! + + float up = sqrt(xi1); // cos(theta) + float over = sqrt(1 - up * up); // sin(theta) + float around = xi2 * 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 ); + } // TODO: IMPLEMENT THIS FUNCTION @@ -91,16 +91,46 @@ __host__ __device__ glm::vec3 calculateRandomDirectionInHemisphere(glm::vec3 nor // 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); + // Uniform distribution of u = cos(theta). + float u = xi1 * 2 - 1; + float theta = xi2 * TWO_PI; + float sqrtOneMinusUSquared = sqrt(1 - u * u); + + glm::vec3 point(sqrtOneMinusUSquared * cos(theta), sqrtOneMinusUSquared * sin(theta), u); + + return point; } -// TODO (PARTIALLY OPTIONAL): 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, - AbsorptionAndScatteringProperties& currentAbsorptionAndScattering, - glm::vec3& color, glm::vec3& unabsorbedColor, material m){ + AbsorptionAndScatteringProperties& currentAbsorptionAndScattering, + glm::vec3& color, glm::vec3& unabsorbedColor, material m){} +*/ - return 1; +// TODO (PARTIALLY OPTIONAL): 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, material m, float randomSeed){ + + thrust::uniform_real_distribution u01(0, 1); + thrust::default_random_engine rng(hash(randomSeed)); + + //diffuse + if (m.hasReflective < ZERO_ABSORPTION_EPSILON && m.hasRefractive < ZERO_ABSORPTION_EPSILON) { + r.direction = calculateRandomDirectionInHemisphere(normal, (float)u01(rng), (float)u01(rng)); + r.origin = intersect + (float)EPSILON * r.direction; + return 0; + } else if (m.hasRefractive > ZERO_ABSORPTION_EPSILON) { // refraction + glm::vec3 dir = r.direction; + glm::vec3 refDir = glm::refract(r.direction, normal, r.ior/m.indexOfRefraction); + r.direction = refDir; + r.origin = intersect + (float)EPSILON * r.direction; + r.ior = m.indexOfRefraction; + return 2; + }else { // reflection + r.direction = glm::reflect(r.direction, normal); + r.origin = intersect + (float)EPSILON * r.direction; + return 1; + } }; #endif diff --git a/src/intersections.h b/src/intersections.h index c9eafb6..0f38ca2 100644 --- a/src/intersections.h +++ b/src/intersections.h @@ -44,76 +44,167 @@ __host__ __device__ bool epsilonCheck(float a, float b){ // Self explanatory __host__ __device__ glm::vec3 getPointOnRay(ray r, float t){ - return r.origin + float(t - .0001f) * glm::normalize(r.direction); + return r.origin + float(t - .0001f) * glm::normalize(r.direction); } // LOOK: This is a custom function for multiplying cudaMat4 4x4 matrixes with vectors. // This is a workaround for GLM matrix multiplication not working properly on pre-Fermi NVIDIA GPUs. // Multiplies a cudaMat4 matrix and a vec4 and returns a vec3 clipped from the vec4 __host__ __device__ glm::vec3 multiplyMV(cudaMat4 m, glm::vec4 v){ - glm::vec3 r(1,1,1); - r.x = (m.x.x*v.x)+(m.x.y*v.y)+(m.x.z*v.z)+(m.x.w*v.w); - r.y = (m.y.x*v.x)+(m.y.y*v.y)+(m.y.z*v.z)+(m.y.w*v.w); - r.z = (m.z.x*v.x)+(m.z.y*v.y)+(m.z.z*v.z)+(m.z.w*v.w); - return r; + glm::vec3 r(1,1,1); + r.x = (m.x.x*v.x)+(m.x.y*v.y)+(m.x.z*v.z)+(m.x.w*v.w); + r.y = (m.y.x*v.x)+(m.y.y*v.y)+(m.y.z*v.z)+(m.y.w*v.w); + r.z = (m.z.x*v.x)+(m.z.y*v.y)+(m.z.z*v.z)+(m.z.w*v.w); + return r; } // Gets 1/direction for a ray __host__ __device__ glm::vec3 getInverseDirectionOfRay(ray r){ - return glm::vec3(1.0/r.direction.x, 1.0/r.direction.y, 1.0/r.direction.z); + return glm::vec3(1.0/r.direction.x, 1.0/r.direction.y, 1.0/r.direction.z); } // Gets sign of each component of a ray's inverse direction __host__ __device__ glm::vec3 getSignOfRay(ray r){ - glm::vec3 inv_direction = getInverseDirectionOfRay(r); - return glm::vec3((int)(inv_direction.x < 0), (int)(inv_direction.y < 0), (int)(inv_direction.z < 0)); + glm::vec3 inv_direction = getInverseDirectionOfRay(r); + return glm::vec3((int)(inv_direction.x < 0), (int)(inv_direction.y < 0), (int)(inv_direction.z < 0)); } // TODO: IMPLEMENT THIS FUNCTION +// IDID // 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){ + // default cube is (-.5, -.5, -.5) to (.5, .5, .5) + + 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))); - return -1; + float tnear, tfar; + ray rt; rt.origin = ro; rt.direction = rd; + + // X plane intersections, slightly simplified because it's the first iteration. + if (epsilonCheck(rd.x, 0) && (ro.x < -0.5 || ro.x > 0.5)) { + return -1; + } + float t1, t2; + t1 = (-0.5f - ro.x) / rd.x; + t2 = (0.5f - ro.x) / rd.x; + + if (t1 > t2) { + float temp; + temp = t1; + t1 = t2; + t2 = temp; + } + + // Here we don't need to check if t1 > tnear, t2 < tfar, since tnear and tfar are - and + infinity, respectively. + tnear = t1; + tfar = t2; + // Here we don't check if tnear > tfar because in the first iteration tfar > tnear. + if (tfar < 0) { + return -1; + } + + // Y plane + if (epsilonCheck(rd.y, 0) && (ro.y < -0.5 || ro.y > 0.5)) { + return -1; + } + t1 = (-0.5f - ro.y) / rd.y; + t2 = (0.5f - ro.y) / rd.y; + + if (t1 > t2) { + float temp; + temp = t1; + t1 = t2; + t2 = temp; + } + + tnear = max(tnear, t1); + tfar = min(tfar, t2); + + if (tnear > tfar || tfar < 0) { + return -1; + } + + // Z plane + if (epsilonCheck(rd.z, 0) && (ro.z < -0.5 || ro.z > 0.5)) { + return -1; + } + t1 = (-0.5f - ro.z) / rd.z; + t2 = (0.5f - ro.z) / rd.z; + + if (t1 > t2) { + float temp; + temp = t1; + t1 = t2; + t2 = temp; + } + + tnear = max(tnear, t1); + tfar = min(tfar, t2); + + if (tnear > tfar || tfar < 0) { + return -1; + } + glm::vec3 localIntersectionPoint = getPointOnRay(rt, tnear); + glm::vec3 absIntersectionPoint = glm::abs(localIntersectionPoint); + // Find the correct normal based on the component with the highest magnitude. + glm::vec3 norm; + if (absIntersectionPoint.x > absIntersectionPoint.y && absIntersectionPoint.x > absIntersectionPoint.z) { + norm = glm::vec3(localIntersectionPoint.x, 0, 0); + } else if (absIntersectionPoint.y > absIntersectionPoint.x && absIntersectionPoint.y > absIntersectionPoint.z) { + norm = glm::vec3(localIntersectionPoint.y, 0, 0); + } else if (absIntersectionPoint.z > absIntersectionPoint.y && absIntersectionPoint.z > absIntersectionPoint.x){ + norm = glm::vec3(localIntersectionPoint.z, 0, 0); + } else { + norm = localIntersectionPoint; + } + + glm::vec3 realIntersectionPoint = multiplyMV(box.transform, glm::vec4(localIntersectionPoint, 1.0)); + glm::vec3 realOrigin = multiplyMV(box.transform, glm::vec4(0,0,0,1)); + normal = glm::normalize(multiplyMV(box.transform, glm::vec4(norm, 0.f))); + intersectionPoint = realIntersectionPoint; + + return glm::length(r.origin - realIntersectionPoint); } // 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){ - - 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) - pow(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); - } else { - t = max(t1, t2); - } - - glm::vec3 realIntersectionPoint = multiplyMV(sphere.transform, glm::vec4(getPointOnRay(rt, t), 1.0)); - glm::vec3 realOrigin = multiplyMV(sphere.transform, glm::vec4(0,0,0,1)); - - intersectionPoint = realIntersectionPoint; - normal = glm::normalize(realIntersectionPoint - realOrigin); - - return glm::length(r.origin - realIntersectionPoint); + + 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) - pow(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); + } else { + t = max(t1, t2); + } + + glm::vec3 realIntersectionPoint = multiplyMV(sphere.transform, glm::vec4(getPointOnRay(rt, t), 1.0)); + glm::vec3 realOrigin = multiplyMV(sphere.transform, glm::vec4(0,0,0,1)); + + intersectionPoint = realIntersectionPoint; + normal = glm::normalize(realIntersectionPoint - realOrigin); + + return glm::length(r.origin - realIntersectionPoint); } // Returns x,y,z half-dimensions of tightest bounding box @@ -137,14 +228,14 @@ __host__ __device__ glm::vec3 getRandomPointOnCube(staticGeom cube, float random thrust::uniform_real_distribution u02(-0.5,0.5); // Get surface areas of sides - glm::vec3 radii = getRadiuses(cube); - float side1 = radii.x * radii.y * 4.0f; //x-y face - float side2 = radii.z * radii.y * 4.0f; //y-z face - float side3 = radii.x * radii.z* 4.0f; //x-z face - float totalarea = 2.0f * (side1+side2+side3); - - // Pick random face, weighted by surface area - float russianRoulette = (float)u01(rng); + glm::vec3 radii = getRadiuses(cube); + float side1 = radii.x * radii.y * 4.0f; //x-y face + float side2 = radii.z * radii.y * 4.0f; //y-z face + float side3 = radii.x * radii.z* 4.0f; //x-z face + float totalarea = 2.0f * (side1+side2+side3); + + // Pick random face, weighted by surface area + float russianRoulette = (float)u01(rng); glm::vec3 point = glm::vec3(.5,.5,.5); @@ -177,8 +268,19 @@ __host__ __device__ glm::vec3 getRandomPointOnCube(staticGeom cube, float random // TODO: IMPLEMENT THIS FUNCTION // Generates a random point on a given sphere __host__ __device__ glm::vec3 getRandomPointOnSphere(staticGeom sphere, float randomSeed){ + thrust::default_random_engine rng(hash(randomSeed)); + + // generate random angles in randians + thrust::uniform_real_distribution u (-1, 1); + glm::vec3 point((float)u(rng), (float)u(rng), (float)u(rng)); + + while (glm::length(point) == 0) { + glm::vec3 point((float)u(rng), (float)u(rng), (float)u(rng)); + } + + glm::vec3 randPoint = multiplyMV(sphere.transform, glm::vec4(glm::normalize(point), 1.0f)); - return glm::vec3(0,0,0); + return randPoint; } #endif diff --git a/src/main.cpp b/src/main.cpp index 05eae28..959cf0b 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include #define GLEW_STATIC + //------------------------------- //-------------MAIN-------------- //------------------------------- @@ -89,7 +90,19 @@ void runCuda(){ // Map OpenGL buffer object for writing from CUDA on a single GPU // No data is moved (Win & Linux). When mapped to CUDA, OpenGL should not use this buffer - + if (renderCam->changed) { + iterations = 0; + renderCam->changed = false; + } + + // calculate the vectors for screen coordinates here first. + glm::vec3 right = glm::normalize(glm::cross(renderCam->views[targetFrame], glm::normalize(renderCam->ups[targetFrame]))); + glm::vec3 up = glm::cross(right, renderCam->views[targetFrame]); + // Image plane should have (0,0) at upper-left, (res.x, rex.y) at bottom-right. + // TODO: Figure out why this is flipped horizontally + renderCam->planeRight = right * (float)tan(renderCam->fov.x * PI / 180); + renderCam->planeDown = -up * (float)tan(renderCam->fov.y * PI / 180); + if(iterations < renderCam->iterations){ uchar4 *dptr=NULL; iterations++; @@ -178,6 +191,8 @@ bool init(int argc, char* argv[]) { } glfwMakeContextCurrent(window); glfwSetKeyCallback(window, keyCallback); + glfwSetCursorPosCallback(window, cursorCallback); + glfwSetMouseButtonCallback(window, mouseButtonCallback); // Set up GL context glewExperimental = GL_TRUE; @@ -320,5 +335,52 @@ void errorCallback(int error, const char* description){ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods){ if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS){ glfwSetWindowShouldClose(window, GL_TRUE); - } + } else if(key == GLFW_KEY_P && action == GLFW_PRESS) { + // print current image, with iteration_# + } +} + +// I apologize for the tank controls (rotate) but glm::rotate is refusing to work for some reason. +// FREE MOTION BLURS YE +void cursorCallback(GLFWwindow* window, double x, double y){ + if (mouse.state == LEFT_MOUSE) { // pan + renderCam->changed = true; + renderCam->positions[targetFrame] += ((float)x - mouse.x) / renderCam->resolution.x * 5 * renderCam->planeRight + + -((float)y - mouse.y) / renderCam->resolution.y * 5 * renderCam->planeDown; + } else if (mouse.state == RIGHT_MOUSE) { // rotate + renderCam->changed = true; + renderCam->views[targetFrame] += ((float)x - mouse.x) / renderCam->resolution.x * renderCam->planeRight + + -((float)y - mouse.y) / renderCam->resolution.y * renderCam->planeDown; + renderCam->views[targetFrame] = glm::normalize(renderCam->views[targetFrame]); + } else if (mouse.state == MIDDLE_MOUSE) { // zoom + renderCam->changed = true; + renderCam->positions[targetFrame] += ((float)y - mouse.y) / renderCam->resolution.y * 2 * renderCam->views[targetFrame]; + } + mouse.x = x; + mouse.y = y; +} + +void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { + if (button == GLFW_MOUSE_BUTTON_1) { + if (action == GLFW_PRESS) { + mouse.state = LEFT_MOUSE; + } else if (mouse.state == LEFT_MOUSE){ + mouse.state = NONE; + } + cout << "m1"< #include #include +#include #include #include #include @@ -101,5 +102,25 @@ void deleteTexture(GLuint* tex); void mainLoop(); void errorCallback(int error, const char *description); void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); +void cursorCallback(GLFWwindow* window, double x, double y); +void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods); + +//------------------------------ +//-----USER INTERACTION--------- +//------------------------------ +enum mouseState { + NONE, + LEFT_MOUSE, + MIDDLE_MOUSE, + RIGHT_MOUSE +}; + +struct Mouse { + float x, y; + mouseState state; +}; + +Mouse mouse; + #endif diff --git a/src/raytraceKernel.cu b/src/raytraceKernel.cu index 9c7bc7d..f323520 100644 --- a/src/raytraceKernel.cu +++ b/src/raytraceKernel.cu @@ -8,6 +8,9 @@ #include #include #include +#include +#include +#include #include "sceneStructs.h" #include "glm/glm.hpp" @@ -36,14 +39,57 @@ __host__ __device__ glm::vec3 generateRandomNumberFromThread(glm::vec2 resolutio } // TODO: IMPLEMENT THIS FUNCTION +// IDID: Implemented camera raycast, random scatter included. // 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 planeRight, glm::vec3 planeDown){ ray r; - r.origin = glm::vec3(0,0,0); - r.direction = glm::vec3(0,0,-1); + r.origin = glm::vec3(eye); + + glm::vec3 dir = view + (2 * x / resolution.x - 1) * planeRight + (2 * y / resolution.y - 1) * planeDown; + + glm::vec3 rand = generateRandomNumberFromThread(resolution, time, x, y) * (glm::length(planeRight) * 2 / resolution.x); + r.direction = glm::normalize(dir + rand); + return r; } +// TODO: IMPLEMENT THIS FUNCTION +// IDID: Implemented camera raycast, random scatter included. +// Function that fills the initial raycast from camera, for stream compation optimization +__global__ void initRaycastFromCamera(glm::vec2 resolution, float time, glm::vec3 eye, glm::vec3 view, float depth, glm::vec3 planeRight, glm::vec3 planeDown, ray* rays){ + 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); + ray r; + r.origin = glm::vec3(eye); + + glm::vec3 dir = view + (2 * x / resolution.x - 1) * planeRight + (2 * y / resolution.y - 1) * planeDown; + + glm::vec3 rand = generateRandomNumberFromThread(resolution, time, x, y) * (glm::length(planeRight) * 5 / resolution.x); + r.direction = glm::normalize(dir + rand); + r.index = index; + r.ended = false; + r.color = glm::vec3(1.0f); + r.ior = 1.0f; + + if (depth > 0) { + rand = (generateRandomNumberFromThread(resolution, time, x, y) - glm::vec3(0.5f))* (glm::length(planeRight) * 2 / resolution.x); + // find r's intersection point with the depth of field plane ***should this be a sphere?*** + glm::vec3 P = r.origin + r.direction * depth / glm::dot(r.direction, view); + + // jitter r.origin + r.origin += glm::normalize(rand); + + // set new values for r. + r.direction = glm::normalize(P - r.origin); + } + + rays[index] = r; + } +} + //Kernel that blacks out a given image buffer __global__ void clearImage(glm::vec2 resolution, glm::vec3* image){ int x = (blockIdx.x * blockDim.x) + threadIdx.x; @@ -54,6 +100,16 @@ __global__ void clearImage(glm::vec2 resolution, glm::vec3* image){ } } +//Kernel that blacks out a given color buffer +__global__ void clearColors(glm::vec2 resolution, glm::vec3* colors){ + 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){ + colors[index] = glm::vec3(0,0,0); + } +} + //Kernel that writes the image to the OpenGL PBO directly. __global__ void sendImageToPBO(uchar4* PBOpos, glm::vec2 resolution, glm::vec3* image){ @@ -91,75 +147,232 @@ __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){ + staticGeom* geoms, int numberOfGeoms, material* materials){ + + 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)){ + glm::vec3 newCol; + ray r = raycastFromCameraKernel(resolution, time, x, y, cam.position, cam.view, cam.planeRight, cam.planeDown); + for (int i = 0; i < rayDepth; i++) { + glm::vec3 point, normal; + float minT = FLT_MAX; + float t; + int minIndex = -1; + for (int j = 0; j < numberOfGeoms; j++) { + if (geoms[j].type == SPHERE) { + t = sphereIntersectionTest(geoms[j], r, point, normal); + } else if (geoms[j].type == CUBE) { + t = boxIntersectionTest(geoms[j], r, point, normal); + } + if (t < minT && t != -1) { + minT = t; + minIndex = j; + } + } + if (minIndex != -1) { + // Simple normal debugging: + //colors[index] += normal; + // Flat colors + newCol += materials[geoms[minIndex].materialid].color; + glm::vec3 rand = generateRandomNumberFromThread(resolution, time, x, y); + // Include a small epsilon * normal term in the new origin to prevent self-intersection. + r.origin += r.direction * (float)minIndex + (float)EPSILON * normal; + r.direction = calculateRandomDirectionInHemisphere(normal, rand.x, rand.y); + } + else { + break; + } + } + colors[index] = (colors[index] * time + newCol) / (time + 1); + } +} - int x = (blockIdx.x * blockDim.x) + threadIdx.x; - int y = (blockIdx.y * blockDim.y) + threadIdx.y; - int index = x + (y * resolution.x); +// IDID: added this function +// Core raytracer kernel for stream compation optimization +__global__ void raytraceRays(glm::vec2 resolution, float time, int traceDepth, ray* rays, int numberOfRays, + staticGeom* geoms, int numberOfGeoms, material* materials){ + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + ray r = rays[index]; + if(index < numberOfRays && !r.ended){ + glm::vec3 point, normal; + glm::vec3 minPt, minNorm; + float minT = FLT_MAX; + float t; + int minIndex = -1; + for (int j = 0; j < numberOfGeoms; j++) { + if (geoms[j].type == SPHERE) { + t = sphereIntersectionTest(geoms[j], r, point, normal); + } else if (geoms[j].type == CUBE) { + t = boxIntersectionTest(geoms[j], r, point, normal); + } + if (t < minT && t != -1) { + minPt = point; + minNorm = normal; + minT = t; + minIndex = j; + } + } + if (minIndex != -1) { + material mat = materials[geoms[minIndex].materialid]; + // Flat colors + if (mat.emittance > ZERO_ABSORPTION_EPSILON) { + r.color = r.color * mat.color * mat.emittance; + r.ended = true; + } else { + r.color = r.color * (mat.color);// + mat.specularColor * pow(glm::dot(-r.direction, normal), 2)); + calculateBSDF(r, minPt, minNorm, mat, index*time); + } + // Simple normal debugging: + //r.color = normal; + } + else { + r.color = glm::vec3(0.f); + r.ended = true; + } + if (traceDepth == 0) { + r.ended = true; + r.color = glm::vec3(0.f); + } + rays[index] = r; + } +} + +// Accumulates the colors of ended rays on this iteration +__global__ void raysToColors(glm::vec2 resolution, ray* rays, int numberOfRays, glm::vec3* colors) { + int index = (blockIdx.x * blockDim.x) + threadIdx.x; - if((x<=resolution.x && y<=resolution.y)){ + // TODO: change this to a single-dimensional grid? + if(index < numberOfRays){ + ray r = rays[index]; + if (r.ended) { + colors[r.index] = r.color; + } + } +} - colors[index] = generateRandomNumberFromThread(resolution, time, x, y); - } +// Adds the colors accumulated from the current raytrace iteration to the image +__global__ void colorsToImage(glm::vec2 resolution, float time, glm::vec3* colors, glm::vec3* image) { + 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)){ + image[index] = (image[index] * time + colors[index]) / (time + 1); + } } +// Predicate that marks a ray as "ended" if its 'ended' value is true. +struct isRayEnded { + __host__ __device__ + bool operator() (const ray r) { + return r.ended; + } +}; + // TODO: FINISH THIS FUNCTION // 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 - // set up crucial magic - int tileSize = 8; - dim3 threadsPerBlock(tileSize, tileSize); - dim3 fullBlocksPerGrid((int)ceil(float(renderCam->resolution.x)/float(tileSize)), (int)ceil(float(renderCam->resolution.y)/float(tileSize))); - - // 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); - - // package geometry and materials and sent to GPU - staticGeom* geomList = new staticGeom[numberOfGeoms]; - for(int i=0; iresolution; - cam.position = renderCam->positions[frame]; - cam.view = renderCam->views[frame]; - cam.up = renderCam->ups[frame]; - cam.fov = renderCam->fov; + int traceDepth = 7; //determines how many bounces the raytracer traces + + // set up crucial magic + int tileSize = 8; + dim3 threadsPerBlock(tileSize, tileSize); + dim3 fullBlocksPerGrid((int)ceil(float(renderCam->resolution.x)/float(tileSize)), (int)ceil(float(renderCam->resolution.y)/float(tileSize))); + + // 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); + + // package geometry and materials and sent to GPU + staticGeom* geomList = new staticGeom[numberOfGeoms]; + for(int i=0; iresolution; + cam.position = renderCam->positions[frame]; + cam.view = renderCam->views[frame]; + cam.up = renderCam->ups[frame]; + cam.fov = renderCam->fov; + cam.depth = renderCam->depths[frame]; + // calculate the vectors for screen coordinates here first. + glm::vec3 right = glm::normalize(glm::cross(renderCam->views[frame], glm::normalize(renderCam->ups[frame]))); + glm::vec3 up = glm::cross(right, renderCam->views[frame]); + // Image plane should have (0,0) at upper-left, (res.x, rex.y) at bottom-right. + // TODO: Figure out why this is flipped horizontally + cam.planeRight = renderCam->planeRight; + cam.planeDown = renderCam->planeDown; + + // package materials + material* cudamaterials = NULL; + cudaMalloc((void**)&cudamaterials, numberOfMaterials*sizeof(material)); + cudaMemcpy(cudamaterials, materials, numberOfMaterials*sizeof(material), cudaMemcpyHostToDevice); + + // raytrace core + int numberOfRays = renderCam->resolution.x * renderCam->resolution.y; + ray* cudarays = NULL; + cudaMalloc((void**)&cudarays, numberOfRays*sizeof(ray)); + initRaycastFromCamera<<>>(renderCam->resolution, (float)iterations, cam.position, cam.view, cam.depth, cam.planeRight, cam.planeDown, cudarays); + + // compile the colors from this iteration + glm::vec3* cudacolors = NULL; + cudaMalloc((void**)&cudacolors, (int)renderCam->resolution.x*(int)renderCam->resolution.y*sizeof(glm::vec3)); + + clearColors<<>>(renderCam->resolution, cudacolors); + int raytraceThreadsPerBlock(tileSize * tileSize); + int raytraceBlocksPerGrid((int)ceil(numberOfRays / (float)raytraceThreadsPerBlock)); + while (traceDepth > 0 && numberOfRays > 0) { + traceDepth--; + raytraceRays<<>>(renderCam->resolution, (float)iterations, traceDepth, cudarays, numberOfRays, cudageoms, numberOfGeoms, cudamaterials); + raysToColors<<>>(renderCam->resolution, cudarays, numberOfRays, cudacolors); + // Using thrust stream compaction + thrust::device_ptr cudaraysStart(cudarays); + + float numRemoved = thrust::count_if(cudaraysStart, cudaraysStart+numberOfRays, isRayEnded()); + thrust::remove_if(cudaraysStart, cudaraysStart+numberOfRays, isRayEnded()); + numberOfRays -= numRemoved; + + } + colorsToImage<<>>(renderCam->resolution, (float)iterations, cudacolors, cudaimage); + + // kernel launches + //raytraceRay<<>>(renderCam->resolution, (float)iterations, cam, traceDepth, cudaimage, cudageoms, numberOfGeoms, cudaMaterials); - // kernel launches - raytraceRay<<>>(renderCam->resolution, (float)iterations, cam, traceDepth, cudaimage, cudageoms, numberOfGeoms); + sendImageToPBO<<>>(PBOpos, renderCam->resolution, cudaimage); - sendImageToPBO<<>>(PBOpos, renderCam->resolution, cudaimage); + // retrieve image from GPU + cudaMemcpy( renderCam->image, cudaimage, (int)renderCam->resolution.x*(int)renderCam->resolution.y*sizeof(glm::vec3), cudaMemcpyDeviceToHost); - // retrieve image from GPU - cudaMemcpy( renderCam->image, cudaimage, (int)renderCam->resolution.x*(int)renderCam->resolution.y*sizeof(glm::vec3), cudaMemcpyDeviceToHost); + // free up stuff, or else we'll leak memory like a madman + cudaFree( cudaimage ); + cudaFree( cudageoms ); + cudaFree( cudamaterials ); + cudaFree( cudarays ); + cudaFree( cudacolors ); - // free up stuff, or else we'll leak memory like a madman - cudaFree( cudaimage ); - cudaFree( cudageoms ); - delete geomList; + delete geomList; - // make certain the kernel has completed - cudaThreadSynchronize(); + // make certain the kernel has completed + cudaThreadSynchronize(); - checkCUDAError("Kernel failed!"); + checkCUDAError("Kernel failed!"); } diff --git a/src/scene.cpp b/src/scene.cpp index 4cbe216..5d57880 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -162,6 +162,7 @@ int scene::loadCamera(){ vector positions; vector views; vector ups; + vector depths; while (!line.empty() && fp_in.good()){ //check frame number @@ -172,7 +173,7 @@ int scene::loadCamera(){ } //load camera properties - for(int i=0; i<3; i++){ + for(int i=0; i<4; i++){ //glm::vec3 translation; glm::vec3 rotation; glm::vec3 scale; utilityCore::safeGetline(fp_in,line); tokens = utilityCore::tokenizeString(line); @@ -182,7 +183,9 @@ 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(), "DEPTH")==0){ + depths.push_back(atof(tokens[1].c_str())); + } } frameCount++; @@ -194,10 +197,12 @@ int scene::loadCamera(){ newCamera.positions = new glm::vec3[frameCount]; newCamera.views = new glm::vec3[frameCount]; newCamera.ups = new glm::vec3[frameCount]; + newCamera.depths = 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.depths[i] = depths[i]; } //calculate fov based on resolution @@ -214,7 +219,8 @@ int scene::loadCamera(){ for(int i=0; i - + @@ -95,6 +95,6 @@ - + \ No newline at end of file diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/debug_bounce.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/debug_bounce.bmp new file mode 100644 index 0000000..8fd45d0 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/debug_bounce.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/debug_normals.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/debug_normals.bmp new file mode 100644 index 0000000..c713328 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/debug_normals.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/depth_comparison.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/depth_comparison.bmp new file mode 100644 index 0000000..6ae1487 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/depth_comparison.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/dof_comparison.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/dof_comparison.bmp new file mode 100644 index 0000000..cb5e7e1 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/dof_comparison.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/iter_comparison.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/iter_comparison.bmp new file mode 100644 index 0000000..dbde4dc Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/iter_comparison.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/scene1.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/scene1.0.bmp new file mode 100644 index 0000000..cee3000 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/scene1.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/scene2.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2.0.bmp new file mode 100644 index 0000000..3a36963 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth20.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth20.0.bmp new file mode 100644 index 0000000..499d84a Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth20.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth4.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth4.0.bmp new file mode 100644 index 0000000..24d0e70 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth4.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth7_iter2000.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth7_iter2000.0.bmp new file mode 100644 index 0000000..daf890c Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_depth7_iter2000.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_doffar.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_doffar.0.bmp new file mode 100644 index 0000000..ad7f69d Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_doffar.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_dofnear.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_dofnear.0.bmp new file mode 100644 index 0000000..31f21f8 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_dofnear.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_iter20.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_iter20.0.bmp new file mode 100644 index 0000000..01cbf8c Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_iter20.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_iter200.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_iter200.0.bmp new file mode 100644 index 0000000..db9152d Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/scene2_iter200.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/test1.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/test1.0.bmp new file mode 100644 index 0000000..77cbb05 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/test1.0.bmp differ diff --git a/windows/Project3-Pathtracer/Project3-Pathtracer/test2.0.bmp b/windows/Project3-Pathtracer/Project3-Pathtracer/test2.0.bmp new file mode 100644 index 0000000..f760ff3 Binary files /dev/null and b/windows/Project3-Pathtracer/Project3-Pathtracer/test2.0.bmp differ