Add source code and project files

This commit is contained in:
Ben 2022-06-01 10:31:18 +02:00
parent af9e9bb39b
commit 94569984f9
Signed by: ben
GPG Key ID: 0F54A7ED232D3319
34 changed files with 48757 additions and 0 deletions

66
.clang-format Normal file
View File

@ -0,0 +1,66 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/.idea
/build
/cmake-build-*

16
CMakeLists.txt Normal file
View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.22)
project(RaytryCpp)
#list(APPEND CMAKE_MODULE_PATH "/usr/share/ECM/modules/")
#set(ECM_ENABLE_SANITIZERS "address;undefined")
#include(ECMEnableSanitizers)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Concurrent)
add_executable(RaytryCpp src/main.cpp src/viewerwindow.cpp src/viewerwindow.h src/ViewerWindow.ui src/Camera.cpp src/Camera.h src/InfinitePlane.cpp src/InfinitePlane.h src/RenderObject.h src/Ray.h src/Scene.cpp src/Scene.h src/Sphere.cpp src/Sphere.h src/Triangle.cpp src/Triangle.h src/RenderMaterial.h src/KDNode.h src/KDTree.cpp src/KDTree.h src/KDAxis.h src/KDLeaf.h src/RenderMaterial.cpp)
target_link_libraries(RaytryCpp PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)

4
newell_teaset/spoon.mtl Normal file
View File

@ -0,0 +1,4 @@
newmtl FrontColor
Ka 0.000000 0.000000 0.000000
Kd 1.000000 1.000000 1.000000
Ks 0.330000 0.330000 0.330000

12494
newell_teaset/spoon.obj Normal file

File diff suppressed because it is too large Load Diff

4
newell_teaset/teacup.mtl Normal file
View File

@ -0,0 +1,4 @@
newmtl FrontColor
Ka 0.000000 0.000000 0.000000
Kd 1.000000 1.000000 1.000000
Ks 0.330000 0.330000 0.330000

13847
newell_teaset/teacup.obj Normal file

File diff suppressed because it is too large Load Diff

4
newell_teaset/teapot.mtl Normal file
View File

@ -0,0 +1,4 @@
newmtl FrontColor
Ka 0.000000 0.000000 0.000000
Kd 1.000000 1.000000 1.000000
Ks 0.330000 0.330000 0.330000

19814
newell_teaset/teapot.obj Normal file

File diff suppressed because it is too large Load Diff

84
src/AABB.h Normal file
View File

@ -0,0 +1,84 @@
#ifndef RAYTRYCPP_AABB_H
#define RAYTRYCPP_AABB_H
#include "KDAxis.h"
#include "RenderObject.h"
#include <array>
#include <utility>
namespace raytry {
struct AABB {
std::array<std::pair<float, float>, KD::Axis::Z + 1> pairs{std::pair{0.0f, 1.0f}, std::pair{0.0f, 1.0f}, std::pair{0.0f, 1.0f}};
[[nodiscard]] std::optional<std::pair<QVector3D, QVector3D>> slabtest(const Ray& ray) const {
QVector3D tNear{-INFINITY, -INFINITY, -INFINITY};
QVector3D tFar{INFINITY, INFINITY, INFINITY};
for (unsigned int a = KD::Axis::X; a <= KD::Axis::Z; ++a) {
if (qFuzzyIsNull(ray.directionVec[a])) {
if (ray.originVec[a] < pairs[a].first || ray.originVec[a] > pairs[a].second) {
return std::nullopt;
}
} else {
float t1 = (pairs[a].first - ray.originVec[a]) / ray.directionVec[a];
float t2 = (pairs[a].second - ray.originVec[a]) / ray.directionVec[a];
if (t1 > t2) {
std::swap(t1, t2);
}
if (t1 > tNear[a]) {
tNear[a] = t1;
}
if (t2 < tFar[a]) {
tFar[a] = t2;
}
if (tNear[a] > tFar[a] || tFar[a] < 0) {
return std::nullopt;
}
}
}
return std::make_pair(tNear, tFar);
}
[[nodiscard]] std::optional<std::pair<QVector3D, QVector3D>> fastslabtest(const Ray& ray, const QVector3D& invDirection) const {
QVector3D t0{pairs[KD::X].first, pairs[KD::Y].first, pairs[KD::Z].first};
QVector3D t1{pairs[KD::X].second, pairs[KD::Y].second, pairs[KD::Z].second};
t0 = (t0 - ray.originVec) * invDirection;
t1 = (t1 - ray.originVec) * invDirection;
float minc = -INFINITY;
float maxc = INFINITY;
for (unsigned int a = KD::Axis::X; a <= KD::Axis::Z; ++a) {
if (t1[a] < t0[a]) {
std::swap(t1[a], t0[a]);
}
minc = std::max(minc, t0[a]);
maxc = std::min(maxc, t1[a]);
}
if (maxc < std::max(0.0f, minc)) {
return std::nullopt;
} else {
return std::make_pair(t0, t1);
}
}
const std::pair<float, float>& operator[](std::size_t idx) const {
return pairs[idx];
}
std::pair<float, float>& operator[](std::size_t idx) {
return pairs[idx];
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg) const {
return dbg << "AABB(" << pairs[KD::X].first << ", " << pairs[KD::Y].first << ", " << pairs[KD::Z].first <<
" -> " << pairs[KD::X].second << ", " << pairs[KD::Y].second << ", " << pairs[KD::Z].second << ")";
}
friend QDebug operator<<(QDebug dbg, const AABB& box) {
return box.operator<<(std::move(dbg));
}
#endif
};
}// namespace raytry
#endif//RAYTRYCPP_AABB_H

29
src/Camera.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "Camera.h"
#include <numbers>
void raytry::Camera::for_each_ray(const QImage &image, const std::function<void(int, int, raytry::Ray)> &consumer) {
const auto widthf = static_cast<float>(image.width());
const auto heightf = static_cast<float>(image.height());
const float widthToHeight = widthf / heightf;
const float tan = std::tan(std::max(1.0f, std::min(hfov, 179.0f)) * std::numbers::pi_v<float> / 360);
const float xblub = 2 * planeDistance * tan / widthf;
QVector3D zeroOrigin{
positionVec.x() - (xblub * (widthf / 2)),
positionVec.y(),
positionVec.z() + (xblub * widthToHeight * (heightf / 2))};
zeroOrigin += forwardVec * planeDistance;
for (int y = 0; y < image.height(); ++y) {
for (int x = 0; x < image.width(); ++x) {
const QVector3D &directionVec = zeroOrigin + QVector3D(static_cast<float>(x) * xblub, 0.0f, -static_cast<float>(y) * xblub * widthToHeight);
consumer(x, y, Ray(positionVec, (directionVec - positionVec).normalized()));
}
}
}
const QVector3D &raytry::Camera::pos() const {
return positionVec;
}
const QVector3D &raytry::Camera::fwd() const {
return forwardVec;
}

25
src/Camera.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef RAYTRYCPP_CAMERA_H
#define RAYTRYCPP_CAMERA_H
#include "Ray.h"
#include <QImage>
#include <QVector3D>
namespace raytry {
class Camera {
private:
QVector3D forwardVec{0.0, 1.0, 0.0};
QVector3D upVec{0.0, 0.0, 1.0};
QVector3D rightVec{1.0, 0.0, 0.0};
QVector3D positionVec{0.0, 0.0, 1.8};
float hfov{110.0f};
float planeDistance{0.2f};
public:
void for_each_ray(const QImage &image, const std::function<void(int, int, raytry::Ray)>& consumer);
const QVector3D& pos() const;
const QVector3D& fwd() const;
};
}// namespace raytry
#endif//RAYTRYCPP_CAMERA_H

28
src/InfinitePlane.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "InfinitePlane.h"
#include "RenderObject.h"
#include <iostream>
raytry::InfinitePlane::InfinitePlane() : zLevel{0.0f}, normalVec{0.0, 0.0, 1.0} {}
raytry::InfinitePlane::InfinitePlane(float zLevel, QVector3D normalVec) : zLevel{zLevel}, normalVec{normalVec} {}
std::optional<float> raytry::InfinitePlane::intersects(const Ray &ray) const {
auto normalDotDir = QVector3D::dotProduct(normalVec, ray.directionVec);
if (qFuzzyIsNull(normalDotDir)) {
return std::nullopt;
}
auto t = (-QVector3D::dotProduct(normalVec, ray.originVec) - zLevel) / normalDotDir;
if (t < RenderObject::nearCrop) {
return std::nullopt;
} else {
return std::make_optional<float>(t);
}
}
raytry::RenderMaterial raytry::InfinitePlane::material() const {
return {{240, 240, 240}};
}
QVector3D raytry::InfinitePlane::calculateNormal(QVector3D hitpoint) const {
return normalVec;
}

25
src/InfinitePlane.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef RAYTRYCPP_INFINITEPLANE_H
#define RAYTRYCPP_INFINITEPLANE_H
#include "RenderObject.h"
#include <QVector3D>
namespace raytry {
class InfinitePlane : public raytry::RenderObject {
public:
InfinitePlane();
InfinitePlane(float zLevel, QVector3D normalVec);
~InfinitePlane() override = default;
[[nodiscard]] std::optional<float> intersects(const Ray& ray) const override;
[[nodiscard]] RenderMaterial material() const override;
[[nodiscard]] QVector3D calculateNormal(QVector3D hitpoint) const override;
private:
float zLevel;
QVector3D normalVec;
};
}// namespace raytry
#endif//RAYTRYCPP_INFINITEPLANE_H

