Implement pathtracing, brdf and rendering equation
This commit is contained in:
parent
cab0b3d91f
commit
8aa1df299d
|
@ -6,6 +6,12 @@ project(PathtryCpp)
|
||||||
#include(ECMEnableSanitizers)
|
#include(ECMEnableSanitizers)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
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_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
#define RAYTRYCPP_CAMERA_H
|
#define RAYTRYCPP_CAMERA_H
|
||||||
|
|
||||||
#include "Ray.h"
|
#include "Ray.h"
|
||||||
|
#include <QApplication>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QVector3D>
|
#include <QVector3D>
|
||||||
|
|
||||||
namespace raytry {
|
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};
|
const constexpr size_t bundle_size{pixel_splits * pixel_splits};
|
||||||
|
|
||||||
class Camera {
|
class Camera {
|
||||||
|
@ -21,8 +22,8 @@ namespace raytry {
|
||||||
public:
|
public:
|
||||||
void for_each_ray(const QImage &image, const std::function<void(int, int, raytry::Ray)>& consumer);
|
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);
|
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;
|
[[nodiscard]] const QVector3D& pos() const;
|
||||||
const QVector3D& fwd() const;
|
[[nodiscard]] const QVector3D& fwd() const;
|
||||||
};
|
};
|
||||||
}// namespace raytry
|
}// namespace raytry
|
||||||
|
|
||||||
|
|
|
@ -1,56 +1,45 @@
|
||||||
#include "RenderMaterial.h"
|
#include "RenderMaterial.h"
|
||||||
|
|
||||||
|
#define DIVIDE_SPECULAR_BY_NDOTL
|
||||||
|
|
||||||
namespace raytry {
|
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} {
|
RenderMaterial::RenderMaterial(QColor materialColor) : diffuseColor{materialColor}, specularFalloff{32.0f} {
|
||||||
ambientColor = mixColor(materialColor, 0.25f);
|
|
||||||
specularColor = addColors({204, 204, 204}, mixColor(materialColor, 0.2f));
|
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} {
|
RenderMaterial::RenderMaterial(const QColor &diffuseColor, const QColor &specularColor, float specularFalloff) : diffuseColor(diffuseColor), specularColor(specularColor), specularFalloff(specularFalloff) {}
|
||||||
assert(ambientIntensity >= 0 && ambientIntensity <= 1);
|
|
||||||
assert(diffuseIntensity >= 0 && diffuseIntensity <= 1);
|
|
||||||
assert(specularIntensity >= 0 && specularIntensity <= 1);
|
|
||||||
assert(qFuzzyCompare(1.0f, ambientIntensity + diffuseIntensity + specularIntensity));
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
QColor RenderMaterial::brdf(const QVector3D &toLight, const QVector3D &normal, const QVector3D &incomingDir, const QVector3D &outgoingDir) const {
|
||||||
assert(ambientIntensity >= 0 && ambientIntensity <= 1);
|
// source: https://github.com/wdas/brdf/blob/main/src/brdfs/blinnphong.brdf
|
||||||
assert(diffuseIntensity >= 0 && diffuseIntensity <= 1);
|
const QVector3D &halfway = (toLight + outgoingDir).normalized();
|
||||||
assert(specularIntensity >= 0 && specularIntensity <= 1);
|
float specularIntensity = std::pow(std::max(0.0f, QVector3D::dotProduct(normal, halfway)), specularFalloff);
|
||||||
assert(qFuzzyCompare(1.0f, ambientIntensity + diffuseIntensity + specularIntensity));
|
#ifdef DIVIDE_SPECULAR_BY_NDOTL
|
||||||
}
|
specularIntensity /= std::max(0.0001f, QVector3D::dotProduct(normal, toLight));
|
||||||
|
#endif
|
||||||
std::optional<Ray> RenderMaterial::refractionRay(const Ray &incoming, const QVector3D &hitpoint, const QVector3D &normal) const {
|
specularIntensity = std::min(1.0f, specularIntensity);
|
||||||
// source: https://www.scratchapixel.com/code.php?id=32&origin=/lessons/3d-basic-rendering/phong-shader-BRDF
|
float diffuseIntensity = 1.0f - specularIntensity;
|
||||||
float cosi = std::clamp(QVector3D::dotProduct(incoming.directionVec, normal), -1.0f, 1.0f);
|
return addColors(mixColor(diffuseColor, diffuseIntensity), mixColor(specularColor, specularIntensity));
|
||||||
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 mixColor(QColor input, float intensity) {
|
QColor mixColor(QColor input, float intensity) {
|
||||||
input.setRedF(input.redF() * intensity);
|
assert(intensity >= 0);
|
||||||
input.setGreenF(input.greenF() * intensity);
|
input.setRedF(std::min(1.0f, input.redF() * intensity));
|
||||||
input.setBlueF(input.blueF() * intensity);
|
input.setGreenF(std::min(1.0f, input.greenF() * intensity));
|
||||||
|
input.setBlueF(std::min(1.0f, input.blueF() * intensity));
|
||||||
return input;
|
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) {
|
QColor addColors(QColor color1, const QColor& color2) {
|
||||||
color1.setRedF(color1.redF() + color2.redF());
|
color1.setRedF(std::min(1.0f, color1.redF() + color2.redF()));
|
||||||
color1.setGreenF(color1.greenF() + color2.greenF());
|
color1.setGreenF(std::min(1.0f, color1.greenF() + color2.greenF()));
|
||||||
color1.setBlueF(color1.blueF() + color2.blueF());
|
color1.setBlueF(std::min(1.0f, color1.blueF() + color2.blueF()));
|
||||||
return color1;
|
return color1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,28 +8,20 @@
|
||||||
namespace raytry {
|
namespace raytry {
|
||||||
class RenderMaterial {
|
class RenderMaterial {
|
||||||
public:
|
public:
|
||||||
float ambientIntensity;
|
|
||||||
QColor ambientColor;
|
|
||||||
float diffuseIntensity;
|
|
||||||
QColor diffuseColor;
|
QColor diffuseColor;
|
||||||
float specularIntensity;
|
|
||||||
float specularFalloff;
|
|
||||||
QColor specularColor;
|
QColor specularColor;
|
||||||
float reflectivity;
|
float specularFalloff;
|
||||||
float transparency;
|
|
||||||
float refractionIndex;
|
|
||||||
|
|
||||||
RenderMaterial(QColor materialColor);
|
RenderMaterial(QColor materialColor);
|
||||||
RenderMaterial(float ambientIntensity, const QColor &ambientColor, float diffuseIntensity, const QColor &diffuseColor, float specularIntensity, float specularFalloff, const QColor &specularColor);
|
RenderMaterial(const QColor &diffuseColor, const QColor &specularColor, float specularFalloff);
|
||||||
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() = default;
|
~RenderMaterial() = default;
|
||||||
|
|
||||||
[[nodiscard]] std::optional<Ray> refractionRay(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;
|
||||||
[[nodiscard]] Ray reflectionRay(const Ray &incoming, const QVector3D &hitpoint, const QVector3D &normal) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QColor mixColor(QColor input, float intensity);
|
QColor mixColor(QColor input, float intensity);
|
||||||
|
QColor mixColors(QColor color1, const QColor& color2);
|
||||||
QColor addColors(QColor color1, const QColor& color2);
|
QColor addColors(QColor color1, const QColor& color2);
|
||||||
}// namespace raytry
|
}// namespace raytry
|
||||||
|
|
||||||
|
|
412
src/Scene.cpp
412
src/Scene.cpp
|
@ -1,228 +1,246 @@
|
||||||
#include "Scene.h"
|
#include "Scene.h"
|
||||||
#include "../thirdparty/OBJ_Loader.h"
|
#include "../thirdparty/OBJ_Loader.h"
|
||||||
#include "Sphere.h"
|
#include "Sphere.h"
|
||||||
#include <QApplication>
|
|
||||||
#include <QMatrix4x4>
|
#include <QMatrix4x4>
|
||||||
#include <QQuaternion>
|
#include <QQuaternion>
|
||||||
|
|
||||||
raytry::Scene::Scene() {
|
namespace raytry {
|
||||||
background.emplace_back(ground);
|
Scene::Scene() {
|
||||||
for (int i = 0; i < spheres.size(); ++i) {
|
background.emplace_back(ground);
|
||||||
float x = -5.f + static_cast<float>(i);
|
for (int i = 0; i < spheres.size(); ++i) {
|
||||||
float spec = static_cast<float>(i) / static_cast<float>(spheres.size() - 1);
|
float x = -5.f + static_cast<float>(i);
|
||||||
auto mat = RenderMaterial{{197, 52, 27}};
|
float spec = std::pow(2.0f, static_cast<float>(i));
|
||||||
mat.specularIntensity = spec;
|
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);
|
spheres[i] = Sphere(camera.pos() + camera.fwd() * 5 + QVector3D(x, 0.f, 0.f), 0.4f, mat);
|
||||||
background.emplace_back(spheres[i]);
|
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);
|
|
||||||
}
|
}
|
||||||
|
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 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()) {
|
||||||
std::list<std::pair<std::reference_wrapper<const raytry::RenderObject>, float>> raytry::Scene::findIntersections(const raytry::Ray &ray) {
|
const QVector3D invDirection{1 / ray.directionVec.x(), 1 / ray.directionVec.y(), 1 / ray.directionVec.z()};
|
||||||
std::list<std::pair<std::reference_wrapper<const RenderObject>, float>> matches{};
|
const std::optional<std::pair<QVector3D, QVector3D>> &treeHit = foregroundTree.getBounds().fastslabtest(ray, invDirection);
|
||||||
if (!foregroundTree.getNodes().empty()) {
|
if (treeHit) {
|
||||||
const QVector3D invDirection{1 / ray.directionVec.x(), 1 / ray.directionVec.y(), 1 / ray.directionVec.z()};
|
// KD!
|
||||||
const std::optional<std::pair<QVector3D, QVector3D>> &treeHit = foregroundTree.getBounds().fastslabtest(ray, invDirection);
|
const KD::Node *node = foregroundTree.getNodes().data();
|
||||||
if (treeHit) {
|
float tMin = treeHit->first[node->getAxis()];
|
||||||
// KD!
|
float tMax = treeHit->second[node->getAxis()];
|
||||||
const KD::Node *node = foregroundTree.getNodes().data();
|
unsigned int currentNodeIdx = 0;
|
||||||
float tMin = treeHit->first[node->getAxis()];
|
std::list<std::tuple<unsigned int, float, float>> nodes{};
|
||||||
float tMax = treeHit->second[node->getAxis()];
|
while (node != nullptr) {
|
||||||
unsigned int currentNodeIdx = 0;
|
if (node->isLeaf()) {
|
||||||
std::list<std::tuple<unsigned int, float, float>> nodes{};
|
assert(node->farIdx() >= 0);
|
||||||
while (node != nullptr) {
|
assert(node->farIdx() < foregroundTree.getLeafs().size());
|
||||||
if (node->isLeaf()) {
|
for (const auto &item: foregroundTree.getLeafs()[node->farIdx()].triangles) {
|
||||||
assert(node->farIdx() >= 0);
|
const auto distance = item->intersects(ray);
|
||||||
assert(node->farIdx() < foregroundTree.getLeafs().size());
|
++triangleIntersections;
|
||||||
for (const auto &item: foregroundTree.getLeafs()[node->farIdx()].triangles) {
|
if (distance) {
|
||||||
const auto distance = item->intersects(ray);
|
bool emplaced = false;
|
||||||
++triangleIntersections;
|
const RenderObject &triangle = *item;
|
||||||
if (distance) {
|
for (auto mpos = matches.begin(); !emplaced && mpos != matches.end(); ++mpos) {
|
||||||
bool emplaced = false;
|
if ((*mpos).second > distance.value()) {
|
||||||
const RenderObject &triangle = *item;
|
matches.emplace(mpos, triangle, distance.value());
|
||||||
for (auto mpos = matches.begin(); !emplaced && mpos != matches.end(); ++mpos) {
|
emplaced = true;
|
||||||
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 (!emplaced) {
|
||||||
if (nodes.empty()) {
|
matches.emplace_back(obj, distance.value());
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (RenderObject &obj: background) {
|
QQuaternion Scene::nextRandomRotation() {
|
||||||
const auto distance = obj.intersects(ray);
|
auto r1 = pipiDistribution(randomGenerator);
|
||||||
if (distance) {
|
auto r2 = halfpiDistribution(randomGenerator);
|
||||||
bool emplaced = false;
|
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});
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
void raytry::Scene::rayTrace(const raytry::Ray &ray, unsigned int depth, QColor &color) {
|
QColor Scene::incomingLight(const QVector3D &point, const QVector3D &incomingDir, unsigned int depth) {
|
||||||
if (depth > maxDepth) {
|
const Ray &lightPathRay{point, -incomingDir};
|
||||||
color = QColor{0, 0, 0};
|
// only background for performance reasons
|
||||||
} else {
|
auto matches = findIntersections(lightPathRay, IntersectionMode::background);
|
||||||
auto matches = findIntersections(ray);
|
|
||||||
if (matches.empty()) {
|
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 {
|
} else {
|
||||||
auto [object, distance] = matches.front();
|
auto [object, distance] = matches.front();
|
||||||
QVector3D hitpoint = ray.originVec + (ray.directionVec * distance);
|
QVector3D hitpoint = lightPathRay.originVec + (lightPathRay.directionVec * distance);
|
||||||
const QVector3D &normal = object.get().calculateNormal(hitpoint);
|
return renderingEquation(object, hitpoint, -lightPathRay.directionVec, depth + 1);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void raytry::Scene::render(raytry::ViewerWindow &window, std::function<void()> updateImage) {
|
QColor Scene::renderingEquation(const RenderObject &object, const QVector3D &hitpoint, const QVector3D &outgoingDir, unsigned int depth) {
|
||||||
size_t bundleC = 0;
|
if (depth > maxDepth) {
|
||||||
QImage &img = window.getDisplayImage();
|
return QColorConstants::Black;
|
||||||
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 mixedColor{QColorConstants::Black};
|
const RenderMaterial &material = object.material();
|
||||||
for (const auto &ray: bundle) {
|
const QVector3D &normal = object.calculateNormal(hitpoint);
|
||||||
QColor rayColor{};
|
const QVector3D &toLight = (pointLight - hitpoint).normalized();
|
||||||
rayTrace(ray, 0, rayColor);
|
|
||||||
mixedColor = addColors(mixedColor, mixColor(rayColor, 1.0f / bundle_size));
|
|
||||||
}
|
|
||||||
img.setPixelColor(x, y, mixedColor);
|
|
||||||
|
|
||||||
if (bundleC % (pixels / 1000) == 0) {
|
// multiple directions on first intersection/depth
|
||||||
updateImage();
|
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
|
||||||
|
|
19
src/Scene.h
19
src/Scene.h
|
@ -11,21 +11,28 @@
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
|
||||||
namespace raytry {
|
namespace raytry {
|
||||||
|
enum IntersectionMode { full, foreground, background };
|
||||||
|
|
||||||
class Scene {
|
class Scene {
|
||||||
public:
|
public:
|
||||||
Scene();
|
Scene();
|
||||||
|
Scene(const Scene&) = delete;
|
||||||
~Scene() = default;
|
~Scene() = default;
|
||||||
|
Scene &operator=(const Scene&) = delete;
|
||||||
|
|
||||||
void render(ViewerWindow& window, std::function<void()> updateImage);
|
void render(ViewerWindow& window, std::function<void()> updateImage);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::list<std::pair<std::reference_wrapper<const RenderObject>, float>> findIntersections(const raytry::Ray &ray);
|
std::list<std::pair<std::reference_wrapper<const RenderObject>, float>> findIntersections(const raytry::Ray &ray, IntersectionMode mode);
|
||||||
void rayTrace(const Ray& ray, unsigned int depth, QColor& color);
|
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 unsigned int maxDepth{4};
|
||||||
static const constexpr float globalIllumination{0.5f};
|
static const constexpr unsigned int pathRandomDirectionCount{16};
|
||||||
static const constexpr QColor backgroundColor{177, 190, 223};
|
static const constexpr QColor backgroundColor{177, 190, 223};
|
||||||
static const constexpr QVector3D pointLight{-10, -10, 10};
|
static const constexpr QVector3D pointLight{-10, -10, 10};
|
||||||
|
static const constexpr QColor lightColor{244, 233, 155};
|
||||||
size_t triangleIntersections{0};
|
size_t triangleIntersections{0};
|
||||||
|
|
||||||
Camera camera{};
|
Camera camera{};
|
||||||
|
@ -35,6 +42,10 @@ namespace raytry {
|
||||||
std::vector<Triangle> teapotMesh{};
|
std::vector<Triangle> teapotMesh{};
|
||||||
std::vector<std::reference_wrapper<RenderObject>> background{};
|
std::vector<std::reference_wrapper<RenderObject>> background{};
|
||||||
KD::Tree foregroundTree{};
|
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
|
}// namespace raytry
|
||||||
|
|
||||||
|
|
|
@ -50,13 +50,12 @@ namespace raytry {
|
||||||
return mat;
|
return mat;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Triangle::setMaterial(RenderMaterial material) {
|
|
||||||
mat = material;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector3D Triangle::calculateNormal(QVector3D hitpoint) const {
|
QVector3D Triangle::calculateNormal(QVector3D hitpoint) const {
|
||||||
// FIXME
|
auto amod = (hitpoint - a).length();
|
||||||
return an;
|
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
|
}// namespace raytry
|
|
@ -15,7 +15,6 @@ namespace raytry {
|
||||||
[[nodiscard]] std::optional<float> intersects(const Ray &ray) const override;
|
[[nodiscard]] std::optional<float> intersects(const Ray &ray) const override;
|
||||||
[[nodiscard]] RenderMaterial material() const override;
|
[[nodiscard]] RenderMaterial material() const override;
|
||||||
[[nodiscard]] QVector3D calculateNormal(QVector3D hitpoint) const override;
|
[[nodiscard]] QVector3D calculateNormal(QVector3D hitpoint) const override;
|
||||||
void setMaterial(RenderMaterial mat);
|
|
||||||
|
|
||||||
[[nodiscard]] const QVector3D& aVec() const;
|
[[nodiscard]] const QVector3D& aVec() const;
|
||||||
[[nodiscard]] const QVector3D& bVec() const;
|
[[nodiscard]] const QVector3D& bVec() const;
|
||||||
|
|
|
@ -11,11 +11,16 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
const auto updateImage = [&](){ QMetaObject::invokeMethod(&window, "updateImageLabel", Qt::QueuedConnection); };
|
const auto updateImage = [&](){ QMetaObject::invokeMethod(&window, "updateImageLabel", Qt::QueuedConnection); };
|
||||||
raytry::Scene scene{};
|
raytry::Scene scene{};
|
||||||
|
#ifdef PROFILING
|
||||||
|
scene.render(window, updateImage);
|
||||||
|
updateImage();
|
||||||
|
#else
|
||||||
QtConcurrent::run(&raytry::Scene::render, &scene, std::reference_wrapper<raytry::ViewerWindow>(window), updateImage)
|
QtConcurrent::run(&raytry::Scene::render, &scene, std::reference_wrapper<raytry::ViewerWindow>(window), updateImage)
|
||||||
.then(updateImage)
|
.then(updateImage)
|
||||||
.onFailed([]() {
|
.onFailed([]() {
|
||||||
qFatal("Rendering scene failed!");
|
qFatal("Rendering scene failed!");
|
||||||
});
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
return QApplication::exec();
|
return QApplication::exec();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue