fixed the cross function in AVX, still incorrect output

main
Brett 2022-10-20 00:46:53 -04:00
parent e320836909
commit d10e9798dc
46 changed files with 5723 additions and 118 deletions

BIN
Step 2/Submission.zip Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.23)
project(Step_2)
set(CMAKE_CXX_STANDARD 20)
# used to debug memory related issues
if (UNIX AND (CMAKE_BUILD_TYPE MATCHES Debug))
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -g")
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
endif()
# enables AVX instructions. if you have issues, try disabling this.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
#Setup project source compilation
set(source_dir "${PROJECT_SOURCE_DIR}/src/")
file(GLOB_RECURSE source_files "${source_dir}/*.cpp")
file(GLOB_RECURSE source_c_files "${source_dir}/*.c")
#Setup project header files
include_directories(${PROJECT_SOURCE_DIR}/include)
add_executable(${PROJECT_NAME} ${source_files} ${source_c_files})

View File

@ -0,0 +1,6 @@
#!/bin/bash
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ../
make -j 16
./Step_2

View File

@ -0,0 +1,56 @@
/*
* Created by Brett Terpstra 6920201 on 14/10/22.
* Copyright (c) Brett Terpstra 2022 All Rights Reserved
*/
#ifndef STEP_2_IMAGE_H
#define STEP_2_IMAGE_H
#include "util/std.h"
#include "math/vectors.h"
namespace Raytracing {
// glorified structure to store our image data.
class Image {
private:
int width;
int height;
Vec4 *pixelData;
public:
Image(int width, int height);
Image(const Image &image);
Image(const Image&& image) = delete;
inline void setPixelColor(int x, int y, const Vec4 &color) {
pixelData[(x * height) + y] = color;
}
[[nodiscard]] inline Vec4 getPixelColor(int x, int y) const {
return pixelData[(x * height) + y];
}
[[nodiscard]] int getPixelR(int x, int y) const;
[[nodiscard]] int getPixelG(int x, int y) const;
[[nodiscard]] int getPixelB(int x, int y) const;
[[nodiscard]] int getPixelA(int x, int y) const;
[[nodiscard]] inline int getWidth() const { return width; }
[[nodiscard]] inline int getHeight() const { return height; }
~Image();
};
// image writer class used to output the image to a file.
class ImageOutput {
private:
Image image;
public:
explicit ImageOutput(const Image& image): image(image) {}
virtual void write(const std::string& file, const std::string& formatExtension);
};
}
#endif //STEP_2_IMAGE_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,157 @@
/*
* Created by Brett Terpstra 6920201 on 17/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#ifndef STEP_2_BVH_H
#define STEP_2_BVH_H
#include <util/std.h>
#include <types.h>
#include <utility>
// A currently pure header implementation of a BVH. TODO: make source file.
// this is also for testing and might not make it into the step 2.
namespace Raytracing {
struct BVHNode {
public:
std::vector<Object*> objs;
AABB aabb;
BVHNode* left;
BVHNode* right;
BVHNode(std::vector<Object*> objs, AABB aabb, BVHNode* left, BVHNode* right): objs(std::move(objs)), aabb(std::move(aabb)),
left(left), right(right) {}
~BVHNode() {
delete (left);
delete (right);
}
};
class BVHTree {
private:
const int MAX_TREE_DEPTH = 50;
BVHNode* root = nullptr;
void del() {
// delete copied objects
for (auto* obj : root->objs)
delete(obj);
delete (root);
}
// splits the objs in the vector based on the provided AABBs
static std::pair<std::vector<Object*>, std::vector<Object*>>
partition(const std::pair<AABB, AABB>& aabbs, const std::vector<Object*>& objs) {
std::vector<Object*> a1;
std::vector<Object*> a2;
for (auto* obj: objs) {
// if this object doesn't have an AABB, we cannot use a BVH on it
if (obj->getAABB().isEmpty()) {
throw std::runtime_error("Invalid AABB provided to the BVH! (Your implementation is flawed)");
}
if (obj->getAABB().intersects(aabbs.first)) {
a1.push_back(obj);
} else if (obj->getAABB().intersects(aabbs.second)) {
a2.push_back(obj);
}
//tlog << "OBJ: " << obj->getAABB() << " " << obj->getAABB().intersects(aabbs.first) << " " << obj->getAABB().intersects(aabbs.second) << " " << objs.size() << "\n";
}
//tlog << "we split into two of sizes: " << a1.size() << " " << a2.size() << " orig size: " << (a1.size() + a2.size()) << "\n";
return {a1, a2};
}
BVHNode* addObjectsRecur(const std::vector<Object*>& objects, unsigned long prevSize) {
//ilog << "size: " << objects.size() << "\n";
// prevSize was required to solve some really weird bugs
// which are a TODO:
if ((objects.size() <= 2 && !objects.empty()) || prevSize == objects.size()) {
AABB local;
for (const auto& obj: objects)
local = local.expand(obj->getAABB());
return new BVHNode(objects, local, nullptr, nullptr);
} else if (objects.empty()) // should never reach here!!
return nullptr;
// create a volume for the entire world.
// yes, we could use the recursion provided AABB,
// but that wouldn't be minimum, only half.
// this ensures that we have a minimum AABB.
AABB world;
for (const auto& obj: objects) {
//tlog << obj->getAABB();
world = world.expand(obj->getAABB());
}
//tlog << "\n";
// then split and partition the world
auto spltAABB = world.splitByLongestAxis();
//dlog << "We have " << world << " being split into: \n\t" << spltAABB.first << "\n\t" << spltAABB.second << "\n";
auto partitionedObjs = partition(spltAABB, objects);
BVHNode* left = nullptr;
BVHNode* right = nullptr;
// don't try to explore nodes which don't have anything in them.
if (!partitionedObjs.first.empty())
left = addObjectsRecur(partitionedObjs.first, objects.size());
if (!partitionedObjs.second.empty())
right = addObjectsRecur(partitionedObjs.second, objects.size());
return new BVHNode(objects, world, left, right);
}
static std::vector<Object*>
traverseFindRayIntersection(BVHNode* node, const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) {
// check for intersections on both sides of the tree
if (node->left != nullptr) {
if (node->left->aabb.intersects(ray, min, max))
return traverseFindRayIntersection(node->left, ray, min, max);
}
// since each aabb should be minimum, we shouldn't have to traverse both sides.
// we want to reduce our problem size by half each iteration anyways
// divide and conquer and so on
if (node->right != nullptr)
if (node->right->aabb.intersects(ray, min, max))
return traverseFindRayIntersection(node->left, ray, min, max);
// return the objects of the lowest BVH node we can find
// if this is implemented properly this should only contain one, maybe two objects
// which is much faster! (especially when dealing with triangles)
return node->objs;
}
public:
std::vector<Object*> noAABBObjects;
explicit BVHTree(const std::vector<Object*>& objectsInWorld) {
addObjects(objectsInWorld);
}
void addObjects(const std::vector<Object*>& objects) {
if (root != nullptr)
del();
// move all the object's aabb's into world position
std::vector<Object*> objs;
for (auto* obj: objects) {
// we don't want to store all the AABBs which don't exist
// ie spheres
if (obj->getAABB().isEmpty()) {
//tlog << "Goodbye\n";
noAABBObjects.push_back(obj);
continue;
}
Object* objCopy = obj->clone();
objCopy->setAABB(obj->getAABB().translate(obj->getPosition()));
objs.push_back(objCopy);
}
root = addObjectsRecur(objs, -1);
}
std::vector<Object*> rayIntersect(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) {
return traverseFindRayIntersection(root, ray, min, max);
}
~BVHTree() {
del();
}
};
}
#endif //STEP_2_BVH_H

View File

@ -0,0 +1,117 @@
/*
* Created by Brett Terpstra 6920201 on 17/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#ifndef STEP_2_COLLIDERS_H
#define STEP_2_COLLIDERS_H
#include <math/vectors.h>
namespace Raytracing {
// 3D Axis Aligned Bounding Box for use in a BVH
class AABB {
protected:
Vec4 min;
Vec4 max;
bool empty = false;
public:
AABB() {
empty = true;
};
AABB(PRECISION_TYPE minX, PRECISION_TYPE minY, PRECISION_TYPE minZ, PRECISION_TYPE maxX, PRECISION_TYPE maxY, PRECISION_TYPE maxZ):
min{minX, minY, minZ}, max{maxX, maxY, maxZ} {
}
AABB(const Vec4& min, const Vec4& max): min(min), max(max) {}
// creates an AABB extending of size centered on x, y, z
AABB(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z, PRECISION_TYPE size):
min{x - size, y - size, z - size}, max{x + size, y + size, z + size} {
}
// translates the AABB to position x,y,z for world collision detection
[[nodiscard]] AABB translate(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z) const {
Vec4 pos = {x, y, z};
return {min + pos, max + pos};
}
[[nodiscard]] AABB translate(const Vec4& vec) const {
Vec4 pos = {vec.x(), vec.y(), vec.z()};
return {min + pos, max + pos};
}
// returns an expanded version of this AABB is the other AABB is larger then this AABB
[[nodiscard]] AABB expand(const AABB& other) const {
PRECISION_TYPE minX = std::min(min.x(), other.min.x());
PRECISION_TYPE minY = std::min(min.y(), other.min.y());
PRECISION_TYPE minZ = std::min(min.z(), other.min.z());
PRECISION_TYPE maxX = std::max(max.x(), other.max.x());
PRECISION_TYPE maxY = std::max(max.y(), other.max.y());
PRECISION_TYPE maxZ = std::max(max.z(), other.max.z());
return {minX, minY, minZ, maxX, maxY, maxZ};
}
[[nodiscard]] inline bool
intersects(PRECISION_TYPE minX, PRECISION_TYPE minY, PRECISION_TYPE minZ, PRECISION_TYPE maxX, PRECISION_TYPE maxY,
PRECISION_TYPE maxZ) const {
return min.x() <= maxX && max.x() >= minX && min.y() <= maxY && max.y() >= minY && min.z() <= maxZ && max.z() >= minZ;
}
[[nodiscard]] inline bool intersects(const Vec4& minV, const Vec4& maxV) const {
return intersects(minV.x(), minV.y(), minV.z(), maxV.x(), maxV.y(), maxV.z());
}
[[nodiscard]] inline bool intersects(const AABB& other) const {
return intersects(other.min, other.max);
}
bool intersects(const Ray& ray, PRECISION_TYPE tmin, PRECISION_TYPE tmax);
bool simpleSlabRayAABBMethod(const Ray& ray, PRECISION_TYPE tmin, PRECISION_TYPE tmax);
[[nodiscard]] inline bool isInside(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z) const {
return x >= min.x() && x <= max.x() && y >= min.y() && y <= max.y() && z >= min.z() && z <= max.z();
}
[[nodiscard]] inline bool intersectsWithYZ(PRECISION_TYPE y, PRECISION_TYPE z) const {
return y >= min.y() && y <= max.y() && z >= min.z() && z <= max.z();
}
[[nodiscard]] inline bool intersectsWithXZ(PRECISION_TYPE x, PRECISION_TYPE z) const {
return x >= min.x() && x <= max.x() && z >= min.z() && z <= max.z();
}
[[nodiscard]] inline bool intersectsWithXY(PRECISION_TYPE x, PRECISION_TYPE y) const {
return x >= min.x() && x <= max.x() && y >= min.y() && y <= max.y();
}
[[nodiscard]] inline Vec4 getCenter() const {
return {min.x() + (max.x() - min.x()) * 0.5, min.y() + (max.y() - min.y()) * 0.5, min.z() + (max.z() - min.z()) * 0.5};
}
[[nodiscard]] PRECISION_TYPE longestDistanceFromCenter() const;
// 0 - x
// 1 - y
// 2 - z
[[nodiscard]] int longestAxis() const;
[[nodiscard]] PRECISION_TYPE longestAxisLength() const;
[[nodiscard]] std::pair<AABB, AABB> splitByLongestAxis();
[[nodiscard]] PRECISION_TYPE avgDistanceFromCenter() const;
[[nodiscard]] inline bool isEmpty() const { return empty; }
[[nodiscard]] Vec4 getMin() const { return min; }
[[nodiscard]] Vec4 getMax() const { return max; }
};
inline std::ostream& operator<<(std::ostream& out, const AABB& v) {
return out << "AABB{min{" << v.getMin().x() << ", " << v.getMin().y() << ", " << v.getMin().z() << "}, max{" << v.getMax().x() << ", " << v.getMax().y()
<< ", " << v.getMax().z() << "}} ";
}
}
#endif //STEP_2_COLLIDERS_H

View File

@ -0,0 +1,355 @@
/*
* Created by Brett Terpstra 6920201 on 14/10/22.
* Copyright (c) Brett Terpstra 2022 All Rights Reserved
*/
#ifndef STEP_2_VECTORS_H
#define STEP_2_VECTORS_H
// AVX512 isn't supported on my CPU. We will use AVX2 since it is supported by most modern CPUs
// THIS IS CURRENTLY BROKEN. DO NOT USE. it's a great idea, but I've run out of time to debug this. Will be in step 3
//#define USE_SIMD_CPU
#ifdef USE_SIMD_CPU
#include <immintrin.h>
#endif
#include <cmath>
#include "util/std.h"
namespace Raytracing {
// when running on the CPU it's fine to be a double
// Your CPU may be faster with floats.
// but if we move to the GPU it has to be a float.
// since GPUs generally are far more optimized for floats
// If using AVX or other SIMD instructions it should be double, only to fit into 256bits.
// TODO would be to add support for 128bit AVX vectors.
#ifdef USE_SIMD_CPU
// don't change this. (working on a float version)
typedef double PRECISION_TYPE;
union AVXConvert {
struct {
double _x, _y, _z, _w;
};
__m256d avxData;
};
class Vec4 {
private:
// makes it easy to convert between AVX and double data types.
union {
struct {
double _x, _y, _z, _w;
};
__m256d avxData;
};
// finally a use for friend!
friend Vec4 operator+(const Vec4& left, const Vec4& right);
friend Vec4 operator-(const Vec4& left, const Vec4& right);
friend Vec4 operator*(const Vec4& left, const Vec4& right);
friend Vec4 operator/(const Vec4& left, const Vec4& right);
public:
Vec4(): avxData{_mm256_set_pd(0.0, 0.0, 0.0, 0.0)} {}
Vec4(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z): avxData{_mm256_set_pd(x, y, z, 0)} {}
Vec4(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z, PRECISION_TYPE w): avxData{_mm256_set_pd(x, y, z, w)} {}
Vec4(const Vec4& vec): avxData{_mm256_set_pd(vec.x(), vec.y(), vec.z(), vec.w())} {}
// most of the modern c++ here is because clang tidy was annoying me
[[nodiscard]] inline PRECISION_TYPE x() const { return _x; }
[[nodiscard]] inline PRECISION_TYPE y() const { return _y; }
[[nodiscard]] inline PRECISION_TYPE z() const { return _z; }
[[nodiscard]] inline PRECISION_TYPE w() const { return _w; }
[[nodiscard]] inline PRECISION_TYPE r() const { return _x; }
[[nodiscard]] inline PRECISION_TYPE g() const { return _y; }
[[nodiscard]] inline PRECISION_TYPE b() const { return _z; }
[[nodiscard]] inline PRECISION_TYPE a() const { return _w; }
// negation operator
Vec4 operator-() const { return {-x(), -y(), -z(), -w()}; }
[[nodiscard]] inline PRECISION_TYPE magnitude() const {
return sqrt(lengthSquared());
}
[[nodiscard]] inline PRECISION_TYPE lengthSquared() const {
return Vec4::dot(*this, *this);
}
// returns the unit-vector.
[[nodiscard]] inline Vec4 normalize() const {
PRECISION_TYPE mag = magnitude();
return {x() / mag, y() / mag, z() / mag, w() / mag};
}
// add operator before the vec returns the magnitude
PRECISION_TYPE operator+() const {
return magnitude();
}
// preforms the dot product of left * right
static inline PRECISION_TYPE dot(const Vec4& left, const Vec4& right) {
// multiply the elements of the vectors
__m256d mul = _mm256_mul_pd(left.avxData, right.avxData);
// horizontal add. element 0 and 2 (or 1 and 3) contain the results which we must scalar add.
__m256d sum = _mm256_hadd_pd(mul, mul);
AVXConvert conv {};
conv.avxData = sum;
// boom! dot product. much easier than cross
return conv._x + conv._y;
}
// preforms the cross product of left X right
// since a general solution to the cross product doesn't exist in 4d
// we are going to ignore the w.
static inline Vec4 cross(const Vec4& left, const Vec4& right) {
// shuffle left values for alignment with the cross algorithm
// (read the shuffle selector from right to left) takes the y and places it in the first element of the resultant vector
// takes the z and places it in the second element of the vector
// takes the x element and places it in the 3rd element of the vector
// and then the w element in the last element of the vector
// creating the alignment {left.y(), left.z(), left.x(), left.w()} (as seen in the cross algorithm
__m256d leftLeftShuffle = _mm256_shuffle_pd(left.avxData, left.avxData, _MM_SHUFFLE(3,0,2,1));
// same thing but produces {right.z(), right.x(), right.y(), right.w()}
__m256d rightLeftShuffle = _mm256_shuffle_pd(right.avxData, right.avxData, _MM_SHUFFLE(3,1,0,2));
// multiply to do the first step of the cross process
__m256d multiLeft = _mm256_mul_pd(leftLeftShuffle, rightLeftShuffle);
// now we have to do the right side multiplications
// {left.z(), left.x(), left.y(), left.w()}
__m256d leftRightShuffle = _mm256_shuffle_pd(left.avxData, left.avxData, _MM_SHUFFLE(3,1,0,2));
// {right.y(), right.z(), right.x(), right.w()}
__m256d rightRightShuffle = _mm256_shuffle_pd(right.avxData, right.avxData, _MM_SHUFFLE(3,0,2,1));
// multiply the right sides of the subtraction sign
__m256d multiRight = _mm256_mul_pd(leftRightShuffle, rightRightShuffle);
// then subtract to produce the cross product
__m256d subs = _mm256_sub_pd(multiLeft, multiRight);
// yes this looks a lot more complicated, but it should be faster!
AVXConvert conv {};
conv.avxData = subs;
return {conv._x, conv._y, conv._z, conv._w};
}
};
// adds the two vectors left and right
inline Vec4 operator+(const Vec4& left, const Vec4& right) {
__m256d added = _mm256_add_pd(left.avxData, right.avxData);
AVXConvert conv {};
conv.avxData = added;
return {conv._x, conv._y, conv._z, conv._w};
}
// subtracts the right vector from the left.
inline Vec4 operator-(const Vec4& left, const Vec4& right) {
__m256d subbed = _mm256_sub_pd(left.avxData, right.avxData);
AVXConvert conv {};
conv.avxData = subbed;
return {conv._x, conv._y, conv._z, conv._w};
}
// multiples the left with the right
inline Vec4 operator*(const Vec4& left, const Vec4& right) {
__m256d multiplied = _mm256_mul_pd(left.avxData, right.avxData);
AVXConvert conv {};
conv.avxData = multiplied;
return {conv._x, conv._y, conv._z, conv._w};
}
// divides each element individually
inline Vec4 operator/(const Vec4& left, const Vec4& right) {
__m256d dived = _mm256_div_pd(left.avxData, right.avxData);
AVXConvert conv {};
conv.avxData = dived;
return {conv._x, conv._y, conv._z, conv._w};
}
#else
// change this if you want
typedef double PRECISION_TYPE;
class Vec4 {
private:
union xType {
PRECISION_TYPE x;
PRECISION_TYPE r;
};
union yType {
PRECISION_TYPE y;
PRECISION_TYPE g;
};
union zType {
PRECISION_TYPE z;
PRECISION_TYPE b;
};
union wType {
PRECISION_TYPE w;
PRECISION_TYPE a;
};
struct valueType {
xType v1;
yType v2;
zType v3;
wType v4;
};
// isn't much of a reason to do it this way
// it's unlikely that we'll need to use the w component
// but it helps better line up with the GPU and other SIMD type instructions, like what's above.
valueType value;
public:
Vec4(): value{0, 0, 0, 0} {}
Vec4(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z): value{x, y, z, 0} {}
Vec4(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z, PRECISION_TYPE w): value{x, y, z, w} {}
Vec4(const Vec4& vec): value{vec.x(), vec.y(), vec.z(), vec.w()} {}
// most of the modern c++ here is because clang tidy was annoying me
[[nodiscard]] inline PRECISION_TYPE x() const { return value.v1.x; }
[[nodiscard]] inline PRECISION_TYPE y() const { return value.v2.y; }
[[nodiscard]] inline PRECISION_TYPE z() const { return value.v3.z; }
[[nodiscard]] inline PRECISION_TYPE w() const { return value.v4.w; }
[[nodiscard]] inline PRECISION_TYPE r() const { return value.v1.r; }
[[nodiscard]] inline PRECISION_TYPE g() const { return value.v2.g; }
[[nodiscard]] inline PRECISION_TYPE b() const { return value.v3.b; }
[[nodiscard]] inline PRECISION_TYPE a() const { return value.v4.a; }
// negation operator
Vec4 operator-() const { return {-x(), -y(), -z(), -w()}; }
[[nodiscard]] inline PRECISION_TYPE magnitude() const {
return sqrt(lengthSquared());
}
[[nodiscard]] inline PRECISION_TYPE lengthSquared() const {
return x() * x() + y() * y() + z() * z() + w() * w();
}
// returns the unit-vector.
[[nodiscard]] inline Vec4 normalize() const {
PRECISION_TYPE mag = magnitude();
return {x() / mag, y() / mag, z() / mag, w() / mag};
}
// add operator before the vec returns the magnitude
PRECISION_TYPE operator+() const {
return magnitude();
}
// preforms the dot product of left * right
static inline PRECISION_TYPE dot(const Vec4& left, const Vec4& right) {
return left.x() * right.x()
+ left.y() * right.y()
+ left.z() * right.z();
}
// preforms the cross product of left X right
// since a general solution to the cross product doesn't exist in 4d
// we are going to ignore the w.
static inline Vec4 cross(const Vec4& left, const Vec4& right) {
return {left.y() * right.z() - left.z() * right.y(),
left.z() * right.x() - left.x() * right.z(),
left.x() * right.y() - left.y() * right.x()};
}
};
// Utility Functions
// adds the two vectors left and right
inline Vec4 operator+(const Vec4& left, const Vec4& right) {
return {left.x() + right.x(), left.y() + right.y(), left.z() + right.z(), left.w() + right.w()};
}
// subtracts the right vector from the left.
inline Vec4 operator-(const Vec4& left, const Vec4& right) {
return {left.x() - right.x(), left.y() - right.y(), left.z() - right.z(), left.w() - right.w()};
}
// multiples the left with the right
inline Vec4 operator*(const Vec4& left, const Vec4& right) {
return {left.x() * right.x(), left.y() * right.y(), left.z() * right.z(), left.w() * right.w()};
}
// divides each element individually
inline Vec4 operator/(const Vec4& left, const Vec4& right) {
return {left.x() / right.x(), left.y() / right.y(), left.z() / right.z(), left.w() / right.w()};
}
#endif
// none of these can be vectorized with AVX instructions
// useful for printing out the vector to stdout
inline std::ostream& operator<<(std::ostream& out, const Vec4& v) {
return out << "Vec4{" << v.x() << ", " << v.y() << ", " << v.z() << ", " << v.w() << "} ";
}
// multiplies the const c with each element in the vector v
inline Vec4 operator*(const PRECISION_TYPE c, const Vec4& v) {
return {c * v.x(), c * v.y(), c * v.z(), c * v.w()};
}
// same as above but for right sided constants
inline Vec4 operator*(const Vec4& v, PRECISION_TYPE c) {
return c * v;
}
// divides the vector by the constant c
inline Vec4 operator/(const Vec4& v, PRECISION_TYPE c) {
return {v.x() / c, v.y() / c, v.z() / c, v.w() / c};
}
// divides each element in the vector by over the constant
inline Vec4 operator/(PRECISION_TYPE c, const Vec4& v) {
return {c / v.x(), c / v.y(), c / v.z(), c / v.w()};
}
class Ray {
private:
// the starting point for our ray
Vec4 start;
// and the direction it is currently traveling
Vec4 direction;
Vec4 inverseDirection;
public:
Ray(const Vec4& start, const Vec4& direction): start(start), direction(direction), inverseDirection(1 / direction) {}
[[nodiscard]] Vec4 getStartingPoint() const { return start; }
[[nodiscard]] Vec4 getDirection() const { return direction; }
// not always needed, but it's good to not have to calculate the inverse inside the intersection
// as that would be very every AABB, and that is expensive
[[nodiscard]] Vec4 getInverseDirection() const { return inverseDirection; }
// returns a point along the ray, extended away from start by the length.
[[nodiscard]] inline Vec4 along(PRECISION_TYPE length) const { return start + length * direction; }
};
inline std::ostream& operator<<(std::ostream& out, const Ray& v) {
return out << "Ray{" << v.getStartingPoint() << " " << v.getDirection() << "} ";
}
}
#endif //STEP_2_VECTORS_H

