diff options
author | Julian T <julian@jtle.dk> | 2020-08-06 19:21:49 +0200 |
---|---|---|
committer | Julian T <julian@jtle.dk> | 2020-08-06 19:22:37 +0200 |
commit | 4348cc9581bfea05359485c5d2d074132d0271da (patch) | |
tree | 0c6d92a90ac4cf9acd326f632dcdc962ddca013a | |
parent | 893176a0b18a2281abe09def716ccc3db5583c3f (diff) |
Renders scenes with a single hardcoded light and green objects
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | app/draw.hpp | 2 | ||||
-rw-r--r-- | app/main.cpp | 19 | ||||
-rw-r--r-- | app/mainwindow.cpp | 10 | ||||
-rw-r--r-- | app/mainwindow.hpp | 8 | ||||
-rw-r--r-- | app/rendercoord.cpp | 75 | ||||
-rw-r--r-- | app/rendercoord.hpp | 61 | ||||
-rw-r--r-- | src/object.cpp | 5 | ||||
-rw-r--r-- | src/object.hpp | 8 | ||||
-rw-r--r-- | src/render.cpp | 124 | ||||
-rw-r--r-- | src/render.hpp | 50 | ||||
-rw-r--r-- | src/scene.cpp | 4 | ||||
-rw-r--r-- | src/scene.hpp | 4 | ||||
-rw-r--r-- | src/vector.cpp | 27 | ||||
-rw-r--r-- | src/vector.hpp | 2 |
15 files changed, 379 insertions, 21 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 95e098b..df9bd70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ 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) diff --git a/app/draw.hpp b/app/draw.hpp index a00de79..41d5d9c 100644 --- a/app/draw.hpp +++ b/app/draw.hpp @@ -5,6 +5,8 @@ #include <qwidget.h> class DrawWidget : public QWidget { + Q_OBJECT + public: DrawWidget(unsigned width, unsigned height); void paintEvent(QPaintEvent*); diff --git a/app/main.cpp b/app/main.cpp index 15262f2..64e413a 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -3,14 +3,31 @@ #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; + scn.addShape(new Sphere(Vec3d(2, 6, -1), 1)); + scn.addShape(new Sphere(Vec3d(0, 4, -1), 1.3)); + scn.addShape(new Sphere(Vec3d(-2, 5, -2), 1.3)); + //scn.addShape(new Sphere(Vec3d(0, 7, 0), 0.5)); - MainWindow main; + scn.addShape(new Plane(Vec3d(0, 0, 0), Vec3d(0, 1, 0))); + scn.addShape(new Plane(Vec3d(0, 10, 0), Vec3d(0, 1, 0))); + scn.addShape(new Plane(Vec3d(0, 0, -5), Vec3d(0, 0, 1))); + scn.addShape(new Plane(Vec3d(-5, 0, 0), Vec3d(1, 0, 0))); + scn.addShape(new Plane(Vec3d(5, 0, 0), Vec3d(1, 0, 0))); + + Renderer render(scn, Vec3d(0, 5, 4), Vec3d(0, 5, 0), 500, 500); + + MainWindow main(render); main.show(); return a.exec(); diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index e5f9bfb..3077168 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -1,8 +1,12 @@ #include "mainwindow.hpp" -MainWindow::MainWindow() - : drawer(500, 500) { +MainWindow::MainWindow(Renderer r) + : m_drawer(500, 500), + m_render(this, m_drawer, r) +{ + + setCentralWidget(&m_drawer); + - setCentralWidget(&drawer); } diff --git a/app/mainwindow.hpp b/app/mainwindow.hpp index a6c1be3..7e356a0 100644 --- a/app/mainwindow.hpp +++ b/app/mainwindow.hpp @@ -4,16 +4,18 @@ #include <QMainWindow> #include "draw.hpp" +#include "rendercoord.hpp" +#include <render.hpp> class MainWindow : public QMainWindow { Q_OBJECT public: - MainWindow(); + MainWindow(Renderer r); private: - DrawWidget drawer; - + DrawWidget m_drawer; + RenderCoordinator m_render; }; #endif diff --git a/app/rendercoord.cpp b/app/rendercoord.cpp new file mode 100644 index 0000000..8db7c97 --- /dev/null +++ b/app/rendercoord.cpp @@ -0,0 +1,75 @@ +#include "rendercoord.hpp" +#include <qobject.h> +#include <iostream> +#include <qrgb.h> + +#include <render.hpp> + +uint32_t colorToUint32(Color &c) { + c.clamp(); + return (0xFF << 24) + + (c.r() << 16) + + (c.g() << 8) + + c.b(); +} + +// Run by main thread +RenderThread::RenderThread(Renderer r, QObject *parent, unsigned id) + : QThread(parent), + m_lock(1), + m_render(r) +{ + m_id = id; +} + +// Run on new thread +void RenderThread::run() { + while (1) { + // Wait for work + m_work.acquire(); + + for (unsigned x = 0; x < m_render.m_width; x++) { + for (unsigned y = 0; y < m_render.m_height; y++) { + auto c = m_render.render(x, y); + m_writebuffer[x + y * m_render.m_height] = + static_cast<QRgb>(colorToUint32(c)); + } + } + + // Signal done + m_lock.release(); + emit done(m_id); + } +} + +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(); + return 0; +} + +RenderCoordinator::RenderCoordinator(QObject *parent, DrawWidget &target, Renderer r) + : QObject(parent), + m_target(target), + m_renderer(r), + m_worker(m_renderer, this) +{ + m_worker.start(); + + QObject::connect(&m_worker, &RenderThread::done, + this, &RenderCoordinator::workerDone); + + m_worker.render(target.m_drawbuffer, 1); + +} + +void RenderCoordinator::workerDone(unsigned workerid) { + std::cout << workerid << " done!" << std::endl; + m_target.repaint(); +} + diff --git a/app/rendercoord.hpp b/app/rendercoord.hpp new file mode 100644 index 0000000..6aa8698 --- /dev/null +++ b/app/rendercoord.hpp @@ -0,0 +1,61 @@ +#ifndef RENDER_THREAD_H +#define RENDER_THREAD_H + +#include "draw.hpp" +#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, QObject *parent = nullptr, unsigned id = 0); + + // Returns 0 if successful or 1 if busy + int render(QRgb *buffer, unsigned samples); + + signals: + void done(unsigned workerid); + + protected: + void run(); + + QSemaphore m_lock; + + QRgb *m_writebuffer; + unsigned m_samples; + + Renderer m_render; + + // Value in here means work is to be done + QSemaphore m_work; + unsigned m_id; +}; + +class RenderCoordinator : public QObject { + Q_OBJECT + + public: + RenderCoordinator(QObject *parent, DrawWidget &target, Renderer r); + void setSamples(unsigned samples); + void render(); + + public slots: + void workerDone(unsigned workerid); + + private: + DrawWidget &m_target; + + Renderer m_renderer; + RenderThread m_worker; + + unsigned m_samples; +}; + +#endif diff --git a/src/object.cpp b/src/object.cpp index e88ae13..179785a 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -1,6 +1,7 @@ #include "object.hpp" #include <math.h> +#include <iostream> #include "common.hpp" Sphere::Sphere(Vec3d center, double radius) { @@ -15,10 +16,6 @@ Plane::Plane(Vec3d start, Vec3d norm) { m_norm.normalize(); } -void Object::setMaterial(std::shared_ptr<Material> m) { - m_mat = m; -} - Vec3d Sphere::norm_at(const Vec3d &point, const Vec3d&) const { auto res = point - m_center; res.normalize(); diff --git a/src/object.hpp b/src/object.hpp index 3194dac..ac5122f 100644 --- a/src/object.hpp +++ b/src/object.hpp @@ -8,17 +8,15 @@ class Material { }; -class Object { +class Shape { public: void setMaterial(std::shared_ptr<Material> m); - std::shared_ptr<Material> m_mat; - virtual Vec3d norm_at(const Vec3d &point, const Vec3d &indir) const = 0; virtual double intersect(const Ray &ray, bool skip_dist) const = 0; }; -class Sphere : Object { +class Sphere : public Shape { public: Sphere(Vec3d center, double radius); Vec3d norm_at(const Vec3d &point, const Vec3d &indir) const; @@ -29,7 +27,7 @@ class Sphere : Object { double m_radius; }; -class Plane : Object { +class Plane : public Shape { public: Plane(Vec3d start, Vec3d norm); Vec3d norm_at(const Vec3d &point, const Vec3d &indir) const; diff --git a/src/render.cpp b/src/render.cpp new file mode 100644 index 0000000..5601863 --- /dev/null +++ b/src/render.cpp @@ -0,0 +1,124 @@ +#include "render.hpp" +#include "vector.hpp" +#include "common.hpp" + +#include <math.h> +#include <iostream> + +#define FOV 1.74533 + +const Vec3d up = Vec3d(0, 1, 0); + +void Color::clamp() { + if (m_x > 1) { m_x = 1; } + if (m_y > 1) { m_y = 1; } + if (m_z > 1) { m_z = 1; } +} + +Renderer::Renderer(const Scene &scn, Vec3d eye, Vec3d target, unsigned width, unsigned height) : + m_scn(scn) +{ + m_eye = eye; + m_target = target; + m_width = width; + m_height = height; + + 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) { + auto r = findray(x, y); + return pathtrace_sample(r, 0); +} + +Color Renderer::pathtrace_sample(const Ray &r, unsigned hop) { + double dist; + auto res = cast_ray(r, 0, &dist); + + if (!res) { + return Color(0, 0, 0); + } + + // Calculate endpoint + auto end = r.m_start + r.m_direction * dist; + + auto norm = res->norm_at(end, r.m_direction); + + // Simulate single light + auto l = Vec3d(0, 7, 0); + + auto tolight = l - end; + auto distance = tolight.length(); + tolight.normalize(); + auto lightray = Ray(end, tolight, false); + + if (cast_ray(lightray, distance, nullptr)) { + return Color(0, 0, 0); + } + + auto green = Color(0, 1, 0); + return Color(green * norm.dot(tolight)); +} + +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 new file mode 100644 index 0000000..3fd84b1 --- /dev/null +++ b/src/render.hpp @@ -0,0 +1,50 @@ +#ifndef RENDER_H +#define RENDER_H + +#include "vector.hpp" +#include "ray.hpp" +#include "scene.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; } + + void clamp(); + +}; + +class Renderer { + public: + Renderer(const Scene &scn, Vec3d eye, Vec3d target, unsigned width, unsigned height); + + Color render(unsigned x, unsigned y); + + unsigned m_width, m_height; + + 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; + + // User options + Vec3d m_eye, m_target; + + // Calculated values + Vec3d m_qx, m_qy, m_blc; + +}; + +#endif diff --git a/src/scene.cpp b/src/scene.cpp index d866fe4..a8c0695 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -1,5 +1,5 @@ #include "scene.hpp" -void Scene::addObject(Object &obj) { - objs.push_back(&obj); +void Scene::addShape(const Shape *obj) { + objs.push_back(obj); } diff --git a/src/scene.hpp b/src/scene.hpp index a06ccb4..f270409 100644 --- a/src/scene.hpp +++ b/src/scene.hpp @@ -6,9 +6,9 @@ class Scene { public: - void addObject(Object &obj); + void addShape(const Shape *obj); - std::vector<Object*> objs; + std::vector<const Shape*> objs; }; diff --git a/src/vector.cpp b/src/vector.cpp index 070f956..1e4f5a1 100644 --- a/src/vector.cpp +++ b/src/vector.cpp @@ -1,6 +1,7 @@ #include "vector.hpp" #include <math.h> +#include <stdexcept> Vec3d::Vec3d() { set(0, 0, 0); @@ -19,7 +20,7 @@ void Vec3d::set(double x, double y, double z) { void Vec3d::normalize() { auto len = length(); if (len == 0) { - throw "Normalizing zero vector"; + throw std::runtime_error("Normalizing zero vector"); } m_x /= len; @@ -35,6 +36,22 @@ 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) const { return Vec3d( m_x - vec.m_x, @@ -51,3 +68,11 @@ Vec3d Vec3d::operator-() const { ); } + +Vec3d Vec3d::operator*(double op) const { + return Vec3d( + m_x * op, + m_y * op, + m_z * op + ); +} diff --git a/src/vector.hpp b/src/vector.hpp index 76eb883..05de25c 100644 --- a/src/vector.hpp +++ b/src/vector.hpp @@ -15,8 +15,10 @@ class Vec3d { Vec3d cross(const Vec3d &vec) const; // Operators + Vec3d operator+(const Vec3d &vec) const; Vec3d operator-(const Vec3d &vec) const; Vec3d operator-() const; + Vec3d operator*(double) const; double m_x, m_y, m_z; }; |