15
src/KDAxis.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef RAYTRYCPP_KDAXIS_H
#define RAYTRYCPP_KDAXIS_H
#include <QVector3D>
namespace raytry::KD {
enum Axis {
X = 0u,
Y = 1u,
Z = 2u,
None = 3u,
};
}// namespace raytry::KD
#endif//RAYTRYCPP_KDAXIS_H

31
src/KDLeaf.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef RAYTRYCPP_KDLEAF_H
#define RAYTRYCPP_KDLEAF_H
#include "KDAxis.h"
#include "Triangle.h"
namespace raytry::KD {
using namespace std::string_literals;
struct Leaf {
std::vector<const Triangle *> triangles{};
AABB boundaries{};
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg) const {
dbg << "KD::Leaf(" << triangles.size() << ", [";
for (unsigned int a = Axis::X; a <= Axis::Z; ++a) {
dbg << ("("s + std::to_string(boundaries[a].first) + ", "s + std::to_string(boundaries[a].second) + ")"s).c_str() << ", ";
}
return dbg << "])";
}
friend QDebug operator<<(QDebug dbg, const Leaf &leaf) {
return leaf.operator<<(std::move(dbg));
}
#endif
};
}// namespace raytry::KD
#endif//RAYTRYCPP_KDLEAF_H

63
src/KDNode.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef RAYTRYCPP_KDNODE_H
#define RAYTRYCPP_KDNODE_H
#include "KDAxis.h"
#include <QDebug>
#include <QVector3D>
#include <array>
namespace raytry::KD {
static constexpr const unsigned int farIdxMask = 0b00111111111111111111111111111111;
struct Node {
static constexpr const size_t shifty{(sizeof(unsigned int) * 8) - 2};
unsigned int farIdxAndAxis = 0;
float value = 0.0f;
Node(Axis axis, unsigned int farIdx, float value) : farIdxAndAxis{(static_cast<unsigned int>(axis) << shifty) | (farIdx & farIdxMask)}, value{value} {
if (farIdx > farIdxMask) {
qFatal("Far index too large");
}
}
[[nodiscard]] Axis getAxis() const {
return static_cast<Axis>(farIdxAndAxis >> shifty);
}
[[nodiscard]] bool isLeaf() const {
return getAxis() == Axis::None;
}
[[nodiscard]] unsigned int farIdx() const {
return farIdxAndAxis & farIdxMask;
}
[[nodiscard]] float val() const {
return value;
}
void setFarIdx(unsigned int farIdx) {
if (farIdx > farIdxMask) {
qFatal("Far index too large");
}
farIdxAndAxis = (farIdxAndAxis & ~farIdxMask) | farIdx;
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg) const {
if (isLeaf()) {
return dbg << "KD::Node(leaf,idx=" << farIdx() << ")";
} else {
return dbg << "KD::Node(axis=" << getAxis() << ",far=" << farIdx() << ",t=" << val() << ")";
}
}
friend QDebug operator<<(QDebug dbg, const Node &node) {
return node.operator<<(std::move(dbg));
}
#endif
};
}// namespace raytry::KD
#endif//RAYTRYCPP_KDNODE_H

252
src/KDTree.cpp Normal file
View File