View File

@ -0,0 +1,109 @@
/*
* Created by Brett Terpstra 6920201 on 16/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*
* The general class for all things raytracing!
*/
#ifndef STEP_2_RAYTRACING_H
#define STEP_2_RAYTRACING_H
#include <math/vectors.h>
#include <image/image.h>
#include <util/parser.h>
#include <world.h>
#include <utility>
namespace Raytracing {
class Camera {
private:
/* Image details */
const Image image;
const PRECISION_TYPE aspectRatio;
/* Camera details */
PRECISION_TYPE viewportHeight;
PRECISION_TYPE viewportWidth;
PRECISION_TYPE focalLength = 1.0;
Vec4 position{0, 0, 0};
Vec4 horizontalAxis;
Vec4 verticalAxis;
Vec4 imageOrigin;
public:
Camera(PRECISION_TYPE fov, const Image& image): image(image),
aspectRatio(double(image.getWidth()) / double(image.getHeight())) {
// scale the viewport height based on the camera's FOV
viewportHeight = (2.0 * tan(degreeeToRadian(fov) / 2));
// with must respect the aspect ratio of the image, otherwise we'd get funky results
viewportWidth = (aspectRatio * viewportHeight);
// horizontal direction from the camera. used to translate the camera
horizontalAxis = (Vec4{viewportWidth, 0, 0, 0});
// virtual direction, also used to translate the camera
verticalAxis = (Vec4{0, viewportHeight, 0, 0});
// lower left of the camera's view port. used to project our vectors from image space to world space
imageOrigin = (position - horizontalAxis / 2 - verticalAxis / 2 - Vec4(0, 0, focalLength, 0));
tlog << viewportHeight << "\n";
tlog << viewportWidth << "\n";
tlog << "\n";
tlog << horizontalAxis << "\n";
tlog << verticalAxis << "\n";
tlog << imageOrigin << "\n";
}
Ray projectRay(PRECISION_TYPE x, PRECISION_TYPE y);
// makes the camera look at the lookatpos from the position p, with respects to the up direction up. (set to 0,1,0)
void lookAt(const Vec4& pos, const Vec4& lookAtPos, const Vec4& up);
void setPosition(const Vec4& pos) { this->position = pos; }
void setRotation(PRECISION_TYPE yaw, PRECISION_TYPE pitch, PRECISION_TYPE roll);
};
static Random rnd{-1, 1};
class Raycaster {
private:
const int maxBounceDepth = 50;
// 50 seems to be the magic number for the point of diminishing returns
// 100 looks like 50 but slightly clearer
// 25 is noisy
// 1 is VERY noisy.
const int raysPerPixel = 50;
Camera& camera;
Image& image;
World& world;
Vec4 raycast(const Ray& ray, int depth);
public:
inline static Vec4 randomUnitVector() {
// there are two methods to generating a random unit sphere
// one which is fast and approximate:
auto v = Vec4(rnd.getDouble(), rnd.getDouble(), rnd.getDouble());
return v.normalize();
// and the one which generates an actual unit vector
/*while (true) {
auto v = Vec4(rnd.getDouble(), rnd.getDouble(), rnd.getDouble());
if (v.lengthSquared() >= 1)
continue;
return v;
}*/
// the second creates better results but is 18% slower (better defined shadows)
// likely due to not over generating unit vectors biased towards the corners
}
Raycaster(Camera& c, Image& i, World& world, const Parser& p): camera(c), image(i), world(world) {
world.generateBVH();
}
void run();
};
}
#endif //STEP_2_RAYTRACING_H

