/// 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();
/// Types
//// Ray
struct Ray
{
vec3 origin;
vec3 direction;
float range;
};
//// Trace
struct Trace
{
float distance;
explicit operator bool() const
{
return distance < infinity;
}
};
//// Sphere
struct Sphere
{
vec3 center;
float radius;
Trace trace(Ray const & ray) const
{
auto const offs = center - ray.origin;
auto const proj = dot(offs, ray.direction);
if (proj < 0.0F)
return {infinity}; // Past.
auto const perp2 = dot(offs, offs) - proj * proj;
auto const over2 = radius * radius - perp2;
if (over2 < 0.0F)
return {infinity}; // Miss.
auto const dist = proj - sqrt(over2);
if (dist < 0.0F)
return {infinity}; // Inside.
return {dist}; // Hit.
}
};
//// Shapes
using Shapes = std::vector<Sphere>;
//// Scene
struct Scene
{
Shapes shapes;
Trace trace(Ray const & ray) const
{
auto nearest = Trace{infinity};
for (auto const & shape : shapes)
{
auto const trace = shape.trace(ray);
if (trace.distance < min(nearest.distance, ray.range))
nearest = trace;
}
return nearest;
}
};
//// Camera
struct Camera
{
float near;
float far;
uvec2 size;
Ray ray(vec4 const & frag_coord) const
{
auto const point = vec3(
(2.0F * vec2(frag_coord.xy) - vec2(size)) / float(size.y),
-1.0F
);
auto const origin = near * point;
auto const stop = far * point;
auto const range = length(stop - origin);
auto const direction = (stop - origin) / range;
return Ray{origin, direction, range};
}
};
//// 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>
void render(Shader const & shader)
{
auto const begin = uvec2(0);
auto const end = size;
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;
auto const frag_coord = vec4(vec2(x, y) + 0.5F, 0.0F, 1.0F);
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())
);
}
};
/// 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);
auto framebuffer = Framebuffer(size);
auto camera = Camera{
1.0F, // near
100.0F, // far
size, // size
};
auto scene = Scene{
{ // shapes
Sphere{{ 6.0F, -4.0F, -24.0F}, 12.0F},
Sphere{{ 2.0F, 6.0F, -16.0F}, 8.0F},
Sphere{{ -4.0F, 0.0F, -10.0F}, 4.0F},
Sphere{{ 0.0F, -4.0F, -6.0F}, 1.0F},
Sphere{{ -1.1F, 0.8F, -1.1F}, 0.2F},
Sphere{{-110.0F, -80.0F, -110.0F}, 20.0F},
},
};
//// Render
framebuffer.render([&](vec4 const & frag_coord)
{
auto const ray = camera.ray(frag_coord);
auto const trace = scene.trace(ray);
auto const rgb = vec3(1.0F / (1.0F + trace.distance));
auto const alpha = (bool)trace;
return vec4(rgb, alpha);
});
//// Finalize
framebuffer.write_tga(path);
return EXIT_SUCCESS;
}