From 04aabb67a5569dd96a661b1e2642a57d111004c1 Mon Sep 17 00:00:00 2001 From: jp Date: Sat, 10 Dec 2022 18:25:29 +0100 Subject: [PATCH 1/2] added task 05 solution --- common/ray.cpp | 2 +- common/ray.h | 2 +- renderer/kdtreerenderer.cpp | 27 +++++- renderer/simplerenderer.cpp | 40 +++++--- renderer/simplerenderer.h | 4 + scene/fastscene.cpp | 189 ++++++++++++++++++++++++++++++++---- 6 files changed, 230 insertions(+), 34 deletions(-) diff --git a/common/ray.cpp b/common/ray.cpp index 2ec18a8..22aee2d 100644 --- a/common/ray.cpp +++ b/common/ray.cpp @@ -1,3 +1,3 @@ #include "ray.h" -int Ray::rayCount = 0; +std::atomic Ray::rayCount(0); diff --git a/common/ray.h b/common/ray.h index 060e290..295fbc7 100644 --- a/common/ray.h +++ b/common/ray.h @@ -39,7 +39,7 @@ private: int remainingBounces = ICG_RAY_BOUNCES; #endif - static int rayCount; + static std::atomic rayCount; }; #endif diff --git a/renderer/kdtreerenderer.cpp b/renderer/kdtreerenderer.cpp index b4ae141..890e3d5 100644 --- a/renderer/kdtreerenderer.cpp +++ b/renderer/kdtreerenderer.cpp @@ -11,8 +11,33 @@ Texture KDTreeRenderer::renderImage(Scene const &scene, Camera const &camera, in Texture KDTreeRenderer::renderKDTree(FastScene const &scene, Camera const &camera, int width, int height) { Texture image(width, height); + float const aspectRatio = static_cast(height) / width; - // IMPLEMENT ME + for (int x = 0; x < image.width(); ++x) { + for (int y = 0; y < image.height(); ++y) { + Ray ray = camera.createRay((static_cast(x) / width * 2 - 1), + -(static_cast(y) / height * 2 - 1) * aspectRatio); + + image.setPixelAt(x, y, Color(float(scene.countNodeIntersections(ray)), 0.0f, 0.0f)); + } + } + + // map color to green -> red map + float maxVal = -INFINITY; + for (int x = 0; x < image.width(); ++x) { + for (int y = 0; y < image.height(); ++y) { + maxVal = std::max(maxVal, image.getPixelAt(x, y).r); + } + } + + Color r(1, 0, 0); + Color g(0, 1, 0); + for (int x = 0; x < image.width(); ++x) { + for (int y = 0; y < image.height(); ++y) { + float factor = image.getPixelAt(x, y).r / maxVal; + image.setPixelAt(x, y, (1.0f - factor) * g + factor * r); + } + } return image; } \ No newline at end of file diff --git a/renderer/simplerenderer.cpp b/renderer/simplerenderer.cpp index d455107..48feed9 100644 --- a/renderer/simplerenderer.cpp +++ b/renderer/simplerenderer.cpp @@ -2,9 +2,25 @@ #include "renderer/simplerenderer.h" #include "scene/scene.h" #include +#include #include #include +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; + 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); + image->setPixelAt(x, y, clamped(scene->traceRay(ray))); + + // Super hacky progress bar! + if (++*k % stepSize == 0) { + std::cout << "=" << std::flush; + } + } + } +} + Texture SimpleRenderer::renderImage(Scene const &scene, Camera const &camera, int width, int height) { Texture image(width, height); @@ -22,22 +38,24 @@ Texture SimpleRenderer::renderImage(Scene const &scene, Camera const &camera, in for (int i = 0; i < barSize - 3 - 5; ++i) std::cout << " "; std::cout << "100% |" << std::endl << "|"; - int k = 0; + std::atomic k(0); // Start timer start = std::chrono::steady_clock::now(); - float const aspectRatio = static_cast(height) / width; - for (int x = 0; x < image.width(); ++x) { - for (int y = 0; y < image.height(); ++y) { - Ray ray = camera.createRay((static_cast(x) / width * 2 - 1), -(static_cast(y) / height * 2 - 1) * aspectRatio); - image.setPixelAt(x, y, clamped(scene.traceRay(ray))); + // 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, width, nThreads, t, height, 1, 0, &k, stepSize); + } - // Super hacky progress bar! - if (++k % stepSize == 0) { - std::cout << "=" << std::flush; - } - } + // Call the renderThread function yourself + renderThread(&scene, &camera, &image, 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 diff --git a/renderer/simplerenderer.h b/renderer/simplerenderer.h index ca7eca3..21e1f69 100644 --- a/renderer/simplerenderer.h +++ b/renderer/simplerenderer.h @@ -1,9 +1,13 @@ #ifndef SIMPLERENDERER_H #define SIMPLERENDERER_H +#include #include "renderer/renderer.h" 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); public: // Constructor / Destructor diff --git a/scene/fastscene.cpp b/scene/fastscene.cpp index 158ee4c..eda7167 100644 --- a/scene/fastscene.cpp +++ b/scene/fastscene.cpp @@ -5,69 +5,218 @@ #include int Node::countNodeIntersections(const Ray &ray, float t0, float t1) const { - // IMPLEMENT ME - return 0; + // If this is a leaf node, we return 0 + if (isLeaf()) { + return 0; + } else { + // Determine the order in which we intersect the child nodes + float const d = (this->split - ray.origin[this->dimension]) / ray.direction[this->dimension]; + int front = ray.direction[this->dimension] < 0 ? 1 : 0; + int back = 1 - front; + + if (d <= t0 || d < 0) { + // t0..t1 is totally behind d, only go through the back node. + return this->child[back]->countNodeIntersections(ray, t0, t1); + } else if (d >= t1) { + // t0..t1 is totally in front of d, only go to front node. + return this->child[front]->countNodeIntersections(ray, t0, t1); + } else { + // Traverse *both* children. Front node first, back node last. + // Be sure to get even triangles which are coincident with the splitting plane + return 1 + this->child[front]->countNodeIntersections(ray, t0, d + SPLT_EPS) + this->child[back]->countNodeIntersections(ray, d - SPLT_EPS, t1); + } + } } 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; + if (isLeaf()) { + bool hit = false; + for (const auto &primitive : this->primitives) + hit |= primitive->intersect(ray); + return hit; + } else { // ... otherwise we continue through the branches + + // Determine the order in which we intersect the child nodes + float const d = (this->split - ray.origin[this->dimension]) / ray.direction[this->dimension]; + int front = ray.direction[this->dimension] < 0 ? 1 : 0; + int back = 1 - front; + + if (d <= t0 || d < 0) { + // t0..t1 is totally behind d, only go through the back node. + return this->child[back]->findIntersection(ray, t0, t1); + } else if (d >= t1) { + // t0..t1 is totally in front of d, only go to front node. + return this->child[front]->findIntersection(ray, t0, t1); + } else { + // Traverse *both* children. Front node first and then back node, if no hit in front of splitting plane. + // for whatever reason, this doesn't work in the fireplace scene?! + return ((this->child[front]->findIntersection(ray, t0 - SPLT_EPS, d + SPLT_EPS) && ray.length <= d + SPLT_EPS) || this->child[back]->findIntersection(ray, d - SPLT_EPS, t1 + SPLT_EPS)); + // return (this->child[front]->findIntersection(ray, t0 - SPLT_EPS, d + SPLT_EPS) | this->child[back]->findIntersection(ray, d - SPLT_EPS, t1 + SPLT_EPS)); + } + } } bool Node::findOcclusion(Ray &ray, float t0, float t1) const { - // IMPLEMENT ME - return false; + // If this is a leaf node, we intersect with all the primitives... + if (isLeaf()) { + float const rayLength = ray.length; + for (const auto &primitive : this->primitives) + // since this is correct, but terribly slow:... + // if (!primitive->shader()->isTransparent() && primitive->intersect(ray)) + // return true; + // we do it this way: + if (primitive->intersect(ray)) { + if (!primitive->shader()->isTransparent()) + return true; + else + ray.length = rayLength; + } + return false; + } else { // ... otherwise we continue through the branches + + // Determine the order in which we intersect the child nodes + float const d = (this->split - ray.origin[this->dimension]) / ray.direction[this->dimension]; + int front = ray.direction[this->dimension] < 0 ? 1 : 0; + int back = 1 - front; + + if (d <= t0 || d < 0) { + // t0..t1 is totally behind d, only go through the back node. + return this->child[back]->findOcclusion(ray, t0, t1); + } else if (d >= t1) { + // t0..t1 is totally in front of d, only go to front node. + return this->child[front]->findOcclusion(ray, t0, t1); + } else { + // Traverse *both* children. Front node first and then back node, if no hit in front of splitting plane. + // for whatever reason, this doesn't work in the fireplace scene?! + return ((this->child[front]->findOcclusion(ray, t0, d + SPLT_EPS) && ray.length <= d + SPLT_EPS) || this->child[back]->findOcclusion(ray, d - SPLT_EPS, t1)); + // return (this->child[front]->findOcclusion(ray, t0, d + SPLT_EPS) | this->child[back]->findOcclusion(ray, d - SPLT_EPS, t1)); + } + } } int FastScene::countNodeIntersections(const Ray &ray) const { - // IMPLEMENT ME - return false; + // Make sure the tree is set up + if (!this->root) + return false; + + // Bounding box intersection + Vector3d const min = componentQuotient(this->absoluteMinimum - ray.origin, ray.direction); + Vector3d const max = componentQuotient(this->absoluteMaximum - ray.origin, ray.direction); + float const tMin = std::max(std::max(std::min(min.x, max.x), std::min(min.y, max.y)), std::min(min.z, max.z)); + float const tMax = std::min(std::min(std::max(min.x, max.x), std::max(min.y, max.y)), std::max(min.z, max.z)); + + // Traverse the tree recursively + if (0 <= tMax && tMin <= tMax) + return this->root->countNodeIntersections(ray, tMin, tMax); + else + return 0; } bool FastScene::findIntersection(Ray &ray) const { - // IMPLEMENT ME - return false; + // Make sure the tree is set up + if (!this->root) + return false; + + // Bounding box intersection + Vector3d const min = componentQuotient(this->absoluteMinimum - ray.origin, ray.direction); + Vector3d const max = componentQuotient(this->absoluteMaximum - ray.origin, ray.direction); + float const tMin = std::max(std::max(std::min(min.x, max.x), std::min(min.y, max.y)), std::min(min.z, max.z)); + float const tMax = std::min(std::min(std::max(min.x, max.x), std::max(min.y, max.y)), std::max(min.z, max.z)); + + // Traverse the tree recursively + if (0 <= tMax && tMin <= tMax) + return this->root->findIntersection(ray, tMin, tMax); + else + return false; } bool FastScene::findOcclusion(Ray &ray) const { - // IMPLEMENT ME - return false; + // Make sure the tree is set up + if (!this->root) + return false; + + // Bounding box intersection + Vector3d const min = componentQuotient(this->absoluteMinimum - ray.origin, ray.direction); + Vector3d const max = componentQuotient(this->absoluteMaximum - ray.origin, ray.direction); + float const tMin = std::max(std::max(std::min(min.x, max.x), std::min(min.y, max.y)), std::min(min.z, max.z)); + float const tMax = std::min(std::min(std::max(min.x, max.x), std::max(min.y, max.y)), std::max(min.z, max.z)); + + // Traverse the tree recursively + if (0 <= tMax && tMin <= tMax) + return this->root->findOcclusion(ray, tMin, tMax); + else + return false; } void FastScene::buildTree(int maximumDepth, int minimumNumberOfPrimitives) { - // IMPLEMENT ME // Set the new depth and number of primitives + this->maximumDepth = maximumDepth; + this->minimumNumberOfPrimitives = minimumNumberOfPrimitives; // Determine the bounding box of the kD-Tree + this->absoluteMinimum = Vector3d(+INFINITY, +INFINITY, +INFINITY); + this->absoluteMaximum = Vector3d(-INFINITY, -INFINITY, -INFINITY); + for (const auto &primitive : this->primitives()) { + for (int d = 0; d < 3; ++d) { + this->absoluteMinimum[d] = std::min(this->absoluteMinimum[d], primitive->minimumBounds(d)); + this->absoluteMaximum[d] = std::max(this->absoluteMaximum[d], primitive->maximumBounds(d)); + } + } // Recursively build the kD-Tree + root = this->build(this->absoluteMinimum, this->absoluteMaximum, this->primitives(), 0); + std::cout << "(FastScene): " << this->primitives().size() << " primitives organized into tree" << std::endl; } 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 + Vector3d const diameter = maximumBounds - minimumBounds; // Test whether we have reached a leaf node... + int minimumDimension = ((diameter.x < diameter.y) ? ((diameter.x < diameter.z) ? 0 : 2) : ((diameter.y < diameter.z) ? 1 : 2)); + if (depth >= this->maximumDepth || (int)primitives.size() <= this->minimumNumberOfPrimitives || (diameter[minimumDimension]) <= EPSILON) { + auto leafNode = std::make_unique(); + leafNode->primitives = primitives; + return leafNode; + } - // ... otherwise create a new inner node by splitting through the widest - // dimension + // ... otherwise create a new inner node by splitting through the widest dimension + auto node = std::make_unique(); + node->dimension = ((diameter.x > diameter.y) ? ((diameter.x > diameter.z) ? 0 : 2) : ((diameter.y > diameter.z) ? 1 : 2)); // Determine the split position // Note: Use the median of the minimum bounds of the primitives + std::vector minimumValues; + for (const auto &primitive : primitives) + minimumValues.push_back(primitive->minimumBounds(node->dimension)); + std::sort(minimumValues.begin(), minimumValues.end()); + node->split = minimumValues[minimumValues.size() / 2]; // 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! + std::vector> leftPrimitives, rightPrimitives; + for (const auto &primitive : primitives) { + if (primitive->minimumBounds(node->dimension) < node->split) + leftPrimitives.push_back(primitive); + if (primitive->maximumBounds(node->dimension) >= node->split) + rightPrimitives.push_back(primitive); + } // Print out the number of primitives in the left and right child node + // std::cout << "(FastScene): Split " << leftPrimitives.size() << " | " << rightPrimitives.size() << std::endl; // Set the left and right split vectors + Vector3d minimumSplit = minimumBounds; + Vector3d maximumSplit = maximumBounds; + minimumSplit[node->dimension] = node->split; + maximumSplit[node->dimension] = node->split; // Recursively build the tree - return nullptr; + depth += 1; + node->child[0] = this->build(minimumBounds, maximumSplit, leftPrimitives, depth); + node->child[1] = this->build(minimumSplit, maximumBounds, rightPrimitives, depth); + return node; } From 00c81b1e4bf1d992b4e5f0d84bc8f7d0cc78da79 Mon Sep 17 00:00:00 2001 From: jp Date: Sat, 10 Dec 2022 18:26:31 +0100 Subject: [PATCH 2/2] added task 06 --- CMakeLists.txt | 6 +++ common/texture.cpp | 2 +- ex6.cpp | 89 +++++++++++++++++++++++++++++++++++++++ shader/materialshader.cpp | 28 ++++++++++++ shader/materialshader.h | 49 +++++++++++++++++++++ 5 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 ex6.cpp create mode 100644 shader/materialshader.cpp create mode 100644 shader/materialshader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c13aa74..e3aedd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,12 @@ target_link_libraries(tracey_ex4 tracey) add_executable(tracey_ex5 ex5.cpp) target_link_libraries(tracey_ex5 tracey) +add_executable(tracey_ex6 ex6.cpp) +target_link_libraries(tracey_ex6 tracey) +if("${CMAKE_CURRENT_LIST_DIR}/renderer/superrenderer.cpp" IN_LIST renderer_src) + add_definitions(-DSUPERRENDERER_FOUND) +endif() + diff --git a/common/texture.cpp b/common/texture.cpp index e3fa49a..b83eac4 100644 --- a/common/texture.cpp +++ b/common/texture.cpp @@ -55,7 +55,7 @@ Color Texture::color(float u, float v, bool interpolate) const { if (!interpolate) { color = this->getPixelAt(int(roundf(u * this->width())), int(roundf(v * this->height()))); } else { - // We will use bilinear filtering in the future, but for now we do a simple color look-up + // IMPLEMENT bilinear interpolation color = this->getPixelAt(int(roundf(u * this->width())), int(roundf(v * this->height()))); } return color; diff --git a/ex6.cpp b/ex6.cpp new file mode 100644 index 0000000..1bd5b59 --- /dev/null +++ b/ex6.cpp @@ -0,0 +1,89 @@ +#include +#include + +#include "camera/perspectivecamera.h" + +#include "scene/fastscene.h" + +#include "primitive/sphere.h" + +#include "renderer/simplerenderer.h" +#ifdef SUPERRENDERER_FOUND +#include "renderer/superrenderer.h" +#endif + +#include "shader/materialshader.h" + +#include "light/ambientlight.h" +#include "light/pointlight.h" +#include "light/spotlight.h" + +int main() { + FastScene scene; + scene.setEnvironmentMap(std::make_shared("data/TychoSkymapII.t5_04096x02048.png")); + + // Set up the camera + PerspectiveCamera camera; + camera.setPosition(Vector3d(0.0f, -0.2f, 0.0f)); + camera.setForwardDirection(normalized(Vector3d(-1.0f, -0.15f, 0.6f))); + camera.setUpDirection(normalized(Vector3d(0.0f, -1.0f, 0.0f))); + camera.setFovAngle(90.0f); + + // Add the earth + auto earthDiffuse = std::make_shared("data/earth_diffuse.ppm"); + auto earthSpecular = std::make_shared("data/earth_specular.ppm"); + auto earthNormal = std::make_shared("data/earth_normal.ppm"); + auto earthClouds = std::make_shared("data/earth_clouds.ppm"); + auto earthCloudShader = std::make_shared(); + earthCloudShader->setDiffuseMap(earthClouds); + earthCloudShader->setAlphaMap(earthClouds); + earthCloudShader->setDiffuseCoefficient(0.9f); + earthCloudShader->setSpecularCoefficient(0.1f); + auto earthShader = std::make_shared(); + earthShader->setDiffuseMap(earthDiffuse); + earthShader->setDiffuseCoefficient(0.5f); + earthShader->setSpecularMap(earthSpecular); + earthShader->setSpecularCoefficient(0.5f); + earthShader->setShininessExponent(15.0f); + earthShader->setNormalMap(earthNormal); + scene.add(std::make_shared(Vector3d(-50.0f, 0.0f, 60.0f), 20.0f, earthShader)); + scene.add(std::make_shared(Vector3d(-50.0f, 0.0f, 60.0f), 20.05f, earthCloudShader)); + + // Add the spaceship + auto spaceshipDiffuse = std::make_shared("data/space_frigate_6_diffuse.ppm"); + auto spaceshipSpecular = std::make_shared("data/space_frigate_6_specular.ppm"); + auto spaceshipNormal = std::make_shared("data/space_frigate_6_normal.ppm"); + auto spaceshipReflection = std::make_shared("data/space_frigate_6_specular.ppm"); + auto spaceshipShader = std::make_shared(); + spaceshipShader->setDiffuseMap(spaceshipDiffuse); + spaceshipShader->setDiffuseCoefficient(0.75f); + spaceshipShader->setSpecularMap(spaceshipSpecular); + spaceshipShader->setNormalMap(spaceshipNormal); + spaceshipShader->setNormalCoefficient(0.8f); + spaceshipShader->setSpecularCoefficient(0.25f); + spaceshipShader->setShininessExponent(30.0f); + spaceshipShader->setReflectionMap(spaceshipReflection); + spaceshipShader->setReflectance(0.75f); + + scene.addObj("data/space_frigate_6.obj", Vector3d(-1.0f, 1.0f, 1.0f) / 20.0f, Vector3d(-1.3f, -0.3f, 0.7f), + spaceshipShader); + + // Add lights + scene.add(std::make_shared(Vector3d(0.0f, 0.0f, 30.0f), 1000.0f)); + scene.add(std::make_shared(0.3f)); + + // build the tree + scene.buildTree(); + + // Render the scene + SimpleRenderer renderer; + renderer.renderImage(scene, camera, 1024, 768).save("result.png"); + +#ifdef SUPERRENDERER_FOUND + SuperRenderer sr; + sr.setSuperSamplingFactor(4); + sr.renderImage(scene, camera, 1024, 768).save("result_super.png"); +#endif + + return 0; +} diff --git a/shader/materialshader.cpp b/shader/materialshader.cpp new file mode 100644 index 0000000..f126a54 --- /dev/null +++ b/shader/materialshader.cpp @@ -0,0 +1,28 @@ +#include "light/light.h" +#include "scene/scene.h" +#include "shader/materialshader.h" +#include + +Vector3d tangentToWorldSpace(const Vector3d &surfaceNormal, const Vector3d &surfaceTangent, const Vector3d &surfaceBitangent, const Vector3d &textureNormal) { + return textureNormal.x * surfaceTangent + textureNormal.y * surfaceBitangent + textureNormal.z * surfaceNormal; +} + +MaterialShader::MaterialShader() : opacity(1.0f), normalCoefficient(1.0f), diffuseCoefficient(0.5f), reflectance(0.0f), specularCoefficient(0.5f), shininessExponent(8) {} + +Color MaterialShader::shade(Scene const &scene, Ray const &ray) const { + Color fragmentColor; + + // IMPLEMENT ME + + // (Normal Map) Calculate the new normal vector + + // (Diffuse-/Specular Map) Accumulate the light over all light sources + + // (Reflection Map) Calculate the reflectance, create a reflection ray + + // (Alpha Map) Calculate the opacity, create a background ray + + return fragmentColor; +} + +bool MaterialShader::isTransparent() const { return this->opacity < 1.0f || this->alphaMap; } diff --git a/shader/materialshader.h b/shader/materialshader.h new file mode 100644 index 0000000..c83c02b --- /dev/null +++ b/shader/materialshader.h @@ -0,0 +1,49 @@ +#ifndef MATERIALSHADER_H +#define MATERIALSHADER_H + +#include "common/texture.h" +#include "shader/shader.h" +#include + +class MaterialShader : public Shader { + +public: + // Constructor + MaterialShader(); + + // Set + void setAlphaMap(std::shared_ptr const &alphaMap) { this->alphaMap = alphaMap; } + void setOpacity(float opacity) { this->opacity = opacity; } + void setNormalMap(std::shared_ptr const &normalMap) { this->normalMap = normalMap; } + void setNormalCoefficient(float normalCoefficient) { this->normalCoefficient = normalCoefficient; } + void setDiffuseMap(std::shared_ptr const &diffuseMap) { this->diffuseMap = diffuseMap; } + void setDiffuseCoefficient(float diffuseCoefficient) { this->diffuseCoefficient = diffuseCoefficient; } + void setSpecularMap(std::shared_ptr const &specularMap) { this->specularMap = specularMap; } + void setSpecularCoefficient(float specularCoefficient) { this->specularCoefficient = specularCoefficient; } + void setShininessExponent(float shininessExponent) { this->shininessExponent = shininessExponent; } + void setReflectionMap(std::shared_ptr const &reflectionMap) { this->reflectionMap = reflectionMap; } + void setReflectance(float reflectance) { this->reflectance = reflectance; } + + // Shader functions + Color shade(Scene const &scene, Ray const &ray) const override; + bool isTransparent() const override; + +private: + std::shared_ptr alphaMap; + float opacity; + + std::shared_ptr normalMap; + float normalCoefficient; + + std::shared_ptr diffuseMap; + float diffuseCoefficient; + + std::shared_ptr reflectionMap; + float reflectance; + + std::shared_ptr specularMap; + float specularCoefficient; + float shininessExponent; +}; + +#endif