diff --git a/example02_threaded_raytracing/src/main.cpp b/example02_threaded_raytracing/src/main.cpp index dfbded3..a5657b2 100644 --- a/example02_threaded_raytracing/src/main.cpp +++ b/example02_threaded_raytracing/src/main.cpp @@ -2,17 +2,27 @@ #include #include "argParse.hpp" +#include "rayTracer.hpp" void printHelp() { std::cout << "Usage:\n" "-h | --help Display this help message\n" "-t \n" - " | --threads Set the number of threads to use (default 1)" + " | --threads Set the number of threads to use (default 1)\n" + "--width Set the width of the output image\n" + "--height Set the height of the output image\n" + "--radius Set the radius of the sphere\n" + "-o \n" + " | --output Set the output filename for the image" << std::endl; } int main(int argc, char **argv) { int threadCount = 1; + unsigned int outputWidth = 1600; + unsigned int outputHeight = 1600; + float sphereRadius = 1.5f; + std::string outputFile = "raytrace_out"; { auto results = Ex02::ArgParse::parseArgs( @@ -25,6 +35,11 @@ int main(int argc, char **argv) { { // double args "-t", "--threads", + "--width", + "--height", + "--radius", + "-o", + "--output", }); if(auto iter = results.find("-h"); iter != results.end()) { @@ -58,7 +73,6 @@ int main(int argc, char **argv) { return result; } } - if(threadCount <= 0) { std::cout << "ERROR: Thread count set to invalid value (" << threadCount @@ -66,7 +80,73 @@ int main(int argc, char **argv) { << std::endl; return 3; } + if(auto iter = results.find("--width"); iter != results.end()) { + try { + outputWidth = std::stoul(iter->second); + } catch (const std::invalid_argument &e) { + std::cout << "ERROR: Failed to parse width (invalid)" + << std::endl; + return 3; + } catch (const std::out_of_range &e) { + std::cout << "ERROR: Failed to parse width (out of range)" + << std::endl; + return 4; + } + } + if(outputWidth == 0) { + std::cout << "ERROR: width cannot be 0" << std::endl; + return 7; + } + if(auto iter = results.find("--height"); iter != results.end()) { + try { + outputHeight = std::stoul(iter->second); + } catch (const std::invalid_argument &e) { + std::cout << "ERROR: Failed to parse height (invalid)" + << std::endl; + return 5; + } catch (const std::out_of_range &e) { + std::cout << "ERROR: Failed to parse height (out of range)" + << std::endl; + return 6; + } + } + if(outputHeight == 0) { + std::cout << "ERROR: height cannot be 0" << std::endl; + return 8; + } + if(auto iter = results.find("--radius"); iter != results.end()) { + try { + sphereRadius = stof(iter->second); + } catch (const std::invalid_argument &e) { + std::cout << "ERROR: Failed to parse radius (invalid)" + << std::endl; + return 9; + } catch (const std::out_of_range &e) { + std::cout << "ERROR: Failed to parse radius (out_of_range)" + << std::endl; + return 10; + } + } + if(sphereRadius <= 0.0f) { + std::cout << "ERROR: radius must be positive and non-zero" + << std::endl; + return 11; + } + if(auto iter = results.find("-o"); iter != results.end()) { + outputFile = iter->second; + } else if(auto iter = results.find("--output"); iter != results.end()) { + outputFile = iter->second; + } + if(outputFile.empty()) { + std::cout << "ERROR: Output filename is empty" << std::endl; + return 12; + } } + auto pixels = Ex02::RT::renderGraySphere( + outputWidth, outputHeight, sphereRadius, threadCount); + + Ex02::RT::writeGrayscaleToFile(pixels, outputWidth, outputFile); + return 0; } diff --git a/example02_threaded_raytracing/src/rayTracer.cpp b/example02_threaded_raytracing/src/rayTracer.cpp index 7b847f9..eebef8c 100644 --- a/example02_threaded_raytracing/src/rayTracer.cpp +++ b/example02_threaded_raytracing/src/rayTracer.cpp @@ -1,6 +1,7 @@ #include "rayTracer.hpp" #include +#include #include #include @@ -15,7 +16,7 @@ glm::vec3 Ex02::RT::Internal::defaultSpherePos() { } glm::vec3 Ex02::RT::Internal::defaultLightPos() { - return glm::vec3{1.0f, 1.0f, 1.0f}; + return glm::vec3{1.0f, 1.0f, 0.0f}; } glm::mat4x4 Ex02::RT::Internal::defaultMVP() { @@ -31,13 +32,65 @@ glm::mat4x4 Ex02::RT::Internal::defaultMVP() { glm::vec3{0.0f, 1.0f, 0.0f}); // model pushes back by 2 units - mvp *= glm::translate( - glm::identity(), - glm::vec3{0.0f, 0.0f, -2.0f}); + mvp = glm::translate( + mvp, + glm::vec3{0.0f, 0.0f, 2.0f}); return mvp; } +std::optional Ex02::RT::Internal::rayToSphere( + glm::vec3 rayPos, + glm::vec3 rayDir, + glm::vec3 spherePos, + float sphereRadius) { + // ensure rayDir is a unit vector + float rayDirLength = std::sqrt( + rayDir.x * rayDir.x + + rayDir.y * rayDir.y + + rayDir.z * rayDir.z); + rayDir /= rayDirLength; + + // check if there is collision + glm::vec3 tempVec = rayPos - spherePos; + float temp = + rayDir.x * tempVec.x + + rayDir.y * tempVec.y + + rayDir.z * tempVec.z; + float delta = temp * temp; + temp = + tempVec.x * tempVec.x + + tempVec.y * tempVec.y + + tempVec.z * tempVec.z + - sphereRadius * sphereRadius; + delta -= temp; + + if(delta < 0.0f) { + return {}; + } else { + temp = + rayDir.x * tempVec.x + + rayDir.y * tempVec.y + + rayDir.z * tempVec.z; + float dist = -temp - std::sqrt(delta); + if(dist < 0.0f) { + dist = -temp + std::sqrt(delta); + if(dist < 0.0f) { + return {}; + } + } + return {rayPos + rayDir * dist}; + } +} + +float Ex02::RT::Internal::angleBetweenRays(glm::vec3 a, glm::vec3 b) { + float dot = a.x * b.x + a.y * b.y + a.z * b.z; + float amag = std::sqrt(a.x * a.x + a.y * a.y + a.z * a.z); + float bmag = std::sqrt(b.x * b.x + b.y * b.y + b.z * b.z); + + return std::acos(dot / amag / bmag); +} + std::vector Ex02::RT::renderGraySphere( unsigned int outputWidth, unsigned int outputHeight, @@ -47,6 +100,66 @@ std::vector Ex02::RT::renderGraySphere( glm::vec3 lightPos, glm::mat4x4 mvp) { std::vector grayscalePixels; + grayscalePixels.resize(outputWidth * outputHeight); + +// glm::vec3 tr_spherePos = mvp * glm::vec4{spherePos, 1.0f}; +// glm::vec3 tr_lightPos = mvp * glm::vec4{lightPos, 1.0f}; + glm::vec3 tr_spherePos = glm::vec3{0.0f, 0.0f, -2.0f}; + glm::vec3 tr_lightPos = glm::vec3{1.0f, 1.0f, 0.0f}; + glm::vec3 rayPos{0.0f, 0.0f, 0.0f}; + if(threadCount == 1) { + for(unsigned int j = 0; j < outputHeight; ++j) { + float offsetY = ((float)j - ((float)outputHeight / 2.0f)); + for(unsigned int i = 0; i < outputWidth; ++i) { + float offsetX = ((float)i - ((float)outputWidth / 2.0f)); + glm::vec3 rayDir = glm::vec3{ + offsetX, offsetY, -EX02_RAY_TRACER_DRAW_PLANE}; + auto rayResult = Internal::rayToSphere( + rayPos, rayDir, tr_spherePos, sphereRadius); + if(rayResult) { + glm::vec3 toLight = *rayResult - tr_lightPos; + toLight /= std::sqrt( + toLight.x * toLight.x + + toLight.y * toLight.y + + toLight.z * toLight.z); + glm::vec3 toLightPos = *rayResult + toLight * 2.4f; + auto collResult = Internal::rayToSphere( + toLightPos, toLight, tr_spherePos, sphereRadius); + if(collResult) { + continue; + } + + glm::vec3 resultToLight = tr_lightPos - *rayResult; + glm::vec3 resultToOrigin = rayPos - *rayResult; + float angle = Internal::angleBetweenRays( + resultToLight, + resultToOrigin); + if(angle <= PI && angle >= 0.0f) { + grayscalePixels.at(i + j * outputWidth) = + (1.0f - angle / PI) * 255.0f; + } + } + } + } + } else { + } return grayscalePixels; } + +void Ex02::RT::writeGrayscaleToFile( + const std::vector &pixels, + unsigned int outputWidth, + std::string filename) { + std::ofstream out(filename + ".pgm"); + out << "P2\n" << outputWidth << ' ' << pixels.size() / outputWidth << " 255" + << '\n'; + + for(unsigned int j = 0; j < pixels.size() / outputWidth; ++j) { + for(unsigned int i = 0; i < outputWidth; ++i) { + out << (int)pixels.at(i + j * outputWidth) + << ' '; + } + out << '\n'; + } +} diff --git a/example02_threaded_raytracing/src/rayTracer.hpp b/example02_threaded_raytracing/src/rayTracer.hpp index 021752f..fef06b5 100644 --- a/example02_threaded_raytracing/src/rayTracer.hpp +++ b/example02_threaded_raytracing/src/rayTracer.hpp @@ -1,11 +1,14 @@ #ifndef EX02_RAY_TRACER_HPP #define EX02_RAY_TRACER_HPP +#define EX02_RAY_TRACER_DRAW_PLANE 500.0f #define EX02_RAY_TRACER_DEFAULT_NEAR_PLANE 0.2f #define EX02_RAY_TRACER_DEFAULT_FAR_PLANE 4.0f #define EX02_RAY_TRACER_COLL_INCREMENT 2.0f #include +#include +#include #include #include @@ -18,6 +21,16 @@ namespace Internal { glm::vec3 defaultSpherePos(); glm::vec3 defaultLightPos(); glm::mat4x4 defaultMVP(); + + std::optional rayToSphere( + glm::vec3 rayPos, + glm::vec3 rayDir, + glm::vec3 spherePos, + float sphereRadius); + + float angleBetweenRays( + glm::vec3 a, + glm::vec3 b); } std::vector renderGraySphere( @@ -30,6 +43,11 @@ std::vector renderGraySphere( glm::mat4x4 mvp = Internal::defaultMVP() ); +void writeGrayscaleToFile( + const std::vector &pixels, + unsigned int outputWidth, + std::string filename); + } // namespace RT } // namespace Ex02