Add source code and project files
This commit is contained in:
parent
af9e9bb39b
commit
94569984f9
66
.clang-format
Normal file
66
.clang-format
Normal 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
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/.idea
|
||||
/build
|
||||
/cmake-build-*
|
16
CMakeLists.txt
Normal file
16
CMakeLists.txt
Normal 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
4
newell_teaset/spoon.mtl
Normal 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
12494
newell_teaset/spoon.obj
Normal file
File diff suppressed because it is too large
Load diff
4
newell_teaset/teacup.mtl
Normal file
4
newell_teaset/teacup.mtl
Normal 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
13847
newell_teaset/teacup.obj
Normal file
File diff suppressed because it is too large
Load diff
4
newell_teaset/teapot.mtl
Normal file
4
newell_teaset/teapot.mtl
Normal 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
19814
newell_teaset/teapot.obj
Normal file
File diff suppressed because it is too large
Load diff
84
src/AABB.h
Normal file
84
src/AABB.h
Normal 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
29
src/Camera.cpp
Normal 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
25
src/Camera.h
Normal 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
28
src/InfinitePlane.cpp
Normal 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
25
src/InfinitePlane.h
Normal 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
15
src/KDAxis.h
Normal 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
31
src/KDLeaf.h
Normal 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
63
src/KDNode.h
Normal 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
252
src/KDTree.cpp
Normal 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
61
src/KDTree.h
Normal 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
27
src/Ray.h
Normal 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
56
src/RenderMaterial.cpp
Normal 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
37
src/RenderMaterial.h
Normal 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
33
src/RenderObject.h
Normal 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
223
src/Scene.cpp
Normal 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
42
src/Scene.h
Normal 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
49
src/Sphere.cpp
Normal 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
37
src/Sphere.h
Normal 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
62
src/Triangle.cpp
Normal 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
36
src/Triangle.h
Normal 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
19
src/ViewerWindow.ui
Normal 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
21
src/main.cpp
Normal 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
26
src/viewerwindow.cpp
Normal 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
31
src/viewerwindow.h
Normal 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
1193
thirdparty/OBJ_Loader.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue