"source": "#include <tuple>\n#include <vector>\n#include <algorithm>\n#include <cmath>\n\n// -------------------- Math & scene types --------------------\n\nstruct vec3 {\n float x = 0, y = 0, z = 0;\n\n float& operator[](const int i) { return i==0 ? x : (1==i ? y : z); }\n const float& operator[](const int i) const { return i==0 ? x : (1==i ? y : z); }\n\n vec3 operator*(const float v) const { return {x*v, y*v, z*v}; }\n float operator*(const vec3& v) const { return x*v.x + y*v.y + z*v.z; }\n vec3 operator+(const vec3& v) const { return {x+v.x, y+v.y, z+v.z}; }\n vec3 operator-(const vec3& v) const { return {x-v.x, y-v.y, z-v.z}; }\n vec3 operator-() const { return {-x, -y, -z}; }\n\n float norm() const { return std::sqrt(x*x + y*y + z*z); }\n vec3 normalized() const { return (*this) * (1.f / norm()); }\n};\n\nvec3 cross(const vec3 v1, const vec3 v2) {\n return {\n v1.y*v2.z - v1.z*v2.y,\n v1.z*v2.x - v1.x*v2.z,\n v1.x*v2.y - v1.y*v2.x\n };\n}\n\nstruct Material {\n float refractive_index = 1.f;\n float albedo[4] = {2.f, 0.f, 0.f, 0.f};\n vec3 diffuse_color = {0.f, 0.f, 0.f};\n float specular_exponent = 0.f;\n};\n\nstruct Sphere {\n vec3 center;\n float radius;\n Material material;\n};\n\n// -------------------- Scene definition --------------------\n\nconstexpr Material ivory = {1.0f, {0.9f, 0.5f, 0.1f, 0.0f}, {0.4f, 0.4f, 0.3f}, 50.f};\nconstexpr Material glass = {1.5f, {0.0f, 0.9f, 0.1f, 0.8f}, {0.6f, 0.7f, 0.8f}, 125.f};\nconstexpr Material red_rubber = {1.0f, {1.4f, 0.3f, 0.0f, 0.0f}, {0.3f, 0.1f, 0.1f}, 10.f};\nconstexpr Material mirror = {1.0f, {0.0f, 16.0f, 0.8f, 0.0f}, {1.0f, 1.0f, 1.0f}, 1425.f};\n\nconstexpr Sphere spheres[] = {\n {{-3.f, 0.f, -16.f}, 2.f, ivory},\n {{-1.0f, -1.5f, -12.f}, 2.f, glass},\n {{ 1.5f, -0.5f, -18.f}, 3.f, red_rubber},\n {{ 7.f, 5.f, -18.f}, 4.f, mirror}\n};\n\nconstexpr vec3 lights[] = {\n {-20.f, 20.f, 20.f},\n { 30.f, 50.f, -25.f},\n { 30.f, 20.f, 30.f}\n};\n\n// -------------------- Ray / intersection helpers --------------------\n\nvec3 reflect(const vec3 &I, const vec3 &N) {\n return I - N * (2.f * (I * N));\n}\n\nvec3 refract(const vec3 &I, const vec3 &N, const float eta_t, const float eta_i = 1.f) {\n float cosi = - std::max(-1.f, std::min(1.f, I * N));\n if (cosi < 0.f)\n return refract(I, -N, eta_i, eta_t);\n\n float eta = eta_i / eta_t;\n float k = 1.f - eta*eta*(1.f - cosi*cosi);\n if (k < 0.f) {\n return {1.f, 0.f, 0.f}; // same hack as original code\n }\n return I*eta + N*(eta*cosi - std::sqrt(k));\n}\n\nstd::tuple<bool, float> ray_sphere_intersect(const vec3 &orig, const vec3 &dir, const Sphere &s) {\n vec3 L = s.center - orig;\n float tca = L * dir;\n float d2 = L * L - tca*tca;\n float r2 = s.radius * s.radius;\n if (d2 > r2) return {false, 0.f};\n float thc = std::sqrt(r2 - d2);\n float t0 = tca - thc;\n float t1 = tca + thc;\n if (t0 > 0.001f) return {true, t0};\n if (t1 > 0.001f) return {true, t1};\n return {false, 0.f};\n}\n\nstd::tuple<bool, vec3, vec3, Material> scene_intersect(const vec3 &orig, const vec3 &dir) {\n vec3 pt, N;\n Material material;\n\n float nearest_dist = 1e10f;\n\n // Checkerboard plane y = -4\n if (std::abs(dir.y) > 0.001f) {\n float d = -(orig.y + 4.f) / dir.y;\n vec3 p = orig + dir * d;\n if (d > 0.001f && d < nearest_dist && std::abs(p.x) < 10.f && p.z < -10.f && p.z > -30.f) {\n nearest_dist = d;\n pt = p;\n N = {0.f, 1.f, 0.f};\n material.diffuse_color =\n ( (int(0.5f*pt.x + 1000.f) + int(0.5f*pt.z)) & 1 )\n ? vec3{0.3f, 0.3f, 0.3f}\n : vec3{0.3f, 0.2f, 0.1f};\n }\n }\n\n for (const Sphere &s : spheres) {\n auto [intersection, d] = ray_sphere_intersect(orig, dir, s);\n if (!intersection || d > nearest_dist)\n continue;\n nearest_dist = d;\n pt = orig + dir * nearest_dist;\n N = (pt - s.center).normalized();\n material = s.material;\n }\n\n return {nearest_dist < 1000.f, pt, N, material};\n}\n\nvec3 cast_ray(const vec3 &orig, const vec3 &dir, const int depth = 0) {\n auto [hit, point, N, material] = scene_intersect(orig, dir);\n if (depth > 4 || !hit)\n return {0.2f, 0.7f, 0.8f};\n\n vec3 reflect_dir = reflect(dir, N).normalized();\n vec3 refract_dir = refract(dir, N, material.refractive_index).normalized();\n vec3 reflect_color = cast_ray(point, reflect_dir, depth + 1);\n vec3 refract_color = cast_ray(point, refract_dir, depth + 1);\n\n float diffuse_light_intensity = 0.f;\n float specular_light_intensity = 0.f;\n\n for (const vec3 &light : lights) {\n vec3 light_dir = (light - point).normalized();\n auto [shadow_hit, shadow_pt, trashnrm, trashmat] = scene_intersect(point, light_dir);\n if (shadow_hit && (shadow_pt - point).norm() < (light - point).norm())\n continue;\n\n diffuse_light_intensity += std::max(0.f, light_dir * N);\n specular_light_intensity += std::pow(std::max(0.f, -reflect(-light_dir, N) * dir),\n material.specular_exponent);\n }\n\n vec3 diffuse = material.diffuse_color * (diffuse_light_intensity * material.albedo[0]);\n vec3 specular = vec3{1.f, 1.f, 1.f} * (specular_light_intensity * material.albedo[1]);\n vec3 reflectc = reflect_color * material.albedo[2];\n vec3 refractc = refract_color * material.albedo[3];\n\n return diffuse + specular + reflectc + refractc;\n}\n\n// -------------------- Render into framebuffer (no I/O) --------------------\n\nconstexpr int IMAGE_WIDTH = 1024;\nconstexpr int IMAGE_HEIGHT = 768;\nconstexpr float FOV = 1.05f; // ~60° in radians\n\nstd::vector<vec3> render_frame()\n{\n std::vector<vec3> framebuffer(IMAGE_WIDTH * IMAGE_HEIGHT);\n\n for (int pix = 0; pix < IMAGE_WIDTH * IMAGE_HEIGHT; ++pix) {\n float dir_x = (pix % IMAGE_WIDTH + 0.5f) - IMAGE_WIDTH / 2.f;\n float dir_y = -(pix / IMAGE_WIDTH + 0.5f) + IMAGE_HEIGHT / 2.f;\n float dir_z = -IMAGE_HEIGHT / (2.f * std::tan(FOV / 2.f));\n\n vec3 dir = vec3{dir_x, dir_y, dir_z}.normalized();\n framebuffer[pix] = cast_ray(vec3{0.f, 0.f, 0.f}, dir);\n }\n\n return framebuffer;\n}\n",
0 commit comments