diff --git a/tinyraytracer.cpp b/tinyraytracer.cpp index a6cf46f..96c89d9 100644 --- a/tinyraytracer.cpp +++ b/tinyraytracer.cpp @@ -1,15 +1,13 @@ -#include #include -#include #include +#include #include -#include #include struct vec3 { float x=0, y=0, z=0; - float& operator[](const size_t i) { assert(i<3); return i==0 ? x : (1==i ? y : z); } - const float& operator[](const size_t i) const { assert(i<3); return i==0 ? x : (1==i ? y : z); } + float& operator[](const int i) { return i==0 ? x : (1==i ? y : z); } + const float& operator[](const int i) const { return i==0 ? x : (1==i ? y : z); } vec3 operator*(const float v) const { return {x*v, y*v, z*v}; } float operator*(const vec3& v) const { return x*v.x + y*v.y + z*v.z; } vec3 operator+(const vec3& v) const { return {x+v.x, y+v.y, z+v.z}; } @@ -23,14 +21,9 @@ vec3 cross(const vec3 v1, const vec3 v2) { return { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; } -struct Light { - vec3 position; - float intensity; -}; - struct Material { float refractive_index = 1; - float albedo[4] = {1,0,0,0}; + float albedo[4] = {2,0,0,0}; vec3 diffuse_color = {0,0,0}; float specular_exponent = 0; }; @@ -41,36 +34,24 @@ struct Sphere { Material material; }; -static const Material ivory = {1.0, {0.6, 0.3, 0.1, 0.0}, {0.4, 0.4, 0.3}, 50.}; -static const Material glass = {1.5, {0.0, 0.5, 0.1, 0.8}, {0.6, 0.7, 0.8}, 125.}; -static const Material red_rubber = {1.0, {0.9, 0.1, 0.0, 0.0}, {0.3, 0.1, 0.1}, 10.}; -static const Material mirror = {1.0, {0.0, 10.0, 0.8, 0.0}, {1.0, 1.0, 1.0}, 1425.}; +static const Material ivory = {1.0, {0.9, 0.5, 0.1, 0.0}, {0.4, 0.4, 0.3}, 50.}; +static const Material glass = {1.5, {0.0, 0.9, 0.1, 0.8}, {0.6, 0.7, 0.8}, 125.}; +static const Material red_rubber = {1.0, {1.4, 0.3, 0.0, 0.0}, {0.3, 0.1, 0.1}, 10.}; +static const Material mirror = {1.0, {0.0, 16.0, 0.8, 0.0}, {1.0, 1.0, 1.0}, 1425.}; -static const std::vector spheres = { - Sphere{vec3{-3, 0, -16}, 2, ivory}, - Sphere{vec3{-1.0, -1.5, -12}, 2, glass}, - Sphere{vec3{ 1.5, -0.5, -18}, 3, red_rubber}, - Sphere{vec3{ 7, 5, -18}, 4, mirror} +static const Sphere spheres[] = { + {{-3, 0, -16}, 2, ivory}, + {{-1.0, -1.5, -12}, 2, glass}, + {{ 1.5, -0.5, -18}, 3, red_rubber}, + {{ 7, 5, -18}, 4, mirror} }; -static const std::vector lights = { - {{-20, 20, 20}, 1.5}, - {{ 30, 50, -25}, 1.8}, - {{ 30, 20, 30}, 1.7} +static const vec3 lights[] = { + {-20, 20, 20}, + { 30, 50, -25}, + { 30, 20, 30} }; -std::tuple ray_sphere_intersect(const vec3 &orig, const vec3 &dir, const Sphere &s) { // ret value is a pair [intersection found, distance] - vec3 L = s.center - orig; - float tca = L*dir; - float d2 = L*L - tca*tca; - if (d2 > s.radius*s.radius) return {false, 0.f}; - float thc = std::sqrt(s.radius*s.radius - d2); - float t0 = tca-thc, t1 = tca+thc; - if (t0>1e-3) return {true, t0}; // offset the original point by 1e-3 to avoid occlusion by the object itself - if (t1>1e-3) return {true, t1}; - return {false, 0.f}; -} - vec3 reflect(const vec3 &I, const vec3 &N) { return I - N*2.f*(I*N); } @@ -83,37 +64,49 @@ vec3 refract(const vec3 &I, const vec3 &N, const float eta_t, const float eta_i= return k<0 ? vec3{1,0,0} : I*eta + N*(eta*cosi - std::sqrt(k)); // k<0 = total reflection, no ray to refract. I refract it anyways, this has no physical meaning } -bool scene_intersect(const vec3 &orig, const vec3 &dir, vec3 &hit, vec3 &N, Material &material) { // TODO: move output values to a return tuple - float spheres_dist = std::numeric_limits::max(); - for (const Sphere &s : spheres) { - if (auto [intersection, dist] = ray_sphere_intersect(orig, dir, s); intersection && dist < spheres_dist) { - spheres_dist = dist; - hit = orig + dir*dist; - N = (hit - s.center).normalized(); - material = s.material; - } - } - - float checkerboard_dist = std::numeric_limits::max(); - if (std::abs(dir.y)>1e-3) { // avoid division by zero - float d = -(orig.y+4)/dir.y; // the checkerboard plane has equation y = -4 - vec3 pt = orig + dir*d; - if (d>1e-3 && fabs(pt.x)<10 && pt.z<-10 && pt.z>-30 && d ray_sphere_intersect(const vec3 &orig, const vec3 &dir, const Sphere &s) { // ret value is a pair [intersection found, distance] + vec3 L = s.center - orig; + float tca = L*dir; + float d2 = L*L - tca*tca; + if (d2 > s.radius*s.radius) return {false, 0}; + float thc = std::sqrt(s.radius*s.radius - d2); + float t0 = tca-thc, t1 = tca+thc; + if (t0>.001) return {true, t0}; // offset the original point by .001 to avoid occlusion by the object itself + if (t1>.001) return {true, t1}; + return {false, 0}; } -vec3 cast_ray(const vec3 &orig, const vec3 &dir, size_t depth=0) { - vec3 point, N; +std::tuple scene_intersect(const vec3 &orig, const vec3 &dir) { + vec3 pt, N; Material material; - if (depth>4 || !scene_intersect(orig, dir, point, N, material)) - return vec3{0.2, 0.7, 0.8}; // background color + float nearest_dist = 1e10; + if (std::abs(dir.y)>.001) { // intersect the ray with the checkerboard, avoid division by zero + float d = -(orig.y+4)/dir.y; // the checkerboard plane has equation y = -4 + vec3 p = orig + dir*d; + if (d>.001 && d-30) { + nearest_dist = d; + pt = p; + N = {0,1,0}; + material.diffuse_color = (int(.5*pt.x+1000) + int(.5*pt.z)) & 1 ? vec3{.3, .3, .3} : vec3{.3, .2, .1}; + } + } + + for (const Sphere &s : spheres) { // intersect the ray with all spheres + auto [intersection, d] = ray_sphere_intersect(orig, dir, s); + if (!intersection || d > nearest_dist) continue; + nearest_dist = d; + pt = orig + dir*nearest_dist; + N = (pt - s.center).normalized(); + material = s.material; + } + return {nearest_dist<1000, pt, N, material}; +} + +vec3 cast_ray(const vec3 &orig, const vec3 &dir, int depth=0) { + auto [hit, point, N, material] = scene_intersect(orig, dir); + if (depth>4 || !hit) + return {0.2, 0.7, 0.8}; // background color vec3 reflect_dir = reflect(dir, N).normalized(); vec3 refract_dir = refract(dir, N, material.refractive_index).normalized(); @@ -121,17 +114,12 @@ vec3 cast_ray(const vec3 &orig, const vec3 &dir, size_t depth=0) { vec3 refract_color = cast_ray(point, refract_dir, depth + 1); float diffuse_light_intensity = 0, specular_light_intensity = 0; - for (const Light light : lights) { - vec3 light_dir = (light.position - point).normalized(); - - vec3 shadow_pt, trashnrm; - Material trashmat; - if (scene_intersect(point, light_dir, shadow_pt, trashnrm, trashmat) && - (shadow_pt-point).norm() < (light.position-point).norm()) // checking if the point lies in the shadow of the light - continue; - - diffuse_light_intensity += light.intensity * std::max(0.f, light_dir*N); - specular_light_intensity += std::pow(std::max(0.f, -reflect(-light_dir, N)*dir), material.specular_exponent)*light.intensity; + for (const vec3 light : lights) { // checking if the point lies in the shadow of the light + vec3 light_dir = (light - point).normalized(); + auto [hit, shadow_pt, trashnrm, trashmat] = scene_intersect(point, light_dir); + if (hit && (shadow_pt-point).norm() < (light-point).norm()) continue; + diffuse_light_intensity += std::max(0.f, light_dir*N); + specular_light_intensity += std::pow(std::max(0.f, -reflect(-light_dir, N)*dir), material.specular_exponent); } return material.diffuse_color * diffuse_light_intensity * material.albedo[0] + vec3{1., 1., 1.}*specular_light_intensity * material.albedo[1] + reflect_color*material.albedo[2] + refract_color*material.albedo[3]; } @@ -139,11 +127,11 @@ vec3 cast_ray(const vec3 &orig, const vec3 &dir, size_t depth=0) { int main() { const int width = 1024; const int height = 768; - const float fov = M_PI/3.; + const float fov = 1.05; // 60 degrees field of view in radians std::vector framebuffer(width*height); #pragma omp parallel for - for (size_t j = 0; j1) c = c*(1./max); - ofs << (char)(255 * c[0]) << (char)(255 * c[1]) << (char)(255 * c[2]); + for (vec3 &color : framebuffer) { + float max = std::max(1.f, std::max(color[0], std::max(color[1], color[2]))); + for (int chan : {0,1,2}) + ofs << (char)(255 * color[chan]/max); } ofs.close(); return 0;