Implement pathtracing, brdf and rendering equation

This commit is contained in:
Ben 2022-07-28 21:02:15 +02:00
parent cab0b3d91f
commit 8aa1df299d
Signed by: ben
GPG key ID: 0F54A7ED232D3319
9 changed files with 282 additions and 262 deletions

View file

@ -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)

View file

@ -2,11 +2,12 @@
#define RAYTRYCPP_CAMERA_H
#include "Ray.h"
#include <QApplication>
#include <QImage>
#include <QVector3D>
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<void(int, int, raytry::Ray)>& consumer);
void for_each_bundle(const QImage &image, const std::function<void(int, int, const std::array<raytry::Ray, raytry::bundle_size>&)>& consumer);
const QVector3D& pos() const;
const QVector3D& fwd() const;
[[nodiscard]] const QVector3D& pos() const;
[[nodiscard]] const QVector3D& fwd() const;
};
}// namespace raytry

View file

@ -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<Ray> 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<Ray>(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;
}
}

View file

@ -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<Ray> 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

View file

@ -1,228 +1,246 @@
#include "Scene.h"
#include "../thirdparty/OBJ_Loader.h"
#include "Sphere.h"
#include <QApplication>
#include <QMatrix4x4>
#include <QQuaternion>
raytry::Scene::Scene() {
background.emplace_back(ground);
for (int i = 0; i < spheres.size(); ++i) {
float x = -5.f + static_cast<float>(i);
float spec = static_cast<float>(i) / static_cast<float>(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<float>(i);
float spec = std::pow(2.0f, static_cast<float>(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<std::pair<std::reference_wrapper<const raytry::RenderObject>, float>> raytry::Scene::findIntersections(const raytry::Ray &ray) {
std::list<std::pair<std::reference_wrapper<const RenderObject>, float>> matches{};
if (!foregroundTree.getNodes().empty()) {
const QVector3D invDirection{1 / ray.directionVec.x(), 1 / ray.directionVec.y(), 1 / ray.directionVec.z()};
const std::optional<std::pair<QVector3D, QVector3D>> &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<std::tuple<unsigned int, float, float>> 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<std::pair<std::reference_wrapper<const RenderObject>, float>> Scene::findIntersections(const Ray &ray, IntersectionMode mode) {
std::list<std::pair<std::reference_wrapper<const RenderObject>, 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<std::pair<QVector3D, QVector3D>> &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<std::tuple<unsigned int, float, float>> 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<Ray> &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<void()> 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<raytry::Ray, raytry::bundle_size>& 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<float>(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<void()> 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<Ray, bundle_size> &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

View file

@ -11,21 +11,28 @@
#include <QImage>
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<void()> updateImage);
private:
std::list<std::pair<std::reference_wrapper<const RenderObject>, float>> findIntersections(const raytry::Ray &ray);
void rayTrace(const Ray& ray, unsigned int depth, QColor& color);
std::list<std::pair<std::reference_wrapper<const RenderObject>, 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<Triangle> teapotMesh{};
std::vector<std::reference_wrapper<RenderObject>> background{};
KD::Tree foregroundTree{};
std::random_device randomDevice{};
std::mt19937 randomGenerator{randomDevice()};
std::uniform_real_distribution<float> pipiDistribution{0.0f, 2 * std::numbers::pi_v<float>};
std::uniform_real_distribution<float> halfpiDistribution{0.0f, std::numbers::pi_v<float> / 2};
};
}// namespace raytry

View file

@ -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

View file

@ -15,7 +15,6 @@ namespace raytry {
[[nodiscard]] std::optional<float> 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;

View file

@ -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<raytry::ViewerWindow>(window), updateImage)
.then(updateImage)
.onFailed([]() {
qFatal("Rendering scene failed!");
});
#endif
return QApplication::exec();
}