View File

@ -0,0 +1,126 @@
/*
* Created by Brett Terpstra 6920201 on 18/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#ifndef STEP_2_TYPES_H
#define STEP_2_TYPES_H
#include <math/vectors.h>
#include <math/colliders.h>
#include <utility>
// there were some files which needed access to these types
// but including them from world.h would've resulted in circular includes,
// so I moved them here.
namespace Raytracing {
struct HitData {
// all the other values only matter if this is true
bool hit{false};
// the hit point on the object
Vec4 hitPoint{};
// the normal of that hit point
Vec4 normal{};
// the length of the vector from its origin in its direction.
PRECISION_TYPE length{0};
};
struct ScatterResults {
// returns true to recast the ray with the provided ray
bool scattered;
// the new ray to be cast if scattered
Ray newRay;
// the color of the material
Vec4 attenuationColor;
};
// triangle type for model loading
struct Triangle {
public:
Vec4 vertex1, vertex2, vertex3;
bool hasNormals = false;
Vec4 normal1, normal2, normal3;
Vec4 uv1, uv2, uv3;
AABB aabb;
Triangle(const Vec4& v1, const Vec4& v2, const Vec4& v3): vertex1(v1), vertex2(v2), vertex3(v3) {}
Triangle(const Vec4& v1, const Vec4& v2, const Vec4& v3,
const Vec4& n1, const Vec4& n2, const Vec4& n3): vertex1(v1), vertex2(v2), vertex3(v3),
hasNormals(true), normal1(n1), normal2(n2), normal3(n3) {}
Triangle(const Vec4& v1, const Vec4& v2, const Vec4& v3,
const Vec4& uv1, const Vec4& uv2, const Vec4& uv3,
const Vec4& n1, const Vec4& n2, const Vec4& n3): vertex1(v1), vertex2(v2), vertex3(v3),
uv1(uv1), uv2(uv2), uv3(uv3),
hasNormals(true), normal1(n1), normal2(n2), normal3(n3) {}
// slow method, not really required as all normals should be equal
[[nodiscard]] Vec4 findClosestNormal(const Vec4& point) const {
// no need to sqrt as exact distance doesn't matter
auto n1Dist = (point - normal1).lengthSquared();
auto n2Dist = (point - normal2).lengthSquared();
auto n3Dist = (point - normal3).lengthSquared();
return (n1Dist < n2Dist && n1Dist < n3Dist) ? normal1 : (n2Dist < n3Dist ? normal2 : normal3);
}
};
// face type for model loading
struct face {
int v1, v2, v3;
int uv1, uv2, uv3;
int n1, n2, n3;
};
class Material {
protected:
// most materials will need an albedo
Vec4 baseColor;
public:
explicit Material(const Vec4& baseColor): baseColor(baseColor) {}
// returns true if the ray was scattered along with the scattered ray, otherwise will return false with empty ray.
// the returned vec4 is the attenuation color
[[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const = 0;
[[nodiscard]] Vec4 getBaseColor() const { return baseColor; }
virtual ~Material() = default;
};
class Object {
protected:
AABB aabb;
Vec4 position;
Material* material;
public:
Object(Material* material, const Vec4& position): material(material), position(position), aabb({}) {};
// return true if the ray intersects with this object, only between min and max
[[nodiscard]] virtual HitData checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const = 0;
[[nodiscard]] Material* getMaterial() const { return material; }
virtual Object* clone() = 0;
virtual AABB& getAABB() { return aabb; }
virtual void setAABB(const AABB& ab) { this->aabb = ab; }
[[nodiscard]] Vec4 getPosition() const { return position; }
virtual ~Object() = default;
};
// used for using an object, mostly BVH
class EmptyObject : public Object {
protected:
public:
Triangle& tri;
EmptyObject(const Vec4& position, const AABB& a, Triangle& tri): Object(nullptr, position), tri(tri) {this->aabb = a;};
// unused
[[nodiscard]] virtual HitData checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const {
wlog << "Warning! A empty object has made its way into the raycaster!\n";
return {};
}
virtual Object* clone(){return new EmptyObject(position, aabb, tri);}
};
}
#endif //STEP_2_TYPES_H

View File

@ -0,0 +1,84 @@
/*
* Created by Brett Terpstra 6920201 on 18/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*
* this is a direct one-to-one copy of the profiler class used in my Game Engine
* it functions very well especially when used in a GUI context,
* so why reinvent the wheel right?
* So to avoid any kind of self plagiarism, I fully credit the source which is here:
* https://github.com/Tri11Paragon/Trapdoor-Engine/tree/dev/C%2B%2B%20Engine
*/
#ifndef STEP_2_DEBUG_H
#define STEP_2_DEBUG_H
#include <util/std.h>
namespace Raytracing {
class profiler;
extern std::unordered_map<std::string, profiler*> profiles;
class DebugTab{
protected:
std::string name;
public:
virtual void render() {}
std::string getName() {
return name;
}
};
class profiler : public DebugTab {
private:
long _start = 0;
long _end = 0;
std::unordered_map<std::string, std::pair<long, long>> timings;
public:
explicit profiler(std::string name);
void start();
void start(const std::string& name);
static void start(const std::string& name, const std::string& tabName) {
auto p = new profiler(name);
profiles.insert(std::pair(name, p));
p->start(tabName);
}
void end();
void end(const std::string& name);
static void end(const std::string& name, const std::string& tabName){
try {
profiles.at(name)->end(tabName);
} catch (std::exception& e){}
}
void print();
static void print(const std::string& name){
try {
profiles.at(name)->print();
delete(profiles.at(name));
} catch (std::exception& e){}
}
void endAndPrint();
static void endAndPrint(const std::string& name, const std::string& tabName){
profiler::end(name, tabName);
profiler::print(name);
}
void render();
static void render(int count) {
for (auto p : profiles)
p.second->render();
}
~profiler();
static void cleanup(){
for (const auto& p : profiles)
delete(p.second);
}
};
}
#endif //STEP_2_DEBUG_H

View File

@ -0,0 +1,27 @@
/*
* Created by Brett Terpstra 6920201 on 14/10/22.
* Copyright (c) Brett Terpstra 2022 All Rights Reserved
*/
#ifndef STEP_2_LOGGING_H
#define STEP_2_LOGGING_H
#include <iostream>
// im only going to make this class if I have time (to add dates, output to file, and colored severity)
// log fatal
#define flog std::cerr << "[Fatal]: "
// log error
#define elog std::cerr << "[Error]: "
// log warning
#define wlog std::cout << "[Warning]: "
// log info
#define ilog std::cout << "[Info]: "
// log debug
#define dlog std::cout << "[Debug]: "
// log trace
#define tlog std::cout << "[Trace]: "
#endif //STEP_2_LOGGING_H

View File

@ -0,0 +1,91 @@
/*
* Created by Brett Terpstra 6920201 on 17/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#ifndef STEP_2_MODELS_H
#define STEP_2_MODELS_H
#include <util/std.h>
#include <math/vectors.h>
#include <math/colliders.h>
#include <math/bvh.h>
namespace Raytracing {
struct ModelData {
public:
// storing all this data is memory inefficient
// since normals and vertices are only vec3s
// and uvs are vec2s
// TODO: create lower order vector classes
std::vector<Vec4> vertices;
std::vector<Vec4> uvs;
std::vector<Vec4> normals;
std::vector<face> faces;
AABB aabb;
std::vector<Triangle> toTriangles() {
std::vector<Triangle> triangles;
PRECISION_TYPE minX = infinity, minY = infinity, minZ = infinity, maxX = ninfinity, maxY = ninfinity, maxZ = ninfinity;
for (face f: faces) {
Triangle t {vertices[f.v1], vertices[f.v2], vertices[f.v3],
uvs[f.uv1], uvs[f.uv2], uvs[f.uv3],
normals[f.n1], normals[f.n2], normals[f.n3]};
PRECISION_TYPE tMinX = infinity, tMinY = infinity, tMinZ = infinity, tMaxX = ninfinity, tMaxY = ninfinity, tMaxZ = ninfinity;
// find the min and max of all the triangles
tMinX = std::min(t.vertex1.x(), std::min(t.vertex2.x(), std::min(t.vertex3.x(), tMinX)));
tMinY = std::min(t.vertex1.y(), std::min(t.vertex2.y(), std::min(t.vertex3.y(), tMinY)));
tMinZ = std::min(t.vertex1.z(), std::min(t.vertex2.z(), std::min(t.vertex3.z(), tMinZ)));
tMaxX = std::max(t.vertex1.x(), std::max(t.vertex2.x(), std::max(t.vertex3.x(), tMaxX)));
tMaxY = std::max(t.vertex1.y(), std::max(t.vertex2.y(), std::max(t.vertex3.y(), tMaxY)));
tMaxZ = std::max(t.vertex1.z(), std::max(t.vertex2.z(), std::max(t.vertex3.z(), tMaxZ)));
// create a AABB for model local BVH
t.aabb = {tMinX, tMinY, tMinZ, tMaxX, tMaxY, tMaxZ};
// and of course for a model AABB,
minX = std::min(tMinX, minX);
minY = std::min(tMinY, minY);
minZ = std::min(tMinZ, minZ);
maxX = std::max(tMaxX, maxX);
maxY = std::max(tMaxY, maxY);
maxZ = std::max(tMaxZ, maxZ);
triangles.push_back(t);
}
// to generate a AABB
aabb = {minX, minY, minZ, maxX, maxY, maxZ};
return triangles;
}
// creates a BVH tree and returns the list of objects we created. make sure to delete them.
static std::vector<Object*> createBVHTree(std::vector<Triangle>& triangles, const Vec4& pos) {
std::vector<Object*> objects;
for (auto& tri : triangles){
Object* obj = new EmptyObject(pos, tri.aabb, tri);
objects.push_back(obj);
}
return objects;
}
};
class ModelLoader {
private:
public:
virtual ModelData loadModel(std::string file) = 0;
};
class OBJLoader : public ModelLoader {
private:
public:
virtual ModelData loadModel(std::string file);
};
}
#endif //STEP_2_MODELS_H

View File

@ -0,0 +1,49 @@
/*
* Created by Brett Terpstra 6920201 on 14/10/22.
* Copyright (c) Brett Terpstra 2022 All Rights Reserved
*/
#ifndef STEP_2_PARSER_H
#define STEP_2_PARSER_H
#include "util/std.h"
namespace Raytracing {
/**
* The parser class is used to parse incoming values from the commandline.
* It is not required that you use it for provided values
* and in fact should work for values provided via stdin
* however they must be provided in single line SSV char* form.
*/
class Parser {
private:
std::unordered_map<std::string, std::string> raw_values;
std::unordered_map<std::string, std::string> defaultValues;
std::vector<std::string> descriptions;
public:
Parser();
// Adds an option (or options) with default values.
// default value defaults to an empty string and will not be added
// useful if you want to add a description to the help menu.
void addOption(const std::string& option, const std::string& description, const std::string& defaultValue = "");
void addOption(const std::vector<std::string>& options, const std::string& description, const std::string& defaultValue = "");
// returns true if the option provided is different from the default option.
bool hasOptionChanged(const std::string& option);
// checks if the option has been provided to the parser
bool hasOption(const std::string& option);
// check if any of the options exist, only use for checking options that lead to the same path
// as this will return true at first option.
bool hasOption(const std::vector<std::string>& options);
// does not check to see if the option exists.
std::string getOptionValue(const std::string& option);
// parse the options from args
int parse(char** args, int argc);
void printDifferenceInInfo();
void printAllInInfo();
};
}
#endif //STEP_2_PARSER_H

