init: create git repo

This commit is contained in:
dvdrw 2022-12-17 18:22:51 +01:00
commit e7bc9882e6
Signed by: dvdrw
GPG Key ID: 044B8425E3CD03E0
11 changed files with 860 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.ccls-cache/
bin/*
obj/*

29
Makefile Normal file
View File

@ -0,0 +1,29 @@
SHELL = /bin/bash
SRC_DIR = ./src
INC_DIR = ./include
OBJ_DIR = ./obj
HEADERS = $(wildcard $(INC_DIR)/*.hpp)
SRC_FILES = $(wildcard $(SRC_DIR)/*.cpp)
OBJ_FILES = $(addprefix $(OBJ_DIR)/,$(notdir $(SRC_FILES:.cpp=.o)))
CXXFLAGS += -Wall -std=c++2a -fopenmp -O3 -Ofast -march=native
all: bin/raytracer
$(OBJ_DIR)/%.o: src/%.cpp
$(CXX) $(CXXFLAGS) -c -o $@ $^
bin/raytracer: $(OBJ_FILES)
@mkdir -p $(OBJ_DIR)
$(CXX) $(CXXFLAGS) -o $@ $^
.PHONY: debug
debug: CXXFLAGS += -O0 -g
debug: clean bin/raytracer
.PHONY: clean
clean:
$(RM) -r obj/*.o
$(RM) bin/*

27
README Normal file
View File

@ -0,0 +1,27 @@
A raytracer that will render to any ANSI terminal with support for 24-bit
colours, at least 80x24 big.
Build with `make' and run the resulting binary in `/bin'.
The raytracer implements the following:
- Planes, spheres, and point lights
- Reflection
- IOR-based refraction
- Cook-Torrance BRDF (a few other models are left in the code)
- Primitive object materials
You can move around with WASD and SPC/Z; zoom with I/O, change the exposure with
+/-, and quit with Q.
The image output is a 24-bit coloured 80x24 (by default, tweakable in
`main.cpp'), 2x supersampled. The output uses Unicode characters and differently
coloured foreground and background to effectively double the output resolution.
You can read the implementation in `buffer.cpp'.
The raytracing loop is parallelised using an OpenMP pragma, which should be well
supported on most systems. You can just comment out the pragma (and remove the
omp flag from the Makefile) to remove parallelisation.
Although the code isn't particularly documented, most functions have a docstring
in their respective header files. Most fun things to play around with are
located in `main.cpp'.

23
include/buffer.hpp Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include "./vec.hpp"
#include "./raytrace.hpp"
struct Frame {
v3* data;
};
void bg_fg(v3* cols, v3& bg, v3& fg, const char*& c);
inline int colcvt(choice_t x) {
if (x < 0.0) return 0;
if (x > 1.0) return 255;
return int(x * 255.0);
}
void display_frame(const Screen& screen, const Frame& frame);
void render_frame(const Screen& screen, Camera& camera, Frame& frame,
const std::vector<Renderable*>& scene,
const std::vector<Renderable*>& objects,
const std::vector<Renderable*>& lights);

38
include/objects.hpp Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include "./vec.hpp"
#include "./raytrace.hpp"
#include <stdio.h>
struct Plane : Renderable {
v3 n;
choice_t offset;
int surface_type;
Plane(const v3& n, choice_t offset, const Material& specs, int surface_type);
Plane(const v3& n, choice_t offset, const Material& specs);
inline v3 normal(const v3& _) { return n; }
int intersect(const v3& eye, const v3& dir, choice_t& hit_dist, v3& hit_loc, v3& hit_normal) override;
};
struct Sphere : Renderable {
v3 o;
choice_t r;
int surface_type;
Sphere(const v3& origin, choice_t r, const Material& specs, int surface_type);
Sphere(const v3& origin, choice_t r);
inline v3 normal(const v3& xyz) const { return (xyz - o).norm(); }
int intersect(const v3& eye, const v3& dir, choice_t& hit_dist, v3& hit_loc, v3& hit_normal) override;
int transmit(const v3& eye, const v3& dir, choice_t& hit_dist, v3& hit_loc, v3& hit_normal) override;
};
struct Light : Sphere {
Light(const v3& origin, choice_t r, const v3& colour);
Light(const v3& origin, choice_t r, const Material& specs);
};

82
include/raytrace.hpp Normal file
View File

@ -0,0 +1,82 @@
#pragma once
#include <vector>
#include "./vec.hpp"
struct Screen {
const int W, H;
const choice_t ASPECT_RATIO;
constexpr Screen(int W, int H, choice_t ASPECT_RATIO)
: W(W), H(H), ASPECT_RATIO(ASPECT_RATIO) {}
};
struct Camera {
v3 pos; v3 rotation; v3 translation{0,0,0};
choice_t zoom = 0.5;
choice_t exposure = -0.6;
choice_t dynamic_range = 1;
};
struct Material {
choice_t specular = 0.1;
choice_t roughness = 0.5;
choice_t ior = 1.2;
choice_t transparency = 0.8;
v3 colour;
};
struct Renderable {
Material specs;
Renderable(const Material& specs);
virtual int intersect(const v3& eye, const v3& dir, choice_t& hit_dist, v3& hit_loc, v3& hit_normal)=0;
/**
Like #intersect, but for transmission rays inside the object. #eye must
be on the surface of the object.
*/
virtual int transmit(const v3& eye, const v3& dir, choice_t& hit_dist, v3& hit_loc, v3& hit_normal) { return -1; }
};
/**
Shoots a ray from #eye in direction #dir, (possibly) hitting the closest object from #objs.
Expects #dir to be normalised.
The hit object's distance, location, normal and type will be written in the
references so-aptly named. If nothing was hit, the contents of those variables is UB.
Returns the pointer to the object it hit, or `nullptr' otherwise.
Respects the initial value of #hit_dist, et al.
Optional parameter #self is a pointer to an object considered 'self' that
will be skipped during collision checks. The objects are compared by pointer
address.
*/
Renderable* shoot_ray_into_objects(const v3& eye, const v3& dir, const std::vector<Renderable*>& objs,
choice_t& hit_dist, v3& hit_loc, v3& hit_normal, int& hit_type,
Renderable* self = nullptr);
/**
Shoots a ray from #eye in (unit) direction #dir into a #scene and returns the
colour of that ray.
#scene must be #objects u #lights.
#depth is how much the ray tracer is allowed to recurse.
*/
v3 ray_trace(const v3& eye, const v3& dir,
const std::vector<Renderable*>& scene,
const std::vector<Renderable*>& objects,
const std::vector<Renderable*>& lights, int depth,
Renderable* skip = nullptr,
choice_t* dist_to_hit = nullptr);
choice_t ggx_ndf(const v3& dir, const v3& h, const v3& normal, choice_t roughness);
choice_t blinn_phong_ndf(const v3& dir, const v3& h, const v3& normal, choice_t roughness);
choice_t cook_torrance_gaf(const v3& dir, const v3& h, const v3& normal, const v3& light);
choice_t cook_torrance_fresnel(const v3& dir, const v3& h, choice_t ior);
v3 cook_torrance(const v3& dir, const v3& normal, const v3& light, choice_t specular, choice_t roughness,
choice_t ior, const v3& light_colour, const v3& surface_colour);

