diff --git a/CMakeLists.txt b/CMakeLists.txt index 836320d..466550e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,10 +24,13 @@ file(GLOB primitive_src "primitive/*.cpp") file(GLOB renderer_src "renderer/*.cpp") file(GLOB scene_src "scene/*.cpp") file(GLOB shader_src "shader/*.cpp") +file(GLOB effect_src "effect/*.cpp") +file(GLOB post_processing_src "post_processing/*.cpp") # The tracey library add_library(tracey STATIC ${common_src} ${noise_src} ${camera_src} ${light_src} - ${primitive_src} ${renderer_src} ${scene_src} ${shader_src} light/ambientlight.cpp light/ambientlight.h light/spotlight.cpp light/spotlight.h) + ${primitive_src} ${renderer_src} ${scene_src} ${shader_src} ${effect_src} + ${post_processing_src}) if(NOT WIN32) target_link_libraries(tracey ${CMAKE_THREAD_LIBS_INIT} ${X11_LIBRARIES}) endif() @@ -72,6 +75,10 @@ endif() add_executable(fancy1 fancy1.cpp) target_link_libraries(fancy1 tracey) +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0") + +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0") + diff --git a/camera/camera.h b/camera/camera.h index 93d82bf..2933d46 100644 --- a/camera/camera.h +++ b/camera/camera.h @@ -12,6 +12,12 @@ public: // Camera functions virtual Ray createRay(float x, float y) const = 0; + + // Setter methods + virtual Vector3d getPosition() const = 0; + virtual Vector3d getRightDirection() const = 0; + virtual Vector3d getUpDirection() const = 0; }; + #endif diff --git a/camera/perspectivecamera.h b/camera/perspectivecamera.h index 638f392..d935200 100644 --- a/camera/perspectivecamera.h +++ b/camera/perspectivecamera.h @@ -13,11 +13,6 @@ public: // Set void setPosition(Vector3d const &position) { this->position = position; } void setForwardDirection(Vector3d const &forwardDirection) { - // IMPLEMENT ME - // Set up a left-handed coordinate system, - // in which the camera looks along the positive z-Axis - // Set up a left-handed coordinate system, - // in which the camera looks along the positive z-Axis std::tie(this->forwardDirection, this->upDirection, this->rightDirection) = orthoNormalized(forwardDirection, this->upDirection, crossProduct(this->upDirection, forwardDirection)); } void setUpDirection(Vector3d const &upDirection) { @@ -36,6 +31,12 @@ public: // Camera functions Ray createRay(float x, float y) const override; + //Getter + Vector3d getPosition() const override { return position; } + Vector3d getRightDirection() const override{ return rightDirection; } + Vector3d getUpDirection() const override { return upDirection; } + + protected: Vector3d position; Vector3d forwardDirection; diff --git a/common/texture.cpp b/common/texture.cpp index f7f0b70..30a5238 100644 --- a/common/texture.cpp +++ b/common/texture.cpp @@ -3,6 +3,7 @@ #include #include #include +#include Texture::Texture(int width, int height) { this->resize(width, height); } @@ -83,3 +84,12 @@ Color Texture::color(float u, float v, bool interpolate) const { Color Texture::color(Vector2d const &surfacePosition, bool interpolate) const { return color(surfacePosition.u, surfacePosition.v, interpolate); } + +CImg Texture::getImage() { + return image_; +} + +void Texture::setTexture(CImg image){ + image_ = std::move(image); +} + diff --git a/common/texture.h b/common/texture.h index 3350a1c..4eb21c6 100644 --- a/common/texture.h +++ b/common/texture.h @@ -9,30 +9,34 @@ using namespace cimg_library; class Texture { public: - // Constructor - Texture(int width, int height); - Texture(char const *fileName); + // Constructor + Texture(int width, int height); + Texture(char const *fileName); - // Image functions - inline void resize(int width, int height) { this->image_.resize(width, height, 1, 3); } - bool load(char const *fileName); - bool save(char const *fileName) const; + // Image functions + inline void resize(int width, int height) { this->image_.resize(width, height, 1, 3); } + bool load(char const *fileName); + bool save(char const *fileName) const; - // Get - inline bool isNull() const { return this->image_.is_empty(); } - inline int width() const { return this->image_.width(); } - inline int height() const { return this->image_.height(); } - Color getPixelAt(int x, int y) const; + // Get + inline bool isNull() const { return this->image_.is_empty(); } + inline int width() const { return this->image_.width(); } + inline int height() const { return this->image_.height(); } + Color getPixelAt(int x, int y) const; - // Set - void setPixelAt(int x, int y, Color const &color); + // Set + void setPixelAt(int x, int y, Color const &color); + void setTexture(CImg image); - // Color functions - Color color(float u, float v, bool interpolate = true) const; - Color color(Vector2d const &surfacePosition, bool interpolate = true) const; + + // Color functions + Color color(float u, float v, bool interpolate = true) const; + Color color(Vector2d const &surfacePosition, bool interpolate = true) const; + + CImg getImage(); private: - CImg image_; + CImg image_; }; #endif diff --git a/ex4.cpp b/ex4.cpp index cadf20c..ee1154a 100644 --- a/ex4.cpp +++ b/ex4.cpp @@ -16,6 +16,7 @@ #include "shader/mirrorshader.h" #include "shader/phongshader.h" #include "shader/cooktorranceshader.h" +#include "renderer/depthoffieldrenderer.h" #include "light/ambientlight.h" #include "light/pointlight.h" @@ -25,6 +26,13 @@ int main() { // Let's create a simple scene... SimpleScene scene; + // Set up the camera + PerspectiveCamera camera; + camera.setFovAngle(90.0f); + camera.setPosition(Vector3d(0.0f, 0.0f, -10.0f)); + camera.setForwardDirection(Vector3d(0.0f, 0.0f, 1.0f)); + camera.setUpDirection(Vector3d(0.0f, 1.0f, 0.0f)); + // Add shaders auto mirror = std::make_shared(); auto white = std::make_shared(Color(0.9f, 0.9f, 0.9f)); @@ -32,11 +40,16 @@ int main() { auto blue = std::make_shared(Color(0.2f, 0.3f, 1.0f)); auto orange = std::make_shared(Color(1.0f, 0.64f, 0.0f), 1.0f, Color(1.0f, 1.0f, 1.0f), 1.0f, 25.0f); auto gold= std::make_shared(Color(0.83f, 0.69f, 0.22f), Color(1.0f, 1.0f, 0.0f), 1.2f, 0.2f); - auto blueMetallic = std::make_shared("data/blue-metallic-paint.binary", Color(7.0f, 7.0f, 7.0f)); - auto darkRed = std::make_shared("data/dark-red-paint.binary", Color(7.0f, 7.0f, 7.0f)); + auto blueMetallic = std::make_shared("../data/blue-metallic-paint.binary", Color(7.0f, 7.0f, 7.0f)); + auto darkRed = std::make_shared("../data/dark-red-paint.binary", Color(7.0f, 7.0f, 7.0f)); + + // DOF Shader + bool dofShader = true; + // Set up the walls // --------------------------------------------------------------------------- + scene.add(std::make_shared(Vector3d(0.0f, 0.0f, +5.0f), Vector3d(0.0f, 0.0f, -1.0f), mirror)); scene.add(std::make_shared(Vector3d(0.0f, 0.0f, -5.0f), Vector3d(0.0f, 0.0f, +1.0f), mirror)); @@ -48,27 +61,34 @@ int main() { scene.add(std::make_shared(Vector3d(-3.0f, 0.0f, 0.0f), 1.0f, blueMetallic)); scene.add(std::make_shared(Vector3d(0.0f, 2.0f, 0.0f), 1.0f, orange)); scene.add(std::make_shared(Vector3d(3.0f, 0.0f, 0.0f), 1.0f, darkRed)); + scene.add(std::make_shared(Vector3d(-3.0f, -3.3f, -4.0f), 1.0f, mirror)); // Add the teapot auto teapot = std::make_shared(gold); - teapot->loadObj("data/teapot.obj", Vector3d(3.0f, 3.0f, 3.0f), Vector3d(0.0f, -5.0f, 0.0f)); + teapot->loadObj("../data/teapot.obj", Vector3d(3.0f, 3.0f, 3.0f), Vector3d(0.0f, -5.0f, 0.0f)); scene.add(teapot); // Add ambient light scene.add(std::make_shared(0.15f)); - scene.add(std::make_shared(Vector3d(0.0f, 4.0f, -4.0f), 15.0f)); - scene.add(std::make_shared(Vector3d(0.0f, 4.0f, 4.0f), 15.0f)); + //scene.add(std::make_shared(Vector3d(0.0f, 4.0f, -4.0f), 15.0f)); + scene.add(std::make_shared(Vector3d(0.0f, 2.5f, -4.0f), 7.0f)); + - // Set up the camera - PerspectiveCamera camera; - camera.setFovAngle(90.0f); - camera.setPosition(Vector3d(0.0f, 0.0f, -10.0f)); - camera.setForwardDirection(Vector3d(0.0f, 0.0f, 1.0f)); - camera.setUpDirection(Vector3d(0.0f, 1.0f, 0.0f)); // Render the scene - SimpleRenderer renderer; - renderer.renderImage(scene, camera, 512, 512).save("result.png"); + // SimpleRenderer renderer; + // renderer.renderImage(scene, camera, 1024, 1024).save("result.png"); + + + // initialize renderer: aperture = lens thickness, secondaryRayCount = how many rays per pixel are created + // focalLength = the area which is in focus + DOFRenderer renderer(0.2, 100, 10.0f); + + // Use DOFRenderer to raytrace + Texture image = renderer.renderImage(scene, camera, 1024, 1024); + + // save image + image.save("result.png"); return 0; } diff --git a/post_processing/bloom.cpp b/post_processing/bloom.cpp new file mode 100644 index 0000000..93291fe --- /dev/null +++ b/post_processing/bloom.cpp @@ -0,0 +1,88 @@ + +#include +#include "bloom.h" + +Bloom::Bloom(CImg image) : image(image) {} + + +CImg Bloom::bloom(float threshold, int kernelSize, float sigma, float intensity) { + // Apply threshold to image + //CImg brightPixels = image_.get_threshold(threshold); + //brightPixels.save("brightpixels.png"); + + // Apply gaussian blur to bright pixels + CImg kernel = computeGaussianKernel(kernelSize, sigma); + CImg blurred = convolution(image, kernel); + for(int i = 0; i < 3; i++){ + kernel = computeGaussianKernel(kernelSize, sigma); + blurred = convolution(image, kernel); + blurred *= intensity; + } + + // Add blurred image back to original image + cimg_forXYC(image, x, y, c) { + float value = image(x,y,0,c) + blurred(x,y,0,c); + image(x,y,0,c) = (value > 1.0f) ? 1.0f : value; + } + + return image; +} + +void Bloom::gaussianBlur(int kernelSize, float sigma) { + CImg kernel = computeGaussianKernel(kernelSize, sigma); + image = convolution(image, kernel); +} + +// Function to compute Gaussian kernel +CImg Bloom::computeGaussianKernel(int kernelSize, float sigma) { + // Create kernel + CImg kernel(kernelSize, kernelSize, 1, 1); + + // Compute Gaussian kernel + float sum = 0.0f; + int i, j; + for (i = 0; i < kernelSize; i++) { + for (j = 0; j < kernelSize; j++) { + kernel(i, j) = exp(-0.5f * (pow((i - kernelSize / 2.f) / sigma, 2.f) + + pow((j - kernelSize / 2.f) / sigma, 2.f))) / (2 * M_PI * sigma * sigma); + sum += kernel(i, j); + } + } + + // Normalize kernel + kernel /= sum; + + return kernel; +} + +// Function to perform convolution +CImg Bloom::convolution(CImg &img, CImg &kernel) { + int kernelSize = kernel.width(); + int imgRows = img.height(); + int imgCols = img.width(); + CImg result(imgCols, imgRows, 1, 3); + float sum; + int i, j, m, n; + int kernelRadius = kernelSize / 2; + + // Perform convolution + cimg_forXYC(img, i, j, c) { + sum = 0; + cimg_forY(kernel, m) { + cimg_forX(kernel, n) { + int x = i + n - kernelRadius; + int y = j + m - kernelRadius; + if(x >= 0 && x < imgCols && y >= 0 && y < imgRows){ + sum += img(x, y, 0, c) * kernel(n, m); + } + + } + } + result(i, j, 0, c) = sum; + } + return result; +} + +void Bloom::scaleBrightness(float scale) { + image *= scale; +} diff --git a/post_processing/bloom.h b/post_processing/bloom.h new file mode 100644 index 0000000..694b0d8 --- /dev/null +++ b/post_processing/bloom.h @@ -0,0 +1,27 @@ + +#ifndef CG1_TRACER_BLOOM_H +#define CG1_TRACER_BLOOM_H + +#include "common/texture.h" +#include "common/vector3d.h" + +class Bloom { + +public: + Bloom(CImg image); + CImg bloom(float threshold, int kernelSize, float sigma, float intensity); + +private: + void scaleBrightness(float scale); + void gaussianBlur(int kernelSize, float sigma); + + CImg convolution(CImg &img, CImg &kernel); + CImg computeGaussianKernel(int kernelSize, float sigma); + + + + CImg image; +}; + + +#endif //CG1_TRACER_BLOOM_H diff --git a/renderer/depthoffieldrenderer.cpp b/renderer/depthoffieldrenderer.cpp new file mode 100644 index 0000000..aba7d57 --- /dev/null +++ b/renderer/depthoffieldrenderer.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include "depthoffieldrenderer.h" +#include +#include "post_processing/bloom.h" + + +DOFRenderer::DOFRenderer(float _aperture, int _secondaryRayCount, float _focalLength) : aperture(_aperture), + numSamples(_secondaryRayCount), focalLength(_focalLength) {} + +std::random_device DOFRenderer::rd; +std::mt19937 DOFRenderer::gen(DOFRenderer::rd()); + +Color DOFRenderer::sample(const Ray &ray, const Scene& scene, const Camera& camera) const { + + std::uniform_real_distribution dis(-1.0, 1.0); + + // calculate the point of focus + Vector3d focusPoint = ray.origin + ray.direction * focalLength; + + // Final color + Color finalColor; + + // Calculate all secondary Rays + for (int i = 0; i < numSamples; i++) { + // create a random point on the aperture + Vector3d rnd = Vector3d(dis(gen), dis(gen), 0); + Vector3d apertureOffset = aperture * (rnd.x * camera.getRightDirection() + rnd.y * camera.getUpDirection()); + + // create the new ray with the offset point + Vector3d dofRayOrigin = ray.origin + apertureOffset; + Vector3d dofRayDirection = normalized(focusPoint - dofRayOrigin); + Ray dofRay(dofRayOrigin, dofRayDirection); + + // get Color of the new Ray + finalColor += scene.traceRay(dofRay); + } + + + // trace the new ray and return the color + return finalColor /= float(numSamples);; +} + +void DOFRenderer::renderThread(const Scene *scene, Camera const *camera, Texture *image, const DOFRenderer *renderer, int width, int widthStep, int widthOffset, int height, int heightStep, int heightOffset, std::atomic *k, int const stepSize) { + float const aspectRatio = static_cast(height) / width; + for (int y = heightOffset; y < height; y += heightStep) { + for (int x = widthOffset; x < width; x += widthStep) { + Ray ray = camera->createRay((static_cast(x) / width * 2 - 1), -(static_cast(y) / height * 2 - 1) * aspectRatio); + + // Trace rays with DOF + image->setPixelAt(x, y, clamped(renderer->sample(ray, *scene, *camera))); + + // Super hacky progress bar! + if (++*k % stepSize == 0) { + std::cout << "=" << std::flush; + } + } + } +} + + +Texture DOFRenderer::renderImage(Scene const &scene, Camera const &camera, int width, int height) { + Texture image(width, height); + + // Setup timer + std::chrono::steady_clock::time_point start, stop; + + // Reset Ray counting + Ray::resetRayCount(); + + // Super-hacky progress bar! + std::cout << "(SimpleRenderer): Begin rendering..." << std::endl; + std::cout << "| 0%"; + int const barSize = 50; + int const stepSize = (width * height) / barSize; + for (int i = 0; i < barSize - 3 - 5; ++i) + std::cout << " "; + std::cout << "100% |" << std::endl << "|"; + std::atomic k(0); + + /* Start timer */ start = std::chrono::steady_clock::now(); + + // Spawn a thread for every logical processor -1, calling the renderThread function + int const nThreads = std::thread::hardware_concurrency(); + std::vector threads; + for (int t = 0; t < nThreads - 1; ++t) { + threads.emplace_back(renderThread, &scene, &camera, &image, this, width, nThreads, t, height, 1, 0, &k, stepSize); + } + + // Call the renderThread function yourself + renderThread(&scene, &camera, &image, this, width, nThreads, nThreads - 1, height, 1, 0, &k, stepSize); + + // Rejoin the threads + for (int t = 0; t < nThreads - 1; ++t) { + threads[t].join(); + } + + // Stop timer + stop = std::chrono::steady_clock::now(); + + std::cout << "| Done!" << std::endl; + + // Calculate the Time taken in seconds + double seconds = std::chrono::duration_cast>(stop - start).count(); + + std::cout << "Time: " << seconds << "s" << std::endl; + + // Get the number of seconds per ray + int rays = Ray::getRayCount(); + + std::cout << "Paths: " << rays << std::endl; + std::cout << "Paths per second: " << std::fixed << std::setprecision(0) << rays / seconds << std::endl; + + // Post-processing + // Bloom shader + + image.save("original.png"); + + Bloom bloomEffect = Bloom(image.getImage()); + image.setTexture(bloomEffect.bloom(0.55f, 5, 10.0f, 0.06f)); + + return image; +} \ No newline at end of file diff --git a/renderer/depthoffieldrenderer.h b/renderer/depthoffieldrenderer.h new file mode 100644 index 0000000..d195735 --- /dev/null +++ b/renderer/depthoffieldrenderer.h @@ -0,0 +1,35 @@ +#ifndef DEPTHOFFIELDSHADER_H +#define DEPTHOFFIELDSHADER_H + +#include "camera/camera.h" +#include +#include "renderer/renderer.h" +#include "scene/simplescene.h" + + +class DOFRenderer : public Renderer { + static void renderThread(const Scene *scene, const Camera *camera, Texture *image, const DOFRenderer *renderer, int width, int widthStep, + int widthOffset, int height, int heightStep, int heightOffset, std::atomic *k, + const int stepSize); + +public: + // Constructor + DOFRenderer(float _aperture, int _secondaryRayCount, float _focalLength); + ~DOFRenderer() override = default; + + //Render Functions + Texture renderImage(Scene const &scene, Camera const &camera, int width, int height) override; + + // DOF sampler + Color sample(const Ray &ray, const Scene& scene, const Camera& camera) const; + +private: + float aperture, focalLength; + int numSamples; + + static std::random_device rd; + static std::mt19937 gen; + +}; + +#endif \ No newline at end of file diff --git a/renderer/simplerenderer.cpp b/renderer/simplerenderer.cpp index feb2495..3647efc 100644 --- a/renderer/simplerenderer.cpp +++ b/renderer/simplerenderer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "post_processing/bloom.h" void SimpleRenderer::renderThread(const Scene *scene, Camera const *camera, Texture *image, int width, int widthStep, int widthOffset, int height, int heightStep, int heightOffset, std::atomic *k, int const stepSize) { float const aspectRatio = static_cast(height) / width; @@ -73,6 +74,13 @@ Texture SimpleRenderer::renderImage(Scene const &scene, Camera const &camera, in std::cout << "Paths: " << rays << std::endl; std::cout << "Paths per second: " << std::fixed << std::setprecision(0) << rays / seconds << std::endl; + // Post-processing + // Bloom shader + + image.save("original.png"); + + Bloom bloomEffect = Bloom(image.getImage()); + image.setTexture(bloomEffect.bloom(0.55f, 5, 10.0f, 0.06f)); return image; -} +} \ No newline at end of file diff --git a/renderer/simplerenderer.h b/renderer/simplerenderer.h index 21e1f69..c1c9ed2 100644 --- a/renderer/simplerenderer.h +++ b/renderer/simplerenderer.h @@ -3,11 +3,12 @@ #include #include "renderer/renderer.h" +#include class SimpleRenderer : public Renderer { - static void renderThread(const Scene *scene, Camera const *camera, Texture *image, int width, int widthStep, - int widthOffset, int height, int heightStep, int heightOffset, std::atomic *k, - int const stepSize); + static void renderThread(const Scene *scene, const Camera *camera, Texture *image, int width, int widthStep, + int widthOffset, int height, int heightStep, int heightOffset, std::atomic *k, + const int stepSize); public: // Constructor / Destructor