View File

@ -0,0 +1,155 @@
/*
* Created by Brett Terpstra 6920201 on 14/10/22.
* Copyright (c) Brett Terpstra 2022 All Rights Reserved
*
* This file is used to include common standard library headers
* There are some things {String, Maps} that I use a lot
* Plus common defines that might be useful in the future.
*
*/
#ifndef STEP_2_STD_H
#define STEP_2_STD_H
/**
* includes
*/
#include <unordered_map>
#include <vector>
#include "util/logging.h"
#include <string>
#include <algorithm>
#include <cctype>
#include <locale>
#include <sstream>
#include <algorithm>
#include <limits>
#include <random>
/**
* defines
*/
#define RAYTRACING_VERSION_MAJOR 0
#define RAYTRACING_VERSION_MINOR 0
#define RAYTRACING_VERSION_PATCH 1
#define RAYTRACING_VERSION_STRING "0.0.1"
/**
* Constants
*/
const double infinity = std::numeric_limits<double>::infinity();
const double ninfinity = -std::numeric_limits<double>::infinity();
// PI, to a large # of digits
const double PI = 3.1415926535897932385;
// very small number
const double EPSILON = 0.0000001;
/**
* classes
*/
static inline double degreeeToRadian(double deg){
return deg * PI/180.0;
}
namespace Raytracing {
class Random {
private:
std::random_device rd; // obtain a random number from hardware
std::mt19937 gen;
std::uniform_real_distribution<double> doubleDistr {0, 1};
public:
Random(): gen(std::mt19937(long(rd.entropy() * 691 * 691))) {}
Random(double min, double max): gen(std::mt19937(long(rd.entropy() * 691 * 691))), doubleDistr{min, max} {}
double getDouble(){
return doubleDistr(gen);
}
};
class String {
public:
static inline std::string toLowerCase(const std::string& s){
std::stringstream str;
std::for_each(s.begin(), s.end(), [&str](unsigned char ch) {
str << (char) std::tolower(ch);
});
return str.str();
}
static inline std::string toUpperCase(const std::string& s){
std::stringstream str;
std::for_each(s.begin(), s.end(), [&str](unsigned char ch) {
str << (char) std::toupper(ch);
});
return str.str();
}
// taken from https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
// extended to return a vector
static inline std::vector<std::string> split(std::string s, const std::string& delim){
size_t pos = 0;
std::vector<std::string> tokens;
while ((pos = s.find(delim)) != std::string::npos) {
auto token = s.substr(0, pos);
tokens.push_back(token);
s.erase(0, pos + delim.length());
}
tokens.push_back(s);
return tokens;
}
// taken from https://stackoverflow.com/questions/216823/how-to-trim-an-stdstring
// would've preferred to use boost lib but instructions said to avoid external libs
// trim from start (in place)
static inline std::string& ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
return s;
}
// trim from end (in place)
static inline std::string& rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
return s;
}
// trim from both ends (in place)
static inline std::string& trim(std::string& s) {
ltrim(s);
rtrim(s);
return s;
}
// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
ltrim(s);
return s;
}
// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
rtrim(s);
return s;
}
// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
trim(s);
return s;
}
};
}
static Raytracing::Random rnd {};
static inline double getRandomDouble(){
return rnd.getDouble();
}
static inline double clamp(double val, double min, double max) {
if (val < min)
return min;
if (val > max)
return max;
return val;
}
#endif //STEP_2_STD_H

View File

@ -0,0 +1,137 @@
/*
* Created by Brett Terpstra 6920201 on 16/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#ifndef STEP_2_WORLD_H
#define STEP_2_WORLD_H
#include <util/std.h>
#include <math/vectors.h>
#include <util/models.h>
#include <math/bvh.h>
#include <types.h>
#include <utility>
namespace Raytracing {
class SphereObject : public Object {
private:
PRECISION_TYPE radius;
public:
SphereObject(const Vec4& position, PRECISION_TYPE radius, Material* material): radius(radius), Object(material, position) {}
[[nodiscard]] virtual HitData checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const;
virtual Object* clone(){
return new SphereObject(position, radius, material);
}
};
class TriangleObject : public Object {
private:
Triangle theTriangle;
public:
TriangleObject(const Vec4& position, Triangle theTriangle, Material* material): Object(material, position),
theTriangle(std::move(theTriangle)) {}
[[nodiscard]] virtual HitData checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const;
virtual Object* clone() {
return new TriangleObject(position, theTriangle, material);
}
};
class ModelObject : public Object {
private:
std::vector<Triangle> triangles;
ModelData& data;
// basically we have to store this crap here because c++ loves to copy stuff
//std::vector<Object*> createdTreeObjects{};
//BVHTree* tree = nullptr;
public:
ModelObject(const Vec4& position, ModelData& data, Material* material): Object(material, position), data(data) {
// since all of this occurs before the main ray tracing algorithm it's fine to do sequentially
triangles = data.toTriangles();
this->aabb = data.aabb;
//createdTreeObjects = Raytracing::ModelData::createBVHTree(triangles, position);
//tree = new BVHTree(createdTreeObjects);
}
[[nodiscard]] virtual HitData checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const;
virtual Object* clone() {
return new ModelObject(position, data, material);
}
virtual ~ModelObject() {
// Disabled for now, causing bugs when on release mode.
//for (auto* p : createdTreeObjects)
// delete(p);
//delete(tree);
}
};
class DiffuseMaterial : public Material {
private:
public:
explicit DiffuseMaterial(const Vec4& scatterColor): Material(scatterColor) {}
[[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const;
};
class MetalMaterial : public Material {
protected:
static inline Vec4 reflect(const Vec4& incomingVector, const Vec4& normal) {
return incomingVector - 2 * Vec4::dot(incomingVector, normal) * normal;
}
public:
explicit MetalMaterial(const Vec4& metalColor): Material(metalColor) {}
[[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const;
};
class BrushedMetalMaterial : public MetalMaterial {
private:
PRECISION_TYPE fuzzyness;
public:
explicit BrushedMetalMaterial(const Vec4& metalColor, PRECISION_TYPE fuzzyness): MetalMaterial(metalColor), fuzzyness(fuzzyness) {}
[[nodiscard]] virtual ScatterResults scatter(const Ray& ray, const HitData& hitData) const;
};
class TexturedMaterial : public Material {
public:
TexturedMaterial(const std::string& file): Material({}) {
}
};
class World {
private:
// store all the objects in the world,
std::vector<Object*> objects;
/*TODO: create a kd-tree or bvh version to store the objects
* this way we can easily tell if a ray is near and object or not
* saving on computation
*/
// TODO: the above todo has been done, now we need to test the performance advantage of the BVH
//BVHTree* bvhTree = nullptr;
std::unordered_map<std::string, Material*> materials;
public:
World() = default;
World(const World& world) = delete;
World(const World&& world) = delete;
// call this after you've added all the objects to the world. (Called by the raycaster class)
void generateBVH();
inline void add(Object* object) { objects.push_back(object); }
inline void addMaterial(const std::string& materialName, Material* mat) { materials.insert({materialName, mat}); }
inline Material* getMaterial(const std::string& materialName) { return materials.at(materialName); }
[[nodiscard]] virtual std::pair<HitData, Object*> checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const;
~World();
};
}
#endif //STEP_2_WORLD_H

View File

@ -0,0 +1,47 @@
# Blender 3.3.1
# www.blender.org
mtllib cube.mtl
o Cube
v -250.000000 -250.000000 250.000000
v -250.000000 250.000000 250.000000
v -250.000000 -250.000000 -250.000000
v -250.000000 250.000000 -250.000000
v 250.000000 -250.000000 250.000000
v 250.000000 250.000000 250.000000
v 250.000000 -250.000000 -250.000000
v 250.000000 250.000000 -250.000000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn -0.0000 1.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 1.000000 1.000000
s 0
f 3/6/1 2/2/1 1/1/1
f 7/14/2 4/8/2 3/4/2
f 5/9/3 8/16/3 7/14/3
f 1/1/4 6/12/4 5/10/4
f 1/1/5 7/15/5 3/5/5
f 6/13/6 4/8/6 8/16/6
f 3/6/1 4/7/1 2/2/1
f 7/14/2 8/16/2 4/8/2
f 5/9/3 6/11/3 8/16/3
f 1/1/4 2/2/4 6/12/4
f 1/1/5 5/10/5 7/15/5
f 6/13/6 2/3/6 4/8/6

View File

@ -0,0 +1,47 @@
# Blender 3.3.1
# www.blender.org
mtllib cubeflipped.mtl
o Cube
v -250.000000 -250.000000 250.000000
v -250.000000 250.000000 250.000000
v -250.000000 -250.000000 -250.000000
v -250.000000 250.000000 -250.000000
v 250.000000 -250.000000 250.000000
v 250.000000 250.000000 250.000000
v 250.000000 -250.000000 -250.000000
v 250.000000 250.000000 -250.000000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 1.000000 1.000000
s 0
f 3/6/1 1/1/1 2/2/1
f 7/14/2 3/4/2 4/8/2
f 5/9/3 7/14/3 8/16/3
f 1/1/4 5/10/4 6/12/4
f 1/1/5 3/5/5 7/15/5
f 6/13/6 8/16/6 4/8/6
f 3/6/1 2/2/1 4/7/1
f 7/14/2 4/8/2 8/16/2
f 5/9/3 8/16/3 6/11/3
f 1/1/4 6/12/4 2/2/4
f 1/1/5 7/15/5 5/10/5
f 6/13/6 4/8/6 2/3/6

View File

