diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CMakeLists.txt | 32 | ||||
-rw-r--r-- | Cargo.toml | 9 | ||||
-rw-r--r-- | app/config.hpp | 22 | ||||
-rw-r--r-- | app/draw.cpp | 32 | ||||
-rw-r--r-- | app/draw.hpp | 30 | ||||
-rw-r--r-- | app/main.cpp | 48 | ||||
-rw-r--r-- | app/mainwindow.cpp | 50 | ||||
-rw-r--r-- | app/mainwindow.hpp | 34 | ||||
-rw-r--r-- | app/rendercoord.cpp | 220 | ||||
-rw-r--r-- | app/rendercoord.hpp | 93 | ||||
-rw-r--r-- | src/common.hpp | 6 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/object.cpp | 103 | ||||
-rw-r--r-- | src/object.hpp | 76 | ||||
-rw-r--r-- | src/ray.cpp | 17 | ||||
-rw-r--r-- | src/ray.hpp | 15 | ||||
-rw-r--r-- | src/render.cpp | 166 | ||||
-rw-r--r-- | src/render.hpp | 61 | ||||
-rw-r--r-- | src/scene.cpp | 5 | ||||
-rw-r--r-- | src/scene.hpp | 15 | ||||
-rw-r--r-- | src/vector.cpp | 106 | ||||
-rw-r--r-- | src/vector.hpp | 33 | ||||
-rw-r--r-- | test/main.cpp | 2 | ||||
-rw-r--r-- | test/object.cpp | 36 | ||||
-rw-r--r-- | test/vector.cpp | 31 |
26 files changed, 13 insertions, 1233 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index df9bd70..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -cmake_minimum_required(VERSION 3.10) - -project(pathtracing) - -# cmake options -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_AUTOMOC ON) - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED True) - -# Compiler options -set(CMAKE_CXX_FLAGS "-Wall -Wextra") -set(CMAKE_CXX_FLAGS_DEBUG "-g") -set(CMAKE_CXX_FLAGS_RELEASE "-O3") - -FILE(GLOB app_sources ${CMAKE_SOURCE_DIR}/app/*.cpp) -FILE(GLOB sources ${CMAKE_SOURCE_DIR}/src/*.cpp) -FILE(GLOB test_sources ${CMAKE_SOURCE_DIR}/test/*.cpp) - -add_executable(pathtracing ${sources} ${app_sources}) -target_include_directories(pathtracing PUBLIC src) - -# Catch2 unittests -add_custom_target(test) -add_executable(run_test EXCLUDE_FROM_ALL ${sources} ${test_sources}) -target_include_directories(run_test PUBLIC src) -add_dependencies(test run_test) - -# Qt -find_package(Qt5 COMPONENTS Widgets REQUIRED) -target_link_libraries(pathtracing Qt5::Widgets) diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..621e7e3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pathtrace" +version = "0.1.0" +authors = ["Julian T <julian@jtle.dk>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/app/config.hpp b/app/config.hpp deleted file mode 100644 index 6e9962c..0000000 --- a/app/config.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -#include <render.hpp> - -class Config { - public: - unsigned m_width, m_height; - unsigned m_maxhops, m_samples; - - unsigned m_framerate, m_workers; -}; - -class RendererConf : public Renderer { - public: - RendererConf(const Scene &scn, Vec3d eye, Vec3d target, Config &conf) - : Renderer(scn, eye, target, conf.m_width, conf.m_height, conf.m_maxhops) { - - } -}; - -#endif diff --git a/app/draw.cpp b/app/draw.cpp deleted file mode 100644 index 88733ef..0000000 --- a/app/draw.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "draw.hpp" -#include <qnamespace.h> -#include <qpainter.h> -#include <qglobal.h> -#include <qimage.h> -#include <qrgb.h> -#include <qtimer.h> -#include <qwindowdefs.h> -#include <iostream> - -DrawWidget::DrawWidget(const Config &conf) : - QWidget(), m_conf(conf) { - m_drawbuffer = new QRgb[conf.m_width * conf.m_height]; - - m_img = QImage((uchar*)m_drawbuffer, conf.m_width, conf.m_height, QImage::Format_ARGB32); - -} - -void DrawWidget::paintEvent(QPaintEvent*) { - QPainter painter(this); - - auto scaled = m_img.scaled(width(), height(), Qt::KeepAspectRatio); - painter.drawImage(0, 0, scaled); -} - -void DrawWidget::redraw() { - repaint(); -} - -DrawWidget::~DrawWidget() { - delete[] m_drawbuffer; -} diff --git a/app/draw.hpp b/app/draw.hpp deleted file mode 100644 index 454558c..0000000 --- a/app/draw.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef DRAW_H -#define DRAW_H - -#include "config.hpp" -#include <qimage.h> -#include <qtimer.h> -#include <qwidget.h> - -class DrawWidget : public QWidget { - Q_OBJECT - - public: - DrawWidget(const Config &conf); - void paintEvent(QPaintEvent*); - - QRgb *m_drawbuffer; - QImage m_img; - unsigned m_width, m_height; - - ~DrawWidget(); - private slots: - void redraw(); - - private: - unsigned char i; - - const Config &m_conf; -}; - -#endif diff --git a/app/main.cpp b/app/main.cpp deleted file mode 100644 index e53b8ad..0000000 --- a/app/main.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include <iostream> -#include <qapplication.h> -#include <qpushbutton.h> - -#include "mainwindow.hpp" -#include "vector.hpp" -#include <scene.hpp> -#include <render.hpp> -#include <object.hpp> - -using namespace std; - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - Scene scn; - Config conf; - conf.m_width = 500; - conf.m_height = 500; - conf.m_maxhops = 5; - conf.m_samples = 100; - conf.m_framerate = 3; - conf.m_workers = 4; - - - Material blue(Color(0.3, 0.3, 1), 1); - Material red(Color(1, 0.3, 0.3), 1); - Material white(Color(1, 1, 1), 1); - Material em(Color(1, 1, 1), 0, 2); - - scn.addShape(new Sphere(red, Vec3d(2, 6, -1), 1)); - scn.addShape(new Sphere(white, Vec3d(0, 4, -1), 1.3)); - scn.addShape(new Sphere(white, Vec3d(-2, 5, -2), 1.3)); - scn.addShape(new Sphere(blue, Vec3d(0, 7, 0), 0.5)); - - scn.addShape(new Plane(em, Vec3d(0, 0, 0), Vec3d(0, 1, 0))); - scn.addShape(new Plane(white, Vec3d(0, 10, 0), Vec3d(0, 1, 0))); - scn.addShape(new Plane(white, Vec3d(0, 0, -5), Vec3d(0, 0, 1))); - scn.addShape(new Plane(red, Vec3d(-5, 0, 0), Vec3d(1, 0, 0))); - scn.addShape(new Plane(blue, Vec3d(5, 0, 0), Vec3d(1, 0, 0))); - - RendererConf render(scn, Vec3d(0, 5, 4), Vec3d(0, 5, 0), conf); - - MainWindow main(render, conf); - main.show(); - - return a.exec(); -} diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp deleted file mode 100644 index eed98f9..0000000 --- a/app/mainwindow.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "mainwindow.hpp" -#include <qaction.h> -#include <qapplication.h> -#include <qlabel.h> -#include <qnamespace.h> -#include <QFileDialog> -#include <QMessageBox> - -MainWindow::MainWindow(Renderer r, const Config &conf) - : m_drawer(conf), - runstatus("Not running", this), - m_render(this, m_drawer, r, conf, &runstatus), - m_conf(conf) -{ - - setCentralWidget(&m_drawer); - - auto saveAct = new QAction(tr("&Save as"), this); - saveAct->setStatusTip(tr("Save the rendered image")); - connect(saveAct, &QAction::triggered, this, &MainWindow::saveimage); - - auto stopAct = new QAction(tr("&Stop"), this); - stopAct->setStatusTip(tr("Stop and sync threads")); - QObject::connect(stopAct, &QAction::triggered, &m_render, &RenderCoordinator::stop); - - fileMenu = menuBar()->addMenu(tr("&File")); - fileMenu->addAction(saveAct); - - fileMenu = menuBar()->addMenu(tr("&Render")); - fileMenu->addAction(stopAct); - - helpMenu = menuBar()->addMenu(tr("&Help")); - helpMenu->addAction(tr("About Qt"), qApp, &QApplication::aboutQt); - - statusBar()->addWidget(&runstatus); -} - -void MainWindow::saveimage() { - - QGuiApplication::setOverrideCursor(Qt::WaitCursor); - QString fileName = QFileDialog::getSaveFileName(this, - tr("Save image"), "", tr("PNG image (*.png);;All Files (*)")); - if (fileName.isEmpty()) { - return; - } - - if (!m_drawer.m_img.save(fileName)) { - QMessageBox::information(this, tr("Unable to save file"), ""); - } -} diff --git a/app/mainwindow.hpp b/app/mainwindow.hpp deleted file mode 100644 index ffb4f08..0000000 --- a/app/mainwindow.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef MAIN_H -#define MAIN_H - -#include <QMainWindow> -#include <QLabel> -#include <QMenuBar> -#include <QStatusBar> - -#include "config.hpp" -#include "draw.hpp" -#include "rendercoord.hpp" -#include <qmainwindow.h> -#include <render.hpp> - -class MainWindow : public QMainWindow { - Q_OBJECT - - public: - MainWindow(Renderer r, const Config &conf); - - private slots: - void saveimage(); - private: - DrawWidget m_drawer; - QLabel runstatus; - RenderCoordinator m_render; - - QMenu *fileMenu; - QMenu *helpMenu; - - const Config &m_conf; -}; - -#endif diff --git a/app/rendercoord.cpp b/app/rendercoord.cpp deleted file mode 100644 index 00a6437..0000000 --- a/app/rendercoord.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#include "rendercoord.hpp" -#include <algorithm> -#include <qobject.h> -#include <iostream> -#include <qrgb.h> - -#include <qsemaphore.h> -#include <render.hpp> -#include <sstream> - -uint32_t colorToUint32(const Color &c) { - Color cnew = Color(c); - cnew.clamp(); - return (0xFF << 24) + - (cnew.r() << 16) + - (cnew.g() << 8) + - cnew.b(); -} - -// Run by main thread -RenderThread::RenderThread(Renderer r, unsigned threads, const Config &conf, QObject *parent, unsigned id) - : QThread(parent), - m_lock(1), - m_render(r), - m_conf(conf), - m_pause(1) -{ - m_id = id; - m_workers = threads; -} - -// Run on new thread -void RenderThread::run() { - while (1) { - // Wait for work - m_work.acquire(); - - // Very expensive, but necesary to get live rendering - Color *sum = new Color[m_render.m_width * m_render.m_height]; - - m_current_samples = 0; - - for (int sample = 0; sample < m_samples; sample++) { - m_current_samples = sample; - // Probably not that smart - m_pause.acquire(); - m_pause.release(); - - for (unsigned y = m_id; y < m_render.m_height; y += m_workers) { - for (unsigned x = 0; x < m_render.m_width; x++) { - auto index = x + y * m_render.m_width; - sum[index] += m_render.render(m_render.m_width - x, m_render.m_height - y, 1); - - m_writebuffer[index] = colorToUint32(sum[index] / (sample+1)); - } - } - - } - - // Signal done - m_lock.release(); - emit done(m_id); - } -} - -void RenderThread::pause() { - m_pause.acquire(); -} - -void RenderThread::resume() { - m_pause.release(); -} - -int RenderThread::render(QRgb *buffer, unsigned samples) { - // Check if already running - if (!m_lock.tryAcquire()) { - return 1; - } - m_writebuffer = buffer; - m_samples = samples; - m_work.release(); - std::cout << samples << std::endl; - - return 0; -} - -unsigned RenderThread::stop() { - stopAt(m_current_samples); - return m_current_samples; -} - -void RenderThread::stopAt(int at) { - m_samples = at; -} - -// Running on main thread -unsigned RenderThread::current_samples() { - // No sync should not be a problem here. - return m_current_samples; -} - -RenderCoordinator::RenderCoordinator(QObject *parent, DrawWidget &target, Renderer r, const Config &conf, QLabel *status) - : QObject(parent), - m_target(target), - m_renderer(r), - m_timer(this), - m_conf(conf) -{ - m_status = status; - - // Create and start workers - for (unsigned i = 0; i < conf.m_workers; i++) { - auto thread = new RenderThread(m_renderer, conf.m_workers, conf, this, i); - - thread->start(); - QObject::connect(thread, &RenderThread::done, this, &RenderCoordinator::workerDone); - - m_workers.push_back(thread); - } - - render(); - -} - -void RenderCoordinator::render() { - m_started = 0; - for (auto thd : m_workers) { - thd->render(m_target.m_drawbuffer, m_conf.m_samples); - m_started++; - } - - m_state = running; - updateUi(); - - QObject::connect(&m_timer, &QTimer::timeout, this, &RenderCoordinator::updateUi); - - m_timer.start(1000.0 / m_conf.m_framerate); -} - -void RenderCoordinator::stop() { - unsigned max = 0; - for (auto thd : m_workers) { - thd->pause(); - - auto val = thd->current_samples(); - if (val>max) { - max = val; - } - } - - std::cout << max << std::endl; - - for (auto thd : m_workers) { - thd->stopAt(max+1); - thd->resume(); - } - - m_state = stopping; - updateUi(); -} - -void RenderCoordinator::workerDone(unsigned workerid) { - std::cout << "Worker " << workerid << " done!" << std::endl; - if (--m_started) { - return; - } - std::cout << "All done :-)" << std::endl; - - // All workers are done - m_state = stopped; - m_timer.stop(); - updateUi(); -} - -unsigned RenderCoordinator::calcStats(unsigned *max, unsigned *min, double *avg) { - unsigned count = 0; - unsigned sum = 0; - for (auto thd : m_workers) { - auto val = thd->current_samples(); - if (min && (val < *min || !count)) { - *min = val; - } - if (max && (val > *max || !count)) { - *max = val; - } - - sum += val; - count++; - } - - if (avg) { - *avg = (double)sum / count; - } - - return count; -} - - -void RenderCoordinator::updateUi() { - m_target.repaint(); - - if (!m_status) { - return; - } - - // Gather statictics from workers - unsigned max; - unsigned min; - double avg; - unsigned count = calcStats(&max, &min, &avg); - - std::ostringstream status; - status << states[m_state] << - " Threads: " << count << - " Max: " << max << " samples" << - " Min: " << min << " samples" << - " Avg: " << avg << " samples"; - - m_status->setText(QString::fromStdString(status.str())); -} diff --git a/app/rendercoord.hpp b/app/rendercoord.hpp deleted file mode 100644 index eea1b40..0000000 --- a/app/rendercoord.hpp +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef RENDER_THREAD_H -#define RENDER_THREAD_H - -#include "draw.hpp" -#include "config.hpp" -#include <atomic> -#include <qlabel.h> -#include <render.hpp> - -#include <qobject.h> -#include <qrgb.h> -#include <qthread.h> -#include <qsemaphore.h> -#include <vector> -// https://doc.qt.io/archives/qt-4.8/qt-threads-mandelbrot-example.html - -class RenderThread : public QThread { - Q_OBJECT - - public: - RenderThread(Renderer r, unsigned threads, const Config &cfg, QObject *parent = nullptr, unsigned id = 0); - - // Returns 0 if successful or 1 if busy - int render(QRgb *buffer, unsigned samples); - - void pause(); - void resume(); - - unsigned stop(); - void stopAt(int at); - - unsigned current_samples(); - - signals: - void done(unsigned workerid); - - protected: - void run(); - - QSemaphore m_lock; - - QRgb *m_writebuffer; - std::atomic_int m_samples; - std::atomic_int m_current_samples; - - Renderer m_render; - - unsigned m_workers; - - Config const m_conf; - - // Value in here means work is to be done - QSemaphore m_work; - QSemaphore m_pause; - unsigned m_id; -}; - -const std::string states[] = { "Stopped", "Running", "Stopping" }; -enum State { stopped, running, stopping }; - -class RenderCoordinator : public QObject { - Q_OBJECT - - public: - RenderCoordinator(QObject *parent, DrawWidget &target, Renderer r, const Config &conf, QLabel *status=nullptr); - void setSamples(unsigned samples); - void render(); - - public slots: - void workerDone(unsigned workerid); - void stop(); - - private slots: - void updateUi(); - - private: - unsigned calcStats(unsigned *max, unsigned *min, double *avg); - - DrawWidget &m_target; - - Renderer m_renderer; - std::vector<RenderThread*> m_workers; - unsigned m_started; - - QLabel *m_status; - QTimer m_timer; - - State m_state; - - const Config &m_conf; -}; - -#endif diff --git a/src/common.hpp b/src/common.hpp deleted file mode 100644 index fd219bf..0000000 --- a/src/common.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef COMMON_H -#define COMMON_H - -#define ZERO_APPROX 1e-6 - -#endif diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/object.cpp b/src/object.cpp deleted file mode 100644 index 1449cbb..0000000 --- a/src/object.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "object.hpp" - -#include <math.h> -#include <iostream> -#include "common.hpp" - -void Color::clamp() { - if (m_x > 1) { m_x = 1; } - if (m_y > 1) { m_y = 1; } - if (m_z > 1) { m_z = 1; } -} - -Material::Material(Color color, double defuse, double emissive) { - m_color = color; - m_defuse = defuse; - m_emissive = emissive; -} - -Color Material::reflect(const Vec3d &normal, const Vec3d &in, const Vec3d &out, const Color &incol) const { - return Vec3d(m_color) * m_emissive + - (Vec3d(m_color) * Vec3d(incol)) * (out.dot(normal) * m_defuse); -} - -Sphere::Sphere(const Material &mat, Vec3d center, double radius) : Shape(mat) { - m_center = center; - m_radius = radius; -} - -Plane::Plane(const Material &mat, Vec3d start, Vec3d norm) : Shape(mat) { - m_start = start; - m_norm = norm; - - m_norm.normalize(); -} - -Vec3d Sphere::norm_at(const Vec3d &point, const Vec3d&) const { - auto res = point - m_center; - res.normalize(); - return res; -} - -// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-sphere-intersection -double Sphere::intersect(const Ray &ray, bool skip_dist) const { - // Calculate O - C used multiple places - auto oc = ray.m_start - m_center; - // Calculate components of quadratic formula - // a = 1 when ray.direction is a unit vector - auto a = 1; - auto b = 2 * ray.m_direction.dot(oc); - auto c = oc.dot(oc) - m_radius * m_radius; - - // Solve quadratic function - auto discr = b * b - 4 * a * c; - if (discr < 0) { - // No solution - return -1; - } - if (skip_dist) { - // Do not calculate distance - return 1; - } - - auto q = (b > 0) ? - -0.5 * (b + sqrt(discr)): - -0.5 * (b - sqrt(discr)); - auto t1 = q; // Assuming a = 1 - auto t0 = c / q; - - // Find correct result - if (t0 <= ZERO_APPROX) { - t0 = t1; - } - - if (t0 <= ZERO_APPROX) { - return -1; - } - - return t0; -} - -Vec3d Plane::norm_at(const Vec3d&, const Vec3d &indir) const { - auto scale = m_norm.dot(indir); - return scale > 0 ? -m_norm : m_norm; -} - -// https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-plane-and-ray-disk-intersection -// Requires that vectors are normalized -// Skip dist is ignored as distance must be calculated -double Plane::intersect(const Ray &ray, bool) const { - // If ray is parallel - auto nr = m_norm.dot(ray.m_direction); - if (abs(nr) < ZERO_APPROX) { - return -1; - } - - // Calculate distance - auto dist = m_norm.dot(m_start - ray.m_start) / nr; - if (dist < 0) { - return -1; - } - - return dist; -} diff --git a/src/object.hpp b/src/object.hpp deleted file mode 100644 index e697ba2..0000000 --- a/src/object.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef OBJECT_H -#define OBJECT_H - -#include <memory> -#include "vector.hpp" -#include "ray.hpp" - -class Color : public Vec3d { - public: - Color() {} - Color(const Vec3d &v) : Vec3d(v) {} - Color(double r, double g, double b) : Vec3d(r, g, b) {} - - uint8_t r() { return m_x * 255; } - uint8_t g() { return m_y * 255; } - uint8_t b() { return m_z * 255; } - - Color& operator+=(const Color& op) { - Vec3d::operator+=(op); - return *this; - } - void clamp(); -}; - -// Implements phong BRDF -class Material { - public: - Material(Color color, double defuse, double emissive=0); - - Color reflect(const Vec3d &normal, const Vec3d &in, const Vec3d &out, const Color &incol) const; - - Color emits() const { - return m_color * m_emissive; - } - - // Whether the material is reflective - bool reflects() const { return m_defuse > 0; } - private: - Color m_color; - double m_defuse; - double m_emissive; -}; - -class Shape { - public: - Shape(const Material &mat) : m_mat(mat) { } - - virtual Vec3d norm_at(const Vec3d &point, const Vec3d &indir) const = 0; - virtual double intersect(const Ray &ray, bool skip_dist) const = 0; - - const Material &m_mat; -}; - -class Sphere : public Shape { - public: - Sphere(const Material &mat, Vec3d center, double radius); - Vec3d norm_at(const Vec3d &point, const Vec3d &indir) const; - double intersect(const Ray &ray, bool skip_dist) const; - - private: - Vec3d m_center; - double m_radius; -}; - -class Plane : public Shape { - public: - Plane(const Material &mat, Vec3d start, Vec3d norm); - Vec3d norm_at(const Vec3d &point, const Vec3d &indir) const; - double intersect(const Ray &ray, bool skip_dist) const; - - private: - Vec3d m_start; - Vec3d m_norm; -}; - -#endif diff --git a/src/ray.cpp b/src/ray.cpp deleted file mode 100644 index 7bc6201..0000000 --- a/src/ray.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "ray.hpp" - -Ray::Ray(Vec3d start, Vec3d direction, bool normalize) { - m_start = start; - m_direction = direction; - - if (normalize) { - m_direction.normalize(); - } -} - -Ray::Ray(Vec3d a, Vec3d b) { - m_start = a; - m_direction = b - a; - - m_direction.normalize(); -} diff --git a/src/ray.hpp b/src/ray.hpp deleted file mode 100644 index 6341d44..0000000 --- a/src/ray.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef RAY_H -#define RAY_H - -#include "vector.hpp" - -class Ray { - public: - Ray(Vec3d start, Vec3d direction, bool normalize); - Ray(Vec3d a, Vec3d b); - - Vec3d m_start; - Vec3d m_direction; -}; - -#endif diff --git a/src/render.cpp b/src/render.cpp deleted file mode 100644 index c278797..0000000 --- a/src/render.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "render.hpp" -#include "vector.hpp" -#include "common.hpp" - -#include <cstdlib> -#include <math.h> -#include <iostream> - -#define FOV 1.74533 - -// Uniform sampling -#define SAMPLING_POWER 0 - -const Vec3d up = Vec3d(0, 1, 0); - -void Random::seed(unsigned seed) { - for (unsigned i = 0; i < seed; i++) { - rand_r(&m_seed); - } -} - -double Random::operator()() { - return (double)rand_r(&m_seed) / (double)RAND_MAX; -} - -Sampler::Sampler(Random &src) : m_src(src) { } - -Vec3d Sampler::sample(const Vec3d &norm) { - /* - auto theta = asin(pow(1 - random(), (double)1 / (1 + SAMPLING_POWER))); - auto phi = 2 * M_PI * random(); - */ - - auto theta = 2.0 * M_PI * m_src(); - auto phi = acos(2.0 * m_src() - 1.0); - - auto sinphi = sin(phi); - - auto newvec = Vec3d(cos(theta) * sinphi, sin(theta) * sinphi, cos(phi)); - - if (newvec.dot(norm) <= 0) { - newvec = -newvec; - } - - return newvec; -} - -Renderer::Renderer(const Scene &scn, Vec3d eye, Vec3d target, unsigned width, unsigned height, unsigned maxhops) : - m_sampler(m_random), - m_scn(scn) -{ - m_eye = eye; - m_target = target; - m_width = width; - m_height = height; - m_maxhops = maxhops; - - recalculate(); -} - -void Renderer::recalculate() { - auto tmp = m_target - m_eye; - - // Orthogonal vector to E - auto b = up.cross(tmp); - - b.normalize(); - tmp.normalize(); - - auto v = tmp.cross(b); - - // Calculate size of viewplane - double gx = tan( FOV / 2); - double gy = gx * ((double) m_height / m_width); - - // Calculate scaling vectors - m_qx = b * ((2 * gx) / (m_width - 1)); - m_qy = v * ((2 * gy) / (m_height - 1)); - - // Calculate starting point - m_blc = tmp - (b * gx) - (v * gy); -} - -Ray Renderer::findray(double x, double y) const { - auto dir = m_blc + (m_qx * x) + (m_qy * y); - return Ray(m_eye, dir, true); -} - -Color Renderer::render(unsigned x, unsigned y, unsigned samples) { - - Color sum(0, 0, 0); - - for (unsigned i = 0; i < samples; i++) { - auto r = findray(x + m_random(), y + m_random()); - sum += pathtrace_sample(r, 0); - } - - if (samples < 2) { - return sum; - } else { - return Vec3d(sum) / (double)samples; - } -} - -Color Renderer::pathtrace_sample(const Ray &r, unsigned hop) { - if (hop >= m_maxhops) { - return Color(0, 0, 0); - } - - double dist; - auto res = cast_ray(r, 0, &dist); - - if (!res) { - return Color(0, 0, 0); - } - - auto col = res->m_mat.emits(); - if (res->m_mat.reflects()) { - // Calculate endpoint - auto end = r.m_start + r.m_direction * dist; - auto norm = res->norm_at(end, r.m_direction); - - auto randdir = m_sampler.sample(norm); - auto newray = Ray(end, randdir, true); - auto incol = pathtrace_sample(newray, hop+1); - - col += res->m_mat.reflect(norm, r.m_direction, newray.m_direction, incol); - } - - return col; -} - -const Shape* Renderer::cast_ray(const Ray &r, double chk_dist, double *dist_ret) { - const Shape *smallest = nullptr; - double dist = 0; - - for (auto obj : m_scn.objs) { - if (!obj) { - continue; - } - auto d = obj->intersect(r, false); - if (d > ZERO_APPROX) { - if (chk_dist > 0 && d < chk_dist) { - dist = d; smallest = obj; - goto exit; - } - if (d < dist || smallest == nullptr) { - dist = d; smallest = obj; - } - } - } - - if (chk_dist > 0) { - // If we reach this it means none of the - // object where within distance. - return nullptr; - } - -exit: - - if (dist_ret) { - *dist_ret = dist; - } - - return smallest; -} diff --git a/src/render.hpp b/src/render.hpp deleted file mode 100644 index 7557fce..0000000 --- a/src/render.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef RENDER_H -#define RENDER_H - -#include "vector.hpp" -#include "ray.hpp" -#include "scene.hpp" - -class Random { - public: - void seed(unsigned seed); - double operator()(); - - private: - unsigned m_seed; -}; - -// Samples a random direction in a hemisphere, cosine weighed -// https://blog.thomaspoulet.fr/uniform-sampling-on-unit-hemisphere/ -class Sampler { - public: - Sampler(Random &src); - - Vec3d sample(const Vec3d &norm); - - private: - Random &m_src; -}; - -class Renderer { - public: - Renderer(const Scene &scn, Vec3d eye, Vec3d target, unsigned width, unsigned height, unsigned maxhops); - - Color render(unsigned x, unsigned y, unsigned samples); - - unsigned m_width, m_height; - Sampler m_sampler; - - private: - void recalculate(); - Ray findray(double x, double y) const ; - - Color pathtrace_sample(const Ray &r, unsigned hop); - // Will return first result less than chk_dist. - // This is ignored if chk_dist is 0. - // If dist is non-null the resulting distance is written here. - const Shape* cast_ray(const Ray &r, double chk_dist, double *dist); - - const Scene &m_scn; - - Random m_random; - - // User options - Vec3d m_eye, m_target; - unsigned m_maxhops; - - // Calculated values - Vec3d m_qx, m_qy, m_blc; - -}; - -#endif diff --git a/src/scene.cpp b/src/scene.cpp deleted file mode 100644 index a8c0695..0000000 --- a/src/scene.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "scene.hpp" - -void Scene::addShape(const Shape *obj) { - objs.push_back(obj); -} diff --git a/src/scene.hpp b/src/scene.hpp deleted file mode 100644 index f270409..0000000 --- a/src/scene.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef SCENE_H -#define SCENE_H - -#include <vector> -#include "object.hpp" - -class Scene { - public: - void addShape(const Shape *obj); - - std::vector<const Shape*> objs; - -}; - -#endif diff --git a/src/vector.cpp b/src/vector.cpp deleted file mode 100644 index 51d8e2e..0000000 --- a/src/vector.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "vector.hpp" - -#include <math.h> -#include <stdexcept> - -Vec3d::Vec3d() { - set(0, 0, 0); -} - -Vec3d::Vec3d(double x, double y, double z) { - set(x, y, z); -} - -void Vec3d::set(double x, double y, double z) { - m_x = x; - m_y = y; - m_z = z; -} - -void Vec3d::normalize() { - auto len = length(); - if (len == 0) { - throw std::runtime_error("Normalizing zero vector"); - } - - m_x /= len; - m_y /= len; - m_z /= len; -} - -double Vec3d::length() const { - return sqrt(m_x * m_x + m_y * m_y + m_z * m_z); -} - -double Vec3d::dot(const Vec3d &vec) const { - return m_x * vec.m_x + m_y * vec.m_y + m_z * vec.m_z; -} - -Vec3d Vec3d::cross(const Vec3d &vec) const { - return Vec3d( - m_y * vec.m_z - m_z * vec.m_y, - m_z * vec.m_x - m_x * vec.m_z, - m_x * vec.m_y - m_y * vec.m_x - ); -} - -Vec3d Vec3d::operator+(const Vec3d &vec) const { - return Vec3d( - m_x + vec.m_x, - m_y + vec.m_y, - m_z + vec.m_z - ); -} - -Vec3d& Vec3d::operator+=(const Vec3d &vec) { - m_x += vec.m_x; - m_y += vec.m_y; - m_z += vec.m_z; - return *this; -} - -Vec3d Vec3d::operator-(const Vec3d &vec) const { - return Vec3d( - m_x - vec.m_x, - m_y - vec.m_y, - m_z - vec.m_z - ); -} - -Vec3d Vec3d::operator-() const { - return Vec3d( - -m_x, - -m_y, - -m_z - ); -} - - -Vec3d Vec3d::operator*(double op) const { - return Vec3d( - m_x * op, - m_y * op, - m_z * op - ); -} - -Vec3d Vec3d::operator*(const Vec3d &vec) const { - return Vec3d( - m_x * vec.m_x, - m_y * vec.m_y, - m_z * vec.m_z - ); -} - -Vec3d Vec3d::operator/(double op) const { - return Vec3d( - m_x / op, - m_y / op, - m_z / op - ); -} - -std::ostream& operator<<(std::ostream &out, const Vec3d &v){ - out << "[ " << v.m_x << ", " << v.m_y << ", " << v.m_z << " ]"; - return out; -} diff --git a/src/vector.hpp b/src/vector.hpp deleted file mode 100644 index 20e8210..0000000 --- a/src/vector.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef VECTOR_H -#define VECTOR_H - -#include <iostream> - -class Vec3d { - public: - Vec3d(); - Vec3d(double x, double y, double z); - - void set(double x, double y, double z); - void normalize(); - - double length() const; - double dot(const Vec3d &vec) const; - - Vec3d cross(const Vec3d &vec) const; - - // Operators - Vec3d operator+(const Vec3d &vec) const; - Vec3d& operator+=(const Vec3d &vec); - Vec3d operator-(const Vec3d &vec) const; - Vec3d operator-() const; - Vec3d operator*(double) const; - Vec3d operator*(const Vec3d &vec) const; - Vec3d operator/(double) const; - - friend std::ostream& operator<<(std::ostream& os, const Vec3d &v); - - double m_x, m_y, m_z; -}; - -#endif diff --git a/test/main.cpp b/test/main.cpp deleted file mode 100644 index 4ed06df..0000000 --- a/test/main.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include <catch2/catch.hpp> diff --git a/test/object.cpp b/test/object.cpp deleted file mode 100644 index bbce055..0000000 --- a/test/object.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include <object.hpp> -#include <ray.hpp> -#include <common.hpp> -#include <vector.hpp> - -#include <catch2/catch.hpp> -#include <math.h> - -TEST_CASE("Sphere normal at", "[sphere]") { - auto sph = Sphere(Vec3d(2, 3, 4), 2); - - auto norm = sph.norm_at(Vec3d(2, 3, 2), Vec3d()); - REQUIRE(norm.m_x == 0); - REQUIRE(norm.m_y == 0); - REQUIRE(norm.m_z == -1); -} - -TEST_CASE("Sphere intersect", "[sphere]") { - auto sph = Sphere(Vec3d(2, 3, 4), 2); - auto ray = Ray(Vec3d(1, 0, 0), Vec3d(0, 1, 1.5), true); - - auto dist = sph.intersect(ray, false); - REQUIRE(abs(dist - 3.28) < 0.01); -} - -TEST_CASE("Plane intersect", "[plane]") { - auto pln = Plane(Vec3d(3, 4, 2), Vec3d(-6, -3, -2)); - auto ray = Ray(Vec3d(0, 0, 0), Vec3d(-2, -1, 5)); - - auto dist = pln.intersect(ray, false); - REQUIRE(dist == -1); - - ray = Ray(Vec3d(-2, -2, 0), Vec3d(-2, -1, 5)); - dist = pln.intersect(ray, false); - REQUIRE(abs(dist - 20.4) < 0.1); -} diff --git a/test/vector.cpp b/test/vector.cpp deleted file mode 100644 index 61648c6..0000000 --- a/test/vector.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include <vector.hpp> -#include <common.hpp> -#include <catch2/catch.hpp> - -TEST_CASE( "Vector length", "[vector]" ) { - auto vec = Vec3d(2, 4, 4); - REQUIRE(vec.length() == 6); - vec.set(0, 0, 0); - REQUIRE(vec.length() == 0); - vec.set(0, 3.5, 0); - REQUIRE(vec.length() == 3.5); -} - -TEST_CASE("Vector_normal", "[vector]") { - auto vec = Vec3d(4, 5, 4545); - REQUIRE(vec.length() != 1.0); - vec.normalize(); - REQUIRE(vec.length() - 1.0 < ZERO_APPROX); - vec.set(0, 0, 0); - REQUIRE_THROWS(vec.normalize()); -} - -TEST_CASE("Vector dot", "[vector]") { - auto a = Vec3d(4, 5, 6); - auto b = Vec3d(1, 2, 3); - REQUIRE(a.dot(b) == 32); - a.set(0, 0, 0); - REQUIRE(a.dot(b) == 0); - a.set(0, 5, 0); - REQUIRE(a.dot(b) == 10); -} |