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