/// 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(); auto const delta = 0.001F; /// Types //// Ray struct Ray { vec3 origin; vec3 direction; vec3 point(float distance) const { return origin + direction * distance; } Ray resumed(float distance) { return {point(distance), direction}; } }; //// Material struct Material { vec4 color; }; //// Trace struct Trace { float distance; Material material; vec3 miss; }; //// Circle struct Circle { vec3 center; float radius; Material material; Trace trace(Ray const & ray) const { auto const offs = center - ray.origin; auto const proj = dot(offs, ray.direction); if (proj < 0.0F) // Past. return {infinity, {}, {}}; auto const perp2 = dot(offs, offs) - proj * proj; auto const pene2 = radius * radius - perp2; if (pene2 < 0.0F) // Miss. { auto const poin = ray.point(proj); auto const rati = 1.0F - radius * inversesqrt(perp2); auto const miss = rati * (center - poin); return {proj, material, miss}; } auto const dist = proj - sqrt(pene2); if (dist < 0.0F) // Inside. return {infinity, {}, {}}; return {dist, material, vec3(0.0F)}; } }; //// Shapes using Shapes = std::vector<Circle>; //// Scene struct Scene { Material background; Shapes shapes; Trace trace(Ray const & ray) const { auto nearest = Trace{infinity, background, {}}; 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}; } 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(); } }; //// 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{ // background {vec4(0.2F, 0.2F, 0.5F, 0.5F)}, // shapes { {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)}}, }, }; auto const tonemap = ACES{vec3(1.0F)}; auto const output = TGA{uvec2(640, 480), path}; //// Render output.render([&](uvec2 const & size, uvec2 const & pixel) { 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; 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); } 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)); return vec4(srgb, alpha); }); }