src/raytracer.cpp
8364786c
 /// Includes
 
 #include <array>
 #include <cstdlib>
 #include <fstream>
 #include <iostream>
 #include <limits>
 #include <vector>
 
 #define GLM_FORCE_SWIZZLE
 #define GLM_FORCE_INTRINSICS // For `GLM_SWIZZLE_OPERATOR`.
 #include <glm/glm.hpp>
 // #include <glm/gtx/io.hpp> // For `operator<<(std::ostream &)`.
 
 /// Namespaces
 
 using namespace glm;
 
 /// Constants
 
 auto const infinity = std::numeric_limits<float>::infinity();
86e4eca2
 auto const delta    = 0.001F;
8364786c
 
 /// Types
 
 //// Ray
 struct Ray
 {
5590d623
     vec3  origin;
     vec3  direction;
     float range;
86e4eca2
     vec3 point(float distance) const
     {
         return origin + direction * distance;
     }
     Ray resumed(float distance) const
     {
         distance += delta;
         return {point(distance), direction, range - distance};
     }
8364786c
 };
 
d52cb1d2
 //// Material
 struct Material
 {
     float alpha;
 };
 
8364786c
 //// Trace
 struct Trace
 {
d52cb1d2
     float    distance;
     Material material;
8364786c
     explicit operator bool() const
     {
         return distance < infinity;
     }
 };
 
 //// Sphere
 struct Sphere
 {
d52cb1d2
     vec3     center;
     float    radius;
     Material material;
8364786c
     Trace trace(Ray const & ray) const
     {
         auto const offs = center - ray.origin;
         auto const proj = dot(offs, ray.direction);
         if (proj < 0.0F)
d52cb1d2
             return {infinity, {}}; // Past.
8364786c
         auto const perp2 = dot(offs, offs) - proj * proj;
         auto const over2 = radius * radius - perp2;
         if (over2 < 0.0F)
d52cb1d2
             return {infinity, {}}; // Miss.
8364786c
         auto const dist = proj - sqrt(over2);
         if (dist < 0.0F)
d52cb1d2
             return {infinity, {}}; // Inside.
         return {dist, material}; // Hit.
8364786c
     }
 };
 
 //// Shapes
 using Shapes = std::vector<Sphere>;
 
 //// Scene
 struct Scene
 {
     Shapes shapes;
     Trace trace(Ray const & ray) const
     {
d52cb1d2
         auto nearest = Trace{infinity, {}};
8364786c
         for (auto const & shape : shapes)
         {
             auto const trace = shape.trace(ray);
5590d623
             if (trace.distance < min(nearest.distance, ray.range))
8364786c
                 nearest = trace;
         }
         return nearest;
     }
 };
 
 //// Camera
 struct Camera
 {
07a62564
     mat4 camera_inverse;
     explicit Camera(
         mat4 const & view_,
         mat4 const & projection_,
         mat4 const & window_
     )
     :
         camera_inverse{inverse(window_ * projection_ * view_)}
     {}
     vec3 unproject(vec3 const & frag_coord) const
8364786c
     {
07a62564
             auto const world_coord = camera_inverse * vec4(frag_coord, 1.0F);
             return vec3(world_coord) / world_coord.w;
     }
     Ray ray(vec4 const & frag_coord, vec2 const & depth_range) const
     {
         auto const origin    = unproject(vec3(frag_coord.xy, depth_range[0]));
         auto const stop      = unproject(vec3(frag_coord.xy, depth_range[1]));
5590d623
         auto const range     = length(stop - origin);
         auto const direction = (stop - origin) / range;
         return Ray{origin, direction, range};
8364786c
     }
 };
 
 //// Framebuffer
 struct Framebuffer
 {
     uvec2               size;
     std::vector<u8vec4> color;
     explicit Framebuffer(uvec2 const & size_)
     :
         size{size_},
         color((std::size_t)(size_.x * size_.y))
     {}
     template<typename Shader>
07a62564
     void render(uvec4 viewport, vec2 depth_range, Shader const & shader)
8364786c
     {
07a62564
         auto const begin = max(uvec2(0),    uvec2(viewport.xy));
         auto const end   = min(uvec2(size), uvec2(viewport.xy + viewport.zw));
         auto const z     = 0.5F * (depth_range[0] + depth_range[1]);
8364786c
         for (auto y = begin.y; y < end.y; ++y)
         for (auto x = begin.x; x < end.x; ++x)
         {
             auto const index      = size.x * y + x;
07a62564
             auto const frag_coord = vec4(vec2(x, y) + 0.5F, z, 1.0F);
8364786c
             auto const frag_color = vec4(shader(frag_coord).bgra);
             color[index] = clamp(frag_color, 0.0F, 1.0F) * 255.0F + 0.5F;
         }
     }
     void write_tga(char const * path) const
     {
         auto header = std::array<uint8_t, 18>{
             0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             (uint8_t)(size.x >> 0U),
             (uint8_t)(size.x >> 8U),
             (uint8_t)(size.y >> 0U),
             (uint8_t)(size.y >> 8U),
             32, 0,
         };
         auto ostream = std::ofstream(path, std::ios::binary);
         write(ostream, header);
         write(ostream, color);
     }
     template<typename Collection>
     void write(std::ostream & ostream, Collection const & collection) const
     {
         ostream.write(
             (char const *)            collection.data(),
             (std::streamsize)(sizeof(*collection.data()) * collection.size())
         );
     }
 };
 