@ -0,0 +1,255 @@
# Blender 3.3.1
# www.blender.org
mtllib house.mtl
o Cube
v -0.800000 -1.000000 1.000000
v -0.800000 1.000000 1.000000
v -0.800000 -1.000000 -1.000000
v -0.800000 1.000000 -1.000000
v 0.800000 -1.000000 1.000000
v 0.800000 1.000000 1.000000
v 0.800000 -1.000000 -1.000000
v 0.800000 1.000000 -1.000000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vt 0.375000 0.000000
vt 0.375000 1.000000
vt 0.125000 0.750000
vt 0.625000 0.000000
vt 0.625000 1.000000
vt 0.875000 0.750000
vt 0.125000 0.500000
vt 0.375000 0.250000
vt 0.625000 0.250000
vt 0.875000 0.500000
vt 0.375000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.500000
vt 0.625000 0.500000
s 0
f 2/4/1 3/8/1 1/1/1
f 4/9/2 7/13/2 3/8/2
f 8/14/3 5/11/3 7/13/3
f 6/12/4 1/2/4 5/11/4
f 7/13/5 1/3/5 3/7/5
f 4/10/6 6/12/6 8/14/6
f 2/4/1 4/9/1 3/8/1
f 4/9/2 8/14/2 7/13/2
f 8/14/3 6/12/3 5/11/3
f 6/12/4 2/5/4 1/2/4
f 7/13/5 5/11/5 1/3/5
f 4/10/6 2/6/6 6/12/6
o Cube.001
v -1.000000 1.719057 0.057522
v -1.000000 1.832386 -0.037572
v -1.000000 0.433482 -1.474567
v -1.000000 0.546811 -1.569661
v 1.000000 1.719057 0.057522
v 1.000000 1.832386 -0.037572
v 1.000000 0.433482 -1.474567
v 1.000000 0.546811 -1.569661
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.6428 -0.7660
vn 1.0000 -0.0000 -0.0000
vn -0.0000 0.6428 0.7660
vn -0.0000 -0.7660 0.6428
vn -0.0000 0.7660 -0.6428
vt 0.375000 0.000000
vt 0.375000 1.000000
vt 0.125000 0.750000
vt 0.625000 0.000000
vt 0.625000 1.000000
vt 0.875000 0.750000
vt 0.125000 0.500000
vt 0.375000 0.250000
vt 0.625000 0.250000
vt 0.875000 0.500000
vt 0.375000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.500000
vt 0.625000 0.500000
s 0
f 10/18/7 11/22/7 9/15/7
f 12/23/8 15/27/8 11/22/8
f 16/28/9 13/25/9 15/27/9
f 14/26/10 9/16/10 13/25/10
f 15/27/11 9/17/11 11/21/11
f 12/24/12 14/26/12 16/28/12
f 10/18/7 12/23/7 11/22/7
f 12/23/8 16/28/8 15/27/8
f 16/28/9 14/26/9 13/25/9
f 14/26/10 10/19/10 9/16/10
f 15/27/11 13/25/11 9/17/11
f 12/24/12 10/20/12 14/26/12
o Cube.002
v -1.000000 0.433482 1.461952
v -1.000000 0.546811 1.557046
v -1.000000 1.719057 -0.070137
v -1.000000 1.832386 0.024957
v 1.000000 0.433482 1.461952
v 1.000000 0.546811 1.557046
v 1.000000 1.719057 -0.070137
v 1.000000 1.832386 0.024957
vn -1.0000 -0.0000 -0.0000
vn -0.0000 0.6428 -0.7660
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.6428 0.7660
vn -0.0000 -0.7660 -0.6428
vn -0.0000 0.7660 0.6428
vt 0.375000 0.000000
vt 0.375000 1.000000
vt 0.125000 0.750000
vt 0.625000 0.000000
vt 0.625000 1.000000
vt 0.875000 0.750000
vt 0.125000 0.500000
vt 0.375000 0.250000
vt 0.625000 0.250000
vt 0.875000 0.500000
vt 0.375000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.500000
vt 0.625000 0.500000
s 0
f 18/32/13 19/36/13 17/29/13
f 20/37/14 23/41/14 19/36/14
f 24/42/15 21/39/15 23/41/15
f 22/40/16 17/30/16 21/39/16
f 23/41/17 17/31/17 19/35/17
f 20/38/18 22/40/18 24/42/18
f 18/32/13 20/37/13 19/36/13
f 20/37/14 24/42/14 23/41/14
f 24/42/15 22/40/15 21/39/15
f 22/40/16 18/33/16 17/30/16
f 23/41/17 21/39/17 17/31/17
f 20/38/18 18/34/18 22/40/18
o Cube.003
v 0.787552 -0.999883 0.157434
v 0.787552 -0.426687 0.157434
v 0.787552 -0.999883 -0.157434
v 0.787552 -0.426687 -0.157434
v 0.836243 -0.999883 0.157434
v 0.836243 -0.426687 0.157434
v 0.836243 -0.999883 -0.157434
v 0.836243 -0.426687 -0.157434
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vt 0.375000 0.000000
vt 0.375000 1.000000
vt 0.125000 0.750000
vt 0.625000 0.000000
vt 0.625000 1.000000
vt 0.875000 0.750000
vt 0.125000 0.500000
vt 0.375000 0.250000
vt 0.625000 0.250000
vt 0.875000 0.500000
vt 0.375000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.500000
vt 0.625000 0.500000
s 0
f 26/46/19 27/50/19 25/43/19
f 28/51/20 31/55/20 27/50/20
f 32/56/21 29/53/21 31/55/21
f 30/54/22 25/44/22 29/53/22
f 31/55/23 25/45/23 27/49/23
f 28/52/24 30/54/24 32/56/24
f 26/46/19 28/51/19 27/50/19
f 28/51/20 32/56/20 31/55/20
f 32/56/21 30/54/21 29/53/21
f 30/54/22 26/47/22 25/44/22
f 31/55/23 29/53/23 25/45/23
f 28/52/24 26/48/24 30/54/24
o Cube.004
v 0.787552 -0.090162 0.721031
v 0.787552 0.409838 0.721031
v 0.787552 -0.090162 0.221031
v 0.787552 0.409838 0.221031
v 0.836243 -0.090162 0.721031
v 0.836243 0.409838 0.721031
v 0.836243 -0.090162 0.221031
v 0.836243 0.409838 0.221031
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vt 0.375000 0.000000
vt 0.375000 1.000000
vt 0.125000 0.750000
vt 0.625000 0.000000
vt 0.625000 1.000000
vt 0.875000 0.750000
vt 0.125000 0.500000
vt 0.375000 0.250000
vt 0.625000 0.250000
vt 0.875000 0.500000
vt 0.375000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.500000
vt 0.625000 0.500000
s 0
f 34/60/25 35/64/25 33/57/25
f 36/65/26 39/69/26 35/64/26
f 40/70/27 37/67/27 39/69/27
f 38/68/28 33/58/28 37/67/28
f 39/69/29 33/59/29 35/63/29
f 36/66/30 38/68/30 40/70/30
f 34/60/25 36/65/25 35/64/25
f 36/65/26 40/70/26 39/69/26
f 40/70/27 38/68/27 37/67/27
f 38/68/28 34/61/28 33/58/28
f 39/69/29 37/67/29 33/59/29
f 36/66/30 34/62/30 38/68/30
o Cube.005
v 0.787552 -0.090162 -0.227981
v 0.787552 0.409838 -0.227981
v 0.787552 -0.090162 -0.727981
v 0.787552 0.409838 -0.727981
v 0.836243 -0.090162 -0.227981
v 0.836243 0.409838 -0.227981
v 0.836243 -0.090162 -0.727981
v 0.836243 0.409838 -0.727981
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vt 0.375000 0.000000
vt 0.375000 1.000000
vt 0.125000 0.750000
vt 0.625000 0.000000
vt 0.625000 1.000000
vt 0.875000 0.750000
vt 0.125000 0.500000
vt 0.375000 0.250000
vt 0.625000 0.250000
vt 0.875000 0.500000
vt 0.375000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.500000
vt 0.625000 0.500000
s 0
f 42/74/31 43/78/31 41/71/31
f 44/79/32 47/83/32 43/78/32
f 48/84/33 45/81/33 47/83/33
f 46/82/34 41/72/34 45/81/34
f 47/83/35 41/73/35 43/77/35
f 44/80/36 46/82/36 48/84/36
f 42/74/31 44/79/31 43/78/31
f 44/79/32 48/84/32 47/83/32
f 48/84/33 46/82/33 45/81/33
f 46/82/34 42/75/34 41/72/34
f 47/83/35 45/81/35 41/73/35
f 44/80/36 42/76/36 46/82/36

View File

@ -0,0 +1,2 @@
# Blender 3.3.1 MTL File: 'None'
# www.blender.org

View File

@ -0,0 +1,16 @@
# Blender 3.3.1
# www.blender.org
mtllib plane.mtl
o Plane
v 0.000001 -8.781555 -9.817993
v -0.000000 -8.781555 9.817993
v 0.000000 8.781555 -9.817993
v -0.000001 8.781555 9.817993
vn 1.0000 -0.0000 -0.0000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
s 0
f 3/3/1 2/2/1 1/1/1
f 3/3/1 4/4/1 2/2/1

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
/*
* Created by Brett Terpstra 6920201 on 17/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
// Yes, globals are bad.
#include <util/debug.h>
namespace Raytracing {
std::unordered_map<std::string, profiler*> profiles;
}

View File

@ -0,0 +1,92 @@
/*
* Created by Brett Terpstra 6920201 on 14/10/22.
* Copyright (c) Brett Terpstra 2022 All Rights Reserved
*/
#include "image/image.h"
#include <ios>
#include <fstream>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <image/stb_image_write.h>
namespace Raytracing {
Image::Image(int width, int height) : width(width), height(height) {
pixelData = new Vec4[width * height];
}
Image::Image(const Image& image) : width(image.width), height(image.height) {
pixelData = new Vec4[image.width * image.height];
for (int i = 0; i < image.width; i++) {
for (int j = 0; j < image.height; j++) {
this->setPixelColor(i, j, image.pixelData[i * image.height + j]);
}
}
}
int Image::getPixelR(int x, int y) const {
// values are stored as a floating point number [0, 1)
// but most formats want an int [0, 255]
return int(255.0 * getPixelColor(x, y).r());
}
int Image::getPixelG(int x, int y) const {
return int(255.0 * getPixelColor(x, y).g());
}
int Image::getPixelB(int x, int y) const {
return int(255.0 * getPixelColor(x, y).b());
}
int Image::getPixelA(int x, int y) const {
return int(255.0 * getPixelColor(x, y).a());
}
Image::~Image() {
delete[](pixelData);
}
void ImageOutput::write(const std::string& file, const std::string& formatExtension) {
auto lowerExtension = Raytracing::String::toLowerCase(formatExtension);
auto fullFile = file + "." + lowerExtension;
if (!lowerExtension.ends_with("hdr")) {
// unfortunately we do have to put the data into a format that STB can read
unsigned char data[image.getWidth() * image.getHeight() * 3];
int pixelIndex = 0;
for (int j = image.getHeight()-1; j >= 0; j--) {
for (int i = 0; i < image.getWidth(); i++) {
data[pixelIndex++] = image.getPixelR(i, j);
data[pixelIndex++] = image.getPixelG(i, j);
data[pixelIndex++] = image.getPixelB(i, j);
}
}
// Writing a PPM was giving me issues, so I switched to using STB Image Write
// It's a single threaded, public domain header only image writing library
// I didn't want to use an external lib for this, however since it is public domain
// I've simply included it in the include directory.
if (lowerExtension.ends_with("bmp")) {
stbi_write_bmp(fullFile.c_str(), image.getWidth(), image.getHeight(), 3, data);
} else if (lowerExtension.ends_with("png")) {
// stride here isn't clearly defined in the docs for some reason,
// but it's just the image's width times the number of channels
stbi_write_png(fullFile.c_str(), image.getWidth(), image.getHeight(), 3, data, image.getWidth() * 3);
} else if (lowerExtension.ends_with("jpg") || lowerExtension.ends_with("jpeg")) {
stbi_write_jpg(fullFile.c_str(), image.getWidth(), image.getHeight(), 3, data, 90);
} else
throw std::runtime_error("Invalid format! Please use bmp, png, or jpg");
} else {
// the TODO: here is to check if HDR is in [0,1] or if we need to transform the value.
float data[image.getWidth() * image.getHeight() * 3];
int pixelIndex = 0;
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
data[pixelIndex++] = (float) image.getPixelColor(i, j).r();
data[pixelIndex++] = (float) image.getPixelColor(i, j).g();
data[pixelIndex++] = (float) image.getPixelColor(i, j).b();
}
}
stbi_write_hdr(fullFile.c_str(), image.getWidth(), image.getHeight(), 3, data);
}
}
}

View File

@ -0,0 +1,117 @@
#include "util/std.h"
#include "util/parser.h"
#include "image/image.h"
#include <raytracing.h>
#include <world.h>
#include <chrono>
/**
* Brett Terpstra 6920201
*
*/
int main(int argc, char** args) {
// not a feature full parser but it'll work for what I need.
Raytracing::Parser parser;
parser.addOption("--single", "Enable Single Thread\n\tUse a single thread for ray tracing\n", "true");
// not implemented yet
parser.addOption("--multi", "Enable Multi-threading\n"
"\tUse multiple threads for ray tracing,\n"
"\tYou can set the max threads using -t or --threads\n");
parser.addOption({{"-t"}, {"--threads"}}, "Max Usable Threads\n"
"\tSet the max threads the ray tracer will attempt to use.\n"
"\tDefaults to all cores of your cpu.\n", "0");
// not implemented yet
parser.addOption({{"--gui"}, {"-g"}}, "Enable GLFW GUI\n"
"\tWill create a GUI using GLFW and display the image there.\n"
"\tRequires the you compile with GLFW enabled. Will do nothing otherwise\n");
// not implemented yet
parser.addOption({{"--gpu"}, {"-c"}}, "Enables GPU Compute\n"
"\tRequires the --gui/-g flag enabled,\n"
"\tWill use OpenGL compute shaders to render the image\n");
parser.addOption("--output", "Output Directory\n"
"\tSet the output directory for the rendered image. Defaults to the local directory.\n", "./");
parser.addOption("--format", "Output Format\n"
"\tSets the output format to BMP, PNG, or JPEG. \n", "PNG");
parser.addOption("-w", "Image Width\n"
"\tSets the width of the output image.\n", "910");
parser.addOption("-h", "Image Height\n"
"\tSets the height of the output image.\n", "512");
parser.addOption("--fov", "Camera FOV\n"
"\tSets the FOV used to render the camera.\n", "90");
parser.addOption("--resources", "Resources Directory\n"
"\tSets the directory where the resources are stored.\n"
"\tThis can be relative.Must have trailing '/' \n", "../resources/");
// disabled because don't currently have a way to parse vectors. TODO
//parser.addOption("--position", "Camera Position\n\tSets the position used to render the scene with the camera.\n", "{0, 0, 0}");
// if the parser returns non-zero then it wants us to stop execution
// likely due to a help function being called.
if (parser.parse(args, argc))
return 0;
tlog << "Parsing complete! Starting raytracer with options:" << std::endl;
// not perfect (contains duplicates) but good enough.
parser.printAllInInfo();
//Raytracing::Image image(445, 256);
Raytracing::Image image(std::stoi(parser.getOptionValue("-w")), std::stoi(parser.getOptionValue("-h")));
Raytracing::Camera camera(std::stoi(parser.getOptionValue("--fov")), image);
//camera.setPosition({0, 0, 1});
camera.lookAt({6,5,6}, {0, 0, 0}, {0, 1, 0});
Raytracing::World world;
Raytracing::OBJLoader loader;
// assumes you are running it from a subdirectory, "build" or "cmake-build-release", etc.
Raytracing::ModelData spider = loader.loadModel(parser.getOptionValue("--resources") + "spider.obj");
Raytracing::ModelData house = loader.loadModel(parser.getOptionValue("--resources") + "house.obj");
Raytracing::ModelData plane = loader.loadModel(parser.getOptionValue("--resources") + "plane.obj");
world.addMaterial("greenDiffuse", new Raytracing::DiffuseMaterial{Raytracing::Vec4{0, 1.0, 0, 1}});
world.addMaterial("redDiffuse", new Raytracing::DiffuseMaterial{Raytracing::Vec4{1.0, 0, 0, 1}});
world.addMaterial("blueDiffuse", new Raytracing::DiffuseMaterial{Raytracing::Vec4{0, 0, 1.0, 1}});
world.addMaterial("greenMetal", new Raytracing::MetalMaterial{Raytracing::Vec4{0.4, 1.0, 0.4, 1}});
world.addMaterial("redMetal", new Raytracing::BrushedMetalMaterial{Raytracing::Vec4{1.0, 0.4, 0.4, 1}, 0.6f});
world.addMaterial("blueMetal", new Raytracing::MetalMaterial{Raytracing::Vec4{0.4, 0.4, 1.0, 1}});
//world.add(new Raytracing::SphereObject({0,0,-1,0{}, 0.5, world.getMaterial("redDiffuse")));
//world.add(new Raytracing::SphereObject({-1,0,-1,0}, 0.5, world.getMaterial("blueMetal")));
//world.add(new Raytracing::SphereObject({1,0,-1,0}, 0.5, world.getMaterial("redMetal")));
world.add(new Raytracing::SphereObject({0,-100.5,-1,0}, 100, world.getMaterial("greenDiffuse")));
//world.add(new Raytracing::TriangleObject({0,0.1,-0.5f,0}, {{-0.5, -0.5, 0.0}, {0.5, -0.5, 0.0}, {0.0, 0.5, 0}}, world.getMaterial("greenDiffuse")));
world.add(new Raytracing::ModelObject({0, 1, 0}, spider, world.getMaterial("redDiffuse")));
world.add(new Raytracing::ModelObject({-5, 0.5, 0}, plane, world.getMaterial("greenMetal")));
world.add(new Raytracing::ModelObject({5, 1, 0}, house, world.getMaterial("redDiffuse")));
world.add(new Raytracing::ModelObject({0, 0, -5}, house, world.getMaterial("blueDiffuse")));
world.add(new Raytracing::ModelObject({0, 0, 5}, house, world.getMaterial("blueDiffuse")));
Raytracing::Raycaster raycaster {camera, image, world, parser};
ilog << "Running raycaster!\n";
raycaster.run();
Raytracing::ImageOutput imageOutput(image);
auto t = std::time(nullptr);
auto now = std::localtime(&t);
std::stringstream timeString;
timeString << (1900 + now->tm_year);
timeString << "-";
timeString << (1 + now->tm_mon);
timeString << "-";
timeString << now->tm_mday;
timeString << " ";
timeString << now->tm_hour;
timeString << ":";
timeString << now->tm_min;
timeString << ":";
timeString << now->tm_sec;
ilog << "Writing Image!\n";
imageOutput.write(parser.getOptionValue("--output") + timeString.str(), parser.getOptionValue("--format"));
return 0;
}

