|
| 1 | +// Idiomatic C++ port of rtbench.ts: Vector/Color are plain value structs, things are |
| 2 | +// std::unique_ptr<Thing> with virtual methods, surfaces hold function pointers. |
| 3 | +#include <cmath> |
| 4 | +#include <cstdio> |
| 5 | +#include <cstdlib> |
| 6 | +#include <limits> |
| 7 | +#include <memory> |
| 8 | +#include <optional> |
| 9 | +#include <vector> |
| 10 | + |
| 11 | +struct Vector { |
| 12 | + double x, y, z; |
| 13 | + static Vector times(double k, Vector v) { return {k * v.x, k * v.y, k * v.z}; } |
| 14 | + static Vector minus(Vector a, Vector b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; } |
| 15 | + static Vector plus(Vector a, Vector b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; } |
| 16 | + static double dot(Vector a, Vector b) { return a.x * b.x + a.y * b.y + a.z * b.z; } |
| 17 | + static double mag(Vector v) { return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z); } |
| 18 | + static Vector norm(Vector v) { |
| 19 | + double m = mag(v); |
| 20 | + double div = (m == 0) ? std::numeric_limits<double>::infinity() : 1.0 / m; |
| 21 | + return times(div, v); |
| 22 | + } |
| 23 | + static Vector cross(Vector a, Vector b) { |
| 24 | + return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; |
| 25 | + } |
| 26 | +}; |
| 27 | + |
| 28 | +struct Color { |
| 29 | + double r, g, b; |
| 30 | + static Color scale(double k, Color v) { return {k * v.r, k * v.g, k * v.b}; } |
| 31 | + static Color plus(Color a, Color b) { return {a.r + b.r, a.g + b.g, a.b + b.b}; } |
| 32 | + static Color times(Color a, Color b) { return {a.r * b.r, a.g * b.g, a.b * b.b}; } |
| 33 | +}; |
| 34 | + |
| 35 | +constexpr Color white{1.0, 1.0, 1.0}; |
| 36 | +constexpr Color grey{0.5, 0.5, 0.5}; |
| 37 | +constexpr Color black{0.0, 0.0, 0.0}; |
| 38 | +constexpr Color background = black; |
| 39 | +constexpr Color defaultColor = black; |
| 40 | + |
| 41 | +struct DrawingColor { double r, g, b; }; |
| 42 | + |
| 43 | +static DrawingColor toDrawingColor(Color c) { |
| 44 | + auto legalize = [](double d) { return d > 1 ? 1.0 : d; }; |
| 45 | + return {std::floor(legalize(c.r) * 255), std::floor(legalize(c.g) * 255), std::floor(legalize(c.b) * 255)}; |
| 46 | +} |
| 47 | + |
| 48 | +struct Camera { |
| 49 | + Vector pos, forward, right, up; |
| 50 | + Camera(Vector pos, Vector lookAt) : pos(pos) { |
| 51 | + Vector down{0.0, -1.0, 0.0}; |
| 52 | + forward = Vector::norm(Vector::minus(lookAt, pos)); |
| 53 | + right = Vector::times(1.5, Vector::norm(Vector::cross(forward, down))); |
| 54 | + up = Vector::times(1.5, Vector::norm(Vector::cross(forward, right))); |
| 55 | + } |
| 56 | +}; |
| 57 | + |
| 58 | +struct Ray { Vector start, dir; }; |
| 59 | + |
| 60 | +struct Thing; |
| 61 | +struct Intersection { const Thing *thing; Ray ray; double dist; }; |
| 62 | + |
| 63 | +struct Surface { |
| 64 | + Color (*diffuse)(Vector); |
| 65 | + Color (*specular)(Vector); |
| 66 | + double (*reflect)(Vector); |
| 67 | + double roughness; |
| 68 | +}; |
| 69 | + |
| 70 | +struct Thing { |
| 71 | + virtual ~Thing() = default; |
| 72 | + virtual std::optional<Intersection> intersect(const Ray &ray) const = 0; |
| 73 | + virtual Vector normal(Vector pos) const = 0; |
| 74 | + const Surface *surface; |
| 75 | + explicit Thing(const Surface *surface) : surface(surface) {} |
| 76 | +}; |
| 77 | + |
| 78 | +struct Light { Vector pos; Color color; }; |
| 79 | + |
| 80 | +struct Scene { |
| 81 | + std::vector<std::unique_ptr<Thing>> things; |
| 82 | + std::vector<Light> lights; |
| 83 | + Camera camera; |
| 84 | +}; |
| 85 | + |
| 86 | +struct Sphere : Thing { |
| 87 | + Vector center; |
| 88 | + double radius2; |
| 89 | + Sphere(Vector center, double radius, const Surface *surface) : Thing(surface), center(center), radius2(radius * radius) {} |
| 90 | + Vector normal(Vector pos) const override { return Vector::norm(Vector::minus(pos, center)); } |
| 91 | + std::optional<Intersection> intersect(const Ray &ray) const override { |
| 92 | + Vector eo = Vector::minus(center, ray.start); |
| 93 | + double v = Vector::dot(eo, ray.dir); |
| 94 | + double dist = 0; |
| 95 | + if (v >= 0) { |
| 96 | + double disc = radius2 - (Vector::dot(eo, eo) - v * v); |
| 97 | + if (disc >= 0) dist = v - std::sqrt(disc); |
| 98 | + } |
| 99 | + if (dist == 0) return std::nullopt; |
| 100 | + return Intersection{this, ray, dist}; |
| 101 | + } |
| 102 | +}; |
| 103 | + |
| 104 | +struct Plane : Thing { |
| 105 | + Vector norm; |
| 106 | + double offset; |
| 107 | + Plane(Vector norm, double offset, const Surface *surface) : Thing(surface), norm(norm), offset(offset) {} |
| 108 | + Vector normal(Vector) const override { return norm; } |
| 109 | + std::optional<Intersection> intersect(const Ray &ray) const override { |
| 110 | + double denom = Vector::dot(norm, ray.dir); |
| 111 | + if (denom > 0) return std::nullopt; |
| 112 | + double dist = (Vector::dot(norm, ray.start) + offset) / (-denom); |
| 113 | + return Intersection{this, ray, dist}; |
| 114 | + } |
| 115 | +}; |
| 116 | + |
| 117 | +static bool odd(Vector pos) { return std::fmod(std::floor(pos.z) + std::floor(pos.x), 2) != 0; } |
| 118 | + |
| 119 | +const Surface shiny{ |
| 120 | + [](Vector) { return white; }, |
| 121 | + [](Vector) { return grey; }, |
| 122 | + [](Vector) { return 0.7; }, |
| 123 | + 250.0}; |
| 124 | + |
| 125 | +const Surface checkerboard{ |
| 126 | + [](Vector pos) { return odd(pos) ? white : black; }, |
| 127 | + [](Vector) { return white; }, |
| 128 | + [](Vector pos) { return odd(pos) ? 0.1 : 0.7; }, |
| 129 | + 150.0}; |
| 130 | + |
| 131 | +struct RayTracer { |
| 132 | + int maxDepth = 5; |
| 133 | + |
| 134 | + std::optional<Intersection> intersections(const Ray &ray, const Scene &scene) const { |
| 135 | + double closest = std::numeric_limits<double>::infinity(); |
| 136 | + std::optional<Intersection> closestInter; |
| 137 | + for (auto &thing : scene.things) { |
| 138 | + auto inter = thing->intersect(ray); |
| 139 | + if (inter && inter->dist < closest) { |
| 140 | + closestInter = inter; |
| 141 | + closest = inter->dist; |
| 142 | + } |
| 143 | + } |
| 144 | + return closestInter; |
| 145 | + } |
| 146 | + |
| 147 | + std::optional<double> testRay(const Ray &ray, const Scene &scene) const { |
| 148 | + auto isect = intersections(ray, scene); |
| 149 | + if (isect) return isect->dist; |
| 150 | + return std::nullopt; |
| 151 | + } |
| 152 | + |
| 153 | + Color traceRay(const Ray &ray, const Scene &scene, int depth) const { |
| 154 | + auto isect = intersections(ray, scene); |
| 155 | + if (!isect) return background; |
| 156 | + return shade(*isect, scene, depth); |
| 157 | + } |
| 158 | + |
| 159 | + Color shade(const Intersection &isect, const Scene &scene, int depth) const { |
| 160 | + Vector d = isect.ray.dir; |
| 161 | + Vector pos = Vector::plus(Vector::times(isect.dist, d), isect.ray.start); |
| 162 | + Vector normal = isect.thing->normal(pos); |
| 163 | + Vector reflectDir = Vector::minus(d, Vector::times(2, Vector::times(Vector::dot(normal, d), normal))); |
| 164 | + Color naturalColor = Color::plus(background, getNaturalColor(isect.thing, pos, normal, reflectDir, scene)); |
| 165 | + Color reflectedColor = (depth >= maxDepth) ? grey : getReflectionColor(isect.thing, pos, normal, reflectDir, scene, depth); |
| 166 | + return Color::plus(naturalColor, reflectedColor); |
| 167 | + } |
| 168 | + |
| 169 | + Color getReflectionColor(const Thing *thing, Vector pos, Vector, Vector rd, const Scene &scene, int depth) const { |
| 170 | + return Color::scale(thing->surface->reflect(pos), traceRay({pos, rd}, scene, depth + 1)); |
| 171 | + } |
| 172 | + |
| 173 | + Color getNaturalColor(const Thing *thing, Vector pos, Vector norm, Vector rd, const Scene &scene) const { |
| 174 | + auto addLight = [&](Color col, const Light &light) { |
| 175 | + Vector ldis = Vector::minus(light.pos, pos); |
| 176 | + Vector livec = Vector::norm(ldis); |
| 177 | + auto neatIsect = testRay({pos, livec}, scene); |
| 178 | + bool isInShadow = neatIsect ? (*neatIsect <= Vector::mag(ldis)) : false; |
| 179 | + if (isInShadow) return col; |
| 180 | + double illum = Vector::dot(livec, norm); |
| 181 | + Color lcolor = (illum > 0) ? Color::scale(illum, light.color) : defaultColor; |
| 182 | + double specular = Vector::dot(livec, Vector::norm(rd)); |
| 183 | + Color scolor = (specular > 0) ? Color::scale(std::pow(specular, thing->surface->roughness), light.color) : defaultColor; |
| 184 | + return Color::plus(col, Color::plus(Color::times(thing->surface->diffuse(pos), lcolor), |
| 185 | + Color::times(thing->surface->specular(pos), scolor))); |
| 186 | + }; |
| 187 | + Color col = defaultColor; |
| 188 | + for (auto &light : scene.lights) col = addLight(col, light); |
| 189 | + return col; |
| 190 | + } |
| 191 | + |
| 192 | + double render(const Scene &scene, double screenWidth, double screenHeight) const { |
| 193 | + auto getPoint = [&](double x, double y, const Camera &camera) { |
| 194 | + auto recenterX = [&](double x) { return (x - (screenWidth / 2.0)) / 2.0 / screenWidth; }; |
| 195 | + auto recenterY = [&](double y) { return -(y - (screenHeight / 2.0)) / 2.0 / screenHeight; }; |
| 196 | + return Vector::norm(Vector::plus(camera.forward, Vector::plus(Vector::times(recenterX(x), camera.right), Vector::times(recenterY(y), camera.up)))); |
| 197 | + }; |
| 198 | + double sum = 0; |
| 199 | + for (double y = 0; y < screenHeight; y++) { |
| 200 | + for (double x = 0; x < screenWidth; x++) { |
| 201 | + Color color = traceRay({scene.camera.pos, getPoint(x, y, scene.camera)}, scene, 0); |
| 202 | + DrawingColor c = toDrawingColor(color); |
| 203 | + sum += c.r + c.g + c.b; |
| 204 | + } |
| 205 | + } |
| 206 | + return sum; |
| 207 | + } |
| 208 | +}; |
| 209 | + |
| 210 | +static Scene defaultScene() { |
| 211 | + Scene scene{{}, {}, Camera({3.0, 2.0, 4.0}, {-1.0, 0.5, 0.0})}; |
| 212 | + scene.things.push_back(std::make_unique<Plane>(Vector{0.0, 1.0, 0.0}, 0.0, &checkerboard)); |
| 213 | + scene.things.push_back(std::make_unique<Sphere>(Vector{0.0, 1.0, -0.25}, 1.0, &shiny)); |
| 214 | + scene.things.push_back(std::make_unique<Sphere>(Vector{-6, 3.0, -5.0}, 1.0, &shiny)); |
| 215 | + scene.things.push_back(std::make_unique<Sphere>(Vector{-1.0, 0.5, 1.5}, 0.5, &shiny)); |
| 216 | + scene.lights = {{{-2.0, 2.5, 0.0}, {0.49, 0.07, 0.07}}, |
| 217 | + {{1.5, 2.5, 1.5}, {0.07, 0.07, 0.49}}, |
| 218 | + {{1.5, 2.5, -1.5}, {0.07, 0.49, 0.071}}, |
| 219 | + {{0.0, 3.5, 0.0}, {0.21, 0.21, 0.35}}}; |
| 220 | + return scene; |
| 221 | +} |
| 222 | + |
| 223 | +int main(int argc, char **argv) { |
| 224 | + std::puts("start..."); |
| 225 | + double size = argc > 1 ? std::atof(argv[1]) : 512; |
| 226 | + RayTracer rayTracer; |
| 227 | + double sum = rayTracer.render(defaultScene(), size, size); |
| 228 | + std::printf("checksum: %.0f\n", sum); |
| 229 | + std::puts("done."); |
| 230 | +} |
0 commit comments