From 8aa1df299d47a2a0993881fd0b0496c99a878120 Mon Sep 17 00:00:00 2001 From: Benedikt Ziemons Date: Thu, 28 Jul 2022 21:02:15 +0200 Subject: [PATCH] Implement pathtracing, brdf and rendering equation --- CMakeLists.txt | 6 + src/Camera.h | 7 +- src/RenderMaterial.cpp | 67 +++---- src/RenderMaterial.h | 16 +- src/Scene.cpp | 412 +++++++++++++++++++++-------------------- src/Scene.h | 19 +- src/Triangle.cpp | 11 +- src/Triangle.h | 1 - src/main.cpp | 5 + 9 files changed, 282 insertions(+), 262 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9c4a6c..1cb6052 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,12 @@ project(PathtryCpp) #include(ECMEnableSanitizers) set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") +#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0") + set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) diff --git a/src/Camera.h b/src/Camera.h index a1c33a7..c302ccb 100644 --- a/src/Camera.h +++ b/src/Camera.h @@ -2,11 +2,12 @@ #define RAYTRYCPP_CAMERA_H #include "Ray.h" +#include #include #include namespace raytry { - const constexpr size_t pixel_splits{4}; + const constexpr size_t pixel_splits{2}; const constexpr size_t bundle_size{pixel_splits * pixel_splits}; class Camera { @@ -21,8 +22,8 @@ namespace raytry { public: void for_each_ray(const QImage &image, const std::function& consumer); void for_each_bundle(const QImage &image, const std::function&)>& consumer); - const QVector3D& pos() const; - const QVector3D& fwd() const; + [[nodiscard]] const QVector3D& pos() const; + [[nodiscard]] const QVector3D& fwd() const; }; }// namespace raytry diff --git a/src/RenderMaterial.cpp b/src/RenderMaterial.cpp index ed1272f..d6be8eb 100644 --- a/src/RenderMaterial.cpp +++ b/src/RenderMaterial.cpp @@ -1,56 +1,45 @@ #include "RenderMaterial.h" +#define DIVIDE_SPECULAR_BY_NDOTL + namespace raytry { - RenderMaterial::RenderMaterial(QColor materialColor) : diffuseColor{materialColor}, ambientIntensity{0.1f}, diffuseIntensity{0.75f}, specularIntensity{0.15f}, specularFalloff{10.0f}, reflectivity{0}, transparency{0}, refractionIndex{0} { - ambientColor = mixColor(materialColor, 0.25f); + RenderMaterial::RenderMaterial(QColor materialColor) : diffuseColor{materialColor}, specularFalloff{32.0f} { specularColor = addColors({204, 204, 204}, mixColor(materialColor, 0.2f)); } - RenderMaterial::RenderMaterial(float ambientIntensity, const QColor &ambientColor, float diffuseIntensity, const QColor &diffuseColor, float specularIntensity, float specularFalloff, const QColor &specularColor) : ambientIntensity(ambientIntensity), ambientColor(ambientColor), diffuseIntensity(diffuseIntensity), diffuseColor(diffuseColor), specularIntensity(specularIntensity), specularFalloff(specularFalloff), specularColor(specularColor), reflectivity{0}, transparency{0}, refractionIndex{0} { - assert(ambientIntensity >= 0 && ambientIntensity <= 1); - assert(diffuseIntensity >= 0 && diffuseIntensity <= 1); - assert(specularIntensity >= 0 && specularIntensity <= 1); - assert(qFuzzyCompare(1.0f, ambientIntensity + diffuseIntensity + specularIntensity)); - } + RenderMaterial::RenderMaterial(const QColor &diffuseColor, const QColor &specularColor, float specularFalloff) : diffuseColor(diffuseColor), specularColor(specularColor), specularFalloff(specularFalloff) {} - RenderMaterial::RenderMaterial(float ambientIntensity, const QColor &ambientColor, float diffuseIntensity, const QColor &diffuseColor, float specularIntensity, float specularFalloff, const QColor &specularColor, float reflectivity, float transparency, float refractionIndex) : ambientIntensity(ambientIntensity), ambientColor(ambientColor), diffuseIntensity(diffuseIntensity), diffuseColor(diffuseColor), specularIntensity(specularIntensity), specularFalloff(specularFalloff), specularColor(specularColor), reflectivity(reflectivity), transparency(transparency), refractionIndex(refractionIndex) { - assert(ambientIntensity >= 0 && ambientIntensity <= 1); - assert(diffuseIntensity >= 0 && diffuseIntensity <= 1); - assert(specularIntensity >= 0 && specularIntensity <= 1); - assert(qFuzzyCompare(1.0f, ambientIntensity + diffuseIntensity + specularIntensity)); - } - - std::optional RenderMaterial::refractionRay(const Ray &incoming, const QVector3D &hitpoint, const QVector3D &normal) const { - // source: https://www.scratchapixel.com/code.php?id=32&origin=/lessons/3d-basic-rendering/phong-shader-BRDF - float cosi = std::clamp(QVector3D::dotProduct(incoming.directionVec, normal), -1.0f, 1.0f); - float etai = 1, etat = refractionIndex; - QVector3D n = normal; - if (cosi < 0) { cosi = -cosi; } else { std::swap(etai, etat); n= -normal; } - float eta = etai / etat; - float k = 1 - eta * eta * (1 - cosi * cosi); - if (k < 0) { - return std::nullopt; - } else { - return std::make_optional(hitpoint, eta * incoming.directionVec + (eta * cosi - sqrtf(k)) * n); - } - } - - Ray RenderMaterial::reflectionRay(const Ray &incoming, const QVector3D &hitpoint, const QVector3D &normal) const { - // source: https://www.scratchapixel.com/code.php?id=32&origin=/lessons/3d-basic-rendering/phong-shader-BRDF - return {hitpoint, incoming.directionVec - (2 * QVector3D::dotProduct(incoming.directionVec, normal) * normal)}; + QColor RenderMaterial::brdf(const QVector3D &toLight, const QVector3D &normal, const QVector3D &incomingDir, const QVector3D &outgoingDir) const { + // source: https://github.com/wdas/brdf/blob/main/src/brdfs/blinnphong.brdf + const QVector3D &halfway = (toLight + outgoingDir).normalized(); + float specularIntensity = std::pow(std::max(0.0f, QVector3D::dotProduct(normal, halfway)), specularFalloff); +#ifdef DIVIDE_SPECULAR_BY_NDOTL + specularIntensity /= std::max(0.0001f, QVector3D::dotProduct(normal, toLight)); +#endif + specularIntensity = std::min(1.0f, specularIntensity); + float diffuseIntensity = 1.0f - specularIntensity; + return addColors(mixColor(diffuseColor, diffuseIntensity), mixColor(specularColor, specularIntensity)); } QColor mixColor(QColor input, float intensity) { - input.setRedF(input.redF() * intensity); - input.setGreenF(input.greenF() * intensity); - input.setBlueF(input.blueF() * intensity); + assert(intensity >= 0); + input.setRedF(std::min(1.0f, input.redF() * intensity)); + input.setGreenF(std::min(1.0f, input.greenF() * intensity)); + input.setBlueF(std::min(1.0f, input.blueF() * intensity)); return input; } + QColor mixColors(QColor color1, const QColor& color2) { + color1.setRedF(color1.redF() * color2.redF()); + color1.setGreenF(color1.greenF() * color2.greenF()); + color1.setBlueF(color1.blueF() * color2.blueF()); + return color1; + } + QColor addColors(QColor color1, const QColor& color2) { - color1.setRedF(color1.redF() + color2.redF()); - color1.setGreenF(color1.greenF() + color2.greenF()); - color1.setBlueF(color1.blueF() + color2.blueF()); + color1.setRedF(std::min(1.0f, color1.redF() + color2.redF())); + color1.setGreenF(std::min(1.0f, color1.greenF() + color2.greenF())); + color1.setBlueF(std::min(1.0f, color1.blueF() + color2.blueF())); return color1; } } diff --git a/src/RenderMaterial.h b/src/RenderMaterial.h index c4b487e..18ae7df 100644 --- a/src/RenderMaterial.h +++ b/src/RenderMaterial.h @@ -8,28 +8,20 @@ namespace raytry { class RenderMaterial { public: - float ambientIntensity; - QColor ambientColor; - float diffuseIntensity; QColor diffuseColor; - float specularIntensity; - float specularFalloff; QColor specularColor; - float reflectivity; - float transparency; - float refractionIndex; + float specularFalloff; RenderMaterial(QColor materialColor); - RenderMaterial(float ambientIntensity, const QColor &ambientColor, float diffuseIntensity, const QColor &diffuseColor, float specularIntensity, float specularFalloff, const QColor &specularColor); - RenderMaterial(float ambientIntensity, const QColor &ambientColor, float diffuseIntensity, const QColor &diffuseColor, float specularIntensity, float specularFalloff, const QColor &specularColor, float reflectivity, float transparency, float refractionIndex); + RenderMaterial(const QColor &diffuseColor, const QColor &specularColor, float specularFalloff); ~RenderMaterial() = default; - [[nodiscard]] std::optional refractionRay(const Ray &incoming, const QVector3D &hitpoint, const QVector3D &normal) const; - [[nodiscard]] Ray reflectionRay(const Ray &incoming, const QVector3D &hitpoint, const QVector3D &normal) const; + [[nodiscard]] QColor brdf(const QVector3D &toLight, const QVector3D &normal, const QVector3D &incomingDir, const QVector3D &outgoingDir) const; }; QColor mixColor(QColor input, float intensity); + QColor mixColors(QColor color1, const QColor& color2); QColor addColors(QColor color1, const QColor& color2); }// namespace raytry diff --git a/src/Scene.cpp b/src/Scene.cpp index c84a116..9e4b7fe 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -1,228 +1,246 @@ #include "Scene.h" #include "../thirdparty/OBJ_Loader.h" #include "Sphere.h" -#include #include #include -raytry::Scene::Scene() { - background.emplace_back(ground); - for (int i = 0; i < spheres.size(); ++i) { - float x = -5.f + static_cast(i); - float spec = static_cast(i) / static_cast(spheres.size() - 1); - auto mat = RenderMaterial{{197, 52, 27}}; - mat.specularIntensity = spec; - spheres[i] = Sphere(camera.pos() + camera.fwd() * 5 + QVector3D(x, 0.f, 0.f), 0.4f, mat); - background.emplace_back(spheres[i]); - } - const auto &triangleTransformation = camera.pos() + camera.fwd() * 10; - - const auto &triangleBase = camera.pos() + camera.fwd() * 10; - triangles[0] = Triangle(QVector3D{triangleBase[0] - 3, triangleBase[1], 0.0}, - QVector3D{triangleBase[0] + 3, triangleBase[1], 0.0}, - QVector3D{triangleBase[0], triangleBase[1], 6.0}); - background.emplace_back(triangles[0]); - - QMatrix4x4 transformation{}; - transformation.translate(camera.pos() + (camera.fwd() * 5)); - transformation.rotate(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f)); - transformation.rotate(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f)); - - std::setlocale(LC_ALL, "C");// the obj parser uses locale-specific number format... - objl::Loader objLoader{}; - if (!objLoader.LoadFile("newell_teaset/teapot.obj")) { - qWarning() << "Could not load teapot.."; - return; - } else { - auto mat = RenderMaterial{{37, 96, 185}}; - mat.transparency = 0.2f; - mat.reflectivity = 0.5f; - teapotMesh.reserve(teapotMesh.size() + (objLoader.LoadedIndices.size() / 3)); - - auto iter = objLoader.LoadedIndices.begin(); - while (iter != objLoader.LoadedIndices.end()) { - auto a = objLoader.LoadedVertices[*iter++]; - auto b = objLoader.LoadedVertices[*iter++]; - auto c = objLoader.LoadedVertices[*iter++]; - QVector3D qta{a.Position.X, a.Position.Y, a.Position.Z}; - QVector3D qtb{b.Position.X, b.Position.Y, b.Position.Z}; - QVector3D qtc{c.Position.X, c.Position.Y, c.Position.Z}; - Triangle &tri = teapotMesh.emplace_back(transformation.map(qta), transformation.map(qtb), transformation.map(qtc)); - tri.setMaterial(mat); +namespace raytry { + Scene::Scene() { + background.emplace_back(ground); + for (int i = 0; i < spheres.size(); ++i) { + float x = -5.f + static_cast(i); + float spec = std::pow(2.0f, static_cast(i)); + auto mat = RenderMaterial{{197, 52, 27}, {240, 240, 240}, spec}; + spheres[i] = Sphere(camera.pos() + camera.fwd() * 5 + QVector3D(x, 0.f, 0.f), 0.4f, mat); + background.emplace_back(spheres[i]); } + const auto &triangleTransformation = camera.pos() + camera.fwd() * 10; + + const auto &triangleBase = camera.pos() + camera.fwd() * 10; + triangles[0] = Triangle(QVector3D{triangleBase[0] - 3, triangleBase[1], 0.0}, + QVector3D{triangleBase[0] + 3, triangleBase[1], 0.0}, + QVector3D{triangleBase[0], triangleBase[1], 6.0}); + background.emplace_back(triangles[0]); + + QMatrix4x4 transformation{}; + transformation.translate(camera.pos() + (camera.fwd() * 5)); + transformation.rotate(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f)); + transformation.rotate(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f)); + + std::setlocale(LC_ALL, "C");// the obj parser uses locale-specific number format... + objl::Loader objLoader{}; + if (!objLoader.LoadFile("newell_teaset/teapot.obj")) { + qWarning() << "Could not load teapot.."; + return; + } else { + auto mat = RenderMaterial{{37, 96, 185}}; + teapotMesh.reserve(teapotMesh.size() + (objLoader.LoadedIndices.size() / 3)); + + auto iter = objLoader.LoadedIndices.begin(); + while (iter != objLoader.LoadedIndices.end()) { + auto a = objLoader.LoadedVertices[*iter++]; + auto b = objLoader.LoadedVertices[*iter++]; + auto c = objLoader.LoadedVertices[*iter++]; + QVector3D qta{a.Position.X, a.Position.Y, a.Position.Z}; + QVector3D qtan{a.Normal.X, a.Normal.Y, a.Normal.Z}; + QVector3D qtb{b.Position.X, b.Position.Y, b.Position.Z}; + QVector3D qtbn{b.Normal.X, b.Normal.Y, b.Normal.Z}; + QVector3D qtc{c.Position.X, c.Position.Y, c.Position.Z}; + QVector3D qtcn{c.Normal.X, c.Normal.Y, c.Normal.Z}; + Triangle &tri = teapotMesh.emplace_back( + transformation.map(qta), + transformation.map(qtb), + transformation.map(qtc), + transformation.mapVector(qtan), + transformation.mapVector(qtbn), + transformation.mapVector(qtcn), + mat); + //tri.setMaterial(mat); + } + } + qInfo() << "Loaded teapot with" << teapotMesh.size() << "triangles."; + + foregroundTree = KD::Tree::build(teapotMesh); } - qInfo() << "Loaded teapot with" << teapotMesh.size() << "triangles."; - foregroundTree = KD::Tree::build(teapotMesh); -} - -std::list, float>> raytry::Scene::findIntersections(const raytry::Ray &ray) { - std::list, float>> matches{}; - if (!foregroundTree.getNodes().empty()) { - const QVector3D invDirection{1 / ray.directionVec.x(), 1 / ray.directionVec.y(), 1 / ray.directionVec.z()}; - const std::optional> &treeHit = foregroundTree.getBounds().fastslabtest(ray, invDirection); - if (treeHit) { - // KD! - const KD::Node *node = foregroundTree.getNodes().data(); - float tMin = treeHit->first[node->getAxis()]; - float tMax = treeHit->second[node->getAxis()]; - unsigned int currentNodeIdx = 0; - std::list> nodes{}; - while (node != nullptr) { - if (node->isLeaf()) { - assert(node->farIdx() >= 0); - assert(node->farIdx() < foregroundTree.getLeafs().size()); - for (const auto &item: foregroundTree.getLeafs()[node->farIdx()].triangles) { - const auto distance = item->intersects(ray); - ++triangleIntersections; - if (distance) { - bool emplaced = false; - const RenderObject &triangle = *item; - for (auto mpos = matches.begin(); !emplaced && mpos != matches.end(); ++mpos) { - if ((*mpos).second > distance.value()) { - matches.emplace(mpos, triangle, distance.value()); - emplaced = true; + std::list, float>> Scene::findIntersections(const Ray &ray, IntersectionMode mode) { + std::list, float>> matches{}; + if ((mode == IntersectionMode::foreground || mode == IntersectionMode::full) && !foregroundTree.getNodes().empty()) { + const QVector3D invDirection{1 / ray.directionVec.x(), 1 / ray.directionVec.y(), 1 / ray.directionVec.z()}; + const std::optional> &treeHit = foregroundTree.getBounds().fastslabtest(ray, invDirection); + if (treeHit) { + // KD! + const KD::Node *node = foregroundTree.getNodes().data(); + float tMin = treeHit->first[node->getAxis()]; + float tMax = treeHit->second[node->getAxis()]; + unsigned int currentNodeIdx = 0; + std::list> nodes{}; + while (node != nullptr) { + if (node->isLeaf()) { + assert(node->farIdx() >= 0); + assert(node->farIdx() < foregroundTree.getLeafs().size()); + for (const auto &item: foregroundTree.getLeafs()[node->farIdx()].triangles) { + const auto distance = item->intersects(ray); + ++triangleIntersections; + if (distance) { + bool emplaced = false; + const RenderObject &triangle = *item; + for (auto mpos = matches.begin(); !emplaced && mpos != matches.end(); ++mpos) { + if ((*mpos).second > distance.value()) { + matches.emplace(mpos, triangle, distance.value()); + emplaced = true; + } + } + if (!emplaced) { + matches.emplace_back(triangle, distance.value()); } } - if (!emplaced) { - matches.emplace_back(triangle, distance.value()); - } + } + + if (nodes.empty()) { + node = nullptr; + } else { + std::tie(currentNodeIdx, tMin, tMax) = nodes.front(); + assert(currentNodeIdx < foregroundTree.getNodes().size()); + node = &foregroundTree.getNodes()[currentNodeIdx]; + nodes.pop_front(); + } + } else { + const auto tPlane = (node->val() - ray.originVec[node->getAxis()]) * invDirection[node->getAxis()]; + + unsigned int firstChild, secondChild; + int belowFirst = (ray.originVec[node->getAxis()] < node->val()) || + (ray.originVec[node->getAxis()] == node->val() && ray.directionVec[node->getAxis()] <= 0); + if (belowFirst) { + firstChild = currentNodeIdx + 1; + secondChild = currentNodeIdx + node->farIdx(); + } else { + firstChild = currentNodeIdx + node->farIdx(); + secondChild = currentNodeIdx + 1; + } + + if (tPlane > tMax || tPlane <= 0) { + currentNodeIdx = firstChild; + } else if (tPlane < tMin) { + currentNodeIdx = secondChild; + } else { + nodes.emplace_back(secondChild, tPlane, tMax); + currentNodeIdx = firstChild; + tMax = tPlane; + } + node = &foregroundTree.getNodes()[currentNodeIdx]; + } + } + } + } + + if (mode == IntersectionMode::background || mode == IntersectionMode::full) { + for (RenderObject &obj: background) { + const auto distance = obj.intersects(ray); + if (distance) { + bool emplaced = false; + for (auto mpos = matches.begin(); !emplaced && mpos != matches.end(); ++mpos) { + if ((*mpos).second > distance.value()) { + matches.emplace(mpos, obj, distance.value()); + emplaced = true; } } - - if (nodes.empty()) { - node = nullptr; - } else { - std::tie(currentNodeIdx, tMin, tMax) = nodes.front(); - assert(currentNodeIdx < foregroundTree.getNodes().size()); - node = &foregroundTree.getNodes()[currentNodeIdx]; - nodes.pop_front(); + if (!emplaced) { + matches.emplace_back(obj, distance.value()); } - } else { - const auto tPlane = (node->val() - ray.originVec[node->getAxis()]) * invDirection[node->getAxis()]; - - unsigned int firstChild, secondChild; - int belowFirst = (ray.originVec[node->getAxis()] < node->val()) || - (ray.originVec[node->getAxis()] == node->val() && ray.directionVec[node->getAxis()] <= 0); - if (belowFirst) { - firstChild = currentNodeIdx + 1; - secondChild = currentNodeIdx + node->farIdx(); - } else { - firstChild = currentNodeIdx + node->farIdx(); - secondChild = currentNodeIdx + 1; - } - - if (tPlane > tMax || tPlane <= 0) { - currentNodeIdx = firstChild; - } else if (tPlane < tMin) { - currentNodeIdx = secondChild; - } else { - nodes.emplace_back(secondChild, tPlane, tMax); - currentNodeIdx = firstChild; - tMax = tPlane; - } - node = &foregroundTree.getNodes()[currentNodeIdx]; } } } + return matches; } - for (RenderObject &obj: background) { - const auto distance = obj.intersects(ray); - if (distance) { - bool emplaced = false; - for (auto mpos = matches.begin(); !emplaced && mpos != matches.end(); ++mpos) { - if ((*mpos).second > distance.value()) { - matches.emplace(mpos, obj, distance.value()); - emplaced = true; - } - } - if (!emplaced) { - matches.emplace_back(obj, distance.value()); - } - } + QQuaternion Scene::nextRandomRotation() { + auto r1 = pipiDistribution(randomGenerator); + auto r2 = halfpiDistribution(randomGenerator); + return QQuaternion::rotationTo({0.0f, 0.0f, 1.0f}, {std::sin(r1) * std::sin(std::acos(r2)), std::cos(r1) * std::sin(std::acos(r2)), r2}); } - return matches; -} -void raytry::Scene::rayTrace(const raytry::Ray &ray, unsigned int depth, QColor &color) { - if (depth > maxDepth) { - color = QColor{0, 0, 0}; - } else { - auto matches = findIntersections(ray); + QColor Scene::incomingLight(const QVector3D &point, const QVector3D &incomingDir, unsigned int depth) { + const Ray &lightPathRay{point, -incomingDir}; + // only background for performance reasons + auto matches = findIntersections(lightPathRay, IntersectionMode::background); if (matches.empty()) { - color = backgroundColor; + float lightIntensity = ((point - pointLight).normalized() - incomingDir.normalized()).lengthSquared(); + if (lightIntensity <= 1) { + return mixColor(lightColor, lightIntensity); + } else { + return QColorConstants::Black; + } } else { auto [object, distance] = matches.front(); - QVector3D hitpoint = ray.originVec + (ray.directionVec * distance); - const QVector3D &normal = object.get().calculateNormal(hitpoint); - assert(normal.normalized() == normal); - QVector3D toLight = pointLight - hitpoint; - float lightDistance = toLight.length(); - toLight.normalize(); - const Ray &shadowRay{hitpoint, toLight}; - auto shadowMatches = findIntersections(shadowRay); - float lightSourceIntensity = 1.0f; - if (!shadowMatches.empty()) { - for (const auto &occlusion: shadowMatches) { - if (occlusion.second > lightDistance) { - break; - } - lightSourceIntensity *= occlusion.first.get().material().transparency; - } - } - const RenderMaterial &material = object.get().material(); - assert(globalIllumination >= 0 && globalIllumination <= 1); - color = mixColor(material.ambientColor, material.ambientIntensity * globalIllumination); - - const float &diffusionModifier = std::max(0.0f, QVector3D::dotProduct(normal, toLight)); - color = addColors(color, mixColor(mixColor(mixColor(material.diffuseColor, diffusionModifier), material.diffuseIntensity), lightSourceIntensity)); - - if (material.reflectivity > 0) { - const Ray &reflectionRay = material.reflectionRay(ray, hitpoint, normal); - QColor reflectionColor{}; - rayTrace(reflectionRay, depth + 1u, reflectionColor); - color = addColors(mixColor(color, 1 - material.reflectivity), mixColor(reflectionColor, material.reflectivity)); - } - - const QVector3D &fromLight = (hitpoint - pointLight).normalized(); - const QVector3D &lightReflection = fromLight - (2 * QVector3D::dotProduct(fromLight, normal) * normal); - const float &specularModifier = std::pow(std::max(0.0f, QVector3D::dotProduct(lightReflection, -ray.directionVec)), material.specularFalloff); - color = addColors(color, mixColor(mixColor(mixColor(material.specularColor, specularModifier), material.specularIntensity), lightSourceIntensity)); - - if (material.transparency > 0) { - const std::optional &optionalRefractionRay = material.refractionRay(ray, hitpoint, normal); - QColor refractionColor{backgroundColor}; - if (optionalRefractionRay) { - rayTrace(optionalRefractionRay.value(), depth + 1u, refractionColor); - } - color = addColors(mixColor(color, 1 - material.transparency), mixColor(refractionColor, material.transparency)); - } + QVector3D hitpoint = lightPathRay.originVec + (lightPathRay.directionVec * distance); + return renderingEquation(object, hitpoint, -lightPathRay.directionVec, depth + 1); } } -} -void raytry::Scene::render(raytry::ViewerWindow &window, std::function updateImage) { - size_t bundleC = 0; - QImage &img = window.getDisplayImage(); - unsigned int pixels = img.width() * img.height(); - camera.for_each_bundle(img, [&](int x, int y, const std::array& bundle) { - ++bundleC; - if (bundleC % (pixels / 20) == 0) { - qInfo() << "Pathtracing #" << bundleC << "/" << pixels << "(" << bundleC * 100 / pixels << "%)"; + QColor Scene::renderingEquation(const RenderObject &object, const QVector3D &hitpoint, const QVector3D &outgoingDir, unsigned int depth) { + if (depth > maxDepth) { + return QColorConstants::Black; } - QColor mixedColor{QColorConstants::Black}; - for (const auto &ray: bundle) { - QColor rayColor{}; - rayTrace(ray, 0, rayColor); - mixedColor = addColors(mixedColor, mixColor(rayColor, 1.0f / bundle_size)); - } - img.setPixelColor(x, y, mixedColor); + const RenderMaterial &material = object.material(); + const QVector3D &normal = object.calculateNormal(hitpoint); + const QVector3D &toLight = (pointLight - hitpoint).normalized(); - if (bundleC % (pixels / 1000) == 0) { - updateImage(); + // multiple directions on first intersection/depth + if (depth == 0u) { + QColor sum = QColor{0, 0, 0}; + float dW = 1.0f / static_cast(pathRandomDirectionCount); + for (unsigned int i = 0; i < pathRandomDirectionCount; ++i) { + auto Wi = -nextRandomRotation().rotatedVector(normal); + sum = addColors(sum, mixColor(addColors(material.brdf(toLight, normal, Wi, outgoingDir), incomingLight(hitpoint, Wi, depth)), std::max(0.0f, -QVector3D::dotProduct(normal, Wi)) * dW)); + } + return sum; + } else { + auto Wi = -nextRandomRotation().rotatedVector(normal); + return mixColor(addColors(material.brdf(toLight, normal, Wi, outgoingDir), incomingLight(hitpoint, Wi, depth)), std::max(0.0f, -QVector3D::dotProduct(normal, Wi))); } - }); - qInfo() << "All pixels computed. Tested" << triangleIntersections << "ray-triangle intersections."; -} + } + + void Scene::render(ViewerWindow &window, std::function updateImage) { + size_t bundleC = 0; + QImage &img = window.getDisplayImage(); + unsigned int pixels = img.width() * img.height(); + +#ifdef TESTING + QColor color = addColors(QColorConstants::White, QColorConstants::White); + qInfo() << "Added colors:" << color; + assert(color == QColorConstants::White); + color = mixColors(QColorConstants::White, QColorConstants::White); + qInfo() << "Mixed colors:" << color; + assert(color == QColorConstants::White); +#endif + + camera.for_each_bundle(img, [&](int x, int y, const std::array &bundle) { + ++bundleC; + if (bundleC % std::max(1u, pixels / 20) == 0) { + qInfo() << "Pathtracing #" << bundleC << "/" << pixels << "(" << bundleC * 100 / pixels << "%)"; + } + + QColor mixedColor{QColorConstants::Black}; + for (const auto &ray: bundle) { + QColor rayColor; + auto matches = findIntersections(ray, full); + if (matches.empty()) { + rayColor = backgroundColor; + } else { + auto [object, distance] = matches.front(); + QVector3D hitpoint = ray.originVec + (ray.directionVec * distance); + rayColor = renderingEquation(object, hitpoint, -ray.directionVec, 0); + } + mixedColor = addColors(mixedColor, mixColor(rayColor, 1.0f / bundle_size)); + } + img.setPixelColor(x, y, mixedColor); + + if (bundleC % std::max(1u, pixels / 1000) == 0) { + updateImage(); + } + }); + qInfo() << "All pixels computed. Tested" << triangleIntersections << "ray-triangle intersections."; + } +}// namespace raytry diff --git a/src/Scene.h b/src/Scene.h index 1add9fa..6013cbf 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -11,21 +11,28 @@ #include namespace raytry { + enum IntersectionMode { full, foreground, background }; + class Scene { public: Scene(); + Scene(const Scene&) = delete; ~Scene() = default; + Scene &operator=(const Scene&) = delete; void render(ViewerWindow& window, std::function updateImage); private: - std::list, float>> findIntersections(const raytry::Ray &ray); - void rayTrace(const Ray& ray, unsigned int depth, QColor& color); + std::list, float>> findIntersections(const raytry::Ray &ray, IntersectionMode mode); + QColor renderingEquation(const RenderObject &object, const QVector3D &hitpoint, const QVector3D &outgoingDir, unsigned int depth); + QColor incomingLight(const QVector3D &point, const QVector3D &incomingDir, unsigned int depth); + QQuaternion nextRandomRotation(); - static const constexpr unsigned int maxDepth{1}; - static const constexpr float globalIllumination{0.5f}; + static const constexpr unsigned int maxDepth{4}; + static const constexpr unsigned int pathRandomDirectionCount{16}; static const constexpr QColor backgroundColor{177, 190, 223}; static const constexpr QVector3D pointLight{-10, -10, 10}; + static const constexpr QColor lightColor{244, 233, 155}; size_t triangleIntersections{0}; Camera camera{}; @@ -35,6 +42,10 @@ namespace raytry { std::vector teapotMesh{}; std::vector> background{}; KD::Tree foregroundTree{}; + std::random_device randomDevice{}; + std::mt19937 randomGenerator{randomDevice()}; + std::uniform_real_distribution pipiDistribution{0.0f, 2 * std::numbers::pi_v}; + std::uniform_real_distribution halfpiDistribution{0.0f, std::numbers::pi_v / 2}; }; }// namespace raytry diff --git a/src/Triangle.cpp b/src/Triangle.cpp index cb726ee..77ff933 100644 --- a/src/Triangle.cpp +++ b/src/Triangle.cpp @@ -50,13 +50,12 @@ namespace raytry { return mat; } - void Triangle::setMaterial(RenderMaterial material) { - mat = material; - } - QVector3D Triangle::calculateNormal(QVector3D hitpoint) const { - // FIXME - return an; + auto amod = (hitpoint - a).length(); + auto bmod = (hitpoint - b).length(); + auto cmod = (hitpoint - c).length(); + auto total = amod + bmod + cmod; + return (an * (1.0f - (amod / total)) + bn * (1.0f - (bmod / total)) + cn * (1.0f - (cmod / total))).normalized(); } }// namespace raytry \ No newline at end of file diff --git a/src/Triangle.h b/src/Triangle.h index 83051da..c0effc2 100644 --- a/src/Triangle.h +++ b/src/Triangle.h @@ -15,7 +15,6 @@ namespace raytry { [[nodiscard]] std::optional intersects(const Ray &ray) const override; [[nodiscard]] RenderMaterial material() const override; [[nodiscard]] QVector3D calculateNormal(QVector3D hitpoint) const override; - void setMaterial(RenderMaterial mat); [[nodiscard]] const QVector3D& aVec() const; [[nodiscard]] const QVector3D& bVec() const; diff --git a/src/main.cpp b/src/main.cpp index b8a9626..670cae4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,11 +11,16 @@ int main(int argc, char **argv) { const auto updateImage = [&](){ QMetaObject::invokeMethod(&window, "updateImageLabel", Qt::QueuedConnection); }; raytry::Scene scene{}; +#ifdef PROFILING + scene.render(window, updateImage); + updateImage(); +#else QtConcurrent::run(&raytry::Scene::render, &scene, std::reference_wrapper(window), updateImage) .then(updateImage) .onFailed([]() { qFatal("Rendering scene failed!"); }); +#endif return QApplication::exec(); }