@ -0,0 +1,252 @@
#include "KDTree.h"
#include "KDAxis.h"
#include "KDNode.h"
#include <set>
//#define RAYTRY_KDTREE_DEBUG
namespace raytry::KD {
void Tree::insertSorted(triangleValueVector &sortedTriangles, float value, const Triangle &triangle) {
bool emplaced = false;
for (auto mpos = sortedTriangles.begin(); !emplaced && mpos != sortedTriangles.end(); ++mpos) {
if ((*mpos).first > value) {
sortedTriangles.emplace(mpos, value, &triangle);
emplaced = true;
}
}
if (!emplaced) {
sortedTriangles.emplace_back(value, &triangle);
}
}
void Tree::insertSortedReversed(triangleValueVector &sortedTriangles, float value, const Triangle &triangle) {
bool emplaced = false;
for (auto mpos = sortedTriangles.begin(); !emplaced && mpos != sortedTriangles.end(); ++mpos) {
if ((*mpos).first < value) {
sortedTriangles.emplace(mpos, value, &triangle);
emplaced = true;
}
}
if (!emplaced) {
sortedTriangles.emplace_back(value, &triangle);
}
}
float Tree::calculateCost(float costConstant, float bndMin, float bndMax, unsigned int count) {
return costConstant + ((bndMax - bndMin) * static_cast<float>(count));
}
Tree::Tree(const Tree &other)
: nodes{other.nodes}, leafs{other.leafs}, boundaries{other.boundaries},
randomDevice{}, randomGenerator{randomDevice()}, randomProbes{other.randomProbes} {}
Tree &Tree::operator=(Tree other) {
std::swap(nodes, other.nodes);
std::swap(leafs, other.leafs);
std::swap(boundaries, other.boundaries);
std::swap(randomProbes, other.randomProbes);
return *this;
}
void Tree::separateAxisData(AxisData &data, std::array<triangleValueVector, Z + 1> &separatingData, std::array<triangleValueVector, Z + 1> &otherData, Axis axis, float tVal, bool separatorIsEnd) {
auto separator = separatingData[axis].begin();
for (; separator != separatingData[axis].end(); ++separator) {
if (separatorIsEnd == separator->first > tVal) {
break;
}
}
std::set<const Triangle *> trianglesToRemove{};
for (auto removeIter = separator; removeIter != separatingData[axis].end(); ++removeIter) {
trianglesToRemove.insert(removeIter->second);
}
separatingData[axis].erase(separator, separatingData[axis].end());
separatingData[axis].shrink_to_fit();
for (unsigned int a = X; a <= Z; ++a) {
if (a != axis) {
separatingData[a].erase(
std::remove_if(separatingData[a].begin(), separatingData[a].end(), [&](const std::pair<float, const Triangle *> &pair) {
return trianglesToRemove.contains(pair.second);
}),
separatingData[a].end());
separatingData[a].shrink_to_fit();
}
otherData[a].erase(
std::remove_if(otherData[a].begin(), otherData[a].end(), [&](const std::pair<float, const Triangle *> &pair) {
return trianglesToRemove.contains(pair.second);
}),
otherData[a].end());
otherData[a].shrink_to_fit();
}
}
float Tree::probeBestT(const Axis &axis, const AxisData &data) {
float bestT = NAN;
if (data.boundaries[axis].first <= data.boundaries[axis].second) {
float bestTCost = calculateCost(0, data.boundaries[axis].first, data.boundaries[axis].second, data.mins[axis].size());
size_t availableSamples = data.mins[axis].size() + data.maxs[axis].size();
assert(availableSamples > 0u);
std::uniform_int_distribution<size_t> dist{1u, availableSamples};
const auto& samples = std::min(availableSamples, randomSamples);
for (size_t i = 1; i <= samples; ++i) {
size_t idx;
if (availableSamples > randomSamples) {
idx = dist(randomGenerator);
++randomProbes;
} else {
idx = i;
}
unsigned int tVertexCountFirst = 0;
unsigned int tVertexCountSecond = 0;
float tVal;
if (idx > data.mins[axis].size()) {
idx -= data.mins[axis].size();
tVal = data.maxs[axis][idx - 1u].first;
tVertexCountSecond = idx;
for (const auto &item: data.mins[axis]) {
if (item.first > tVal) {
break;
}
++tVertexCountFirst;
}
} else {
assert(idx > 0u);
tVal = data.mins[axis][idx - 1u].first;
tVertexCountFirst = idx;
for (const auto &item: data.maxs[axis]) {
if (item.first < tVal) {
break;
}
++tVertexCountSecond;
}
}
#ifdef RAYTRY_KDTREE_DEBUG
qDebug() << "T:" << tVal << ", VC1:" << tVertexCountFirst << ", VC2:" << tVertexCountSecond;
#endif
float tCost = calculateCost(traversalCost, data.boundaries[axis].first, tVal, tVertexCountFirst) +
calculateCost(traversalCost, tVal, data.boundaries[axis].second, tVertexCountSecond);
if (tCost < bestTCost) {
#ifdef RAYTRY_KDTREE_DEBUG
qDebug() << "Found better T:" << tVal << "with cost" << tCost << "Better than T:" << bestT << "with cost" << bestTCost;
#endif
bestT = tVal;
bestTCost = tCost;
}
#ifdef RAYTRY_KDTREE_DEBUG
else {
qDebug() << "Found worse T:" << tVal << "with cost" << tCost << "Worse than T:" << bestT << "with cost" << bestTCost;
}
#endif
}
}
return bestT;
}
unsigned int Tree::recursiveNodeBuild(const unsigned int depthLeft, const Axis axis, const AxisData &data) {
if (depthLeft <= 0 || data.mins[X].empty()) {
unsigned int nearIdx = nodes.size();
Leaf leaf{};
assert(data.mins[X].size() == data.maxs[X].size());
assert(data.mins[X].size() == data.mins[Y].size());
assert(data.mins[Y].size() == data.maxs[Y].size());
assert(data.mins[Y].size() == data.mins[Z].size());
assert(data.mins[Z].size() == data.maxs[Z].size());
for (const auto &item: data.mins[X]) {
leaf.triangles.push_back(item.second);
}
leaf.boundaries = data.boundaries;
unsigned long leafIdx = leafs.size();
leafs.push_back(leaf);
#ifdef RAYTRY_KDTREE_DEBUG
qDebug() << "Adding leaf node" << leafIdx << "with" << leaf.triangles.size() << "triangles";
#endif
nodes.emplace_back(None, leafIdx, NAN);
return nearIdx;
}
float bestT = probeBestT(axis, data);
unsigned int newAxis = axis + 1u;
unsigned int newDepth = depthLeft;
if (newAxis > Z) {
newAxis = X;
--newDepth;
}
if (qIsNaN(bestT)) {
#ifdef RAYTRY_KDTREE_DEBUG
qDebug() << "No partitioning with better cost on axis" << axis << "in depth" << depthLeft;
#endif
return recursiveNodeBuild(newDepth, static_cast<Axis>(newAxis), data);
} else {
unsigned int nearIdx = nodes.size();
#ifdef RAYTRY_KDTREE_DEBUG
qDebug() << "Adding node on axis:" << axis << "in depth" << depthLeft << ", node idx:" << nearIdx;
#endif
nodes.emplace_back(axis, 0, bestT);
AxisData nearAxisData = AxisData(data);
nearAxisData.boundaries[axis] = std::pair<float, float>{nearAxisData.boundaries[axis].first, bestT};
separateAxisData(nearAxisData, nearAxisData.mins, nearAxisData.maxs, axis, bestT, true);
recursiveNodeBuild(newDepth, static_cast<Axis>(newAxis), nearAxisData);
AxisData farAxisData = AxisData(data);
farAxisData.boundaries[axis] = std::pair<float, float>{bestT, farAxisData.boundaries[axis].second};
separateAxisData(farAxisData, farAxisData.maxs, farAxisData.mins, axis, bestT, false);
unsigned int absoluteFarIdx = recursiveNodeBuild(newDepth, static_cast<Axis>(newAxis), farAxisData);
nodes[nearIdx].setFarIdx(absoluteFarIdx - nearIdx);
return nearIdx;
}
}
Tree Tree::build(const std::vector<Triangle> &triangles) {
if (triangles.empty()) {
return {};
}
AxisData data = AxisData();
assert(data.mins.size() > Z);
assert(data.maxs.size() > Z);
for (unsigned int a = X; a <= Z; ++a) {
data.mins[a].reserve(triangles.size());
data.maxs[a].reserve(triangles.size());
}
for (const auto &item: triangles) {
for (unsigned int a = X; a <= Z; ++a) {
Axis axis = static_cast<Axis>(a);
const auto itemMin = std::min(item.aVec()[axis], std::min(item.bVec()[axis], item.cVec()[axis]));
insertSorted(data.mins[a], itemMin, item);
const auto itemMax = std::max(item.aVec()[axis], std::max(item.bVec()[axis], item.cVec()[axis]));
insertSortedReversed(data.maxs[a], itemMax, item);
}
}
for (unsigned int a = X; a <= Z; ++a) {
data.boundaries[a].first = data.mins[a].front().first;
data.boundaries[a].second = data.maxs[a].front().first;
}
Tree ret{};
ret.boundaries = data.boundaries; // tree boundary = root boundary
qDebug() << "Tree boundaries:" << ret.boundaries;
ret.recursiveNodeBuild(maxDepth, X, data);
#ifdef RAYTRY_KDTREE_DEBUG
qDebug() << nodes;
qDebug() << leafs;
#endif
qInfo() << "Tree built. Probed random source" << ret.randomProbes << "times";
return ret;
}
const std::vector<KD::Node> &Tree::getNodes() {
return nodes;
}
const std::vector<Leaf> &Tree::getLeafs() {
return leafs;
}
const AABB &Tree::getBounds() {
return boundaries;
}
}// namespace raytry::KD