View File

@ -0,0 +1,108 @@
/*
* Created by Brett Terpstra 6920201 on 17/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#include <math/colliders.h>
namespace Raytracing {
PRECISION_TYPE AABB::longestDistanceFromCenter() const {
Vec4 center = getCenter();
PRECISION_TYPE maxX = std::abs(max.x() - center.x());
PRECISION_TYPE minX = std::abs(min.x() - center.x());
PRECISION_TYPE maxY = std::abs(max.y() - center.y());
PRECISION_TYPE minY = std::abs(min.y() - center.y());
PRECISION_TYPE maxZ = std::abs(max.z() - center.z());
PRECISION_TYPE minZ = std::abs(min.z() - center.z());
return std::max(maxX, std::max(minX, std::max(maxY, std::max(minY, std::max(maxZ, minZ)))));
}
PRECISION_TYPE AABB::avgDistanceFromCenter() const {
Vec4 center = getCenter();
PRECISION_TYPE maxX = std::abs(max.x() - center.x());
PRECISION_TYPE minX = std::abs(min.x() - center.x());
PRECISION_TYPE maxY = std::abs(max.y() - center.y());
PRECISION_TYPE minY = std::abs(min.y() - center.y());
PRECISION_TYPE maxZ = std::abs(max.z() - center.z());
PRECISION_TYPE minZ = std::abs(min.z() - center.z());
maxX *= maxX;
minX *= minX;
maxY *= maxY;
minY *= minY;
maxZ *= maxZ;
minZ *= minZ;
return std::sqrt(maxX + minX + maxY + minY + maxZ + minZ);
}
int AABB::longestAxis() const {
PRECISION_TYPE X = std::abs(max.x() - min.x());
PRECISION_TYPE Y = std::abs(max.y() - min.y());
PRECISION_TYPE Z = std::abs(max.z() - min.z());
return X > Y && X > Z ? 0 : Y > Z ? 1 : 2;
}
PRECISION_TYPE AABB::longestAxisLength() const {
PRECISION_TYPE X = std::abs(max.x() - min.x());
PRECISION_TYPE Y = std::abs(max.y() - min.y());
PRECISION_TYPE Z = std::abs(max.z() - min.z());
return X > Y && X > Z ? X : Y > Z ? Y : Z;
}
std::pair<AABB, AABB> AABB::splitByLongestAxis() {
PRECISION_TYPE X = std::abs(max.x() - min.x());
PRECISION_TYPE X2 = X/2;
PRECISION_TYPE Y = std::abs(max.y() - min.y());
PRECISION_TYPE Y2 = Y/2;
PRECISION_TYPE Z = std::abs(max.z() - min.z());
PRECISION_TYPE Z2 = Z/2;
// return the new split AABBs based on the calculated max lengths, but only in their respective axis.
if (X > Y && X > Z) {
return {{min.x(), min.y(), min.z(), max.x()-X2, max.y(), max.z()},
// start the second AABB at the end of the first AABB.
{min.x()+X2, min.y(), min.z(), max.x(), max.y(), max.z()}};
} else if (Y > Z) {
return {{min.x(), min.y(), min.z(), max.x(), max.y()-Y2, max.z()}, {min.x(), min.y()+Y2, min.z(), max.x(), max.y(), max.z()}};
} else {
return {{min.x(), min.y(), min.z(), max.x(), max.y(), max.z()-Z2}, {min.x(), min.y(), min.z()+Z2, max.x(), max.y(), max.z()}};
}
}
/*
* Sources for designing these various algorithms
* TODO: test these methods for performance
* https://www.realtimerendering.com/intersections.html
* https://web.archive.org/web/20090803054252/http://tog.acm.org/resources/GraphicsGems/gems/RayBox.c
* https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection
* https://tavianator.com/2011/ray_box.html
* https://medium.com/@bromanz/another-view-on-the-classic-ray-aabb-intersection-algorithm-for-bvh-traversal-41125138b525
*/
bool AABB::simpleSlabRayAABBMethod(const Ray& ray, PRECISION_TYPE tmin, PRECISION_TYPE tmax){
// branch less design
// adapted from 2d to fit our 3d scene.
// (turns out this is actually a pretty standard design, but could use some optimization)
PRECISION_TYPE tx1 = (min.x() - ray.getStartingPoint().x())*ray.getInverseDirection().x();
PRECISION_TYPE tx2 = (max.x() - ray.getStartingPoint().x())*ray.getInverseDirection().x();
tmin = std::min(tmin, std::min(tx1, tx2));
tmax = std::max(tmax, std::max(tx1, tx2));
PRECISION_TYPE ty1 = (min.y() - ray.getStartingPoint().y())*ray.getInverseDirection().y();
PRECISION_TYPE ty2 = (max.y() - ray.getStartingPoint().y())*ray.getInverseDirection().y();
tmin = std::max(tmin, std::min(ty1, ty2));
tmax = std::min(tmax, std::max(ty1, ty2));
PRECISION_TYPE tz1 = (min.z() - ray.getStartingPoint().z())*ray.getInverseDirection().z();
PRECISION_TYPE tz2 = (max.z() - ray.getStartingPoint().z())*ray.getInverseDirection().z();
tmin = std::max(tmin, std::min(tz1, tz2));
tmax = std::min(tmax, std::max(tz1, tz2));
return tmax > std::max(tmin, 0.0);
}
bool AABB::intersects(const Ray& ray, PRECISION_TYPE tmin, PRECISION_TYPE tmax) {
return simpleSlabRayAABBMethod(ray, tmin, tmax);
}
}

View File

@ -0,0 +1,69 @@
/*
* Created by Brett Terpstra 6920201 on 16/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#include <raytracing.h>
#include <queue>
namespace Raytracing {
Ray Camera::projectRay(PRECISION_TYPE x, PRECISION_TYPE y) {
// transform the x and y to points from image coords to be inside the camera's viewport.
double transformedX = (x / (image.getWidth() - 1));
auto transformedY = (y / (image.getHeight() - 1));
// then generate a ray which extends out from the camera position in the direction with respects to its position on the image
return {position, imageOrigin + transformedX * horizontalAxis + transformedY * verticalAxis - position};
}
void Camera::lookAt(const Vec4& pos, const Vec4& lookAtPos, const Vec4& up) {
// standard camera lookAt function
auto w = (pos - lookAtPos).normalize();
auto u = (Vec4::cross(up, w)).normalize();
auto v = Vec4::cross(w, u);
position = pos;
horizontalAxis = viewportWidth * u;
verticalAxis = viewportHeight * v;
imageOrigin = position - horizontalAxis/2 - verticalAxis/2 - w;
}
void Camera::setRotation(const PRECISION_TYPE yaw, const PRECISION_TYPE pitch, const PRECISION_TYPE roll) {
// TODO:
}
void Raycaster::run() {
for (int i = 0; i < image.getWidth(); i++){
for (int j = 0; j < image.getHeight(); j++){
Raytracing::Vec4 color;
// TODO: profile for speed;
for (int s = 0; s < raysPerPixel; s++){
// simulate anti aliasing by generating rays with very slight random directions
color = color + raycast(camera.projectRay(i + rnd.getDouble(), j + rnd.getDouble()), 0);
}
PRECISION_TYPE sf = 1.0 / raysPerPixel;
// apply pixel color with gamma correction
image.setPixelColor(i, j, {std::sqrt(sf * color.r()), std::sqrt(sf * color.g()), std::sqrt(sf * color.b())});
}
}
}
Vec4 Raycaster::raycast(const Ray& ray, int depth) {
if (depth > maxBounceDepth)
return {0,0,0};
auto hit = world.checkIfHit(ray, 0.001, infinity);
if (hit.first.hit) {
auto object = hit.second;
auto scatterResults = object->getMaterial()->scatter(ray, hit.first);
// if the material scatters the ray, ie casts a new one,
if (scatterResults.scattered) // attenuate the recursive raycast by the material's color
return scatterResults.attenuationColor * raycast(scatterResults.newRay, depth + 1);
//tlog << "Not scattered? " << object->getMaterial() << "\n";
return {0,0,0};
}
// skybox color
return {0.5, 0.7, 1.0};
}
}

View File

@ -0,0 +1,61 @@
/*
* Created by Brett Terpstra 6920201 on 18/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#include <util/debug.h>
#include <chrono>
namespace Raytracing {
profiler::profiler(std::string name) {
this->name = name;
// currently unused as we don't have a UI yet.
//TD::debugUI::addTab(this);
}
void profiler::start() {
start("Unnamed");
}
void profiler::start(const std::string& name) {
auto p1 = std::chrono::high_resolution_clock::now();
_start = std::chrono::duration_cast<std::chrono::nanoseconds>(p1.time_since_epoch()).count();
timings[name] = std::pair<long, long>(_start, 0);
}
void profiler::end() {
end("Unnamed");
}
void profiler::end(const std::string& name) {
auto p1 = std::chrono::high_resolution_clock::now();
_end = std::chrono::duration_cast<std::chrono::nanoseconds>(p1.time_since_epoch()).count();
timings[name] = std::pair<long, long>(timings[name].first, _end);
}
void profiler::print() {
ilog << "Profiler " << name << " recorded: ";
for (std::pair<std::string, std::pair<long, long>> e : timings){
ilog << "\t" << e.first << " took " << ((double)(e.second.second - e.second.first) / 1000000.0) << "ms to run!";
}
}
void profiler::endAndPrint() {
end();
print();
}
void profiler::render() {
// currently unused as we don't have a UI yet.
/*ImGui::Text("CPU Timings:");
ImGui::Indent();
for (std::pair<std::string, std::pair<long, long>> e : timings) {
ImGui::Text("Elapsed Time(%s): %fms", e.first.c_str(), (double) ((e.second.second - e.second.first) / 1000000.0));
}
ImGui::Unindent();
ImGui::NewLine();*/
}
profiler::~profiler() {
// currently unused as we don't have a UI yet.
//TD::debugUI::deleteTab(this);
}
}

View File

