From af1740bde341ef7703696cb7fd26d66cd8a37462 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Tue, 14 May 2024 17:26:52 -0400 Subject: [PATCH] move classes to headers --- main.cpp | 241 ++++++++++--------------------------------------------- random.h | 14 ++++ sphere.h | 80 ++++++++++++++++++ vec3.h | 18 +---- view.h | 64 +++++++++++++++ world.h | 38 +++++++++ 6 files changed, 243 insertions(+), 212 deletions(-) create mode 100644 random.h create mode 100644 sphere.h create mode 100644 view.h create mode 100644 world.h diff --git a/main.cpp b/main.cpp index 329b495..034aefa 100644 --- a/main.cpp +++ b/main.cpp @@ -1,16 +1,15 @@ -#include - -inline double randomN() -{ - static std::uniform_real_distribution distribution (0.0, 1.0); - static std::mt19937 generator; - return distribution(generator); -} +constexpr unsigned Width = 1000; +constexpr double Aspect = 16.0 / 9.0; +constexpr unsigned Height = Width / Aspect; +constexpr unsigned Threads = 8; #include "color.h" #include "ray.h" #include "renderer.h" +#include "sphere.h" #include "vec3.h" +#include "view.h" +#include "world.h" #include "imgui.h" #include "imgui_impl_sdl2.h" @@ -21,205 +20,19 @@ inline double randomN() #include #include #include -#include -#include #include #include -#include -#include - -constexpr unsigned Width = 1000; -constexpr double Aspect = 16.0 / 9.0; -constexpr unsigned Height = Width / Aspect; -constexpr unsigned Threads = 8; - -enum class Material : int { - Lambertian, - Metal, - Dielectric -}; - -struct View -{ - static constexpr auto lookat = point3(0, 0, -1); // Point camera is looking at - static constexpr auto vup = vec3(0, 1, 0); // Camera-relative "up" direction - - float fieldOfView = 90.f; - float focalLength; - float viewportHeight; - float viewportWidth; - - point3 camera; - vec3 viewportX; - vec3 viewportY; - vec3 pixelDX; - vec3 pixelDY; - vec3 viewportUL; - vec3 pixelUL; - - View() { - recalculate(); - } - - void recalculate() { - focalLength = (camera - lookat).length(); - viewportHeight = 2 * std::tan(fieldOfView * 3.14159265 / 180.0 / 2.0) * focalLength; - viewportWidth = viewportHeight * Aspect; - - const auto w = (camera - lookat).normalize(); - const auto u = cross(vup, w).normalize(); - const auto v = cross(w, u); - - viewportX = viewportWidth * u; - viewportY = -viewportHeight * v; - - pixelDX = viewportX / Width; - pixelDY = viewportY / Height; - viewportUL = camera - focalLength * w - viewportX / 2 - viewportY / 2; - pixelUL = viewportUL + 0.5 * (pixelDX + pixelDY); - } - - ray getRay(int x, int y, bool addRandom = false) const { - double X = x; - double Y = y; - - if (addRandom) { - X += randomN() - 0.5; - Y += randomN() - 0.5; - } - - auto pixel = pixelUL + X * pixelDX + Y * pixelDY; - return ray(camera, pixel - camera); - } -}; - -struct Sphere -{ - point3 center; - double radius; - Material M; - color tint; - - std::pair scatter(const ray& r, double root) const { - const auto p = r.at(root); - auto normal = (p - center) / radius; - - if (M == Material::Lambertian) { - return {tint, ray(p, normal + randomUnitSphere())}; - } else if (M == Material::Metal) { - return {tint, ray(p, r.direction().reflect(normal))}; - } else if (M == Material::Dielectric) { - constexpr auto index = 1.0 / 1.33; - - const bool front = r.direction().dot(normal) < 0; - const auto ri = front ? 1.0 / index : index; - if (!front) - normal *= -1; - - const auto dir = r.direction().normalize(); - const double costh = std::fmin((-dir).dot(normal), 1); - const double sinth = std::sqrt(1 - costh * costh); - - if (ri * sinth > 1) - return {color(1, 1, 1), ray(p, dir.reflect(normal))}; - else - return {color(1, 1, 1), ray(p, dir.refract(normal, ri))}; - } else { - return {}; - } - } - - std::optional hit(const ray& r, double tmin, double tmax) const { - const vec3 oc = center - r.origin(); - const auto a = r.direction().length_squared(); - const auto h = r.direction().dot(oc); - const auto c = oc.length_squared() - radius * radius; - const auto discriminant = h * h - a * c; - - if (discriminant < 0) { - return {}; // No hit - } else { - const auto sqrtd = sqrt(discriminant); - - // Find the nearest root that lies in the acceptable range. - auto root = (h - sqrtd) / a; - if (root <= tmin || tmax <= root) { - root = (h + sqrtd) / a; - if (root <= tmin || tmax <= root) - return {}; - } - - return root; - } - } -}; - -struct World -{ - std::vector objects; - - void add(auto&&... args) { - objects.emplace_back(args...); - } - - std::optional> hit(const ray& r) const { - double closest = std::numeric_limits::infinity(); - Sphere sphere; - - for (const auto& o : objects) { - if (auto t = o.hit(r, 0.001, closest); t) { - closest = *t; - sphere = o; - } - } - - if (closest != std::numeric_limits::infinity()) - return std::pair {closest, sphere}; - else - return {}; - } -}; static World world; -static color ray_color(const ray& r, int depth = 50) -{ - if (depth <= 0) - return {}; - - if (auto hit = world.hit(r); hit) { - const auto& [closest, sphere] = *hit; - const auto [atten, scat] = sphere.scatter(r, closest); - return atten * ray_color(scat, depth - 1); - } else { - const auto unitDir = r.direction().normalize(); - const auto a = 0.5 * (unitDir.y() + 1.0); - return (1.0 - a) * color(1.0, 1.0, 1.0) + a * color(0.5, 0.7, 1.0); - } -} - static View Camera; static int SamplesPerPixel = 20; static std::unique_ptr> renderer; static std::chrono::time_point renderStart; static std::chrono::duration renderTime; -void initiateRender(SDL_Surface *canvas) -{ - renderStart = std::chrono::high_resolution_clock::now(); - renderTime = std::chrono::duration::zero(); - - auto func = [format = canvas->format](auto x, auto y, auto pbuf) { - auto col = std::ranges::fold_left(std::views::iota(0, SamplesPerPixel), color(), - [y, x](color c, int i) { return c + ray_color(Camera.getRay(x, y, true)); }); - - col = col / SamplesPerPixel * 255; - pbuf[y * Width + x] = SDL_MapRGBA(format, col.x(), col.y(), col.z(), 255); - }; - - Camera.recalculate(); - renderer.reset(new Renderer(func, Width, Height, (uint32_t *)canvas->pixels)); -} +static color ray_color(const ray& r, int depth = 50); +static void initiateRender(SDL_Surface *canvas); int main() { @@ -302,10 +115,10 @@ int main() } } ImGui::End(); + auto tex = SDL_CreateTextureFromSurface(painter, canvas); + ImGui::Render(); - //SDL_RenderSetScale(painter, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); SDL_RenderClear(painter); - auto tex = SDL_CreateTextureFromSurface(painter, canvas); SDL_RenderCopy(painter, tex, nullptr, nullptr); ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); SDL_RenderPresent(painter); @@ -324,3 +137,35 @@ int main() SDL_Quit(); } +color ray_color(const ray& r, int depth) +{ + if (depth <= 0) + return {}; + + if (auto hit = world.hit(r); hit) { + const auto& [closest, sphere] = *hit; + const auto [atten, scat] = sphere.scatter(r, closest); + return atten * ray_color(scat, depth - 1); + } else { + const auto unitDir = r.direction().normalize(); + const auto a = 0.5 * (unitDir.y() + 1.0); + return (1.0 - a) * color(1.0, 1.0, 1.0) + a * color(0.5, 0.7, 1.0); + } +} + +void initiateRender(SDL_Surface *canvas) +{ + renderStart = std::chrono::high_resolution_clock::now(); + renderTime = std::chrono::duration::zero(); + + auto func = [format = canvas->format](auto x, auto y, auto pbuf) { + auto col = std::ranges::fold_left(std::views::iota(0, SamplesPerPixel), color(), + [y, x](color c, int i) { return c + ray_color(Camera.getRay(x, y, true)); }); + + col = col / SamplesPerPixel * 255; + pbuf[y * Width + x] = SDL_MapRGBA(format, col.x(), col.y(), col.z(), 255); + }; + + Camera.recalculate(); + renderer.reset(new Renderer(func, Width, Height, (uint32_t *)canvas->pixels)); +} diff --git a/random.h b/random.h new file mode 100644 index 0000000..fb0c7b3 --- /dev/null +++ b/random.h @@ -0,0 +1,14 @@ +#ifndef RANDOM_H +#define RANDOM_H + +#include + +inline double randomN() +{ + static std::uniform_real_distribution distribution (0.0, 1.0); + static std::mt19937 generator; + return distribution(generator); +} + +#endif // RANDOM_H + diff --git a/sphere.h b/sphere.h new file mode 100644 index 0000000..c27f29b --- /dev/null +++ b/sphere.h @@ -0,0 +1,80 @@ +#ifndef SPHERE_H +#define SPHERE_H + +#include "color.h" +#include "ray.h" +#include "vec3.h" + +#include +#include +#include + +enum class Material : int { + Lambertian, + Metal, + Dielectric +}; + +struct Sphere +{ + point3 center; + double radius; + Material M; + color tint; + + std::pair scatter(const ray& r, double root) const { + const auto p = r.at(root); + auto normal = (p - center) / radius; + + if (M == Material::Lambertian) { + return {tint, ray(p, normal + randomUnitSphere())}; + } else if (M == Material::Metal) { + return {tint, ray(p, r.direction().reflect(normal))}; + } else if (M == Material::Dielectric) { + constexpr auto index = 1.0 / 1.33; + + const bool front = r.direction().dot(normal) < 0; + const auto ri = front ? 1.0 / index : index; + if (!front) + normal *= -1; + + const auto dir = r.direction().normalize(); + const double costh = std::fmin((-dir).dot(normal), 1); + const double sinth = std::sqrt(1 - costh * costh); + + if (ri * sinth > 1) + return {color(1, 1, 1), ray(p, dir.reflect(normal))}; + else + return {color(1, 1, 1), ray(p, dir.refract(normal, ri))}; + } else { + return {}; + } + } + + std::optional hit(const ray& r, double tmin, double tmax) const { + const vec3 oc = center - r.origin(); + const auto a = r.direction().length_squared(); + const auto h = r.direction().dot(oc); + const auto c = oc.length_squared() - radius * radius; + const auto discriminant = h * h - a * c; + + if (discriminant < 0) { + return {}; // No hit + } else { + const auto sqrtd = std::sqrt(discriminant); + + // Find the nearest root that lies in the acceptable range. + auto root = (h - sqrtd) / a; + if (root <= tmin || tmax <= root) { + root = (h + sqrtd) / a; + if (root <= tmin || tmax <= root) + return {}; + } + + return root; + } + } +}; + +#endif // SPHERE_H + diff --git a/vec3.h b/vec3.h index d81a1a1..46367d2 100644 --- a/vec3.h +++ b/vec3.h @@ -1,11 +1,11 @@ #ifndef VEC3_H #define VEC3_H +#include "random.h" + #include #include -using std::sqrt; - struct vec3 { public: double e[3]; @@ -64,7 +64,7 @@ struct vec3 { } constexpr double length() const { - return sqrt(length_squared()); + return std::sqrt(length_squared()); } constexpr double length_squared() const { @@ -94,20 +94,11 @@ struct vec3 { auto rpara = v * -std::sqrt(std::fabs(1.0 - rperp.length_squared())); return rperp + rpara; } - - static vec3 random() { - return vec3(randomN(), randomN(), randomN()); - } - - //static vec3 random(double min, double max) { - // return vec3(randomN(min,max), randomN(min,max), randomN(min,max)); - //} }; // point3 is just an alias for vec3, but useful for geometric clarity in the code. using point3 = vec3; - // Vector Utility Functions inline std::ostream& operator<<(std::ostream& out, const vec3& v) { @@ -122,7 +113,6 @@ constexpr inline vec3 operator/(double t, const vec3& v) { return v * (1 / t); } - inline constexpr vec3 cross(const vec3& u, const vec3& v) { return vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1], u.e[2] * v.e[0] - u.e[0] * v.e[2], @@ -135,7 +125,7 @@ inline constexpr vec3 unit_vector(const vec3& v) { inline vec3 randomUnitSphere() { for (;;) { - if (auto p = vec3::random() * 2 - vec3(1, 1, 1); p.length_squared() < 1) + if (auto p = vec3(randomN(), randomN(), randomN()) * 2 - vec3(1, 1, 1); p.length_squared() < 1) return p; } } diff --git a/view.h b/view.h new file mode 100644 index 0000000..e27176e --- /dev/null +++ b/view.h @@ -0,0 +1,64 @@ +#ifndef VIEW_H +#define VIEW_H + +#include "random.h" +#include "vec3.h" + +#include + +struct View +{ + static constexpr auto lookat = point3(0, 0, -1); // Point camera is looking at + static constexpr auto vup = vec3(0, 1, 0); // Camera-relative "up" direction + + float fieldOfView = 90.f; + float focalLength; + float viewportHeight; + float viewportWidth; + + point3 camera; + vec3 viewportX; + vec3 viewportY; + vec3 pixelDX; + vec3 pixelDY; + vec3 viewportUL; + vec3 pixelUL; + + View() { + recalculate(); + } + + void recalculate() { + focalLength = (camera - lookat).length(); + viewportHeight = 2 * std::tan(fieldOfView * 3.14159265 / 180.0 / 2.0) * focalLength; + viewportWidth = viewportHeight * Aspect; + + const auto w = (camera - lookat).normalize(); + const auto u = cross(vup, w).normalize(); + const auto v = cross(w, u); + + viewportX = viewportWidth * u; + viewportY = -viewportHeight * v; + + pixelDX = viewportX / Width; + pixelDY = viewportY / Height; + viewportUL = camera - focalLength * w - viewportX / 2 - viewportY / 2; + pixelUL = viewportUL + 0.5 * (pixelDX + pixelDY); + } + + ray getRay(int x, int y, bool addRandom = false) const { + double X = x; + double Y = y; + + if (addRandom) { + X += randomN() - 0.5; + Y += randomN() - 0.5; + } + + auto pixel = pixelUL + X * pixelDX + Y * pixelDY; + return ray(camera, pixel - camera); + } +}; + +#endif // VIEW_H + diff --git a/world.h b/world.h new file mode 100644 index 0000000..4e01bcf --- /dev/null +++ b/world.h @@ -0,0 +1,38 @@ +#ifndef WORLD_H +#define WORLD_H + +#include "sphere.h" + +#include +#include +#include +#include + +struct World +{ + std::vector objects; + + void add(auto&&... args) { + objects.emplace_back(args...); + } + + std::optional> hit(const ray& r) const { + double closest = std::numeric_limits::infinity(); + Sphere sphere; + + for (const auto& o : objects) { + if (auto t = o.hit(r, 0.001, closest); t) { + closest = *t; + sphere = o; + } + } + + if (closest != std::numeric_limits::infinity()) + return std::pair {closest, sphere}; + else + return {}; + } +}; + +#endif // WORLD_H +