#include "rayTracer.hpp" #include #include #include #include #include #include #include #include #include #include #include const float PI = std::acos(-1.0F); Ex02::RT::Pixel::Pixel() : r(0), g(0), b(0) {} Ex02::RT::Image::Image(unsigned int width, unsigned int height) : width(width) { data.resize(width * height); } Ex02::RT::Pixel &Ex02::RT::Image::getPixel(unsigned int x, unsigned int y) { return data.at(x + y * width); } const Ex02::RT::Pixel &Ex02::RT::Image::getPixel(unsigned int x, unsigned int y) const { return data.at(x + y * width); } std::string Ex02::RT::Image::writeToFile(const std::string &filename) const { std::string outfilename = filename + ".ppm"; std::ofstream out(outfilename); out << "P3\n" << width << ' ' << data.size() / width << " 255" << '\n'; for (unsigned int j = 0; j < data.size() / width; ++j) { for (unsigned int i = 0; i < width; ++i) { out << static_cast(data.at(i + j * width).r) << ' ' << static_cast(data.at(i + j * width).g) << ' ' << static_cast(data.at(i + j * width).b) << ' '; } out << '\n'; } return outfilename; } std::string Ex02::RT::Image::writeToPNG(const std::string &filename) const { std::string outfilename = filename + ".png"; FILE *outfile = fopen(outfilename.c_str(), "wb"); if (outfile == nullptr) { return "ERROR"; } const static auto pngErrorLFn = [](png_structp /*unused*/, png_const_charp message) { std::cerr << "WARNING [libpng]: " << message << std::endl; }; const static auto pngWarnLFn = [](png_structp /*unused*/, png_const_charp message) { std::cerr << "ERROR [libpng]: " << message << std::endl; }; png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, pngErrorLFn, pngWarnLFn); if (png_ptr == nullptr) { fclose(outfile); return "ERROR"; } png_infop info_ptr = png_create_info_struct(png_ptr); if (info_ptr == nullptr) { png_destroy_write_struct(&png_ptr, nullptr); fclose(outfile); return "ERROR"; } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); fclose(outfile); return "ERROR"; } png_init_io(png_ptr, outfile); png_set_IHDR(png_ptr, info_ptr, this->width, this->data.size() / this->width, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png_ptr, info_ptr); png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); for (unsigned int j = 0; j < this->data.size() / this->width; ++j) { unsigned char *dataPtr = reinterpret_cast(&this->data.at(j * this->width)); png_write_rows(png_ptr, &dataPtr, 1); } png_write_end(png_ptr, nullptr); png_destroy_write_struct(&png_ptr, &info_ptr); fclose(outfile); return outfilename; } /* glm::mat4x4 Ex02::RT::Internal::defaultMVP() { glm::mat4x4 mvp = glm::perspective( PI / 2.0F, 1.0F, EX02_RAY_TRACER_DEFAULT_NEAR_PLANE, EX02_RAY_TRACER_DEFAULT_FAR_PLANE); mvp *= glm::lookAt( glm::vec3{0.0F, 0.0F, 0.0F}, glm::vec3{0.0F, 0.0F, -1.0F}, glm::vec3{0.0F, 1.0F, 0.0F}); // model pushes back by 2 units mvp = glm::translate( mvp, glm::vec3{0.0F, 0.0F, 2.0F}); return mvp; } */ Ex02::RT::Internal::LightSource::LightSource() : pos{0.0F, 0.0F, 0.0F}, falloffStart(2.0F), falloffEnd(7.0F), color{1.0F, 1.0F, 1.0F} {} void Ex02::RT::Internal::LightSource::applyLight(glm::vec3 pos, Pixel &pixelOut) const { pos = this->pos - pos; float dist = std::sqrt(pos.x * pos.x + pos.y * pos.y + pos.z * pos.z); if (dist < falloffStart) { const auto applyColor = [](auto *color, unsigned char *out) { unsigned int temp = static_cast(*out) + static_cast(*color * 255.0F); if (temp > 255) { *out = 255; } else { *out = temp; } }; applyColor(&color.x, &pixelOut.r); applyColor(&color.y, &pixelOut.g); applyColor(&color.z, &pixelOut.b); } else if (dist >= falloffStart && dist <= falloffEnd) { const auto applyFalloffColor = [](auto *color, unsigned char *out, float f) { unsigned int temp = static_cast(*out) + static_cast(*color * 255.0F * f); if (temp > 255) { *out = 255; } else { *out = temp; } }; float f = (1.0F - (dist - falloffStart) / (falloffEnd - falloffStart)); applyFalloffColor(&color.x, &pixelOut.r, f); applyFalloffColor(&color.y, &pixelOut.g, f); applyFalloffColor(&color.z, &pixelOut.b, f); } } Ex02::RT::Internal::Sphere::Sphere() : pos{0.0F, 0.0F, 0.0F}, radius(2.5F) {} std::optional Ex02::RT::Internal::Sphere::rayToSphere(glm::vec3 rayPos, glm::vec3 rayDirUnit) const { return Ex02::RT::Internal::rayToSphere(rayPos, rayDirUnit, pos, radius); } Ex02::RT::Internal::RTSVisibleType Ex02::RT::Internal::Sphere::rayToSphereVisible(glm::vec3 rayPos, glm::vec3 rayDirUnit, const LightSource &light) const { return Ex02::RT::Internal::rayToSphereVisible(rayPos, rayDirUnit, pos, radius, light.pos); } std::optional Ex02::RT::Internal::rayToSphere(glm::vec3 rayPos, glm::vec3 rayDirUnit, glm::vec3 spherePos, float sphereRadius) { // check if there is collision const glm::vec3 tempVec = rayPos - spherePos; float temp = rayDirUnit.x * tempVec.x + rayDirUnit.y * tempVec.y + rayDirUnit.z * tempVec.z; float delta = temp * temp - (tempVec.x * tempVec.x + tempVec.y * tempVec.y + tempVec.z * tempVec.z - sphereRadius * sphereRadius); if (delta < 0.0F) { return {}; } else { temp = rayDirUnit.x * tempVec.x + rayDirUnit.y * tempVec.y + rayDirUnit.z * tempVec.z; const float temp2 = std::sqrt(delta); const float dist = -temp - temp2; const float dist2 = -temp + temp2; if (dist < 0.0F) { if (dist2 < 0.0F) { return {}; } else { return {rayPos + rayDirUnit * dist2}; } } else { return {rayPos + rayDirUnit * 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); } float Ex02::RT::Internal::distBetweenPositions(glm::vec3 a, glm::vec3 b) { a = a - b; return std::sqrt(a.x * a.x + a.y * a.y + a.z * a.z); } Ex02::RT::Internal::RTSVisibleType Ex02::RT::Internal::rayToSphereVisible(glm::vec3 rayPos, glm::vec3 rayDirUnit, glm::vec3 spherePos, float sphereRadius, glm::vec3 lightPos) { auto collPos = rayToSphere(rayPos, rayDirUnit, spherePos, sphereRadius); if (collPos) { glm::vec3 toLight = lightPos - *collPos; glm::vec3 toLightUnit = toLight / std::sqrt(toLight.x * toLight.x + toLight.y * toLight.y + toLight.z * toLight.z); glm::vec3 toLightPos = *collPos + toLight / 3.0F; auto collResult = Internal::rayToSphere(toLightPos, toLightUnit, spherePos, sphereRadius); if (collResult) { return {}; } else { return {{*collPos, toLight}}; } } else { return {}; } } Ex02::RT::Image Ex02::RT::renderGraySphere(unsigned int outputWidth, unsigned int outputHeight, unsigned int /*unused*/) { const glm::vec3 spherePos{0.0F, 0.0F, -2.5F}; const glm::vec3 lightPos{4.0F, 4.0F, 0.0F}; Image image(outputWidth, outputHeight); const glm::vec3 rayPos{0.0F, 0.0F, 0.0F}; const float lightFalloffStart = 4.5F; const float lightFalloffEnd = 7.0F; // if(threadCount <= 1) { for (unsigned int j = 0; j < outputHeight; ++j) { float offsetY = (static_cast(j) + 0.5F - (static_cast(outputHeight) / 2.0F)); for (unsigned int i = 0; i < outputWidth; ++i) { float offsetX = (static_cast(i) + 0.5F - (static_cast(outputWidth) / 2.0F)); glm::vec3 rayDir{offsetX, offsetY, -static_cast(outputHeight) * EX02_RAY_TRACER_VIEW_RATIO}; glm::vec3 rayDirUnit = rayDir / std::sqrt(rayDir.x * rayDir.x + rayDir.y * rayDir.y + rayDir.z * rayDir.z); auto rayResult = Internal::rayToSphereVisible( rayPos, rayDirUnit, spherePos, EX02_RAY_TRACER_GRAY_SPHERE_RADIUS, lightPos); if (rayResult) { glm::vec3 *toLight = &std::get<1>(rayResult.value()); float dist = std::sqrt(toLight->x * toLight->x + toLight->y * toLight->y + toLight->z * toLight->z); if (dist < lightFalloffStart) { image.getPixel(i, j).r = 255; image.getPixel(i, j).g = 255; image.getPixel(i, j).b = 255; } else if (dist >= lightFalloffStart && dist <= lightFalloffEnd) { image.getPixel(i, j).r = static_cast( (1.0F - (dist - lightFalloffStart) / (lightFalloffEnd - lightFalloffStart)) * 255.0F); image.getPixel(i, j).g = image.getPixel(i, j).r; image.getPixel(i, j).b = image.getPixel(i, j).r; } } } } // } else { // } return image; } Ex02::RT::Image Ex02::RT::renderColorsWithSpheres(unsigned int outputWidth, unsigned int outputHeight, unsigned int threadCount) { Image image(outputWidth, outputHeight); const glm::vec3 rayPos{0.0F, 0.0F, 0.0F}; std::array spheres; std::array lights; spheres[0].pos.x = 2.0F; spheres[0].pos.y = -2.0F; spheres[0].pos.z = -4.5F; spheres[0].radius = 1.0F; spheres[1].pos.x = -2.0F; spheres[1].pos.y = 2.0F; spheres[1].pos.z = -4.5F; spheres[1].radius = 1.0F; spheres[2].pos.x = 0.0F; spheres[2].pos.y = 0.0F; spheres[2].pos.z = -6.0F; spheres[2].radius = 2.0F; spheres[3].pos.x = 2.0F; spheres[3].pos.y = 2.0F; spheres[3].pos.z = -2.5; spheres[3].radius = 1.0F; spheres[4].pos.x = -2.0F; spheres[4].pos.y = -2.0F; spheres[4].pos.z = -2.5; spheres[4].radius = 1.0F; spheres[5].pos.x = -0.7F; spheres[5].pos.y = -0.7F; spheres[5].pos.z = -4.0F; spheres[5].radius = -0.7F; spheres[6].pos.x = 0.7F; spheres[6].pos.y = 0.7F; spheres[6].pos.z = -4.0F; spheres[6].radius = -0.7F; lights[0].color.r = 1.0F; lights[0].color.g = 0.0F; lights[0].color.b = 0.0F; lights[0].pos.x = 0.0F; lights[0].pos.y = -1.0F; lights[0].pos.z = 0.0F; lights[0].falloffStart = 3.0F; lights[0].falloffEnd = 7.0F; lights[1].color.r = 0.0F; lights[1].color.g = 1.0F; lights[1].color.b = 0.0F; lights[1].pos.x = std::cos(PI / 3.0F); lights[1].pos.y = std::sin(PI / 3.0F); lights[1].pos.z = 0.0F; lights[1].falloffStart = 3.0F; lights[1].falloffEnd = 7.0F; lights[2].color.r = 0.0F; lights[2].color.g = 0.0F; lights[2].color.b = 1.0F; lights[2].pos.x = std::cos(PI * 2.0F / 3.0F); lights[2].pos.y = std::sin(PI * 2.0F / 3.0F); lights[2].pos.z = 0.0F; lights[2].falloffStart = 3.0F; lights[2].falloffEnd = 7.0F; const auto yIteration = [&spheres, &lights, &image, outputWidth, outputHeight, rayPos](unsigned int j) { float offsetY = (static_cast(j) + 0.5F - (static_cast(outputHeight) / 2.0F)); for (unsigned int i = 0; i < outputWidth; ++i) { float offsetX = (static_cast(i) + 0.5F - (static_cast(outputWidth) / 2.0F)); glm::vec3 rayDir{offsetX, offsetY, -static_cast(outputHeight) * EX02_RAY_TRACER_VIEW_RATIO}; glm::vec3 rayDirUnit = rayDir / std::sqrt(rayDir.x * rayDir.x + rayDir.y * rayDir.y + rayDir.z * rayDir.z); // cast ray to all spheres, finding closest result std::optional> closestResult; for (unsigned int idx = 0; idx < spheres.size(); ++idx) { auto result = spheres.at(idx).rayToSphere(rayPos, rayDirUnit); if (result) { float dist = Internal::distBetweenPositions(rayPos, spheres.at(idx).pos); if (closestResult) { if (dist < std::get<1>(*closestResult)) { closestResult = {{*result, dist, idx}}; } } else { closestResult = {{*result, dist, idx}}; } } } if (!closestResult) { continue; } // cast ray to each light checking if colliding with other // spheres for (const auto &light : lights) { glm::vec3 toLight = light.pos - std::get<0>(*closestResult); glm::vec3 toLightUnit = toLight / std::sqrt(toLight.x * toLight.x + toLight.y * toLight.y + toLight.z * toLight.z); bool isBlocked = false; for (unsigned int idx = 0; idx < spheres.size(); ++idx) { if (idx == std::get<2>(*closestResult)) { continue; } auto result = spheres.at(idx).rayToSphere(std::get<0>(*closestResult), toLightUnit); if (result) { isBlocked = true; break; } } if (isBlocked) { continue; } // at this point, it is known that no spheres blocks ray // to light light.applyLight(std::get<0>(*closestResult), image.getPixel(i, j)); } } }; if (threadCount <= 1) { for (unsigned int j = 0; j < outputHeight; ++j) { yIteration(j); } } else { std::vector threads; unsigned int range = outputHeight / threadCount; for (unsigned int threadIdx = 0; threadIdx < threadCount; ++threadIdx) { unsigned int start = range * threadIdx; unsigned int end = range * (threadIdx + 1); if (threadIdx + 1 == threadCount) { end = outputHeight; } threads.emplace_back(std::thread( [&yIteration](unsigned int start, unsigned int end) { for (unsigned int y = start; y < end; ++y) { yIteration(y); } }, start, end)); } for (std::thread &thread : threads) { thread.join(); } } return image; }