move classes to headers

main
Clyne 6 months ago
parent 8a8fab0c68
commit af1740bde3
Signed by: clyne
GPG Key ID: 3267C8EBF3F9AFC7

@ -1,16 +1,15 @@
#include <random> constexpr unsigned Width = 1000;
constexpr double Aspect = 16.0 / 9.0;
inline double randomN() constexpr unsigned Height = Width / Aspect;
{ constexpr unsigned Threads = 8;
static std::uniform_real_distribution<double> distribution (0.0, 1.0);
static std::mt19937 generator;
return distribution(generator);
}
#include "color.h" #include "color.h"
#include "ray.h" #include "ray.h"
#include "renderer.h" #include "renderer.h"
#include "sphere.h"
#include "vec3.h" #include "vec3.h"
#include "view.h"
#include "world.h"
#include "imgui.h" #include "imgui.h"
#include "imgui_impl_sdl2.h" #include "imgui_impl_sdl2.h"
@ -21,205 +20,19 @@ inline double randomN()
#include <chrono> #include <chrono>
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <memory>
#include <optional>
#include <ranges> #include <ranges>
#include <thread> #include <thread>
#include <tuple>
#include <vector>
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<color, ray> 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<double> 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<Sphere> objects;
void add(auto&&... args) {
objects.emplace_back(args...);
}
std::optional<std::pair<double, Sphere>> hit(const ray& r) const {
double closest = std::numeric_limits<double>::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<double>::infinity())
return std::pair {closest, sphere};
else
return {};
}
};
static World world; 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 View Camera;
static int SamplesPerPixel = 20; static int SamplesPerPixel = 20;
static std::unique_ptr<Renderer<Threads>> renderer; static std::unique_ptr<Renderer<Threads>> renderer;
static std::chrono::time_point<std::chrono::high_resolution_clock> renderStart; static std::chrono::time_point<std::chrono::high_resolution_clock> renderStart;
static std::chrono::duration<double> renderTime; static std::chrono::duration<double> renderTime;
void initiateRender(SDL_Surface *canvas) static color ray_color(const ray& r, int depth = 50);
{ static void initiateRender(SDL_Surface *canvas);
renderStart = std::chrono::high_resolution_clock::now();
renderTime = std::chrono::duration<double>::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<Threads>(func, Width, Height, (uint32_t *)canvas->pixels));
}
int main() int main()
{ {
@ -302,10 +115,10 @@ int main()
} }
} ImGui::End(); } ImGui::End();
auto tex = SDL_CreateTextureFromSurface(painter, canvas);
ImGui::Render(); ImGui::Render();
//SDL_RenderSetScale(painter, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y);
SDL_RenderClear(painter); SDL_RenderClear(painter);
auto tex = SDL_CreateTextureFromSurface(painter, canvas);
SDL_RenderCopy(painter, tex, nullptr, nullptr); SDL_RenderCopy(painter, tex, nullptr, nullptr);
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
SDL_RenderPresent(painter); SDL_RenderPresent(painter);
@ -324,3 +137,35 @@ int main()
SDL_Quit(); 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<double>::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<Threads>(func, Width, Height, (uint32_t *)canvas->pixels));
}

@ -0,0 +1,14 @@
#ifndef RANDOM_H
#define RANDOM_H
#include <random>
inline double randomN()
{
static std::uniform_real_distribution<double> distribution (0.0, 1.0);
static std::mt19937 generator;
return distribution(generator);
}
#endif // RANDOM_H

@ -0,0 +1,80 @@
#ifndef SPHERE_H
#define SPHERE_H
#include "color.h"
#include "ray.h"
#include "vec3.h"
#include <cmath>
#include <optional>
#include <tuple>
enum class Material : int {
Lambertian,
Metal,
Dielectric
};
struct Sphere
{
point3 center;
double radius;
Material M;
color tint;
std::pair<color, ray> 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<double> 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

@ -1,11 +1,11 @@
#ifndef VEC3_H #ifndef VEC3_H
#define VEC3_H #define VEC3_H
#include "random.h"
#include <cmath> #include <cmath>
#include <iostream> #include <iostream>
using std::sqrt;
struct vec3 { struct vec3 {
public: public:
double e[3]; double e[3];
@ -64,7 +64,7 @@ struct vec3 {
} }
constexpr double length() const { constexpr double length() const {
return sqrt(length_squared()); return std::sqrt(length_squared());
} }
constexpr double length_squared() const { constexpr double length_squared() const {
@ -94,20 +94,11 @@ struct vec3 {
auto rpara = v * -std::sqrt(std::fabs(1.0 - rperp.length_squared())); auto rpara = v * -std::sqrt(std::fabs(1.0 - rperp.length_squared()));
return rperp + rpara; 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. // point3 is just an alias for vec3, but useful for geometric clarity in the code.
using point3 = vec3; using point3 = vec3;
// Vector Utility Functions // Vector Utility Functions
inline std::ostream& operator<<(std::ostream& out, const vec3& v) { 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); return v * (1 / t);
} }
inline constexpr vec3 cross(const vec3& u, const vec3& v) { inline constexpr vec3 cross(const vec3& u, const vec3& v) {
return vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1], 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], 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() { inline vec3 randomUnitSphere() {
for (;;) { 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; return p;
} }
} }

@ -0,0 +1,64 @@
#ifndef VIEW_H
#define VIEW_H
#include "random.h"
#include "vec3.h"
#include <cmath>
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

@ -0,0 +1,38 @@
#ifndef WORLD_H
#define WORLD_H
#include "sphere.h"
#include <limits>
#include <optional>
#include <tuple>
#include <vector>
struct World
{
std::vector<Sphere> objects;
void add(auto&&... args) {
objects.emplace_back(args...);
}
std::optional<std::pair<double, Sphere>> hit(const ray& r) const {
double closest = std::numeric_limits<double>::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<double>::infinity())
return std::pair {closest, sphere};
else
return {};
}
};
#endif // WORLD_H
Loading…
Cancel
Save