07a62564
 /// Transformations
 
 //// window
 mat4 window(uvec4 viewport, vec2 depth_range)
 {
     auto const size = vec3(uvec2(viewport.zw), depth_range[1]-depth_range[0]);
     auto const offs = vec3(uvec2(viewport.xy),                depth_range[0]);
     auto const s = 0.5F * size;
     auto const t = 0.5F * size + offs;
     auto const S = matrixCompMult(mat3(1.0F), outerProduct(vec3(1.0F), s));
     return mat4(mat4x3(S[0], S[1], S[2], t));
 }
 
 //// ortho
 mat4 ortho(float left, float right, float bottom, float top, float near, float far)
 {
     auto const size = vec3(right-left, top-bottom, far-near);
     auto const offs = vec3(     -left,    -bottom,    +near);
     auto const s = vec3(2.0F, 2.0F, -2.0F) / size;
     auto const t = s * offs - 1.0F;
     auto const S = matrixCompMult(mat3(1.0F), outerProduct(vec3(1.0F), s));
     return mat4(mat4x3(S[0], S[1], S[2], t));
 }
 
 //// frustum
 mat4 frustum(float left, float right, float bottom, float top, float near, float far)
 {
     auto const z = vec4(0.0F, 0.0F, near+far, -1.0F);
     auto const w = vec4(0.0F, 0.0F, near*far,  0.0F);
     auto const S = mat4(near);
     return ortho(left, right, bottom, top, near, far) * mat4(S[0], S[1], z, w);
 }
 
 //// perspective
 mat4 perspective(float fovy, float aspect, float near, float far)
 {
     auto const y = near * tan(0.5F * radians(fovy));
     auto const x = y * aspect;
     return frustum(-x, +x, -y, +y, near, far);
 }
 
 //// lookat
 mat4 lookat(vec3 position, vec3 target, vec3 up)
 {
     auto const z = normalize(position - target);
     auto const x = normalize(cross(up, z));
     auto const y = cross(z, x);
     auto const R_inv = transpose(mat3(x, y, z));
     auto const t_inv = -position;
     return mat4(mat4x3(R_inv[0], R_inv[1], R_inv[2], R_inv * t_inv));
 }
 
8364786c
 /// Main
 
 int main(int argc, char const * argv[])
 {
     //// Arguments
     if (argc != 2)
     {
         std::cerr << "Usage: raytracer <path>" << std::endl;
         return EXIT_FAILURE;
     }
     auto const * path = argv[1]; // NOLINT
     //// Configure
     auto size        = uvec2(640, 480);
07a62564
     auto aspect      = (float)size.x / (float)size.y;
     auto viewport    = uvec4(uvec2(0), size);
     auto depth_range = vec2(0.0F, 1.0F);
8364786c
     auto framebuffer = Framebuffer(size);
07a62564
     auto camera = Camera(
         lookat( // view
             {0.0F, 0.0F,  0.0F}, // position
             {0.0F, 0.0F, -1.0F}, // target
             {0.0F, 1.0F,  0.0F}  // up
         ),
         perspective( // projection
             90.0F,  // fovy
             aspect, // aspect
             1.0F,   // near
             100.0F  // far
         ),
         window(viewport, depth_range)
     );
5590d623
     auto scene = Scene{
8364786c
         { // shapes
d52cb1d2
             Sphere{{   6.0F,  -4.0F,  -24.0F}, 12.0F, 1.0F},
             Sphere{{   2.0F,   6.0F,  -16.0F},  8.0F, 1.0F},
             Sphere{{  -4.0F,   0.0F,  -10.0F},  4.0F, 1.0F},
             Sphere{{   0.0F,  -4.0F,   -6.0F},  1.0F, 0.5F},
             Sphere{{  -1.1F,   0.8F,   -1.1F},  0.2F, 1.0F},
             Sphere{{-110.0F, -80.0F, -110.0F}, 20.0F, 1.0F},
8364786c
         },
     };
     //// Render
07a62564
     framebuffer.render(viewport, depth_range, [&](vec4 const & frag_coord)
8364786c
     {
86e4eca2
         auto ray    = camera.ray(frag_coord, depth_range);
         auto trace  = Trace{};
         auto traces = 0;
d52cb1d2
         auto trans  = 1.0F;
         while (trans != 0.0F && (trace = scene.trace(ray)))
86e4eca2
         {
             ++traces;
d52cb1d2
             trans *= 1.0F - trace.material.alpha;
86e4eca2
             ray = ray.resumed(trace.distance);
         }
         auto const rgb   = vec3(1.0F / (1.0F + (float)traces));
d52cb1d2
         auto const alpha = 1.0F - trans;
8364786c
         return vec4(rgb, alpha);
     });
     //// Finalize
     framebuffer.write_tga(path);
     return EXIT_SUCCESS;
 }