/// 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(); auto const delta = 0.001F; /// Types //// Ray struct Ray { vec3 origin; vec3 direction; float range; vec3 point(float distance) const { return origin + direction * distance; } Ray resumed(float distance) const { distance += delta; return {point(distance), direction, range - distance}; } }; //// 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 { 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 { 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])); 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(uvec4 viewport, vec2 depth_range, Shader const & shader) { 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]); 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, z, 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()) ); } }; /// 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)); } /// 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 aspect = (float)size.x / (float)size.y; auto viewport = uvec4(uvec2(0), size); auto depth_range = vec2(0.0F, 1.0F); auto framebuffer = Framebuffer(size); 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) ); 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(viewport, depth_range, [&](vec4 const & frag_coord) { auto ray = camera.ray(frag_coord, depth_range); auto trace = Trace{}; auto traces = 0; while ((trace = scene.trace(ray))) { ++traces; ray = ray.resumed(trace.distance); } auto const rgb = vec3(1.0F / (1.0F + (float)traces)); auto const alpha = (bool)traces; return vec4(rgb, alpha); }); //// Finalize framebuffer.write_tga(path); return EXIT_SUCCESS; }