61
src/KDTree.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef RAYTRYCPP_KDTREE_H
#define RAYTRYCPP_KDTREE_H
#include "AABB.h"
#include "KDAxis.h"
#include "KDLeaf.h"
#include "KDNode.h"
#include "Triangle.h"
#include <random>
#include <vector>
namespace raytry::KD {
typedef std::vector<std::pair<float, const Triangle *>> triangleValueVector;
struct AxisData {
AxisData() = default;
~AxisData() = default;
AABB boundaries{};
std::array<triangleValueVector, Z + 1> mins{triangleValueVector{}, triangleValueVector{}, triangleValueVector{}};
std::array<triangleValueVector, Z + 1> maxs{triangleValueVector{}, triangleValueVector{}, triangleValueVector{}};
};
class Tree {
public:
Tree() = default;
Tree(const Tree &);
~Tree() = default;
Tree &operator=(Tree other);
static Tree build(const std::vector<Triangle> &triangles);
const std::vector<KD::Node> &getNodes();
const std::vector<KD::Leaf> &getLeafs();
const AABB &getBounds();
private:
static constexpr unsigned int maxDepth{7};
static constexpr size_t randomSamples{101};
static constexpr float traversalCost{5};
static void insertSorted(triangleValueVector &sortedTriangles, float value, const Triangle &triangle);
static void insertSortedReversed(triangleValueVector &sortedTriangles, float value, const Triangle &triangle);
static float calculateCost(float costConstant, float bndMin, float bndMax, unsigned int count);
static void separateAxisData(AxisData &data, std::array<triangleValueVector, Z + 1> &separatingData, std::array<triangleValueVector, Z + 1> &otherData, Axis axis, float tVal, bool separatorIsEnd);
float probeBestT(const Axis &axis, const AxisData &data);
unsigned int recursiveNodeBuild(unsigned int depthLeft, Axis axis, const AxisData &axisData);
std::vector<KD::Node> nodes{};
std::vector<KD::Leaf> leafs{};
AABB boundaries{};
std::random_device randomDevice{};
std::mt19937 randomGenerator{randomDevice()};
unsigned int randomProbes{0};
};
}// namespace raytry::KD
#endif//RAYTRYCPP_KDTREE_H