@ -0,0 +1,65 @@
/*
* Created by Brett Terpstra 6920201 on 17/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#include <util/models.h>
#include <fstream>
#include <ios>
Raytracing::ModelData Raytracing::OBJLoader::loadModel(std::string file) {
std::ifstream modelFile;
modelFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
modelFile.open(file);
std::stringstream modelSource;
std::cout.flush();
// read the entire file into a string
modelSource << modelFile.rdbuf();
modelFile.close();
std::string modelUnprocessedSource = modelSource.str();
auto lines = String::split(modelUnprocessedSource, "\n");
ilog << "Loading of model file " << file << " complete! Now processing " << lines.size() << " lines.\n";
ModelData data;
for (const auto& line : lines){
auto spaces = String::split(line, " ");
if (line.starts_with("v ")) { // vertex
data.vertices.emplace_back(std::stof(spaces[1]), std::stof(spaces[2]), std::stof(spaces[3]));
} else if (line.starts_with("vt ")){ // uv
data.uvs.emplace_back(std::stof(spaces[1]), std::stof(spaces[2]), 0);
} else if (line.starts_with("vn ")){ // normal
data.normals.emplace_back(std::stof(spaces[1]), std::stof(spaces[2]), std::stof(spaces[3]));
} else if (line.starts_with("f ")){ // face
// we've reached the faces, we'll need to process them later.
auto t1 = String::split(spaces[1], "/");
auto t2 = String::split(spaces[2], "/");
auto t3 = String::split(spaces[3], "/");
face f {};
// obj files are 1 indexed,
// but arrays are 0 indexed,
// must be transformed.
f.v1 = std::stoi(t1[0])-1;
f.v2 = std::stoi(t2[0])-1;
f.v3 = std::stoi(t3[0])-1;
f.uv1 = std::stoi(t1[1])-1;
f.uv2 = std::stoi(t2[1])-1;
f.uv3 = std::stoi(t3[1])-1;
f.n1 = std::stoi(t1[2])-1;
f.n2 = std::stoi(t2[2])-1;
f.n3 = std::stoi(t3[2])-1;
data.faces.push_back(f);
}
}
ilog << "Completed extracting vertex data from model file " << file << "!\n";
return data;
}

View File

@ -0,0 +1,115 @@
/*
* Created by Brett Terpstra 6920201 on 14/10/22.
* Copyright (c) Brett Terpstra 2022 All Rights Reserved
*/
#include "util/parser.h"
#include <sstream>
namespace Raytracing {
Parser::Parser() {
// only to add the description (very annoying we have to have double {}!)
addOption({{"--help"}, {"-h"}}, "Show this help menu!");
}
std::string Parser::getOptionValue(const std::string& option) {
return raw_values.contains(option) ? raw_values.at(option) : defaultValues.at(option);
}
int Parser::parse(char **args, int argc) {
// first arg is always the command path
// which we don't care about.
for (int i = 1; i < argc; i++) {
std::string option = std::string(args[i]);
std::string value = "true";
// we only want to get the value if it was actually provided.
if (i + 1 < argc) {
value = std::string(args[i + 1]);
// must make sure we don't skip over valuable parasble options!
if (!value.starts_with("-")) {
// we must add to the value of i since we don't want to try and parse values.
i++;
} else // if it does start with -, it's an option, we need to reset to true.
value = "true";
}
// if values are provided to us with -- or - we simply don't care about them!
if (option.starts_with("-"))
raw_values.insert(std::pair(option, value));
else
throw std::runtime_error("Unfortunately an invalid argument was provided! {" + option + "}");
}
if (hasOption({{"--help"}, {"-h"}})){
std::cout << "Raytracer help information:" << std::endl;
for (const auto& d : descriptions){
std::cout << d;
}
return true;
}
return false;
}
void Parser::addOption(const std::string& option, const std::string& description, const std::string& defaultValue) {
// we only want to add if the default value isn't empty,
// since we might want to use the option as a single optional flag and not a value.
if (!Raytracing::String::trim_copy(defaultValue).empty())
defaultValues.insert(std::pair(option, defaultValue));
descriptions.emplace_back(option + " :" + description);
}
bool Parser::hasOptionChanged(const std::string& option) {
return raw_values.contains(option) && defaultValues.contains(option) && raw_values.at(option) != defaultValues.at(option);
}
void Parser::addOption(const std::vector<std::string>& options, const std::string &description, const std::string &defaultValue) {
// add option like normal
for (const auto& option : options){
if (!Raytracing::String::trim_copy(defaultValue).empty())
defaultValues.insert(std::pair(option, defaultValue));
}
// but improve the description for multi-option
std::stringstream desStr;
// add all the options to the description, seperated by ","
for (int i = 0; i < options.size(); i++){
desStr << options[i];
if (i != options.size()-1)
desStr << ", ";
}
desStr << " :";
desStr << description;
descriptions.emplace_back(desStr.str());
}
bool Parser::hasOption(const std::string &option) {
return raw_values.contains(option);
}
bool Parser::hasOption(const std::vector<std::string> &options) {
for (const auto& option : options)
if (hasOption(option))
return true;
return false;
}
void Parser::printAllInInfo() {
// print all with default values
for (const auto& opt : defaultValues){
ilog << opt.first << ": " << (raw_values.contains(opt.first) ? raw_values.at(opt.first) : opt.second) << " (default: " << opt.second << ")" << std::endl;
}
// then just print all the ones which don't have but where provided
for (const auto& opt : raw_values){
if (!defaultValues.contains(opt.first))
ilog << "With: " << opt.first;
}
}
void Parser::printDifferenceInInfo() {
for (const auto& opt : raw_values){
if (defaultValues.contains(opt.first))
if (opt.second == defaultValues.at(opt.first))
ilog << opt.first << ": " << opt.second << " (default: " << defaultValues.at(opt.first) << ")" << std::endl;
}
}
}

View File

@ -0,0 +1,212 @@
/*
* Created by Brett Terpstra 6920201 on 16/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#include <world.h>
#include <raytracing.h>
namespace Raytracing {
World::~World() {
for (auto* p: objects)
delete (p);
for (const auto& p: materials)
delete (p.second);
//delete(bvhTree);
}
HitData SphereObject::checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const {
PRECISION_TYPE radiusSquared = radius * radius;
// move the ray to be with respects to the sphere
Vec4 RayWRTSphere = ray.getStartingPoint() - position;
// now determine the discriminant for the quadratic formula for the function of line sphere intercept
PRECISION_TYPE a = ray.getDirection().lengthSquared();
PRECISION_TYPE b = Raytracing::Vec4::dot(RayWRTSphere, ray.getDirection());
PRECISION_TYPE c = RayWRTSphere.lengthSquared() - radiusSquared;
// > 0: the hit has two roots, meaning we hit both sides of the sphere
// = 0: the ray has one root, we hit the edge of the sphere
// < 0: ray isn't inside the sphere.
PRECISION_TYPE discriminant = b * b - (a * c);
// < 0: ray isn't inside the sphere. Don't need to bother calculating the roots.
if (discriminant < 0)
return {false, Vec4(), Vec4(), 0};
// now we have to find the root which exists inside our range [min,max]
auto root = (-b - std::sqrt(discriminant)) / a;
// if the first root isn't in our range
if (root < min || root > max) {
// check the second root
root = (-b + std::sqrt(discriminant)) / a;
if (root < min || root > max) {
// if the second isn't in the range then we also must return false.
return {false, Vec4(), Vec4(), 0};
}
}
// the hit point is where the ray is when extended to the root
auto RayAtRoot = ray.along(root);
// The normal of a sphere is just the point of the hit minus the center position
auto normal = (RayAtRoot - position).normalize();
/*if (Raytracing::vec4::dot(ray.getDirection(), normal) > 0.0) {
tlog << "ray inside sphere\n";
} else
tlog << "ray outside sphere\n";
*/
return {true, RayAtRoot, normal, root};
}
std::pair<HitData, Object*> World::checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const {
/*if (bvhTree != nullptr){
auto hResult = HitData{false, Vec4(), Vec4(), max};
Object* objPtr = nullptr;
auto intersected = bvhTree->rayIntersect(ray, min, max);
//dlog << "Intersections " << intersected.size() << " " << ray << "\n";
for (auto* ptr : intersected) {
auto cResult = ptr->checkIfHit(ray, min, hResult.length);
if (cResult.hit) {
hResult = cResult;
objPtr = ptr;
}
}
// after we check the BVH, we have to check for other missing objects
// since stuff like spheres currently don't have AABB and AABB isn't a requirement
// for the object class (to be assigned)
for (auto* obj: bvhTree->noAABBObjects) {
// check up to the point of the last closest hit,
// will give the closest object's hit result
auto cResult = obj->checkIfHit(ray, min, hResult.length);
if (cResult.hit) {
hResult = cResult;
objPtr = obj;
}
}
return {hResult, objPtr};
} else {*/
// rejection algo without using a binary space partitioning data structure
auto hResult = HitData{false, Vec4(), Vec4(), max};
Object* objPtr = nullptr;
for (auto* obj: objects) {
// check up to the point of the last closest hit,
// will give the closest object's hit result
auto cResult = obj->checkIfHit(ray, min, hResult.length);
if (cResult.hit) {
hResult = cResult;
objPtr = obj;
}
}
return {hResult, objPtr};
//}
}
void World::generateBVH() {
//bvhTree = new BVHTree(objects);
}
ScatterResults DiffuseMaterial::scatter(const Ray& ray, const HitData& hitData) const {
Vec4 newRay = hitData.normal + Raytracing::Raycaster::randomUnitVector().normalize();
// rays that are close to zero are liable to floating point precision errors
if (newRay.x() < EPSILON && newRay.y() < EPSILON && newRay.z() < EPSILON && newRay.w() < EPSILON)
newRay = hitData.normal;
return {true, Ray{hitData.hitPoint, newRay}, getBaseColor()};
}
ScatterResults MetalMaterial::scatter(const Ray& ray, const HitData& hitData) const {
// create a ray reflection
Vec4 newRay = reflect(ray.getDirection().normalize(), hitData.normal);
// make sure our reflected ray is outside the sphere and doesn't point inwards
bool shouldReflect = Vec4::dot(newRay, hitData.normal) > 0;
return {shouldReflect, Ray{hitData.hitPoint, newRay}, getBaseColor()};
}
ScatterResults BrushedMetalMaterial::scatter(const Ray& ray, const HitData& hitData) const {
// create a ray reflection
Vec4 newRay = reflect(ray.getDirection().normalize(), hitData.normal);
// make sure our reflected ray is outside the sphere and doesn't point inwards
bool shouldReflect = Vec4::dot(newRay, hitData.normal) > 0;
return {shouldReflect, Ray{hitData.hitPoint, newRay + Raycaster::randomUnitVector() * fuzzyness}, getBaseColor()};
}
static HitData checkIfTriangleGotHit(const Triangle& theTriangle, const Vec4& position, const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) {
// MöllerTrumbore intersection algorithm
// https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
Vec4 edge1, edge2, h, s, q;
PRECISION_TYPE a, f, u, v;
edge1 = (theTriangle.vertex2 + position) - (theTriangle.vertex1 + position);
edge2 = (theTriangle.vertex3 + position) - (theTriangle.vertex1 + position);
h = Vec4::cross(ray.getDirection(), edge2);
a = Vec4::dot(edge1, h);
if (a > -EPSILON && a < EPSILON)
return {false, Vec4(), Vec4(), 0}; //parallel to triangle
f = 1.0 / a;
s = ray.getStartingPoint() - (theTriangle.vertex1 + position);
u = f * Vec4::dot(s, h);
if (u < 0.0 || u > 1.0)
return {false, Vec4(), Vec4(), 0};
q = Vec4::cross(s, edge1);
v = f * Vec4::dot(ray.getDirection(), q);
if (v < 0.0 || u + v > 1.0)
return {false, Vec4(), Vec4(), 0};
// At this stage we can compute t to find out where the intersection point is on the line.
PRECISION_TYPE t = f * Vec4::dot(edge2, q);
// keep t in reasonable bounds, ensuring we respect depth
if (t > EPSILON && t >= min && t <= max) {
// ray intersects
Vec4 rayIntersectionPoint = ray.along(t);
Vec4 normal;
// normal = theTriangle.findClosestNormal(rayIntersectionPoint - position);
if (theTriangle.hasNormals) // returning the closest normal is extra computation when n1 would likely be fine.
normal = theTriangle.normal1;
else {
// standard points to normal algorithm but using already computed edges
normal = Vec4{edge1.y() * edge2.z(), edge1.z() * edge2.x(), edge1.x() * edge2.y()} -
Vec4{edge1.z() * edge2.y(), edge1.x() * edge2.z(), edge1.y() * edge2.x()};
}
return {true, rayIntersectionPoint, normal, t};
}
return {false, Vec4(), Vec4(), 0};
}
HitData TriangleObject::checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const {
return checkIfTriangleGotHit(theTriangle, position, ray, min, max);
}
HitData ModelObject::checkIfHit(const Ray& ray, PRECISION_TYPE min, PRECISION_TYPE max) const {
auto hResult = HitData{false, Vec4(), Vec4(), max};
// must check through all the triangles in the object
// respecting depth along the way
// but reducing the max it can reach my the last longest vector length.
for (const Triangle& t : triangles) {
auto cResult = checkIfTriangleGotHit(t, position, ray, min, hResult.length);
if (cResult.hit)
hResult = cResult;
}
/*auto hResult = HitData{false, Vec4(), Vec4(), max};
auto intersected = tree->rayIntersect(ray, min, max);
for (auto t : intersected){
// apparently this kind of casting is okay
// which makes sense since the actual data behind it is a empty object
// just this is really bad and im too annoyed to figure out a better way. TODO:.
auto cResult = checkIfTriangleGotHit(((EmptyObject*)(t))->tri, position, ray, min, hResult.length);
if (cResult.hit)
hResult = cResult;
}*/
return hResult;
}
}

View File

