src/raytracer.cpp
0ebf47e0
 /// Includes
 
 #include <array>
 #include <fstream>
 #include <iostream>
 #include <limits>
 #include <string>
 #include <vector>
 
 #define GLM_FORCE_SWIZZLE
 #include <glm/glm.hpp>
 #include <glm/fwd.hpp>
 #include <glm/ext/matrix_transform.hpp>  // https://glm.g-truc.net/0.9.9/api/a00247.html
 #include <glm/ext/matrix_clip_space.hpp> // https://glm.g-truc.net/0.9.9/api/a00243.html
 #include <glm/ext/matrix_projection.hpp> // https://glm.g-truc.net/0.9.9/api/a00245.html
 #include <glm/gtc/color_space.hpp>       // https://glm.g-truc.net/0.9.9/api/a00289.html
 // #include <glm/gtx/io.hpp>                // https://glm.g-truc.net/0.9.9/api/a00332.html
 
 
 /// Namespaces
 
 using namespace glm;
 
 
 /// Constants
 
 auto const infinity = std::numeric_limits<float>::infinity();
cf699a09
 auto const delta    = 0.001F;
0ebf47e0
 
 
 /// Types
 
 //// Ray
 struct Ray
 {
     vec3 origin;
     vec3 direction;
cf699a09
     vec3 point(float distance) const
     {
         return origin + direction * distance;
     }
     Ray resumed(float distance)
     {
         return {point(distance), direction};
     }
0ebf47e0
 };
 
5d4183a9
 //// Material
 struct Material
 {
cf699a09
     vec4 color;
5d4183a9
 };
 