105
include/vec.hpp Normal file
View File

@ -0,0 +1,105 @@
#pragma once
#include <math.h>
typedef float choice_t;
#define do_op(o, r, i) \
inline void operator o##= (const r & rhs) { for(unsigned n=0; n<(i); n++) c[n] o##= rhs.c[n]; } \
inline void operator o##= (choice_t d) { for(unsigned n=0; n<(i); n++) c[n] o##= d; } \
inline r operator o (const r & rhs) const { r tmp(*this); tmp o##= rhs; return tmp; } \
inline r operator o (choice_t d) const { r tmp(*this); tmp o##= d; return tmp; }
#define do_ops(c, i) \
do_op(*, c, i); \
do_op(/, c, i); \
do_op(+, c, i); \
do_op(-, c, i);
struct v3 {
union {
choice_t c[4];
struct {
choice_t x, y, z, w;
};
};
v3(choice_t x, choice_t y, choice_t z) : c{x,y,z} {}
v3(choice_t f) : v3(f,f,f) {}
v3() : v3(0,0,0) {}
do_ops(v3, 3);
inline v3 operator-(void) const { return {-x, -y, -z}; }
inline choice_t operator^(const v3& rhs) const
{ return x * rhs.x + y * rhs.y + z * rhs.z; }
/* Return normalised (unit) vector. */
inline v3 norm() const
{ return *this / len(); }
inline choice_t len() const
{ return sqrtf(x*x + y*y + z*z); }
inline choice_t lensquared() const
{ return x*x + y*y + z*z; }
/* Return vector reflected around `n'. Expects `this' and `n' to be unit. */
inline v3 ref(const v3& n) const
{ return n * ((*this ^ n) * 2) - *this;}
/* Cross product. */
inline v3 cross(const v3& b) const
{ return { y*b.z - z*b.y,
x*b.z - z*b.x,
x*b.y - y*b.x }; }
inline v3 refract(const v3& n, choice_t ior_ratio) const
{
return n * sqrtf(1 - powf(ior_ratio, 2) * (1 - powf(*this ^ n, 2))) + (*this - n * (*this ^ n)) * ior_ratio;
}
inline v3 refract(const v3& n, choice_t ior_incident, choice_t ior_refract) const
{ return refract(n, ior_incident / ior_refract); }
inline v3 clamp(const v3& from, const v3& to) const
{ return { fmin(fmax(x, from.x), to.x),
fmin(fmax(y, from.y), to.y),
fmin(fmax(z, from.z), to.z) }; }
inline v3 exp() const
{ return { ::exp(x), ::exp(y), ::exp(z) }; }
inline v3 htan(choice_t exposure = 0) const
{
return { powf((tanh(x + exposure) + 1) / 2, 2.2), powf((tanh(y + exposure) + 1) / 2, 2.2), powf((tanh(z + exposure) + 1) / 2, 2.2) };
return { tanh(x + exposure), tanh(y + exposure), tanh(z + exposure) };
}
};
#undef do_ops
#undef do_op
#define V3_FMT "v3(%.2f, %.2f, %.2f)"
#define V3_ARG(v) (v).x, (v).y, (v).z
struct Matrix
{
v3 m[4];
void init_rotate(const v3& angle)
{
choice_t Cx = cos(angle.c[0]), Cy = cos(angle.c[1]), Cz = cos(angle.c[2]);
choice_t Sx = sin(angle.c[0]), Sy = sin(angle.c[1]), Sz = sin(angle.c[2]);
choice_t sxsz = Sx*Sz, cxsz = Cx*Sz;
choice_t cxcz = Cx*Cz, sxcz = Sx*Cz;
Matrix result = {{ { Cy*Cz, Cy*Sz, -Sy },
{ sxcz*Sy - cxsz, sxsz*Sy + cxcz, Sx*Cy },
{ cxcz*Sy + sxsz, cxsz*Sy - sxcz, Cx*Cy } }};
*this = result;
}
v3 transform(const v3& vec) const
{ return { m[0] ^ vec, m[1] ^ vec, m[2] ^ vec }; }
};

143
src/buffer.cpp Normal file
View File

@ -0,0 +1,143 @@
#include "../include/buffer.hpp"
#include <string>
void bg_fg(v3* cols, v3& bg, v3& fg, const char*& c)
{
// 0 0 0 0
// | | | |
// | | | \_ top left
// | | \_ top right
// | \_ bottom left
// \_ bottom right
static const char8_t* chars[16] = {
u8"", // 0000
u8"", // 0001
u8"", // 0010
u8"", // 0011
u8"", // 0100
u8"", // 0101
u8"", // 0110
u8"", // 0111
u8"", // 1000
u8"", // 1001
u8"", // 1010
u8"", // 1011
u8"", // 1100
u8"", // 1101
u8"", // 1110
u8"", // 1111
};
v3 ava = cols[0];
v3 avb = cols[1];
{
choice_t max = -1.0;
for (int i = 1; i < 4; ++i) {
choice_t len = (cols[i] - cols[0]).lensquared();
if (len > max) {
max = len;
avb = cols[i];
}
}
}
int m = 0b0000;
for (int i = 0; i < 4; ++i) {
v3 nava {};
v3 navb {};
int na = 0;
int nb = 0;
for (int j = 0; j < 4; ++j) {
float lena = (ava - cols[j]).len();
float lenb = (avb - cols[j]).len();
if (lena > lenb) {
m |= 1 << j;
nava += cols[j];
++na;
} else {
m &= ~(1 << j);
navb += cols[j];
++nb;
}
}
ava = nava / na;
avb = navb / nb;
}
bg = ava;
fg = avb;
c = (const char*) chars[m];
// c = (const char*) chars[15];
}
void display_frame(const Screen& screen, const Frame& frame)
{
char chr[64];
for (int y = 0; y < screen.H; y += 2) {
std::string text;
for (int x = 0; x < screen.W; x += 2) {
v3 cols[4] = {
frame.data[(y + 0) * screen.W + (x + 0)],
frame.data[(y + 0) * screen.W + (x + 1)],
frame.data[(y + 1) * screen.W + (x + 0)],
frame.data[(y + 1) * screen.W + (x + 1)]
};
const char* c;
v3 bg;
v3 fg;
bg_fg(cols, bg, fg, c);
snprintf(chr, 64, "\033[38;2;%d;%d;%dm\033[48;2;%d;%d;%dm%s",
colcvt(bg.x), colcvt(bg.y), colcvt(bg.z),
colcvt(fg.x), colcvt(fg.y), colcvt(fg.z),
c
);
text += chr;
}
printf("%.*s\033[0m\n", (int)text.size(), text.data());
}
}
void render_frame(const Screen& screen, Camera& camera, Frame& frame,
const std::vector<Renderable*>& scene,
const std::vector<Renderable*>& objects,
const std::vector<Renderable*>& lights)
{
Matrix m1; m1.init_rotate({camera.rotation.x,0,0});
Matrix m2; m2.init_rotate({0,camera.rotation.y,0});
// The translation vector tells us where to translate the camera,
// in 'camera tangent space'. So, we rotate the translation vector,
// add it to camera's position, and then reset it to 0.
camera.pos += m2.transform(m1.transform(camera.translation));
camera.translation = {};
#pragma omp parallel for
for(int y = 0; y < screen.H; y++) {
for (int x = 0; x < screen.W; x++) {
v3 camray = {
(choice_t)x / (choice_t)(screen.W) - 0.5f,
(choice_t)y / (choice_t)(screen.H) - 0.5f,
camera.zoom
};
camray.x *= screen.ASPECT_RATIO; // aspect ratio correction
camray = m2.transform(m1.transform(camray.norm()));
v3 pix = ray_trace(camera.pos, camray, scene, objects, lights, 3);
pix = pix.htan(camera.exposure);
if (pix.x == 0.0) {
frame.data[y * screen.W + x] = v3 {0.0, 0.0, 0.0};
} else {
frame.data[y * screen.W + x] = pix;
}
}
}
}

176
src/main.cpp Normal file
View File

@ -0,0 +1,176 @@
#include <string>
#include <chrono>
#include <iostream>
#include <vector>
#include <stdio.h>
#include "../include/objects.hpp"
#include "../include/buffer.hpp"
#include <termios.h>
#include <unistd.h>
#include <algorithm>
static std::vector<Renderable*> lights {
new Light(v3{0, 0, 19}, 2, v3{1, 1, 1}),
new Light(v3{-7, 15, 0}, 2, v3{1, 1, 1}),
new Light(v3{17, -9, 16}, 2, v3{1, 1, 1}),
// new Light(v3{0, 0, 19}, 2, v3{0.8, 0.8, 0.9}),
// new Light(v3{-7, 15, 0}, 2, v3{0.5, 0.8, 0.9}),
// new Light(v3{17, -9, 16}, 2, v3{0.1, 0.8, 0.4}),
};
static const Material PLANE_MAT = {
.specular = 0.12,
.roughness = 0.55,
.ior = 2,
.transparency = 0,
.colour = {1, 1, 1},
};
static const Material SPHERE_MAT_TRANS = {
.specular = 0.85,
.roughness = 0.5,
.ior = 1.2,
.transparency = 0.95,
.colour = {0, 1, 0},
};
static const Material SPHERE_MAT_METAL = {
.specular = 0.87,
.roughness = 0.53,
.ior = 2.3,
.transparency = 0.0,
.colour = {0, 0, 0},
};
static std::vector<Renderable*> objects
{
new Sphere(v3{10, 0, 6}, 3.0, SPHERE_MAT_TRANS, 1),
new Sphere(v3{2, -2, 6}, 3.0, SPHERE_MAT_METAL, 1),
new Plane{v3{1, 0, 0}, -30, PLANE_MAT},
new Plane{v3{-1, 0, 0}, -30, PLANE_MAT},
new Plane{v3{0, 1, 0}, -30, PLANE_MAT},
new Plane{v3{0, -1, 0}, -30, PLANE_MAT},
new Plane{v3{0, 0, 1}, -30, PLANE_MAT},
new Plane{v3{0, 0, -1}, -30, PLANE_MAT},
};
static std::vector<Renderable*> scene;
// static constexpr int W = 294*2, H = 82*2;
// static constexpr int W = 2 * 640 * 2.9 * 1.8, H = 2 * 480 * 1.5 * 2.5;
static constexpr int W = 2*80, H = 2*24;
static constexpr choice_t ASPECT_RATIO = W/(H * 1.9);
static constexpr Screen screen = {W, H, ASPECT_RATIO};
static Camera camera = { {15.31, -3.3, 7.72}, {0.56, 1.84, 0}, {} };
static inline void gotoxy(int x, int y)
{ printf("%c[%d;%dH",0x1B, (y+1), (x+1)); }
char getch()
{
char c;
// lseek(0, 0, SEEK_END);
read(0, &c, 1);
// tcflush(0, TCIFLUSH);
return c;
}
inline void clrscr() { printf("\x1B[2J\x1B[H"); }
int main()
{
termios old, current;
tcgetattr(0, &old);
current = old;
current.c_lflag &= ~ICANON & ~ECHO; /* disable buffered i/o, no echo */
current.c_cc[VMIN] = 1; // control chars (MIN value) = 1
current.c_cc[VTIME] = 0; // control chars (TIME value) = 0 (No time)
tcsetattr(0, TCSANOW, &current);
std::copy(objects.begin(), objects.end(), std::back_inserter(scene));
std::copy(lights.begin(), lights.end(), std::back_inserter(scene));
Frame frame;
frame.data = new v3[W * H];
printf("\e[?1049h");
// choice_t times = 0;
// int samples = 0;
char c='\n';
do {
// clrscr();
gotoxy(0,0);
switch(c) {
case 65: // Up
camera.rotation.x -= 0.08 * (0.5 / camera.zoom);
break;
case 66: // Down
camera.rotation.x += 0.08 * (0.5 / camera.zoom);
break;
case 67: // Right
camera.rotation.y -= 0.08 * (0.5 / camera.zoom);
break;
case 68: // Left
camera.rotation.y += 0.08 * (0.5 / camera.zoom);
break;
case 'a':
camera.translation.x -= 0.5;
break;
case 'd':
camera.translation.x += 0.5;;
break;
case 'w':
camera.translation.z += 0.5;;
break;
case 's':
camera.translation.z -= 0.5;;
break;
case ' ':
camera.translation.y -= 0.5;
break;
case 'z':
camera.translation.y += 0.5;
break;
case '+':
camera.exposure -= 0.015;
break;
case '-':
camera.exposure += 0.015;
break;
case 'i':
camera.zoom *= 1.015;
break;
case 'o':
camera.zoom /= 1.015;
break;
case 'p':
printf("pos: " V3_FMT "\nrot: " V3_FMT, V3_ARG(camera.pos), V3_ARG(camera.rotation));
break;
case '\n':
case 'q':
break;
default:
continue;
}
// const auto before = std::chrono::high_resolution_clock::now();
render_frame(screen, camera, frame, scene, objects, lights);
// const auto after = std::chrono::high_resolution_clock::now();
// times += std::chrono::duration<choice_t, std::milli>(after - before).count();
// samples++;
display_frame(screen, frame);
//std::cout << 1000/(times/samples) << " FPS" << std::endl;
} while((c = getch()) != 'q');
printf("\e[?1049l");
tcsetattr(0, TCSANOW, &old);
return 0;
}

70
src/objects.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "../include/objects.hpp"
/* Plane */
Plane::Plane(const v3& n, choice_t offset, const Material& specs, int surface_type)
: Renderable(specs), n(n), offset(offset), surface_type(surface_type) {}
Plane::Plane(const v3& n, choice_t offset, const Material& specs) : Plane(n, offset, specs, 2) {}
int Plane::intersect(const v3& eye, const v3& dir, choice_t& hit_dist, v3& hit_loc, v3& hit_normal)
{
const choice_t d = dir ^ n;
if(d <= 0) return 0;
const choice_t k = -((eye ^ n) + offset) / d;
hit_loc = eye + dir * k;
hit_normal = n;
hit_dist = (hit_loc - eye).len();
return surface_type;
}
/* Sphere */
Sphere::Sphere(const v3& origin, choice_t r, const Material& specs, int surface_type)
: Renderable(specs), o(origin), r(r), surface_type(surface_type) {}
Sphere::Sphere(const v3& origin, choice_t r) : Sphere(origin, r, Material(), 1) {}
template<int K = 1>
static inline int _sphere_intersect(Sphere& self, const v3& eye, const v3& dir, choice_t& hit_dist, v3& hit_loc, v3& hit_normal)
{
const v3 d = self.o - eye;
const choice_t dist_above_o = dir ^ d;
// We're facing directly away from the sphere
if(dist_above_o < 0) { return -1; }
// The ray is pointing directly at the center of the sphere
// if(fabs(dist_above_o - d.len()) < 1e-5) {
// hit_loc = eye + d.norm() * (d.len() - self.r);
// hit_normal = self.normal(hit_loc);
// hit_dist = (hit_loc - eye).len();
// return self.surface_type;
// }
const choice_t rejection = sqrtf(d.len() * d.len() - dist_above_o * dist_above_o);
// The ray is tangent/missing the sphere
if(rejection >= self.r) return -1;
choice_t inside = sqrt(self.r*self.r - rejection*rejection) * K;
hit_dist = dist_above_o - inside;
hit_loc = eye + dir * hit_dist;
hit_normal = self.normal(hit_loc);
return self.surface_type;
}
int Sphere::intersect(const v3& eye, const v3& dir, choice_t& hit_dist, v3& hit_loc, v3& hit_normal)
{ return _sphere_intersect(*this, eye, dir, hit_dist, hit_loc, hit_normal); }
int Sphere::transmit(const v3& eye, const v3& dir, choice_t& hit_dist, v3& hit_loc, v3& hit_normal)
{ return _sphere_intersect<-1>(*this, eye, dir, hit_dist, hit_loc, hit_normal); }
/* Light */
Light::Light(const v3& origin, choice_t r, const v3& colour) : Light(origin, r,
Material{ .specular = 0,
.roughness = 1,
.colour = colour }) {}
Light::Light(const v3& origin, choice_t r, const Material& specs) : Sphere(origin, r, specs, 0) {}

164
src/raytrace.cpp Normal file
View File

@ -0,0 +1,164 @@
#include "../include/raytrace.hpp"
#include "../include/objects.hpp"
#include <math.h>
#include <random>
Renderable::Renderable(const Material& specs) : specs(specs) {}
Renderable* shoot_ray_into_objects(const v3& eye, const v3& dir, const std::vector<Renderable*>& objs,
choice_t& hit_dist, v3& hit_loc, v3& hit_normal, int& hit_type,
Renderable* self)
{
hit_type = -1;
Renderable* hit_object = nullptr;
// Iterate across objects to find the closest hit
for(auto* r : objs) {
if(r == self) continue;
choice_t dist = 1e10;
v3 loc, normal;
int type = -1;
type = r->intersect(eye, dir, dist, loc, normal);
// Hit is closer than closest hit
if(type != -1 && dist < 1e9 && dist < hit_dist) {
hit_dist = dist; hit_loc = loc;
hit_normal = normal; hit_type = type;
hit_object = r;
}
}
return hit_object;
}
v3 ray_trace(const v3& eye, const v3& dir,
const std::vector<Renderable*>& scene,
const std::vector<Renderable*>& objects,
const std::vector<Renderable*>& lights, int depth,
Renderable* skip, choice_t* dist_to_hit)
{
choice_t hit_dist = 1e10;
v3 hit_loc, hit_normal;
int hit_type;
Renderable* hit_obj = shoot_ray_into_objects(eye, dir, scene, hit_dist, hit_loc, hit_normal, hit_type, skip);
if(dist_to_hit != nullptr) *dist_to_hit = hit_dist;
v3 colour = v3{0.05, 0.05, 0.05};
if (hit_type == 0) {
Light *l = (Light *)hit_obj;
const choice_t kDistance = powf((eye - l->o).norm() ^ hit_normal, 3);
colour += ((Light*)hit_obj)->specs.colour * 8 * kDistance;
if(depth > 0) colour += ray_trace(eye, dir, scene, objects, lights, depth - 1, l) * (1 - kDistance);
} else if (hit_type > 0) {
// Figure out which lights are visible from the hit location
choice_t dist;
v3 loc, normal;
v3 light_dir;
int type;
Light *l = nullptr;
for (auto* _l : lights) {
l = (Light *)_l;
const v3 to_light = l->o - hit_loc;
light_dir = to_light.norm();
dist = to_light.len();
shoot_ray_into_objects(hit_loc, light_dir, objects,
dist, loc, normal, type, hit_obj);
// We hit the light directly: shade the pixel.
if (type == -1) {
const choice_t kDistance = to_light.lensquared() / 1000;
colour += cook_torrance(dir, hit_normal, light_dir, hit_obj->specs.specular,
hit_obj->specs.roughness, hit_obj->specs.ior,
hit_obj->specs.colour, l->specs.colour)
* (1 - hit_obj->specs.transparency) / kDistance;
}
}
// Add the colour from the reflection, if we have enough depth
const choice_t kReflectionAtt = hit_obj->specs.specular * (1 - hit_obj->specs.roughness);
if(kReflectionAtt > 1e-4 && depth > 0) {
colour += ray_trace(hit_loc, -(dir).ref(hit_normal),
scene, objects, lights, depth-1)
* kReflectionAtt;
}
if (hit_obj->specs.transparency >= 1e-3 && depth > 0) {
choice_t dist = 1e10; v3 loc, normal, light_dir;
const v3 refract_dir = -dir.refract(hit_normal, 1/hit_obj->specs.ior);
if(hit_obj->transmit(hit_loc, refract_dir, dist, loc, normal) != -1) {
colour += ray_trace(loc, dir, scene, objects, lights, depth-1, hit_obj)
* hit_obj->specs.transparency * (hit_obj->specs.colour + 0.75).norm();
}
}
// // Random sample
// std::random_device rd;
// std::mt19937 gen(rd());
// std::uniform_real_distribution<> dis(0.0, 1.0);
// const v3 y = hit_normal.cross(dir);
// for(int i = 0; i < 2; ++i) {
// const v3 sample_dir = (hit_normal + dir * dis(gen) + y * dis(gen)).norm();
// choice_t dist = 1;
// const v3 hit_colour = ray_trace(hit_loc, sample_dir, scene, objects, lights, 0, hit_obj, &dist);
// colour += hit_colour / (dist * dist);
// }
}
return colour;
}
static constexpr inline choice_t chi(choice_t x) { return x > 0 ? 1 : 0; }
choice_t ggx_ndf(const v3& dir, const v3& h, const v3& normal, choice_t roughness)
{
const choice_t alpha_sq = powf(roughness, 2);
const choice_t mn_sq = powf(-dir ^ normal, 2);
// return alpha_sq * chi(h ^ normal) / (M_PI * powf((1 - mn_sq * (1 + alpha_sq)), 2));
return alpha_sq * chi(h ^ normal) / (M_PI * powf(mn_sq * (alpha_sq + (1 - mn_sq) / mn_sq), 2));
}
choice_t blinn_phong_ndf(const v3& dir, const v3& h, const v3& normal, choice_t roughness)
{
const choice_t alpha_sq = powf(roughness, 4);
return 1 / (M_PI * alpha_sq) * powf(normal ^ h, 2 / alpha_sq - 2);
}
choice_t ggx_gaf_partial(const v3& dir, const v3& normal, const v3& light)
{
}
choice_t ggx_gaf(const v3& dir, const v3& normal, const v3& light)
{
}
choice_t cook_torrance_gaf(const v3& dir, const v3& h, const v3& normal, const v3& light)
{
const choice_t fac = 2 * (h ^ normal) / (-dir ^ h);
return fmin(1, fmin(fac * (-dir ^ normal), fac * (light ^ normal)));
}
choice_t cook_torrance_fresnel(const v3& dir, const v3& h, choice_t ior)
{
const choice_t f_naught = powf(ior - 1, 2) / powf(ior + 1, 2);
return f_naught + (1 - f_naught) * powf(1 - (-dir ^ h), 5);
}
v3 cook_torrance(const v3& dir, const v3& normal, const v3& light, choice_t specular, choice_t roughness, choice_t ior,
const v3& light_colour, const v3& surface_colour)
{
const v3 h = (light - dir).norm();
const choice_t r_spec = ggx_ndf(dir, h, light, roughness) *
cook_torrance_gaf(dir, h, normal, light) * cook_torrance_fresnel(dir, h, ior) /
(4 * (normal ^ light) * (-dir ^ normal));
return (surface_colour * (1 - specular) / M_PI + light_colour * (specular * r_spec)) * fabs(normal ^ light);
}