27
src/Ray.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef RAYTRYCPP_RAY_H
#define RAYTRYCPP_RAY_H
#include <QDebug>
#include <QVector3D>
#include <utility>
namespace raytry {
struct Ray {
Ray(QVector3D originVec, QVector3D directionVec) : originVec{originVec}, directionVec{directionVec} {}
QVector3D originVec;
QVector3D directionVec;
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg) const {
return dbg << "Ray(" << originVec << "->" << directionVec << ")";
}
friend QDebug operator<<(QDebug dbg, const Ray& ray) {
return ray.operator<<(std::move(dbg));
}
#endif
};
}// namespace raytry
#endif//RAYTRYCPP_RAY_H

56
src/RenderMaterial.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "RenderMaterial.h"
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);
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(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 mixColor(QColor input, float intensity) {
input.setRedF(input.redF() * intensity);
input.setGreenF(input.greenF() * intensity);
input.setBlueF(input.blueF() * intensity);
return input;
}
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());
return color1;
}
}

37
src/RenderMaterial.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef RAYTRYCPP_RENDERMATERIAL_H
#define RAYTRYCPP_RENDERMATERIAL_H
#include "Ray.h"
#include <QColor>
#include <QVector3D>
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;
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() = 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;
};
QColor mixColor(QColor input, float intensity);
QColor addColors(QColor color1, const QColor& color2);
}// namespace raytry
#endif//RAYTRYCPP_RENDERMATERIAL_H

33
src/RenderObject.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef RAYTRYCPP_RENDEROBJECT_H
#define RAYTRYCPP_RENDEROBJECT_H
#include "Ray.h"
#include "RenderMaterial.h"
#include <optional>
namespace raytry {
class RenderObject {
public:
RenderObject() = default;
RenderObject(RenderObject const&) = default;
virtual ~RenderObject() = default;
[[nodiscard]] virtual std::optional<float> intersects(const Ray& ray) const {
return std::nullopt;
}
[[nodiscard]] virtual RenderMaterial material() const {
return {{197, 52, 27}};
}
[[nodiscard]] virtual QVector3D calculateNormal(QVector3D hitpoint) const {
hitpoint *= -1;
hitpoint.normalize();
return hitpoint;
}
constexpr static const float nearCrop{0.1f};
};
}// namespace raytry
#endif//RAYTRYCPP_RENDEROBJECT_H

223
src/Scene.cpp Normal file
View File

@ -0,0 +1,223 @@
#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..";
} 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);
}
}
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;
}
}
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];
}
}
}
}
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());
}
}
}
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);
if (matches.empty()) {
color = backgroundColor;
} 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));
}
}
}
}
void raytry::Scene::render(raytry::ViewerWindow &window, std::function<void()> updateImage) {
size_t rayC = 0;
QImage &img = window.getDisplayImage();
unsigned int pixels = img.width() * img.height();
camera.for_each_ray(img, [&](int x, int y, Ray r) {
++rayC;
if (rayC % (pixels / 20) == 0) {
qInfo() << "Raytracing #" << rayC << "/" << pixels << "(" << rayC * 100 / pixels << "%)";
}
QColor color{};
rayTrace(r, 0, color);
img.setPixelColor(x, y, color);
if (rayC % (pixels / 1000) == 0) {
updateImage();
}
});
qInfo() << "All pixels computed. Tested" << triangleIntersections << "ray-triangle intersections.";
}

42
src/Scene.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef RAYTRYCPP_SCENE_H
#define RAYTRYCPP_SCENE_H
#include "Camera.h"
#include "viewerwindow.h"
#include "InfinitePlane.h"
#include "KDNode.h"
#include "KDTree.h"
#include "Sphere.h"
#include "Triangle.h"
#include <QImage>
namespace raytry {
class Scene {
public:
Scene();
~Scene() = default;
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);
static const constexpr unsigned int maxDepth{1};
static const constexpr float globalIllumination{0.5f};
static const constexpr QColor backgroundColor{177, 190, 223};
static const constexpr QVector3D pointLight{-10, -10, 10};
size_t triangleIntersections{0};
Camera camera{};
InfinitePlane ground{};
std::array<Sphere, 11> spheres{};
std::array<Triangle, 3> triangles{};
std::vector<Triangle> teapotMesh{};
std::vector<std::reference_wrapper<RenderObject>> background{};
KD::Tree foregroundTree{};
};
}// namespace raytry
#endif//RAYTRYCPP_SCENE_H

49
src/Sphere.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "Sphere.h"
namespace raytry {
Sphere::Sphere(QVector3D position, float radius) : position{position}, radius{radius}, mat{RenderObject::material()} {}
Sphere::Sphere(QVector3D position, float radius, RenderMaterial material) : position{position}, radius{radius}, mat{material} {}
Sphere::Sphere() : position(), radius{1.0f}, mat{RenderObject::material()} {}
float inline squared(const float& a) {
return a * a;
}
std::optional<float> Sphere::intersects(const Ray &ray) const {
float b = 2 * ((ray.originVec.x() - position.x()) * ray.directionVec.x() +
(ray.originVec.y() - position.y()) * ray.directionVec.y() +
(ray.originVec.z() - position.z()) * ray.directionVec.z());
float c = (squared(ray.originVec.x() - position.x()) +
squared(ray.originVec.y() - position.y()) +
squared(ray.originVec.z() - position.z()) -
squared(radius));
float b2m4c = (b * b) - 4 * c;
if (b2m4c < RenderObject::nearCrop) {
return std::nullopt;
}
float t0 = (-b - std::sqrt(b2m4c)) / 2;
float t1 = (-b + std::sqrt(b2m4c)) / 2;
if (t0 < RenderObject::nearCrop && t1 < RenderObject::nearCrop) {
return std::nullopt;
} else if (t0 < RenderObject::nearCrop) {
return std::make_optional<float>(t1);
} else if (t1 < RenderObject::nearCrop) {
return std::make_optional<float>(t0);
} else {
return std::make_optional<float>(std::min(t0, t1));
}
}
RenderMaterial Sphere::material() const {
return mat;
}
QVector3D Sphere::calculateNormal(QVector3D hitpoint) const {
hitpoint -= position;
hitpoint.normalize();
return hitpoint;
}
}// namespace raytry

37
src/Sphere.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef RAYTRYCPP_SPHERE_H
#define RAYTRYCPP_SPHERE_H
#include "RenderObject.h"
#include <QDebug>
namespace raytry {
class Sphere : public RenderObject {
public:
Sphere(QVector3D position, float radius);
Sphere(QVector3D position, float radius, RenderMaterial material);
Sphere();
~Sphere() override = default;
[[nodiscard]] std::optional<float> intersects(const Ray &ray) const override;
[[nodiscard]] RenderMaterial material() const override;
[[nodiscard]] QVector3D calculateNormal(QVector3D hitpoint) const override;
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug dbg) const {
return dbg << "Sphere(" << position << ", radius" << radius << ")";
}
friend QDebug operator<<(QDebug dbg, const Sphere& sphere) {
return sphere.operator<<(std::move(dbg));
}
#endif
private:
RenderMaterial mat;
QVector3D position;
float radius;
};
}// namespace raytry
#endif//RAYTRYCPP_SPHERE_H