0ebf47e0
 //// Trace
 struct Trace
 {
5d4183a9
     float    distance;
     Material material;
178b953d
     vec3     miss;
0ebf47e0
 };
 
 //// Circle
 struct Circle
 {
5d4183a9
     vec3     center;
     float    radius;
     Material material;
0ebf47e0
     Trace trace(Ray const & ray) const
     {
         auto const offs = center - ray.origin;
         auto const proj = dot(offs, ray.direction);
         if (proj < 0.0F) // Past.
178b953d
             return {infinity, {}, {}};
0ebf47e0
         auto const perp2 = dot(offs, offs) - proj * proj;
         auto const pene2 = radius * radius - perp2;
         if (pene2 < 0.0F) // Miss.
178b953d
         {
             auto const poin = ray.point(proj);
             auto const rati = 1.0F - radius * inversesqrt(perp2);
             auto const miss = rati * (center - poin);
             return {proj, material, miss};
         }
0ebf47e0
         auto const dist = proj - sqrt(pene2);
         if (dist < 0.0F) // Inside.
178b953d
             return {infinity, {}, {}};
         return {dist, material, vec3(0.0F)};
0ebf47e0
     }
 };
 
 //// Shapes
 using Shapes = std::vector<Circle>;
 
 //// Scene
 struct Scene
 {
5d4183a9
     Material background;
     Shapes   shapes;
0ebf47e0
     Trace trace(Ray const & ray) const
     {
178b953d
         auto nearest = Trace{infinity, background, {}};
0ebf47e0
         for (auto const & shape : shapes)
         {
             auto const trace = shape.trace(ray);
             if (trace.distance < nearest.distance)
                 nearest = trace;
         }
         return nearest;
     }
 };
 
 //// Camera
 struct Camera
 {
     vec3  position;
     vec3  target;
     float fovy;
     float near;
     float far;
     Ray ray(uvec2 const & size, uvec2 const & pixel) const
     {
         auto const aspect    = (float)size.x / (float)size.y;
         auto const viewport  = uvec4(0, 0, size);
         auto const window    = vec3(vec2(pixel) + 0.5F, 0.0F);
         auto const up        = vec3(0.0F, 1.0F, 0.0F);
         auto const view      = glm::lookAt(position, target, up);
         auto const proj      = glm::perspective(fovy, aspect, near, far);
         auto const world     = glm::unProject(window, view, proj, viewport);
         auto const direction = normalize(world.xyz() - position);
         return Ray{position, direction};
     }
178b953d
     vec2 project(uvec2 const & size, vec3 const & point) const
     {
         auto const aspect   = (float)size.x / (float)size.y;
         auto const viewport = uvec4(0, 0, size);
         auto const up       = vec3(0.0F, 1.0F, 0.0F);
         auto const view     = glm::lookAt(position, target, up);
         auto const proj     = glm::perspective(fovy, aspect, near, far);
         auto const window   = glm::project(point, view, proj, viewport);
         return window.xy();
     }
0ebf47e0
 };
 
 //// ACES
 struct ACES
 {
     vec3 whitepoint;
     vec3 transform(vec3 color) const
     {
         color /= whitepoint;
         return
             (color * (2.51F * color + 0.03F)) /
             (color * (2.43F * color + 0.59F) + 0.14F);
     }
 };
 
 //// TGA
 struct TGA
 {
     uvec2       size;
     std::string path;
     template<typename ColorFunc>
     void render(ColorFunc const & color_func) const
     {
         auto frame_size = size.x * size.y;
         auto frame      = std::vector<u8vec4>(frame_size);
         auto header     = std::array<u8, 18>{
             0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
             (u8)(size.x >> 0U),
             (u8)(size.x >> 8U),
             (u8)(size.y >> 0U),
             (u8)(size.y >> 8U),
             32, 0,
         };
         for (auto y = 0U; y < size.y; ++y)
         {
             for (auto x = 0U; x < size.x; ++x)
             {
                 auto const color = color_func(size, uvec2(x, y));
                 auto const index = y * size.x + x;
                 frame[index] = clamp(color.bgra(), 0.0F, 1.0F) * 255.0F + 0.5F;
             }
         }
         auto ostream = std::ofstream(path, std::ios::binary);
         write(ostream, header);
         write(ostream, frame);
     }
     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())
         );
     }
 };
 
 
 /// Main
 
 int main(int argc, char const * argv[])
 {
     //// Arguments
     if (argc != 2)
     {
         std::cerr << "Usage: raytrace <path>" << std::endl;
         return 0;
     }
     auto const * path = argv[1]; // NOLINT
     //// Configure
     auto const camera = Camera{
         vec3(0.0F, 0.0F,  0.0F), // position
         vec3(0.0F, 0.0F, -1.0F), // target
         radians(45.0F),          // fovy
         0.01F,                   // near
         100.0F,                  // far
     };
     auto const scene = Scene{
5d4183a9
         // background
cf699a09
         {vec4(0.2F, 0.2F, 0.5F, 0.5F)},
0ebf47e0
         // shapes
         {
cf699a09
             {vec3( 2.0F, -1.0F, -10.0F), 3.0F, {vec4(1.0F, 0.0F, 0.0F, 1.0F)}},
             {vec3( 1.0F,  2.0F,  -7.0F), 2.0F, {vec4(0.0F, 1.0F, 0.0F, 1.0F)}},
             {vec3(-1.0F,  0.0F,  -4.0F), 1.0F, {vec4(0.0F, 0.0F, 1.0F, 1.0F)}},
             {vec3( 0.0F, -1.0F,  -3.0F), 0.2F, {vec4(0.0F, 1.0F, 1.0F, 0.5F)}},
0ebf47e0
         },
     };
     auto const tonemap = ACES{vec3(1.0F)};
     auto const output  = TGA{uvec2(640, 480), path};
     //// Render
     output.render([&](uvec2 const & size, uvec2 const & pixel) {
cf699a09
         auto rgb   = vec3(0.0F); // Pre-multiplied alpha (opacity).
         auto trans = 1.0F;       // 1 - alpha (opacity).
         auto ray   = camera.ray(size, pixel);
         while (true)
         {
             auto trace = scene.trace(ray);
             auto color = trace.material.color;
178b953d
             if (trace.miss != vec3(0.0F))
             {
                 auto const point = ray.point(trace.distance);
                 auto const dist  = distance(
                     camera.project(size, point),
                     camera.project(size, point + trace.miss)
                 );
                 color.a *= 1.0F - clamp(dist, 0.0F, 1.0F);
             }
cf699a09
             rgb   += color.rgb() * color.a * trans;
             trans *= 1.0F - color.a;
             if (trace.distance == infinity || trans == 0.0F)
                 break;
             ray = ray.resumed(trace.distance + delta);
         }
         auto const alpha = 1.0F - trans;
         auto const srgb  = convertLinearToSRGB(tonemap.transform(rgb / alpha));
0ebf47e0
         return vec4(srgb, alpha);
     });
 }