mirror of
https://github.com/MorizzG/ray-tracer.git
synced 2025-12-06 04:22:42 +00:00
Ray class now contains some of the hit code
Rectangle class added
This commit is contained in:
parent
97784e54ae
commit
e1360466de
11 changed files with 455 additions and 135 deletions
65
src/2dshapes.h
Normal file
65
src/2dshapes.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "interval.h"
|
||||
#include "ray.h"
|
||||
#include "raytracer.h"
|
||||
#include "renderobject.h"
|
||||
#include "vec3.h"
|
||||
|
||||
class Rectangle : public RenderObject {
|
||||
public:
|
||||
Rectangle() = default;
|
||||
|
||||
Rectangle(Point3 origin, Vec3 a, Vec3 b, std::shared_ptr<Material> mat)
|
||||
: origin_{origin}, a_{a}, b_{b}, normal_{cross(a, b).normed()}, mat_{std::move(mat)} {
|
||||
assert(is_zero(dot(a_, b_)));
|
||||
}
|
||||
|
||||
std::optional<HitRecord> hit(const Ray& ray, Interval ts) const override {
|
||||
auto t_maybe = ray.HitPlane(origin_, normal_);
|
||||
|
||||
if (!t_maybe.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
f64 t = t_maybe.value();
|
||||
|
||||
if (!ts.contains(t)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Point3 p = ray.At(t);
|
||||
|
||||
f64 _a = dot(p - origin_, a_);
|
||||
f64 _b = dot(p - origin_, b_);
|
||||
|
||||
if (_a <= 0.0 || _b <= 0.0 || _a >= a_.norm() || _b >= b_.norm()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
HitRecord hit_record;
|
||||
|
||||
hit_record.t = t;
|
||||
hit_record.front_face = dot(normal_, ray.direction()) > 0;
|
||||
hit_record.p = p;
|
||||
hit_record.mat = mat_.get();
|
||||
hit_record.normal = normal_;
|
||||
|
||||
return hit_record;
|
||||
}
|
||||
|
||||
private:
|
||||
Point3 origin_;
|
||||
|
||||
Vec3 a_;
|
||||
Vec3 b_;
|
||||
|
||||
Vec3 normal_;
|
||||
|
||||
std::shared_ptr<Material> mat_;
|
||||
};
|
||||
180
src/camera.h
180
src/camera.h
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
#include "raytracer.h"
|
||||
|
|
@ -7,39 +8,102 @@
|
|||
|
||||
class Camera {
|
||||
public:
|
||||
constexpr Camera(Point3 centre, u32 image_width, u32 image_height, f64 focal_length)
|
||||
: centre_{centre},
|
||||
focal_length_{focal_length},
|
||||
image_width_{image_width},
|
||||
image_height_{image_height} {
|
||||
Recalculate();
|
||||
constexpr Camera(Point3 centre, u32 image_width, u32 image_height)
|
||||
: centre_{centre}, image_width_{image_width}, image_height_{image_height} {
|
||||
Update();
|
||||
}
|
||||
|
||||
// can be changed, need to recalculate parameters
|
||||
// change paramters
|
||||
|
||||
constexpr Point3 centre() const { return centre_; }
|
||||
constexpr void centre(Point3 centre) {
|
||||
centre_ = centre;
|
||||
Recalculate();
|
||||
|
||||
focal_length_ = (look_at_ - centre_).norm();
|
||||
|
||||
need_update_ = true;
|
||||
}
|
||||
|
||||
constexpr Vec3 u() const { return u_; }
|
||||
constexpr Vec3 v() const { return v_; }
|
||||
constexpr Vec3 w() const { return w_; }
|
||||
constexpr Point3 look_at() const { return look_at_; }
|
||||
constexpr void look_at(Point3 look_at) {
|
||||
look_at_ = look_at;
|
||||
|
||||
constexpr void look_at(Point3 look_at, Vec3 up = Vec3::e_y) {
|
||||
w_ = (look_at - centre_).normed();
|
||||
focal_length_ = (look_at_ - centre_).norm();
|
||||
|
||||
v_ = (up - dot(up, w_) * w_).normed();
|
||||
|
||||
u_ = cross(v_, w_);
|
||||
|
||||
focal_length_ = (look_at - centre_).norm();
|
||||
|
||||
Recalculate();
|
||||
need_update_ = true;
|
||||
}
|
||||
|
||||
constexpr Vec3 up() const { return up_; }
|
||||
constexpr void up(Vec3 up) {
|
||||
up_ = up;
|
||||
|
||||
need_update_ = true;
|
||||
}
|
||||
|
||||
constexpr f64 fov() const { return fov_; }
|
||||
constexpr void fov(f64 fov) {
|
||||
fov_ = fov;
|
||||
|
||||
need_update_ = true;
|
||||
}
|
||||
|
||||
constexpr f64 focal_length() const { return focal_length_; }
|
||||
constexpr void focal_length(f64 focal_length) {
|
||||
focal_length_ = focal_length;
|
||||
|
||||
need_update_ = true;
|
||||
}
|
||||
|
||||
constexpr f64 defocus_angle() const { return defocus_angle_; }
|
||||
constexpr void defocus_angle(f64 defocus_angle) { defocus_angle_ = defocus_angle; }
|
||||
|
||||
// const-only accessors
|
||||
|
||||
constexpr Vec3 u() const {
|
||||
Update();
|
||||
|
||||
return u_;
|
||||
}
|
||||
constexpr Vec3 v() const {
|
||||
Update();
|
||||
|
||||
return v_;
|
||||
}
|
||||
constexpr Vec3 w() const {
|
||||
Update();
|
||||
|
||||
return w_;
|
||||
}
|
||||
|
||||
constexpr Vec3 d_u_pixel() const {
|
||||
Update();
|
||||
|
||||
return d_u_pixel_;
|
||||
}
|
||||
constexpr Vec3 d_v_pixel() const {
|
||||
Update();
|
||||
|
||||
return d_v_pixel_;
|
||||
}
|
||||
|
||||
constexpr u32 image_width() const { return image_width_; }
|
||||
constexpr u32 image_height() const { return image_height_; }
|
||||
|
||||
constexpr Vec3 PixelToWorld(f64 i, f64 j) const {
|
||||
Update();
|
||||
|
||||
return pixel_00_ + i * d_u_pixel_ + j * d_v_pixel_;
|
||||
}
|
||||
|
||||
constexpr f64 defocus_radius() const {
|
||||
return focal_length() * std::tan(deg2rad(defocus_angle()) / 2);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
constexpr void rotate(f64 deg) {
|
||||
Update();
|
||||
|
||||
f64 rad = deg2rad(deg);
|
||||
|
||||
Vec3 u = std::cos(rad) * u_ + std::sin(rad) * v_;
|
||||
|
|
@ -47,37 +111,29 @@ class Camera {
|
|||
|
||||
u_ = u;
|
||||
v_ = v;
|
||||
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
constexpr f64 fov_angle_v() const { return fov_deg_v_; }
|
||||
constexpr void fov_angle_v(f64 fov) {
|
||||
fov_deg_v_ = fov;
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
// const-only accessors
|
||||
|
||||
constexpr Vec3 d_u_pixel() const { return d_u_pixel_; }
|
||||
constexpr Vec3 d_v_pixel() const { return d_v_pixel_; }
|
||||
|
||||
constexpr u32 image_width() const { return image_width_; }
|
||||
constexpr u32 image_height() const { return image_height_; }
|
||||
|
||||
constexpr Vec3 PixelToWorld(u32 i, u32 j) const {
|
||||
return pixel_00_ + i * d_u_pixel_ + j * d_v_pixel_;
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr void Recalculate() {
|
||||
const f64 aspect_ratio = static_cast<f64>(image_width_) / image_height_;
|
||||
constexpr void Update() const {
|
||||
if (!need_update_) {
|
||||
return;
|
||||
}
|
||||
|
||||
f64 fov_rad_v = deg2rad(fov_deg_v_);
|
||||
f64 h = std::tan(fov_rad_v / 2);
|
||||
f64 aspect_ratio = static_cast<f64>(image_width_) / image_height_;
|
||||
|
||||
// constexpr f64 viewport_height = 2.0;
|
||||
const f64 viewport_height = 2 * h * focal_length_;
|
||||
w_ = -(look_at_ - centre_).normed();
|
||||
|
||||
v_ = (up_ - dot(up_, w_) * w_).normed();
|
||||
|
||||
u_ = cross(v_, w_);
|
||||
|
||||
assert(is_zero(dot(u_, v_)));
|
||||
assert(is_zero(dot(u_, w_)));
|
||||
assert(is_zero(dot(v_, w_)));
|
||||
|
||||
f64 h = focal_length_ * std::tan(deg2rad(fov_) / 2);
|
||||
|
||||
const f64 viewport_height = 2 * h;
|
||||
|
||||
f64 viewport_width = viewport_height * aspect_ratio;
|
||||
|
||||
|
|
@ -92,28 +148,38 @@ class Camera {
|
|||
auto viewport_upper_left = centre_ - u_viewport / 2 - v_viewport / 2 - focal_length_ * w_;
|
||||
|
||||
pixel_00_ = viewport_upper_left + 0.5 * d_u_pixel_ + 0.5 * d_v_pixel_;
|
||||
|
||||
need_update_ = false;
|
||||
}
|
||||
|
||||
// settable camera parameters
|
||||
// fundamental camera parameters
|
||||
|
||||
Point3 centre_ = Vec3::origin;
|
||||
Point3 centre_ = -Point3::e_z;
|
||||
Point3 look_at_ = Point3::origin;
|
||||
Vec3 up_ = Vec3::e_y;
|
||||
|
||||
Vec3 u_ = Vec3::e_x; // points to right of camera
|
||||
Vec3 v_ = Vec3::e_y; // points to up of camera
|
||||
Vec3 w_ = Vec3::e_z; // points backward of camera
|
||||
f64 fov_ = 80;
|
||||
|
||||
f64 focal_length_;
|
||||
f64 defocus_angle_ = 0;
|
||||
|
||||
f64 fov_deg_v_ = 80;
|
||||
// saved camera properties, need to be recalculated after updating parameters
|
||||
|
||||
// saved camera properties
|
||||
mutable bool need_update_ = true;
|
||||
|
||||
mutable Vec3 u_ = Vec3::e_x; // points to right of camera
|
||||
mutable Vec3 v_ = Vec3::e_y; // points to up of camera
|
||||
mutable Vec3 w_ = Vec3::e_z; // points backward of camera
|
||||
|
||||
mutable f64 focal_length_ = 1.0;
|
||||
|
||||
// position of pixel 0, 0
|
||||
Vec3 pixel_00_;
|
||||
mutable Vec3 pixel_00_;
|
||||
|
||||
// Vec3 pointing from pixel to right/lower neighbour resp.
|
||||
Vec3 d_u_pixel_;
|
||||
Vec3 d_v_pixel_;
|
||||
mutable Vec3 d_u_pixel_;
|
||||
mutable Vec3 d_v_pixel_;
|
||||
|
||||
// const
|
||||
|
||||
u32 image_width_;
|
||||
u32 image_height_;
|
||||
|
|
|
|||
57
src/main.cc
57
src/main.cc
|
|
@ -1,6 +1,7 @@
|
|||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "2dshapes.h"
|
||||
#include "camera.h"
|
||||
#include "colour.h"
|
||||
#include "image.h"
|
||||
|
|
@ -16,6 +17,8 @@ int main(int /* argc */, char* /* argv */[]) {
|
|||
|
||||
RenderObjectList world;
|
||||
|
||||
constexpr f64 focal_length = 5.0;
|
||||
|
||||
/* auto lamb = std::make_shared<Lambertian>(Colour{0.7, 0.0, 0.5});
|
||||
auto metal = std::make_shared<Metal>(Colour{0.3, 0.3, 0.3});
|
||||
|
||||
|
|
@ -26,25 +29,42 @@ int main(int /* argc */, char* /* argv */[]) {
|
|||
world.Add(std::move(sphere)); */
|
||||
|
||||
auto mat_ground = std::make_shared<Lambertian>(Colour(0.8, 0.8, 0.0));
|
||||
auto mat_lamb = std::make_shared<Lambertian>(Colour(0.1, 0.2, 0.5));
|
||||
auto mat_lamb = std::make_shared<Lambertian>(Colour(1.0, 0.0, 0.0));
|
||||
auto mat_metal = std::make_shared<Metal>(Colour(0.9, 0.4, 0.6), 0.0);
|
||||
auto mat_dielec = std::make_shared<Dielectric>(1.5, 0.0);
|
||||
auto mat_dielec2 = std::make_shared<Dielectric>(0.66, 0.0);
|
||||
auto mat_metal2 = std::make_shared<Metal>(Colour(0.3, 0.5, 0.9), 0.0);
|
||||
// auto mat_dielec = std::make_shared<Dielectric>(1.5, 0.0);
|
||||
// auto mat_dielec2 = std::make_shared<Dielectric>(0.66, 0.0);
|
||||
|
||||
world.Add(std::make_unique<Sphere>(Point3(0.0, -100.5, -1.0), 100.0, mat_ground));
|
||||
world.Add(std::make_unique<Sphere>(Point3(0, -100, 0), 100, mat_ground));
|
||||
|
||||
world.Add(std::make_unique<Sphere>(Point3(0.0, 0.0, -1.0), 0.5, mat_lamb));
|
||||
world.Add(std::make_unique<Rectangle>(Point3(-2.5, -2.5, 50), 5 * Vec3::e_x, 5 * Vec3::e_y,
|
||||
mat_lamb));
|
||||
|
||||
world.Add(std::make_unique<Sphere>(Point3(-1.0, 0.0, -1.0), 0.5, mat_dielec));
|
||||
world.Add(std::make_unique<Sphere>(Point3(-1.0, 0.0, -1.0), 0.4, mat_dielec2));
|
||||
/* constexpr i32 N = 5;
|
||||
|
||||
world.Add(std::make_unique<Sphere>(Point3(1.0, 0.0, -1.0), 0.5, mat_metal));
|
||||
for (i32 x = -N; x <= N; x++) {
|
||||
for (i32 y = 0; y <= N; y++) {
|
||||
world.Add(std::make_unique<Rectangle>(Point3(x, y, 50), Vec3::e_x, Vec3::e_y,
|
||||
(x + y) % 2 == 0 ? mat_lamb : mat_lamb2));
|
||||
}
|
||||
} */
|
||||
|
||||
// world.Add(std::make_unique<Sphere>(Point3(-1.0, 0.0, 0.0), 0.5, mat_dielec));
|
||||
// world.Add(std::make_unique<Sphere>(Point3(-1.0, 0.0, 0.0), 0.4, mat_dielec2));
|
||||
|
||||
// world.Add(std::make_unique<Sphere>(Point3{-1, 0, 0}, 0.5, mat_metal2));
|
||||
|
||||
// world.Add(std::make_unique<Sphere>(Point3(1, 0, 0), 0.5, mat_metal));
|
||||
|
||||
/* for (f64 z = 0.5; z < 10; z += 0.5) {
|
||||
world.Add(std::make_unique<Sphere>(Point3(1, 0, z), 1 * z / focal_length, mat_metal));
|
||||
} */
|
||||
|
||||
// camera
|
||||
|
||||
constexpr f64 aspect_ratio = 16.0 / 9.0;
|
||||
|
||||
constexpr u32 image_width = 800;
|
||||
constexpr u32 image_width = 1200;
|
||||
constexpr auto image_height = static_cast<u32>(image_width / aspect_ratio);
|
||||
|
||||
// constexpr u32 image_width = 1920;
|
||||
|
|
@ -52,21 +72,18 @@ int main(int /* argc */, char* /* argv */[]) {
|
|||
|
||||
static_assert(image_height >= 1);
|
||||
|
||||
constexpr f64 focal_length = 1.0;
|
||||
constexpr Point3 camera_centre{0, 1, -1};
|
||||
|
||||
constexpr Point3 camera_centre{0.0, 0.0, 0.0};
|
||||
Camera camera{camera_centre, image_width, image_height};
|
||||
|
||||
Camera camera{camera_centre, image_width, image_height, focal_length};
|
||||
camera.look_at(Vec3{0, 0, 1});
|
||||
camera.focal_length(focal_length);
|
||||
|
||||
std::clog << camera.u() << newline;
|
||||
std::clog << camera.v() << newline;
|
||||
std::clog << camera.w() << newline << newline;
|
||||
camera.defocus_angle(1);
|
||||
// camera.focal_length(1);
|
||||
|
||||
camera.look_at(Point3{0.8, 0.0, 1.0}, Vec3::e_x);
|
||||
|
||||
std::clog << camera.u() << newline;
|
||||
std::clog << camera.v() << newline;
|
||||
std::clog << camera.w() << newline << newline;
|
||||
// camera.fov(40);
|
||||
// camera.centre(Point3{-2, 2, 1});
|
||||
|
||||
// render
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class Lambertian : public Material {
|
|||
|
||||
std::optional<std::tuple<Colour, Ray>> Scatter(const Ray& /* in */,
|
||||
const HitRecord& hit_record) const override {
|
||||
auto scatter_dir = hit_record.normal + RandomGen::GenInstance().GenOnUnitSphere();
|
||||
auto scatter_dir = hit_record.normal + RandomGen::GenInstance().UnitSphereVec3();
|
||||
|
||||
if (scatter_dir.almost_zero()) {
|
||||
scatter_dir = hit_record.normal;
|
||||
|
|
@ -59,7 +59,7 @@ class Metal : public Material {
|
|||
const HitRecord& hit_record) const override {
|
||||
auto reflect_dir = in.direction().reflect(hit_record.normal).normed();
|
||||
|
||||
reflect_dir += fuzz_ * RandomGen::GenInstance().GenOnUnitSphere();
|
||||
reflect_dir += fuzz_ * RandomGen::GenInstance().UnitSphereVec3();
|
||||
|
||||
Ray scattered{hit_record.p, reflect_dir};
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ class Dielectric : public Material {
|
|||
return r2 + (1 - r2) * std::pow(1 - cos, 5);
|
||||
};
|
||||
|
||||
if (eta * sin_theta > 1.0 || reflectance(cos_theta, eta) > rand.GenUniform()) {
|
||||
if (eta * sin_theta > 1.0 || reflectance(cos_theta, eta) > rand.Uniform()) {
|
||||
// reflect
|
||||
out_dir = in.direction().reflect(hit_record.normal);
|
||||
} else {
|
||||
|
|
@ -101,7 +101,7 @@ class Dielectric : public Material {
|
|||
out_dir = in.direction().refract(hit_record.normal, eta);
|
||||
}
|
||||
|
||||
out_dir += fuzz_ * rand.GenOnUnitSphere();
|
||||
out_dir += fuzz_ * rand.UnitSphereVec3();
|
||||
|
||||
Ray out{hit_record.p, out_dir};
|
||||
|
||||
|
|
|
|||
103
src/parallelepiped.h
Normal file
103
src/parallelepiped.h
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "interval.h"
|
||||
#include "ray.h"
|
||||
#include "raytracer.h"
|
||||
#include "renderobject.h"
|
||||
#include "vec3.h"
|
||||
|
||||
class Parallelepiped : public RenderObject {
|
||||
public:
|
||||
constexpr Parallelepiped() = default;
|
||||
|
||||
constexpr Parallelepiped(Point3 centre, Vec3 u, Vec3 v, Vec3 w, f64 a = 1.0, f64 b = 1.0,
|
||||
f64 c = 1.0)
|
||||
: centre_{centre}, u_{u.normed()}, v_{v.normed()}, w_{w.normed()}, a_{a}, b_{b}, c_{c} {
|
||||
assert(a >= 0);
|
||||
assert(b >= 0);
|
||||
assert(c >= 0);
|
||||
|
||||
// make sure all axes lie in unit cube
|
||||
|
||||
if (dot(u_, v_) < 0) {
|
||||
v_ = -v_;
|
||||
}
|
||||
|
||||
if (dot(u_, w_) < 0) {
|
||||
w_ = -w_;
|
||||
}
|
||||
|
||||
// make u, v, w are right-handed (not sure if neccessary, but why not)
|
||||
if (dot(cross(u_, v_), w_) < 0.0) {
|
||||
std::swap(v_, w_);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Vec3 u() const { return u_; }
|
||||
constexpr Vec3 v() const { return v_; }
|
||||
constexpr Vec3 w() const { return w_; }
|
||||
|
||||
constexpr f64 a() const { return a_; }
|
||||
constexpr f64 b() const { return b_; }
|
||||
constexpr f64 c() const { return c_; }
|
||||
|
||||
std::optional<HitRecord> hit(const Ray& ray, Interval ts) const override {
|
||||
// check if ray intersects outsphere first
|
||||
const f64 radius_ = (a_ * u_ + b_ * v_ + c_ * w_).norm() / 2;
|
||||
|
||||
Vec3 oc = ray.origin() - centre_;
|
||||
|
||||
f64 a = ray.direction().squared();
|
||||
f64 b_half = dot(oc, ray.direction());
|
||||
f64 c = oc.squared() - radius_ * radius_;
|
||||
|
||||
f64 discr = b_half * b_half - a * c;
|
||||
|
||||
if (discr < 0) {
|
||||
// miss
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// since the sphere completely envelops the cuboid
|
||||
|
||||
// smaller of the ts that hit the sphere
|
||||
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
|
||||
// will be even larger
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (t <= ts.min()) {
|
||||
// if the smaller t is too small, check the larger one
|
||||
|
||||
t = (-b_half + std::sqrt(discr)) / a;
|
||||
|
||||
if (!ts.surronds(t)) {
|
||||
// larger t also out of range: return false
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Point3 centre_;
|
||||
|
||||
// face normals
|
||||
Vec3 u_;
|
||||
Vec3 v_;
|
||||
Vec3 w_;
|
||||
|
||||
// face distances
|
||||
f64 a_ = 0.0;
|
||||
f64 b_ = 0.0;
|
||||
f64 c_ = 0.0;
|
||||
};
|
||||
33
src/rand.h
33
src/rand.h
|
|
@ -21,22 +21,22 @@ class RandomGen {
|
|||
return rand;
|
||||
}
|
||||
|
||||
constexpr u64 GenU64() { return philox_.Next(); }
|
||||
constexpr u64 U64() { return philox_.Next(); }
|
||||
// constexpr u32 GenU32() { return static_cast<u32>(GenU64()); }
|
||||
|
||||
constexpr f64 GenUniform() { return philox_.NextF64(); }
|
||||
constexpr f64 Uniform() { return philox_.NextF64(); }
|
||||
|
||||
constexpr f64 GenUniform(f64 min, f64 max) { return min + (max - min) * GenUniform(); }
|
||||
constexpr f64 Uniform(f64 min, f64 max) { return min + (max - min) * Uniform(); }
|
||||
|
||||
constexpr Vec3 GenVec3() { return Vec3{GenUniform(), GenUniform(), GenUniform()}; }
|
||||
constexpr Vec3 UniformVec3() { return Vec3{Uniform(), Uniform(), Uniform()}; }
|
||||
|
||||
constexpr Vec3 GenVec3(f64 min, f64 max) {
|
||||
return Vec3{GenUniform(min, max), GenUniform(min, max), GenUniform(min, max)};
|
||||
constexpr Vec3 UniformVec3(f64 min, f64 max) {
|
||||
return Vec3{Uniform(min, max), Uniform(min, max), Uniform(min, max)};
|
||||
}
|
||||
|
||||
constexpr Vec3 GenInUnitBall() {
|
||||
constexpr Vec3 UnitBallVec3() {
|
||||
while (true) {
|
||||
Vec3 v = GenVec3(-1.0, 1.0);
|
||||
Vec3 v = UniformVec3(-1.0, 1.0);
|
||||
|
||||
if (v.norm() < 1.0) {
|
||||
return v;
|
||||
|
|
@ -44,10 +44,10 @@ class RandomGen {
|
|||
}
|
||||
}
|
||||
|
||||
constexpr Vec3 GenOnUnitSphere() { return GenInUnitBall().normed(); }
|
||||
constexpr Vec3 UnitSphereVec3() { return UnitBallVec3().normed(); }
|
||||
|
||||
constexpr Vec3 GenOnHemisphere(const Vec3& normal) {
|
||||
Vec3 v = GenOnUnitSphere();
|
||||
constexpr Vec3 HemisphereVec3(Vec3 normal) {
|
||||
Vec3 v = UnitSphereVec3();
|
||||
|
||||
if (dot(v, normal) < 0) {
|
||||
v = -v;
|
||||
|
|
@ -56,6 +56,17 @@ class RandomGen {
|
|||
return v;
|
||||
}
|
||||
|
||||
constexpr Vec3 UnitDiskVec3(Vec3 normal) {
|
||||
while (true) {
|
||||
Vec3 v = UniformVec3();
|
||||
v -= dot(v, normal) * normal;
|
||||
|
||||
if (v.norm() < 1) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
RandomGen() = default;
|
||||
|
||||
|
|
|
|||
44
src/ray.h
44
src/ray.h
|
|
@ -1,5 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "raytracer.h"
|
||||
#include "vec3.h"
|
||||
|
||||
|
|
@ -7,15 +12,48 @@ class Ray {
|
|||
public:
|
||||
constexpr Ray() = default;
|
||||
|
||||
constexpr Ray(const Point3& origin, const Vec3& direction)
|
||||
: orig_{origin}, dir_{direction.normed()} {}
|
||||
constexpr Ray(const Point3& origin, Vec3 direction) : orig_{origin}, dir_{direction.normed()} {}
|
||||
|
||||
Point3 origin() const { return orig_; }
|
||||
Vec3 direction() const { return dir_; }
|
||||
|
||||
constexpr Point3 At(f64 t) const { return orig_ + t * dir_; }
|
||||
|
||||
// return ts at which the ray intersects sphere or nullopt if no intersection
|
||||
// ts may the the same if tangential
|
||||
constexpr std::optional<std::pair<f64, f64>> HitSphere(Point3 centre, f64 radius) const {
|
||||
Vec3 oc = origin() - centre;
|
||||
|
||||
f64 a = 1.0; // direction().squared() is always 1
|
||||
f64 b_half = dot(oc, direction());
|
||||
f64 c = oc.squared() - radius * radius;
|
||||
|
||||
f64 discr = b_half * b_half - a * c;
|
||||
|
||||
if (discr < 0) {
|
||||
// miss
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// hit
|
||||
|
||||
return std::make_pair((-b_half - std::sqrt(discr)) / a, (-b_half + std::sqrt(discr)) / a);
|
||||
}
|
||||
|
||||
// return t a which the ray intersects the plane containing `v` with normal `normal`
|
||||
std::optional<f64> HitPlane(Point3 v, Vec3 normal) const {
|
||||
// if the ray is parallel to the plane, return nullopt, even if the ray runs inside the
|
||||
// plane!
|
||||
if (is_zero(dot(direction(), normal))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
f64 t = dot(v - origin(), normal) / dot(direction(), normal);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
private:
|
||||
Point3 orig_;
|
||||
Vec3 dir_;
|
||||
Vec3 dir_; // unit vector
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <numbers>
|
||||
|
|
@ -7,6 +9,9 @@
|
|||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
using i32 = int32_t;
|
||||
using i64 = int64_t;
|
||||
|
||||
using f64 = double;
|
||||
|
||||
constexpr f64 kInf = std::numeric_limits<f64>::infinity();
|
||||
|
|
@ -16,6 +21,11 @@ constexpr f64 kPi = std::numbers::pi_v<f64>;
|
|||
|
||||
constexpr char newline = '\n';
|
||||
|
||||
constexpr u32 FToU8(f64 x) { return static_cast<uint16_t>(255.999 * x); }
|
||||
constexpr u32 FToU8(f64 x) { return static_cast<u32>(255.999 * x); }
|
||||
|
||||
constexpr f64 deg2rad(f64 deg) { return deg * kPi / 180.0; }
|
||||
|
||||
constexpr bool is_zero(f64 x, f64 atol = 1e-5) {
|
||||
assert(atol >= 0.0);
|
||||
return std::fabs(x) < atol;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,18 +91,22 @@ class Renderer {
|
|||
|
||||
private:
|
||||
Ray SampleRay(u32 i, u32 j) {
|
||||
auto pixel_centre = camera_.PixelToWorld(i, j);
|
||||
|
||||
auto& rand = RandomGen::GenInstance();
|
||||
|
||||
Vec3 random_shift = rand.GenUniform(-0.5, 0.5) * camera_.d_u_pixel() +
|
||||
rand.GenUniform(-0.5, 0.5) * camera_.d_v_pixel();
|
||||
auto ray_origin = camera_.centre();
|
||||
|
||||
pixel_centre += random_shift;
|
||||
/* if (camera_.defocus_angle() > 0) {
|
||||
ray_origin += camera_.defocus_radius() * rand.UnitDiskVec3(camera_.w());
|
||||
} */
|
||||
|
||||
auto ray_direction = (pixel_centre - camera_.centre()).normed();
|
||||
f64 x_dist = rand.Uniform(-0.5, 0.5);
|
||||
f64 y_dist = rand.Uniform(-0.5, 0.5);
|
||||
|
||||
return Ray{camera_.centre(), ray_direction};
|
||||
auto pixel_centre = camera_.PixelToWorld(i + x_dist, j + y_dist);
|
||||
|
||||
auto ray_direction = pixel_centre - camera_.centre();
|
||||
|
||||
return Ray{ray_origin, ray_direction};
|
||||
}
|
||||
|
||||
constexpr Colour Cast(const Ray& ray, const RenderObject& world, u32 bounces) {
|
||||
|
|
|
|||
40
src/sphere.h
40
src/sphere.h
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
|
|
@ -27,49 +26,50 @@ class Sphere : public RenderObject {
|
|||
std::optional<HitRecord> hit(const Ray& ray, Interval ts) const override {
|
||||
HitRecord hit_record{};
|
||||
|
||||
Vec3 oc = ray.origin() - centre_;
|
||||
auto t_low_high = ray.HitSphere(centre_, radius_);
|
||||
|
||||
f64 a = ray.direction().squared();
|
||||
f64 b_half = dot(oc, ray.direction());
|
||||
f64 c = oc.squared() - radius_ * radius_;
|
||||
|
||||
f64 discr = b_half * b_half - a * c;
|
||||
|
||||
if (discr < 0) {
|
||||
if (!t_low_high.has_value()) {
|
||||
// miss
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// hit
|
||||
auto [t_low, t_high] = t_low_high.value();
|
||||
|
||||
// smaller of the ts that hit the sphere
|
||||
f64 t = (-b_half - std::sqrt(discr)) / a;
|
||||
// we need to only distinguish 4 cases here:
|
||||
// 1. t_low higher than interval automatically implies t_high is also higher than interval,
|
||||
// so return
|
||||
// 2. t_low is in the interval, so return the t_low hit (t_high doesn't matter)
|
||||
// 3. t_low is too low, t_high is in interval, so return t_high
|
||||
// 4. both t_low is too low and t_high is too high
|
||||
|
||||
if (t >= ts.max()) {
|
||||
if (t_low >= ts.max()) {
|
||||
// if the smaller t is already too large, we don't need to check the second one since it
|
||||
// will be even larger
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (t <= ts.min()) {
|
||||
if (t_low > ts.min()) {
|
||||
// t_min is hit
|
||||
|
||||
hit_record.t = t_low;
|
||||
// hit_record.front_face = true;
|
||||
} else {
|
||||
// if the smaller t is too small, check the larger one
|
||||
|
||||
t = (-b_half + std::sqrt(discr)) / a;
|
||||
|
||||
if (!ts.surronds(t)) {
|
||||
if (!ts.surronds(t_high)) {
|
||||
// larger t also out of range: return false
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// smaller t hits front side, smaller t hits back side
|
||||
// TODO: what if our hit t is less than 0? is that case even relevant?
|
||||
// t_low hits front side, t_high hits back side
|
||||
hit_record.t = t_high;
|
||||
hit_record.front_face = false;
|
||||
}
|
||||
|
||||
hit_record.t = t;
|
||||
hit_record.p = ray.At(t);
|
||||
hit_record.p = ray.At(hit_record.t);
|
||||
hit_record.mat = mat_.get();
|
||||
|
||||
if (hit_record.front_face) {
|
||||
|
|
|
|||
30
src/vec3.h
30
src/vec3.h
|
|
@ -39,7 +39,7 @@ class Vec3 {
|
|||
return xyz_[i];
|
||||
}
|
||||
|
||||
constexpr Vec3& operator+=(const Vec3& v) {
|
||||
constexpr Vec3& operator+=(Vec3 v) {
|
||||
x() += v.x();
|
||||
y() += v.y();
|
||||
z() += v.z();
|
||||
|
|
@ -47,6 +47,14 @@ class Vec3 {
|
|||
return *this;
|
||||
}
|
||||
|
||||
constexpr Vec3& operator-=(Vec3 v) {
|
||||
x() -= v.x();
|
||||
y() -= v.y();
|
||||
z() -= v.z();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Vec3& operator*=(f64 t) {
|
||||
x() *= t;
|
||||
y() *= t;
|
||||
|
|
@ -88,37 +96,35 @@ constexpr Vec3 Vec3::e_z{0.0, 0.0, 1.0};
|
|||
|
||||
using Point3 = Vec3;
|
||||
|
||||
constexpr std::ostream& operator<<(std::ostream& out, const Vec3& v) {
|
||||
constexpr std::ostream& operator<<(std::ostream& out, Vec3 v) {
|
||||
return out << v.x() << ' ' << v.y() << ' ' << v.z();
|
||||
}
|
||||
|
||||
constexpr Vec3 operator+(const Vec3& u, const Vec3& v) {
|
||||
constexpr Vec3 operator+(Vec3 u, Vec3 v) {
|
||||
Vec3 out = u;
|
||||
out += v;
|
||||
return out;
|
||||
}
|
||||
|
||||
constexpr Vec3 operator-(const Vec3& u, const Vec3& v) { return u + (-v); }
|
||||
constexpr Vec3 operator-(Vec3 u, Vec3 v) { return u + (-v); }
|
||||
|
||||
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*(Vec3 u, Vec3 v) { return {u.x() * v.x(), u.y() * v.y(), u.z() * v.z()}; }
|
||||
|
||||
constexpr Vec3 operator*(f64 t, const Vec3& v) {
|
||||
constexpr Vec3 operator*(f64 t, Vec3 v) {
|
||||
Vec3 out = v;
|
||||
out *= t;
|
||||
return out;
|
||||
}
|
||||
|
||||
constexpr Vec3 operator/(const Vec3& v, f64 t) { return (1 / t) * v; }
|
||||
constexpr Vec3 operator/(Vec3 v, f64 t) { return (1 / t) * v; }
|
||||
|
||||
constexpr f64 dot(const Vec3& u, const Vec3& v) {
|
||||
constexpr f64 dot(Vec3 u, Vec3 v) {
|
||||
Vec3 tmp = u * v;
|
||||
return tmp.x() + tmp.y() + tmp.z();
|
||||
}
|
||||
|
||||
constexpr Vec3 cross(const Vec3& u, const Vec3& v) {
|
||||
return {u.y() * v.z() - u.z() - v.y(), u.z() * v.x() - u.x() * v.z(),
|
||||
constexpr Vec3 cross(Vec3 u, Vec3 v) {
|
||||
return {u.y() * v.z() - u.z() * v.y(), u.z() * v.x() - u.x() * v.z(),
|
||||
u.x() * v.y() - u.y() * v.x()};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue