moved rendering code into own class

implemented antialiasing using random subsampling
This commit is contained in:
Moritz Gmeiner 2023-12-27 20:14:29 +01:00
commit 29832e8f1b
13 changed files with 238 additions and 124 deletions

View file

@ -1,19 +1,14 @@
#pragma once
#include <iostream>
#include "image.h"
#include "ray.h"
#include "raytracer.h"
#include "renderobject.h"
#include "vec3.h"
class Camera {
public:
constexpr Camera(Vec3 centre, u32 image_width, u32 image_height, double focal_length)
: centre_{centre} {
constexpr double viewport_height = 2.0;
double viewport_width = viewport_height * static_cast<double>(image_width) / image_height;
constexpr Camera(Vec3 centre, u32 image_width, u32 image_height, f64 focal_length)
: centre_{centre}, image_width_{image_width}, image_height_{image_height} {
constexpr f64 viewport_height = 2.0;
f64 viewport_width = viewport_height * static_cast<f64>(image_width) / image_height;
Vec3 u_viewport = viewport_width * Vec3::e_x;
Vec3 v_viewport = -viewport_height * Vec3::e_y;
@ -31,31 +26,17 @@ class Camera {
constexpr Vec3 centre() const { return centre_; }
constexpr void Render(Image& img, const RenderObject& world) const {
for (u32 j = 0; j < img.height(); j++) {
std::clog << "\rWriting line " << j << " of " << img.height() << std::flush;
constexpr Vec3 d_u_pixel() const { return d_u_pixel_; }
constexpr Vec3 d_v_pixel() const { return d_v_pixel_; }
for (u32 i = 0; i < img.width(); i++) {
auto ray = CastRay(i, j);
constexpr u32 image_width() const { return image_width_; }
constexpr u32 image_height() const { return image_height_; }
img[i, j] = Cast(ray, world);
}
}
}
private:
constexpr Vec3 Image2World(u32 i, u32 j) const {
constexpr Vec3 PixelToWorld(u32 i, u32 j) const {
return pixel_00_ + i * d_u_pixel_ + j * d_v_pixel_;
}
constexpr Ray CastRay(u32 i, u32 j) const {
auto pixel_centre = Image2World(i, j);
auto ray_direction = pixel_centre - centre();
return Ray{centre(), ray_direction};
}
private:
Vec3 centre_;
// position of pixel 0, 0
@ -64,4 +45,7 @@ class Camera {
// Vec3 pointing from pixel to right/lower neighbour resp.
Vec3 d_u_pixel_;
Vec3 d_v_pixel_;
u32 image_width_;
u32 image_height_;
};

View file

@ -17,21 +17,31 @@ class Colour {
static const Colour kWhite;
constexpr Colour() = default;
constexpr Colour(double r, double g, double b) : r_{r}, g_{g}, b_{b} {
assert(0.0 <= r && r <= 1.0);
assert(0.0 <= g && g <= 1.0);
assert(0.0 <= b && b <= 1.0);
constexpr Colour(f64 r, f64 g, f64 b) : r_{r}, g_{g}, b_{b} {}
constexpr void CheckValid() const {
assert(0.0 <= r() && r() <= 1.0);
assert(0.0 <= g() && g() <= 1.0);
assert(0.0 <= b() && b() <= 1.0);
}
constexpr explicit Colour(Vec3 v) : Colour{v.x(), v.y(), v.z()} {}
constexpr double& r() { return r_; }
constexpr double& g() { return g_; }
constexpr double& b() { return b_; }
constexpr f64& r() { return r_; }
constexpr f64& g() { return g_; }
constexpr f64& b() { return b_; }
constexpr double r() const { return r_; }
constexpr double g() const { return g_; }
constexpr double b() const { return b_; }
constexpr f64 r() const { return r_; }
constexpr f64 g() const { return g_; }
constexpr f64 b() const { return b_; }
constexpr Colour& operator+=(const Colour& c) {
r_ += c.r();
g_ += c.g();
b_ += c.b();
return *this;
}
std::string to_string() const {
std::stringstream ss;
@ -46,17 +56,22 @@ class Colour {
}
private:
double r_;
double g_;
double b_;
f64 r_ = 0.0;
f64 g_ = 0.0;
f64 b_ = 0.0;
};
constexpr Colour Colour::kBlack{0.0, 0.0, 0.0};
constexpr Colour operator*(double t, Colour col) { return {t * col.r(), t * col.g(), t * col.b()}; }
constexpr Colour operator*(f64 t, Colour col) { return {t * col.r(), t * col.g(), t * col.b()}; }
constexpr Colour operator/(Colour col, f64 t) { return (1.0 / t) * col; }
constexpr Colour operator+(Colour c1, Colour c2) {
return {c1.r() + c2.r(), c1.g() + c2.g(), c1.b() + c2.b()};
Colour out{c1};
out += c2;
return out;
}
inline std::ostream& operator<<(std::ostream& os, Colour colour) {

View file

@ -57,6 +57,8 @@ constexpr std::ostream& operator<<(std::ostream& os, const Image& img) {
for (u32 i = 0; i < img.width(); i++) {
const Colour col = img[i, j];
col.CheckValid();
os << col << newline;
}
}

View file

@ -1,22 +1,26 @@
#pragma once
#include <algorithm>
#include "raytracer.h"
class Interval {
public:
static const Interval kEmpty, kFull, kPositive;
constexpr Interval() = default;
constexpr Interval(double min, double max) : min_{min}, max_{max} {}
constexpr Interval(f64 min, f64 max) : min_{min}, max_{max} {}
constexpr double min() const { return min_; }
constexpr double max() const { return max_; }
constexpr f64 min() const { return min_; }
constexpr f64 max() const { return max_; }
constexpr bool contains(double x) const { return min_ <= x && x <= max_; }
constexpr bool surronds(double x) const { return min_ < x && x < max_; }
constexpr bool contains(f64 x) const { return min_ <= x && x <= max_; }
constexpr bool surronds(f64 x) const { return min_ < x && x < max_; }
constexpr double clamp(f64 x) const { return std::clamp(x, min_, max_); }
private:
double min_ = kInf;
double max_ = -kInf;
f64 min_ = kInf;
f64 max_ = -kInf;
};
constexpr Interval Interval::kEmpty{kInf, -kInf};

View file

@ -4,42 +4,43 @@
#include "camera.h"
#include "image.h"
#include "raytracer.h"
#include "renderer.h"
#include "renderobject.h"
#include "renderobjectlist.h"
#include "sphere.h"
#include "vec3.h"
int main(int /* argc */, char* /* argv */[]) {
// image
constexpr double aspect_ratio = 16.0 / 9.0;
constexpr u32 image_width = 800;
constexpr auto image_height = static_cast<u32>(image_width / aspect_ratio);
static_assert(image_height >= 1);
Image img{image_width, image_height};
// world
RenderObjectList world;
world.Add(std::make_shared<Sphere>(Vec3{0, -100.5, -1}, 100));
// world.Add(std::make_shared<Sphere>(Vec3{0, -100.5, -1}, 100));
world.Add(std::make_shared<Sphere>(-Vec3::e_z, 0.5));
// camera
constexpr double focal_length = 1.0;
// constexpr f64 aspect_ratio = 16.0 / 9.0;
// constexpr u32 image_width = 800;
// constexpr auto image_height = static_cast<u32>(image_width / aspect_ratio);
constexpr u32 image_width = 1920;
constexpr u32 image_height = 1080;
static_assert(image_height >= 1);
constexpr f64 focal_length = 1.0;
constexpr Point3 camera_centre{0.0, 0.0, 0.0};
Camera camera{camera_centre, img.width(), img.height(), focal_length};
Camera camera{camera_centre, image_width, image_height, focal_length};
// render
camera.Render(img, world);
Renderer renderer{camera};
auto img = renderer.Render(world);
std::cout << img;

47
src/rand.h Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include <cassert>
#include "raytracer.h"
class Random {
public:
constexpr explicit Random(u64 j) {
assert(j != v_);
u_ = j ^ v_;
GenU64();
v_ = u_;
GenU64();
w_ = v_;
GenU64();
}
constexpr u64 GenU64() {
u_ = u_ * 2862933555777941757ULL + 7046029254386353087ULL;
v_ ^= v_ >> 17;
v_ ^= v_ << 31;
v_ ^= v_ >> 8;
w_ = 4294957665ULL * (w_ & 0xffffffffULL) + (w_ >> 32);
u64 x = u_ ^ (u_ << 21);
x ^= x >> 35;
x ^= x << 4;
return (x + v_) ^ w_;
}
constexpr u32 GenU32() { return static_cast<u32>(GenU64()); }
constexpr f64 GenF64() { return 5.42101086242752217E-20 * static_cast<f64>(GenU64()); }
constexpr f64 GenF64(f64 min, f64 max) { return min + (max - min) * GenF64(); }
private:
u64 u_ = 0ULL;
u64 v_ = 4101842887655102017ULL;
u64 w_ = 1ULL;
};

View file

@ -1,5 +1,6 @@
#pragma once
#include "raytracer.h"
#include "vec3.h"
class Ray {
@ -11,7 +12,7 @@ class Ray {
Point3 origin() const { return orig_; }
Vec3 direction() const { return dir_; }
constexpr Point3 At(double t) const { return orig_ + t * dir_; }
constexpr Point3 At(f64 t) const { return orig_ + t * dir_; }
private:
Point3 orig_;

View file

@ -5,12 +5,15 @@
#include <numbers>
using u32 = uint32_t;
using u64 = uint64_t;
constexpr double kInf = std::numeric_limits<double>::infinity();
constexpr double kPi = std::numbers::pi_v<double>;
using f64 = double;
constexpr f64 kInf = std::numeric_limits<f64>::infinity();
constexpr f64 kPi = std::numbers::pi_v<f64>;
constexpr char newline = '\n';
constexpr u32 FToU8(double x) { return static_cast<uint16_t>(255.999 * x); }
constexpr u32 FToU8(f64 x) { return static_cast<uint16_t>(255.999 * x); }
constexpr double deg2rad(double deg) { return deg * kPi / 180.0; }
constexpr f64 deg2rad(f64 deg) { return deg * kPi / 180.0; }

79
src/renderer.h Normal file
View file

@ -0,0 +1,79 @@
#pragma once
#include <iostream>
#include "camera.h"
#include "colour.h"
#include "image.h"
#include "interval.h"
#include "rand.h"
#include "ray.h"
#include "raytracer.h"
#include "renderobject.h"
#include "vec3.h"
class Renderer {
public:
constexpr explicit Renderer(Camera camera, u32 samples_per_pixel = 10)
: camera_{camera}, rand_{0}, samples_per_pixel_{samples_per_pixel} {}
constexpr Image Render(const RenderObject& world) {
Image img{camera_.image_width(), camera_.image_height()};
for (u32 j = 0; j < img.height(); j++) {
std::clog << "\rWriting line " << j << " of " << img.height() << std::flush;
for (u32 i = 0; i < img.width(); i++) {
Colour colour_sum{0.0, 0.0, 0.0};
for (u32 num_sample = 0; num_sample < samples_per_pixel_; num_sample++) {
auto ray = SampleRay(i, j);
colour_sum += Cast(ray, world);
}
img[i, j] = colour_sum / samples_per_pixel_;
}
}
return img;
}
private:
constexpr Ray SampleRay(u32 i, u32 j) {
auto pixel_centre = camera_.PixelToWorld(i, j);
pixel_centre += rand_.GenF64(-0.5, 0.5) * camera_.d_u_pixel();
pixel_centre += rand_.GenF64(-0.5, 0.5) * camera_.d_v_pixel();
auto ray_direction = pixel_centre - camera_.centre();
return Ray{camera_.centre(), ray_direction};
}
static constexpr Colour Cast(const Ray& ray, const RenderObject& world) {
auto hit_record = world.hit(ray, Interval::kPositive);
if (hit_record.has_value()) {
Vec3 n = hit_record->normal;
if (!hit_record->front_face) {
return Colour::kBlack;
}
return Colour{0.5 * (n + Vec3{1, 1, 1})};
}
auto unit_dir = ray.direction().normed();
f64 a = 0.5 * (unit_dir.y() + 1.0);
return (1.0 - a) * Colour{1.0, 1.0, 1.0} + a * Colour{0.5, 0.7, 1.0};
}
Camera camera_;
Random rand_;
u32 samples_per_pixel_;
};

View file

@ -3,7 +3,6 @@
#include <memory>
#include <optional>
#include "colour.h"
#include "interval.h"
#include "ray.h"
#include "vec3.h"
@ -11,7 +10,7 @@
struct HitRecord {
Point3 p;
Vec3 normal;
double t = 0.0;
f64 t = 0.0;
bool front_face = true;
};
@ -31,27 +30,3 @@ class RenderObject {
};
using SharedRenderObject = std::shared_ptr<RenderObject>;
constexpr Colour Cast(const Ray& ray, const RenderObject& world) {
auto hit_record = world.hit(ray, Interval::kPositive);
// auto hit_record = world.hit(ray, Interval{0.505, kInf});
if (hit_record.has_value()) {
// assert(hit_record.front_face);
Vec3 n = hit_record->normal;
if (!hit_record->front_face) {
// return Colour{0.0, 0.0, 0.0};
return Colour::kBlack;
}
return Colour{0.5 * (n + Vec3{1, 1, 1})};
}
auto unit_dir = ray.direction().normed();
double a = 0.5 * (unit_dir.y() + 1.0);
return (1.0 - a) * Colour{1.0, 1.0, 1.0} + a * Colour{0.5, 0.7, 1.0};
}

View file

@ -27,7 +27,7 @@ class RenderObjectList : public RenderObject {
std::optional<HitRecord> hit(const Ray& ray, Interval ts) const override {
std::optional<HitRecord> closest_hit_record = std::nullopt;
double closest_t = ts.max();
f64 closest_t = ts.max();
for (const auto& obj : objs_) {
Interval _ts{ts.min(), closest_t};

View file

@ -6,29 +6,30 @@
#include "interval.h"
#include "ray.h"
#include "raytracer.h"
#include "renderobject.h"
#include "vec3.h"
class Sphere : public RenderObject {
public:
constexpr Sphere() = default;
constexpr Sphere(Point3 centre, double radius) : centre_{centre}, radius_{radius} {
constexpr Sphere(Point3 centre, f64 radius) : centre_{centre}, radius_{radius} {
assert(radius >= 0);
}
constexpr Point3 centre() const { return centre_; }
constexpr double radius() const { return radius_; }
constexpr f64 radius() const { return radius_; }
std::optional<HitRecord> hit(const Ray& ray, Interval ts) const override {
HitRecord hit_record;
Vec3 oc = ray.origin() - centre_;
double a = ray.direction().squared();
double b_half = dot(oc, ray.direction());
double c = oc.squared() - radius_ * radius_;
f64 a = ray.direction().squared();
f64 b_half = dot(oc, ray.direction());
f64 c = oc.squared() - radius_ * radius_;
auto discr = b_half * b_half - a * c;
f64 discr = b_half * b_half - a * c;
if (discr < 0) {
// miss
@ -38,7 +39,7 @@ class Sphere : public RenderObject {
// hit
// smaller of the ts that hit the sphere
double t = (-b_half - std::sqrt(discr)) / a;
f64 t = (-b_half - std::sqrt(discr)) / a;
if (t >= ts.max()) {
// if the smaller t is already too large, we don't need to check the second one since it
@ -77,5 +78,5 @@ class Sphere : public RenderObject {
private:
Point3 centre_{};
double radius_ = 0.0;
f64 radius_ = 0.0;
};

View file

@ -6,31 +6,33 @@
#include <cstddef>
#include <ostream>
#include "raytracer.h"
class Vec3 {
public:
static const Vec3 e_x;
static const Vec3 e_y;
static const Vec3 e_z;
constexpr Vec3(double x, double y, double z) : xyz_{x, y, z} {}
constexpr Vec3(f64 x, f64 y, f64 z) : xyz_{x, y, z} {}
constexpr Vec3() : Vec3{0, 0, 0} {}
constexpr double& x() { return xyz_[0]; }
constexpr double& y() { return xyz_[1]; }
constexpr double& z() { return xyz_[2]; }
constexpr f64& x() { return xyz_[0]; }
constexpr f64& y() { return xyz_[1]; }
constexpr f64& z() { return xyz_[2]; }
constexpr double x() const { return xyz_[0]; }
constexpr double y() const { return xyz_[1]; }
constexpr double z() const { return xyz_[2]; }
constexpr f64 x() const { return xyz_[0]; }
constexpr f64 y() const { return xyz_[1]; }
constexpr f64 z() const { return xyz_[2]; }
constexpr Vec3 operator-() const { return Vec3{-x(), -y(), -z()}; }
constexpr double& operator[](std::size_t i) {
constexpr f64& operator[](std::size_t i) {
assert(i <= 3);
return xyz_[i];
}
constexpr double operator[](std::size_t i) const {
constexpr f64 operator[](std::size_t i) const {
assert(i <= 3);
return xyz_[i];
}
@ -43,7 +45,7 @@ class Vec3 {
return *this;
}
constexpr Vec3& operator*=(double t) {
constexpr Vec3& operator*=(f64 t) {
x() *= t;
y() *= t;
z() *= t;
@ -51,15 +53,15 @@ class Vec3 {
return *this;
}
constexpr Vec3& operator/=(double t) {
constexpr Vec3& operator/=(f64 t) {
*this *= 1 / t;
return *this;
}
constexpr double squared() const { return x() * x() + y() * y() + z() * z(); }
constexpr f64 squared() const { return x() * x() + y() * y() + z() * z(); }
constexpr double norm() const { return std::sqrt(squared()); }
constexpr f64 norm() const { return std::sqrt(squared()); }
constexpr Vec3 normed() const {
auto r = *this;
@ -68,7 +70,7 @@ class Vec3 {
}
private:
std::array<double, 3> xyz_;
std::array<f64, 3> xyz_;
};
constexpr Vec3 Vec3::e_x{1.0, 0.0, 0.0};
@ -93,15 +95,15 @@ constexpr Vec3 operator*(const Vec3& u, const Vec3& v) {
return {u.x() * v.x(), u.y() * v.y(), u.z() * v.z()};
}
constexpr Vec3 operator*(double t, const Vec3& v) {
constexpr Vec3 operator*(f64 t, const Vec3& v) {
Vec3 out = v;
out *= t;
return out;
}
constexpr Vec3 operator/(const Vec3& v, double t) { return (1 / t) * v; }
constexpr Vec3 operator/(const Vec3& v, f64 t) { return (1 / t) * v; }
constexpr double dot(const Vec3& u, const Vec3& v) {
constexpr f64 dot(const Vec3& u, const Vec3& v) {
Vec3 tmp = u * v;
return tmp.x() + tmp.y() + tmp.z();
}