@ -1,105 +1,18 @@
# ninja log v5 # ninja log v5
1 1269 1666191947233881035 CMakeFiles/Step_2.dir/src/main.cpp.o 387235f0a298f257 1 1911 1666240985448103414 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1 1329 1666208294813201609 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o 7957915dde476a69 1 1263 1666240984800084042 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
10 1796 1666150208085056398 CMakeFiles/Step_2.dir/src/util/parser.cpp.o 4bae007146d86363
1 840 1666191946805877238 CMakeFiles/Step_2.dir/src/raytracing.cpp.o e78f62563fb6428
1269 1307 1666191947269881356 Step_2 2fd6ed1a72fdaa11
9 1453 1666150207745047625 CMakeFiles/Step_2.dir/src/util/debug.cpp.o e1fa95ca3d6a9ce2
1 1770 1666208295257215005 CMakeFiles/Step_2.dir/src/util/models.cpp.o 12734a9214cf8718
1 1112 1666150207401038752 CMakeFiles/Step_2.dir/src/globals.cpp.o 5fc3599d89d4b8a2
2 1036 1666191947001878976 CMakeFiles/Step_2.dir/src/world.cpp.o 713f22c63a585103
1 2726 1666208296213243852 CMakeFiles/Step_2.dir/src/image/image.cpp.o 4d16c0d3199b9aa8
1 874 1666208421288995782 CMakeFiles/Step_2.dir/src/globals.cpp.o 8caaf06f90e0c038
2 1437 1666208421849012493 CMakeFiles/Step_2.dir/src/util/debug.cpp.o 2bec3f4c06e82095
2 1440 1666208421853012610 CMakeFiles/Step_2.dir/src/util/parser.cpp.o 67ef710b86dd32d6 2 1440 1666208421853012610 CMakeFiles/Step_2.dir/src/util/parser.cpp.o 67ef710b86dd32d6
2 1496 1666208421909014283 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153 1 1419 1666240984956088704 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
2 1709 1666208422121020608 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323 2673 2712 1666240986252127454 Step_2 7437176fc9e68547
2 1728 1666208422141021205 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660 2 1437 1666208421849012493 CMakeFiles/Step_2.dir/src/util/debug.cpp.o 2bec3f4c06e82095
2 1966 1666208422381028366 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e 2 1611 1666240985148094447 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 2034 1666208422449030392 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1 1 874 1666208421288995782 CMakeFiles/Step_2.dir/src/globals.cpp.o 8caaf06f90e0c038
1 2886 1666208423301055812 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271 2 1517 1666240985056091694 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
2887 2929 1666208423341057004 Step_2 7437176fc9e68547 1 2673 1666240986212126256 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2 1224 1666210629933631775 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323 1 1202 1666241022097198348 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
2 1374 1666210630081636055 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153 1 1575 1666241022473209572 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
2 1548 1666210630257641141 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660 1 1914 1666241022809219604 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
2 1652 1666210630361644148 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e 1 1942 1666241022837220441 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
2 1913 1666210630621651665 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1 2 1962 1666241022857221038 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
1 2643 1666210631353672824 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271 1 2624 1666241023521240863 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2643 2681 1666210631389673865 Step_2 7437176fc9e68547 2624 2663 1666241023561242054 Step_2 7437176fc9e68547
1 1212 1666212386048431924 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
1 1360 1666212386196434896 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
1 1484 1666212386320437387 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
1 1611 1666212386448439957 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 1875 1666212386712445259 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1 2656 1666212387492460928 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2656 2703 1666212387540461893 Step_2 7437176fc9e68547
2 1250 1666212415865034379 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
2 1375 1666212415989036899 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
2 1566 1666212416181040803 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
2 1617 1666212416233041863 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 1928 1666212416545048206 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1 2621 1666212417237062279 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2621 2660 1666212417273063011 Step_2 7437176fc9e68547
1 1269 1666212741664012313 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
1 1368 1666212741764014542 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
2 1543 1666212741940018466 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
2 1646 1666212742044020786 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 1910 1666212742308026673 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1 2600 1666212742996042017 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2600 2642 1666212743036042909 Step_2 7437176fc9e68547
1 1791 1666215595209880342 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
1 1840 1666215595257881412 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
1 1888 1666215595305882480 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 1990 1666215595409884798 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
1 2395 1666215595813893798 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
0 3052 1666215596469908420 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
3052 3125 1666215596541910026 Step_2 7437176fc9e68547
1 788 1666215661443385226 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
1 949 1666215661603388929 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
1 1072 1666215661727391802 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
1 1204 1666215661859394855 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 1441 1666215662095400322 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1 2153 1666215662807416814 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2153 2191 1666215662847417737 Step_2 7437176fc9e68547
1 1023 1666216381510163196 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
1 1068 1666216381554164424 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
1 1274 1666216381762170234 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 1520 1666216382006177047 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
1 1732 1666216382218182968 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1 2646 1666216383134208548 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2646 2694 1666216383182209890 Step_2 7437176fc9e68547
1 878 1666217200233659155 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
0 1380 1666217200733673732 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1380 1421 1666217200773674899 Step_2 7437176fc9e68547
1 1341 1666217588772696599 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1341 1380 1666217588812697660 Step_2 7437176fc9e68547
0 1305 1666217602745067311 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1305 1345 1666217602785068374 Step_2 7437176fc9e68547
0 1381 1666217890128871360 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1381 1424 1666217890172872578 Step_2 7437176fc9e68547
0 997 1666218374062493836 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
0 1320 1666218374386503078 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1320 1358 1666218374422504103 Step_2 7437176fc9e68547
1 942 1666218674383099244 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
1 1007 1666218674447101084 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
1 1289 1666218674727109140 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 1342 1666218674779110635 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
1 1597 1666218675035117999 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
0 2433 1666218675871142048 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2433 2478 1666218675919143428 Step_2 7437176fc9e68547
0 1373 1666218737704921878 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1373 1413 1666218737744923030 Step_2 7437176fc9e68547
2 1448 1666220004262511612 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
2 1482 1666220004294511493 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
2 1663 1666220004478510816 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
2 1879 1666220004694510019 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 2049 1666220004862509401 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1 2908 1666220005722506266 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2909 2956 1666220005770506091 Step_2 7437176fc9e68547
1 1232 1666220317311320782 CMakeFiles/Step_2.dir/src/math/colliders.cpp.o c0cac41f151ac323
1 1413 1666220317491322171 CMakeFiles/Step_2.dir/src/raytracing.cpp.o d0ae86d239f7153
1 1568 1666220317647323373 CMakeFiles/Step_2.dir/src/world.cpp.o 9ae7336b91871660
1 1708 1666220317783324422 CMakeFiles/Step_2.dir/src/util/models.cpp.o 7aa2ab72df0658e
1 2010 1666220318087326770 CMakeFiles/Step_2.dir/src/main.cpp.o 8da3d580eb6e9a1
1 2840 1666220318919333214 CMakeFiles/Step_2.dir/src/image/image.cpp.o f94f9df081e81271
2840 2881 1666220318959333521 Step_2 7437176fc9e68547

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

View File

@ -1,3 +1,3 @@
Start testing: Oct 19 18:58 EDT Start testing: Oct 20 00:46 EDT
---------------------------------------------------------- ----------------------------------------------------------
End testing: Oct 19 18:58 EDT End testing: Oct 20 00:46 EDT

View File

@ -1 +1,4 @@
cmake -DCMAKE_BUILD_TYPE=Deug -G Ninja .. #!/bin/bash
cmake -DCMAKE_BUILD_TYPE=Release -G Ninja ..
ninja
./Step_2

View File

@ -8,7 +8,7 @@
// AVX512 isn't supported on my CPU. We will use AVX2 since it is supported by most modern CPUs // AVX512 isn't supported on my CPU. We will use AVX2 since it is supported by most modern CPUs
// THIS IS CURRENTLY BROKEN. DO NOT USE. it's a great idea, but I've run out of time to debug this. Will be in step 3 // THIS IS CURRENTLY BROKEN. DO NOT USE. it's a great idea, but I've run out of time to debug this. Will be in step 3
//#define USE_SIMD_CPU #define USE_SIMD_CPU
#ifdef USE_SIMD_CPU #ifdef USE_SIMD_CPU
@ -44,7 +44,8 @@ namespace Raytracing {
// makes it easy to convert between AVX and double data types. // makes it easy to convert between AVX and double data types.
union { union {
struct { struct {
double _x, _y, _z, _w; PRECISION_TYPE _x{}, _y{}, _z{}, _w{};
//PRECISION_TYPE _w, _z, _y, _x;
}; };
__m256d avxData; __m256d avxData;
}; };
@ -56,10 +57,16 @@ namespace Raytracing {
friend Vec4 operator/(const Vec4& left, const Vec4& right); friend Vec4 operator/(const Vec4& left, const Vec4& right);
public: public:
Vec4(): avxData{_mm256_set_pd(0.0, 0.0, 0.0, 0.0)} {} Vec4(): avxData(_mm256_setzero_pd()) {}
Vec4(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z): avxData{_mm256_set_pd(x, y, z, 0)} {} Vec4(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z): avxData(_mm256_setr_pd(x, y, z, 0.0)) {
Vec4(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z, PRECISION_TYPE w): avxData{_mm256_set_pd(x, y, z, w)} {} //tlog << x << ":" << _x << " " << y << ":" << _y << " " << z << ":" << _z << "\n";
Vec4(const Vec4& vec): avxData{_mm256_set_pd(vec.x(), vec.y(), vec.z(), vec.w())} {} }
Vec4(PRECISION_TYPE x, PRECISION_TYPE y, PRECISION_TYPE z, PRECISION_TYPE w): avxData(_mm256_setr_pd(x, y, z, w)) {
//dlog << x << ":" << _x << " " << y << ":" << _y << " " << z << ":" << _z << "\n";
}
Vec4(const Vec4& vec): avxData(_mm256_setr_pd(vec.x(), vec.y(), vec.z(), vec.w())) {
//ilog << vec.x() << ":" << _x << " " << vec.y() << ":" << _y << " " << vec.z() << ":" << _z << "\n";
}
// most of the modern c++ here is because clang tidy was annoying me // most of the modern c++ here is because clang tidy was annoying me
[[nodiscard]] inline PRECISION_TYPE x() const { return _x; } [[nodiscard]] inline PRECISION_TYPE x() const { return _x; }
@ -122,16 +129,16 @@ namespace Raytracing {
// takes the x element and places it in the 3rd element of the vector // takes the x element and places it in the 3rd element of the vector
// and then the w element in the last element of the vector // and then the w element in the last element of the vector
// creating the alignment {left.y(), left.z(), left.x(), left.w()} (as seen in the cross algorithm // creating the alignment {left.y(), left.z(), left.x(), left.w()} (as seen in the cross algorithm
__m256d leftLeftShuffle = _mm256_shuffle_pd(left.avxData, left.avxData, _MM_SHUFFLE(3,0,2,1)); __m256d leftLeftShuffle = _mm256_permute4x64_pd(left.avxData, _MM_SHUFFLE(3,0,2,1));
// same thing but produces {right.z(), right.x(), right.y(), right.w()} // same thing but produces {right.z(), right.x(), right.y(), right.w()}
__m256d rightLeftShuffle = _mm256_shuffle_pd(right.avxData, right.avxData, _MM_SHUFFLE(3,1,0,2)); __m256d rightLeftShuffle = _mm256_permute4x64_pd(right.avxData, _MM_SHUFFLE(3,1,0,2));
// multiply to do the first step of the cross process
__m256d multiLeft = _mm256_mul_pd(leftLeftShuffle, rightLeftShuffle);
// now we have to do the right side multiplications // now we have to do the right side multiplications
// {left.z(), left.x(), left.y(), left.w()} // {left.z(), left.x(), left.y(), left.w()}
__m256d leftRightShuffle = _mm256_shuffle_pd(left.avxData, left.avxData, _MM_SHUFFLE(3,1,0,2)); __m256d leftRightShuffle = _mm256_permute4x64_pd(left.avxData, _MM_SHUFFLE(3,1,0,2));
// {right.y(), right.z(), right.x(), right.w()} // {right.y(), right.z(), right.x(), right.w()}
__m256d rightRightShuffle = _mm256_shuffle_pd(right.avxData, right.avxData, _MM_SHUFFLE(3,0,2,1)); __m256d rightRightShuffle = _mm256_permute4x64_pd(right.avxData, _MM_SHUFFLE(3,0,2,1));
// multiply to do the first step of the cross process
__m256d multiLeft = _mm256_mul_pd(leftLeftShuffle, rightLeftShuffle);
// multiply the right sides of the subtraction sign // multiply the right sides of the subtraction sign
__m256d multiRight = _mm256_mul_pd(leftRightShuffle, rightRightShuffle); __m256d multiRight = _mm256_mul_pd(leftRightShuffle, rightRightShuffle);
// then subtract to produce the cross product // then subtract to produce the cross product
@ -139,6 +146,11 @@ namespace Raytracing {
// yes this looks a lot more complicated, but it should be faster! // yes this looks a lot more complicated, but it should be faster!
AVXConvert conv {}; AVXConvert conv {};
conv.avxData = subs; conv.avxData = subs;
/*auto b = Vec4{left.y() * right.z() - left.z() * right.y(),
left.z() * right.x() - left.x() * right.z(),
left.x() * right.y() - left.y() * right.x()};
tlog << b._x << " " << b._y << " " << b._z << "\n";
tlog << conv._x << " " << conv._y << " " << conv._z << "\n\n";*/
return {conv._x, conv._y, conv._z, conv._w}; return {conv._x, conv._y, conv._z, conv._w};
} }
}; };