62
src/Triangle.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "Triangle.h"
namespace raytry {
Triangle::Triangle(QVector3D a, QVector3D b, QVector3D c) : a{a}, b{b}, c{c} {
an = bn = cn = QVector3D::crossProduct(b - a, c - a).normalized();
}
Triangle::Triangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &an, const QVector3D &bn, const QVector3D &cn, const RenderMaterial &mat) : a(a), b(b), c(c), an(an), bn(bn), cn(cn), mat(mat) {}
std::optional<float> Triangle::intersects(const Ray &ray) const {
// Möller, Trumbore
const QVector3D &v0v1 = b - a;
const QVector3D &v0v2 = c - a;
const auto pvec = QVector3D::crossProduct(ray.directionVec, v0v2);
const float det = QVector3D::dotProduct(v0v1, pvec);
if (qFuzzyIsNull(det)) {
return std::nullopt;
}
const float invDet = 1 / det;
const auto tvec = ray.originVec - a;
const float u = QVector3D::dotProduct(tvec, pvec) * invDet;
if (u < 0 || u > 1) {
return std::nullopt;
}
const auto qvec = QVector3D::crossProduct(tvec, v0v1);
const float v = QVector3D::dotProduct(ray.directionVec, qvec) * invDet;
if (v < 0 || u + v > 1) {
return std::nullopt;
}
const auto& t = QVector3D::dotProduct(v0v2, qvec) * invDet;
if (t < nearCrop) {
return std::nullopt;
}
return std::make_optional<float>(t);
}
const QVector3D &Triangle::aVec() const {
return a;
}
const QVector3D &Triangle::bVec() const {
return b;
}
const QVector3D &Triangle::cVec() const {
return c;
}
RenderMaterial Triangle::material() const {
return mat;
}
void Triangle::setMaterial(RenderMaterial material) {
mat = material;
}
QVector3D Triangle::calculateNormal(QVector3D hitpoint) const {
// FIXME
return an;
}
}// namespace raytry

36
src/Triangle.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef RAYTRYCPP_TRIANGLE_H
#define RAYTRYCPP_TRIANGLE_H
#include "RenderObject.h"
namespace raytry {
class Triangle : public RenderObject {
public:
Triangle() = default;
Triangle(QVector3D a, QVector3D b, QVector3D c);
Triangle(const QVector3D &a, const QVector3D &b, const QVector3D &c, const QVector3D &an, const QVector3D &bn, const QVector3D &cn, const RenderMaterial &mat);
~Triangle() override = default;
[[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;
[[nodiscard]] const QVector3D& cVec() const;
private:
QVector3D a;
QVector3D b;
QVector3D c;
QVector3D an;
QVector3D bn;
QVector3D cn;
RenderMaterial mat{RenderObject::material()};
};
}// namespace raytry
#endif//RAYTRYCPP_TRIANGLE_H

19
src/ViewerWindow.ui Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>raytry::ViewerWindow</class>
<widget class="QMainWindow" name="raytry::ViewerWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Raytracer for Advanced Computer Graphics</string>
</property>
</widget>
<resources/>
<connections/>
</ui>

21
src/main.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "Scene.h"
#include "viewerwindow.h"
#include <QApplication>
#include <QtConcurrent/QtConcurrent>
int main(int argc, char **argv) {
QApplication app(argc, argv);
raytry::ViewerWindow window;
window.show();
const auto updateImage = [&](){ QMetaObject::invokeMethod(&window, "updateImageLabel", Qt::QueuedConnection); };
raytry::Scene scene{};
QtConcurrent::run(&raytry::Scene::render, &scene, std::reference_wrapper<raytry::ViewerWindow>(window), updateImage)
.then(updateImage)
.onFailed([]() {
qFatal("Rendering scene failed!");
});
return QApplication::exec();
}

26
src/viewerwindow.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "viewerwindow.h"
#include "ui_ViewerWindow.h"
namespace raytry {
ViewerWindow::ViewerWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::ViewerWindow), imageLabel{} {
ui->setupUi(this);
displayImage = QImage(QSize(window()->width(), window()->height()), QImage::Format::Format_RGBA8888);
displayImage.fill(QColorConstants::Black);
imageLabel.setBackgroundRole(QPalette::Base);
imageLabel.setScaledContents(true);
updateImageLabel();
setCentralWidget(&imageLabel);
}
ViewerWindow::~ViewerWindow() {
delete ui;
}
QImage &ViewerWindow::getDisplayImage() {
return displayImage;
}
void ViewerWindow::updateImageLabel() {
imageLabel.setPixmap(QPixmap::fromImage(displayImage));
}
}// namespace raytry

31
src/viewerwindow.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef RAYTRYCPP_VIEWERWINDOW_H
#define RAYTRYCPP_VIEWERWINDOW_H
#include <QLabel>
#include <QMainWindow>
namespace raytry {
QT_BEGIN_NAMESPACE
namespace Ui {
class ViewerWindow;
}
QT_END_NAMESPACE
class ViewerWindow : public QMainWindow {
Q_OBJECT
public:
explicit ViewerWindow(QWidget *parent = nullptr);
~ViewerWindow() override;
QImage &getDisplayImage();
Q_INVOKABLE void updateImageLabel();
private:
Ui::ViewerWindow *ui;
QImage displayImage;
QLabel imageLabel;
};
}// namespace raytry
#endif//RAYTRYCPP_VIEWERWINDOW_H

1193
thirdparty/OBJ_Loader.h vendored Normal file

File diff suppressed because it is too large Load Diff