Implement pathtracing, brdf and rendering equation
This commit is contained in:
parent
cab0b3d91f
commit
8aa1df299d
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
412
src/Scene.cpp
412
src/Scene.cpp
|
@ -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
|
||||
|
|
19
src/Scene.h
19
src/Scene.h
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue