diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bd7f45..8fb3d54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,9 @@ add_executable(tracey_ex4 ex4.cpp) target_link_libraries(tracey_ex4 tracey) +add_executable(tracey_ex5 ex5.cpp) +target_link_libraries(tracey_ex5 tracey) + diff --git a/ex5.cpp b/ex5.cpp new file mode 100644 index 0000000..a2ede32 --- /dev/null +++ b/ex5.cpp @@ -0,0 +1,54 @@ +#include +#include + +#include "camera/perspectivecamera.h" + +#include "scene/fastscene.h" + +#include "renderer/kdtreerenderer.h" +#include "renderer/simplerenderer.h" + +#include "shader/cooktorranceshader.h" + +#include "light/ambientlight.h" +#include "light/pointlight.h" +#include "light/spotlight.h" + +#include "primitive/objmodel.h" + +int main() { + FastScene scene; + scene.setEnvironmentMap(std::make_shared("data/space.png")); + + // Set up the camera + PerspectiveCamera camera; + camera.setFovAngle(90.0f); + camera.setPosition(Vector3d(0.0f, -2.5f, 10.0f)); + camera.setForwardDirection(Vector3d(0.0f, 0.6f, -1.0f)); + camera.setUpDirection(Vector3d(0.0f, 1.0f, 0.0f)); + + // Add shaders + auto redCook = std::make_shared(Color(1.0f, 0.0f, 0.0f), Color(1.0f, 1.0f, 1.0f), 1.0f, 0.3f); + auto goldCook = std::make_shared(Color(0.83f, 0.69f, 0.22f), Color(1.0f, 1.0f, 0.0f), 1.2f, 0.2f); + + scene.addObj("data/teapot_stadium.obj", Vector3d(1.0f, 1.0f, 1.0f) * 40.0f, Vector3d(-0.25f, -0.5f, -2.5f), goldCook); + scene.addObj("data/stadium.obj", Vector3d(1.0f, 1.0f, 1.0f) * 70.0f, Vector3d(-0.5f, -1.0f, -3.0f), redCook); + + // Add lights + scene.add(std::make_shared(0.1f)); + scene.add(std::make_shared(Vector3d(-0.25f, 20.0f, -3.0f), 60.0f)); + scene.add( + std::make_shared(Vector3d(-0.25f, 7.0f, -4.0f), Vector3d(0.0f, -1.0f, 0.0f), 10.0f, 30.0f, 25.0f)); + + // build the tree + scene.buildTree(); + + // Render the scene + SimpleRenderer renderer; + renderer.renderImage(scene, camera, 1920, 1080).save("result.png"); + + KDTreeRenderer kd_renderer; + kd_renderer.renderKDTree(scene, camera, 1920, 1080).save("result_kd.png"); + + return 0; +} diff --git a/primitive/triangle.cpp b/primitive/triangle.cpp index 326b27f..d4c048b 100644 --- a/primitive/triangle.cpp +++ b/primitive/triangle.cpp @@ -112,12 +112,13 @@ bool Triangle::intersect(Ray &ray) const { return false; // Calculate the normal - // IMPLEMENT smooth triangles, if available - if (this->normal[0] == Vector3d{} || this->normal[1] == Vector3d{} || this->normal[2] == Vector3d{}) { - ray.normal = normalized(crossProduct(edge1, edge2)); - } else { - ray.normal = u * this->normal[1] + v * this->normal[2] + (1 - u - v) * this->normal[0]; - } + if (length(this->normal[0]) * length(this->normal[1]) * length(this->normal[2]) > EPSILON) + ray.normal = normalized(u * this->normal[1] + v * this->normal[2] + (1 - u - v) * this->normal[0]); + else + ray.normal = normalized(crossProduct(edge1, edge2)); + // calculate the tangent and bitangent vectors as well + ray.tangent = normalized(u * this->tangent[1] + v * this->tangent[2] + (1 - u - v) * this->tangent[0]); + ray.bitangent = normalized(u * this->bitangent[1] + v * this->bitangent[2] + (1 - u - v) * this->bitangent[0]); // Calculate the surface position diff --git a/renderer/kdtreerenderer.cpp b/renderer/kdtreerenderer.cpp new file mode 100644 index 0000000..b4ae141 --- /dev/null +++ b/renderer/kdtreerenderer.cpp @@ -0,0 +1,18 @@ +#include "camera/camera.h" +#include "renderer/kdtreerenderer.h" +#include "scene/fastscene.h" +#include + +Texture KDTreeRenderer::renderImage(Scene const &scene, Camera const &camera, int width, int height) { + Texture image(width, height); + std::cout << "KD Tree renderer will only output KD Tree visualization" << std::endl; + return image; +} + +Texture KDTreeRenderer::renderKDTree(FastScene const &scene, Camera const &camera, int width, int height) { + Texture image(width, height); + + // IMPLEMENT ME + + return image; +} \ No newline at end of file diff --git a/renderer/kdtreerenderer.h b/renderer/kdtreerenderer.h new file mode 100644 index 0000000..9a1a15c --- /dev/null +++ b/renderer/kdtreerenderer.h @@ -0,0 +1,22 @@ +#ifndef KDTREERENDERER_H +#define KDTREERENDERER_H + +#include "renderer/renderer.h" + +// Forward Declaration +class FastScene; + +class KDTreeRenderer : public Renderer { + +public: + // Constructor / Destructor + KDTreeRenderer() = default; + ~KDTreeRenderer() override = default; + + // Render functions + Texture renderImage(Scene const &scene, Camera const &camera, int width, int height) override; + + Texture renderKDTree(FastScene const &scene, Camera const &camera, int width, int height); +}; + +#endif \ No newline at end of file diff --git a/scene/fastscene.cpp b/scene/fastscene.cpp new file mode 100644 index 0000000..158ee4c --- /dev/null +++ b/scene/fastscene.cpp @@ -0,0 +1,73 @@ +#include "primitive/primitive.h" +#include "scene/fastscene.h" +#include "shader/shader.h" +#include +#include + +int Node::countNodeIntersections(const Ray &ray, float t0, float t1) const { + // IMPLEMENT ME + return 0; +} + +bool Node::findIntersection(Ray &ray, float t0, float t1) const { + // IMPLEMENT ME + // If this is a leaf node, we intersect with all the primitives... + // ... otherwise we continue through the branches + // Determine the order in which we intersect the child nodes + // Traverse the necessary children + return false; +} + +bool Node::findOcclusion(Ray &ray, float t0, float t1) const { + // IMPLEMENT ME + return false; +} + +int FastScene::countNodeIntersections(const Ray &ray) const { + // IMPLEMENT ME + return false; +} + +bool FastScene::findIntersection(Ray &ray) const { + // IMPLEMENT ME + return false; +} + +bool FastScene::findOcclusion(Ray &ray) const { + // IMPLEMENT ME + return false; +} + +void FastScene::buildTree(int maximumDepth, int minimumNumberOfPrimitives) { + // IMPLEMENT ME + // Set the new depth and number of primitives + + // Determine the bounding box of the kD-Tree + + // Recursively build the kD-Tree +} + +std::unique_ptr FastScene::build(Vector3d const &minimumBounds, Vector3d const &maximumBounds, const std::vector> &primitives, int depth) { + // IMPLEMENT ME + // Determine the diameter of the bounding box + + // Test whether we have reached a leaf node... + + // ... otherwise create a new inner node by splitting through the widest + // dimension + + // Determine the split position + // Note: Use the median of the minimum bounds of the primitives + + // Divide primitives into the left and right lists + // Remember: A primitive can be in both lists! + // Also remember: You split exactly at the minimum of a primitive, + // make sure *that* primitive does *not* appear in both lists! + + // Print out the number of primitives in the left and right child node + + // Set the left and right split vectors + + // Recursively build the tree + return nullptr; +} diff --git a/scene/fastscene.h b/scene/fastscene.h new file mode 100644 index 0000000..fd59324 --- /dev/null +++ b/scene/fastscene.h @@ -0,0 +1,46 @@ +#ifndef FASTSCENE_H +#define FASTSCENE_H + +#include "scene/scene.h" + +struct Node { + // Constructor / Destructor + Node() : dimension(0), split(0) {} + + // Traversal function + bool findIntersection(Ray &ray, float t0, float t1) const; + bool findOcclusion(Ray &ray, float t0, float t1) const; + int countNodeIntersections(const Ray &ray, float t0, float t1) const; + inline bool isLeaf() const { return (!this->primitives.empty() || (!this->child[0] && !this->child[1])); } + + // Branch split + std::unique_ptr child[2]; + int dimension; + float split; + + // Leaf primitives + std::vector> primitives; +}; + +class FastScene : public Scene { + +public: + // Raytracing functions + bool findIntersection(Ray &ray) const override; + bool findOcclusion(Ray &ray) const override; + int countNodeIntersections(const Ray &ray) const; + + // Setup functions + void buildTree(int maximumDepth = 10, int minimumNumberOfPrimitives = 2); + +private: + std::unique_ptr build(Vector3d const &minimumBounds, Vector3d const &maximumBounds, + const std::vector> &primitives, int depth); + + std::unique_ptr root; + int maximumDepth; + int minimumNumberOfPrimitives; + Vector3d absoluteMinimum, absoluteMaximum; +}; + +#endif diff --git a/shader/brdfshader.cpp b/shader/brdfshader.cpp index 17271af..07f054d 100644 --- a/shader/brdfshader.cpp +++ b/shader/brdfshader.cpp @@ -8,90 +8,42 @@ BrdfShader::BrdfShader(char const *fileName, Color const &scale) : scale(scale), brdf(std::make_unique(fileName)) {} Color BrdfShader::shade(Scene const &scene, Ray const &ray) const { - Color illuminationColor; - /* - * Arvids Code - static auto rebase = [](Vector3d const &axis_x, - Vector3d const &axis_y, - Vector3d const &axis_z, - Vector3d const &vec) { - auto det = axis_x.x * axis_y.y * axis_z.z + - axis_x.y * axis_y.z * axis_z.x + - axis_x.z * axis_y.x * axis_z.y - - axis_x.x * axis_y.z * axis_z.y - - axis_x.y * axis_y.x * axis_z.z - - axis_x.z * axis_y.y * axis_z.x; - // Calculate resulting vector by using inverse matrix of new base vectors - return Vector3d{ - ((axis_y.y * axis_z.z - axis_y.z * axis_z.y) * vec.x + - (axis_y.z * axis_z.x - axis_y.x * axis_z.z) * vec.y + - (axis_y.x * axis_z.y - axis_z.x * axis_y.y) * vec.z) / det, - ((axis_x.z * axis_z.y - axis_x.y * axis_z.z) * vec.x + - (axis_x.x * axis_z.z - axis_x.z * axis_z.x) * vec.y + - (axis_z.x * axis_x.y - axis_x.x * axis_z.y) * vec.z) / det, - ((axis_x.y * axis_y.z - axis_x.z * axis_y.y) * vec.x + - (axis_y.x * axis_x.z - axis_x.x * axis_y.z) * vec.y + - (axis_x.x * axis_y.y - axis_y.x * axis_x.y) * vec.z) / det - }; - }; + // Calculate theta and phi + float thetaIn = std::acos(dotProduct(-ray.normal, ray.direction)); + float phiIn = 0.0f; - for (auto &light: scene.lights()) { + // Derive local coordinate system + Vector3d const x = crossProduct(-ray.direction, ray.normal); + Vector3d const y = crossProduct(ray.normal, x); - auto illum = light->illuminate(scene, ray); - if (dotProduct(ray.normal, illum.direction) <= EPSILON) { - continue; - } + // Accumulate the light over all light sources + Color illuminationColor; + for (const auto &light : scene.lights()) { + Light::Illumination illum; + illum = light->illuminate(scene, ray); - auto axis = orthoNormalized(ray.normal, ray.direction, illum.direction); - auto axis_y = std::get<0>(axis); - auto axis_x = std::get<1>(axis); - auto axis_z = std::get<2>(axis); + // Diffuse term + float const cosine = dotProduct(-illum.direction, ray.normal); + if (cosine > 0) { + Color color; - auto N = normalized(rebase(axis_x, axis_y, axis_z, ray.normal)); - auto D = normalized(rebase(axis_x, axis_y, axis_z, -ray.direction)); - auto L = normalized(rebase(axis_x, axis_y, axis_z, -illum.direction)); - D = axis_y * D.y + axis_z * D.z; - L = axis_y * L.y + axis_z * L.z; + // Avoid numeric instability + if (cosine < 1) { + float const thetaOut = std::acos(cosine); - illuminationColor += brdf->lookupBrdfValues(std::acos(dotProduct(ray.normal, -ray.direction)), - 0.0f, - std::acos(dotProduct(illum.direction, ray.normal)), - std::acos(dotProduct(D, L))); + // Project outgoing vector into local coordinate system + Vector3d const c = crossProduct(-illum.direction, ray.normal); + float const phiOut = std::atan2(dotProduct(c, y), dotProduct(c, x)); + + color = Color(brdf->lookupBrdfValues(thetaIn, phiIn, thetaOut, phiOut)); + } else { + color = Color(brdf->lookupBrdfValues(thetaIn, phiIn, 0, 0)); + } + + // Calculate colors + Color const diffuseColor = scale * color * cosine; + illuminationColor += diffuseColor * illum.color; } - */ - // IMPLEMENT ME - - for(auto& light : scene.lights()){ - Light::Illumination illum = light->illuminate(scene, ray); - - Vector3d invertedDirection = -ray.direction; - Vector3d invertedIllum = -illum.direction; - - // the dot-product cant be negative otherwise the light ray would come from the inside - if(dotProduct(invertedIllum, ray.normal) < 0) - continue; - - // Calculate coordinate System - Vector3d axisX = crossProduct(invertedDirection, ray.normal); - Vector3d axisY = crossProduct(axisX, ray.normal); - - // Project ray.direction and illum.direction into plane - Vector2d projectedIllum = Vector2d(dotProduct(axisX, invertedIllum), dotProduct(axisY, invertedIllum)); - Vector2d projectedDirection = Vector2d(dotProduct(axisX, invertedDirection), dotProduct(axisY, invertedDirection)); - - // get Z coordinate for theta angles - double coordinateZDirection = dotProduct(ray.normal, invertedDirection); - double coordinateZIllum = dotProduct(ray.normal, invertedIllum); - - // calculate theta1 and 2 - double theta1Correct = std::atan2(length(projectedDirection), coordinateZDirection); - double theta2Correct = std::atan2(length(projectedIllum), coordinateZIllum); - - // calculate phi - double phi = std::atan2(projectedIllum.u, projectedIllum.v); - - illuminationColor += brdf->lookupBrdfValues(theta1Correct, 0.0, theta2Correct, phi); - - } - return illuminationColor; + } + return illuminationColor; } diff --git a/shader/cooktorranceshader.cpp b/shader/cooktorranceshader.cpp index b2ad327..a57414b 100644 --- a/shader/cooktorranceshader.cpp +++ b/shader/cooktorranceshader.cpp @@ -7,16 +7,51 @@ CookTorranceShader::CookTorranceShader(Color const &diffCol, Color const &ctCol, ctColor(ctCol * ctCoeff), F0(IOR), m(roughness) {} +float CookTorranceShader::D(float NdotH) const { + // Beckmann distribution + float const r2 = m * m; + float const NdotH2 = NdotH * NdotH; + return expf((NdotH2 - 1.0f) / (r2 * NdotH2)) / (4.0f * r2 * powf(NdotH, 4.0f)); +} + +float CookTorranceShader::F(float VdotH) const { + // Schlicks approximation + return F0 + (1.0f - F0) * powf(1.0f - VdotH, 5); +} + +float CookTorranceShader::G(float NdotH, float NdotV, float VdotH, float NdotL) const { return std::min(1.0f, std::min(2.0f * NdotH * NdotV / VdotH, 2.0f * NdotH * NdotL / VdotH)); } + Color CookTorranceShader::shade(Scene const &scene, Ray const &ray) const { Color fragmentColor; - // IMPLEMENT ME - for (auto &light: scene.lights()) { - auto illum = light->illuminate(scene, ray); - auto N = ray.normal; - auto V = ray.direction; - auto L = illum.direction; - auto H = normalized(V + L); + if (m >= 0.0f) { + // Accumulate the light over all light sources + for (const auto &light : scene.lights()) { + Light::Illumination illum; + illum = light->illuminate(scene, ray); + + float const NdotL = std::max(0.0f, dotProduct(-illum.direction, ray.normal)); + if (NdotL <= 0.0f) + continue; + + // Diffuse term + Color const diffuse = this->diffuseColor / float(PI); + fragmentColor += diffuse * NdotL * illum.color; + + // Cook-Torrance term + // half angle vector + Vector3d const H = normalized(-illum.direction - ray.direction); + float const NdotH = std::max(0.0f, dotProduct(ray.normal, H)); + float const NdotV = std::max(0.0f, dotProduct(ray.normal, -ray.direction)); + float const VdotH = std::max(0.0f, dotProduct(-ray.direction, H)); + + if (NdotV * NdotL > EPSILON) { + Color const specular = this->ctColor * (F(VdotH) * D(NdotH) * G(NdotH, NdotV, VdotH, NdotL)) / (float(PI) * NdotV * NdotL); + + fragmentColor += specular * NdotL * illum.color; + } + } + } auto NV = dotProduct(N, V); auto NL = dotProduct(N, L); diff --git a/shader/lambertshader.cpp b/shader/lambertshader.cpp index 5e94254..0e2c4a6 100644 --- a/shader/lambertshader.cpp +++ b/shader/lambertshader.cpp @@ -7,11 +7,13 @@ LambertShader::LambertShader(Color const &diffuseColor) : diffuseColor(diffuseCo Color LambertShader::shade(Scene const &scene, Ray const &ray) const { Color fragmentColor; - // IMPLEMENT ME - for (auto& light : scene.lights()) { - auto illum = light->illuminate(scene, ray); - auto lightAngle = dotProduct(ray.normal, normalized(illum.direction)); - fragmentColor += illum.color * std::abs(lightAngle); + // Accumulate the light over all light sources + for (const auto &light : scene.lights()) { + Light::Illumination const illum = light->illuminate(scene, ray); + // Diffuse term + Color const diffuse = this->diffuseColor * std::max(dotProduct(-illum.direction, ray.normal), 0.0f); + fragmentColor += diffuse * illum.color; } - return fragmentColor * diffuseColor; + + return fragmentColor; } diff --git a/shader/phongshader.cpp b/shader/phongshader.cpp index 10390b4..5c31218 100644 --- a/shader/phongshader.cpp +++ b/shader/phongshader.cpp @@ -10,6 +10,27 @@ PhongShader::PhongShader(Color const &diffuseColor, float diffuseCoefficient, Co Color PhongShader::shade(Scene const &scene, Ray const &ray) const { Color fragmentColor; + // Calculate the reflection vector + Vector3d const reflection = ray.direction - 2 * dotProduct(ray.normal, ray.direction) * ray.normal; + + // Accumulate the light over all light sources + for (const auto &light : scene.lights()) { + Light::Illumination illum; + illum = light->illuminate(scene, ray); + + // Diffuse term + Color const diffuse = + this->diffuseCoefficient * this->diffuseColor * std::max(dotProduct(-illum.direction, ray.normal), 0.0f); + fragmentColor += diffuse * illum.color; + + // Specular term + float const cosine = dotProduct(-illum.direction, reflection); + if (cosine > 0) { + Color const specular = this->specularCoefficient * this->specularColor // highlight + * powf(cosine, this->shininessExponent); // shininess factor + fragmentColor += specular * illum.color; + } + } for (auto &light: scene.lights()) { auto illum = light->illuminate(scene, ray);