Add multithreaded ray tracer implementation

This commit is contained in:
2026-02-17 01:29:10 +05:30
parent ab057ea279
commit 250403809b
14 changed files with 765 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 763 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

+187
View File
@@ -0,0 +1,187 @@
#ifndef CAMERA_H
#define CAMERA_H
#include "hittable.h"
#include "material.h"
#include <thread>
#include <vector>
class camera {
public:
double aspect_ratio = 1.0;
int image_width = 100;
int samples_per_pixel = 10;
int max_depth = 10;
double vfov = 90;
point3 lookfrom = point3(0,0,0);
point3 lookat = point3(0,0,-1);
vec3 vup = vec3(0,1,0);
double defocus_angle = 0;
double focus_dist = 10;
void render(const hittable& world) {
initialize();
std::vector<color> framebuffer(image_width * image_height);
int thread_count = std::thread::hardware_concurrency();
if (thread_count == 0) thread_count = 8; // fallback
std::vector<std::thread> threads;
int rows_per_thread = image_height / thread_count;
auto render_rows = [&](int start_row, int end_row) {
for (int j = start_row; j < end_row; j++) {
for (int i = 0; i < image_width; i++) {
color pixel_color(0,0,0);
for (int sample = 0; sample < samples_per_pixel; sample++) {
ray r = get_ray(i, j);
pixel_color += ray_color(r, max_depth, world);
}
framebuffer[j * image_width + i] =
pixel_samples_scale * pixel_color;
}
}
};
for (int t = 0; t < thread_count; t++) {
int start = t * rows_per_thread;
int end = (t == thread_count - 1)
? image_height
: start + rows_per_thread;
threads.emplace_back(render_rows, start, end);
}
for (auto& t : threads)
t.join();
// Print image after rendering completes (single-threaded)
std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n";
for (int j = 0; j < image_height; j++) {
for (int i = 0; i < image_width; i++) {
write_color(std::cout,
framebuffer[j * image_width + i]);
}
}
std::clog << "Done.\n";
}
private:
/* Private Camera Variables Here */
int image_height; // Rendered image height
point3 center; // Camera center
double pixel_samples_scale;// color scale factor for a sum of pixel samples
point3 pixel00_loc; // Location of pixel 0, 0
vec3 pixel_delta_u; // Offset to pixel to the right
vec3 pixel_delta_v; // Offset to pixel below
vec3 u,v,w;
vec3 defocus_disk_u; // Defocus disk horizontal radius
vec3 defocus_disk_v; // Defocus disk vertical radius
void initialize() {
image_height = int(image_width / aspect_ratio);
image_height = (image_height < 1) ? 1 : image_height;
pixel_samples_scale = 1.0/ samples_per_pixel;
center = lookfrom;
// Determine viewport dimensions.
auto theta = degrees_to_radians(vfov);
auto h = std::tan(theta/2);
auto viewport_height = 2 * h * focus_dist;
auto viewport_width = viewport_height * (double(image_width)/image_height);
w = unit_vector(lookfrom - lookat);
u = unit_vector(cross(vup,w));
v = cross(w,u);
// Calculate the vectors across the horizontal and down the vertical viewport edges.
vec3 viewport_u = viewport_width*u;
vec3 viewport_v = viewport_height*-v;
// Calculate the horizontal and vertical delta vectors from pixel to pixel.
pixel_delta_u = viewport_u / image_width;
pixel_delta_v = viewport_v / image_height;
// Calculate the location of the upper left pixel.
auto viewport_upper_left = center - (focus_dist * w) - viewport_u/2 - viewport_v/2;
pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);
// Calculate the camera defocus disk basis vectors.
auto defocus_radius = focus_dist * std::tan(degrees_to_radians(defocus_angle / 2));
defocus_disk_u = u * defocus_radius;
defocus_disk_v = v * defocus_radius;
}
ray get_ray(int i, int j) const {
// Construct a camera ray originating from the origin and directed at randomly sampled
// point around the pixel location i, j.
auto offset = sample_square();
auto pixel_sample = pixel00_loc
+ ((i + offset.x()) * pixel_delta_u)
+ ((j + offset.y()) * pixel_delta_v);
auto ray_origin = (defocus_angle <= 0) ? center : defocus_disk_sample();
auto ray_direction = pixel_sample - ray_origin;
return ray(ray_origin, ray_direction);
}
vec3 sample_square() const {
// Returns the vector to a random point in the [-.5,-.5]-[+.5,+.5] unit square.
return vec3(random_double() - 0.5, random_double() - 0.5, 0);
}
point3 defocus_disk_sample() const {
// Returns a random point in the camera defocus disk.
auto p = random_in_unit_disk();
return center + (p[0] * defocus_disk_u) + (p[1] * defocus_disk_v);
}
color ray_color(const ray& r,int depth, const hittable& world) const {
if(depth<=0)
return color(0,0,0);
hit_record rec;
if (world.hit(r, interval(0.001, infinity), rec)) {
ray scattered;
color attenuation;
if (rec.mat->scatter(r, rec, attenuation, scattered))
return attenuation * ray_color(scattered, depth-1, world);
return color(0,0,0);
}
vec3 unit_direction = unit_vector(r.direction());
auto a = 0.5*(unit_direction.y() + 1.0);
return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}
};
#endif
+37
View File
@@ -0,0 +1,37 @@
#ifndef COLOR_H
#define COLOR_H
#include "interval.h"
#include "vec3.h"
#include <iostream>
using color = vec3;
inline double linear_to_gamma(double linear_component)
{
if (linear_component > 0)
return std::sqrt(linear_component);
return 0;
}
inline void write_color(std::ostream& out, const color& pixel_color) {
auto r = pixel_color.x();
auto g = pixel_color.y();
auto b = pixel_color.z();
// Apply a linear to gamma transform for gamma 2
r = linear_to_gamma(r);
g = linear_to_gamma(g);
b = linear_to_gamma(b);
// Translate the [0,1] component values to the byte range [0,255].
static const interval intensity(0.000, 0.999);
int rbyte = int(256 * intensity.clamp(r));
int gbyte = int(256 * intensity.clamp(g));
int bbyte = int(256 * intensity.clamp(b));
// Write out the pixel color components.
out << rbyte << ' ' << gbyte << ' ' << bbyte << '\n';
}
#endif
+33
View File
@@ -0,0 +1,33 @@
#ifndef HITTABLE_H
#define HITTABLE_H
#include "ray.h"
class material;
class hit_record {
public:
point3 p;
vec3 normal;
double t;
shared_ptr<material> mat;
bool front_face;
void set_face_normal(const ray& r, const vec3& outward_normal){
// Sets the hit record normal vector.
// NOTE: the parameter `outward_normal` is assumed to have unit length.
front_face = dot(r.direction(), outward_normal) < 0;
normal = front_face ? outward_normal : -outward_normal;
}
};
class hittable {
public:
virtual ~hittable() = default;
virtual bool hit(const ray& r, interval ray_t, hit_record& rec) const = 0;
};
#endif
+42
View File
@@ -0,0 +1,42 @@
#ifndef HITTABLE_LIST_H
#define HITTABLE_LIST_H
#include "hittable.h"
#include <memory>
#include <vector>
using std::make_shared;
using std::shared_ptr;
class hittable_list : public hittable {
public:
std::vector<shared_ptr<hittable>> objects;
hittable_list() {}
hittable_list(shared_ptr<hittable> object) { add(object); }
void clear() { objects.clear(); }
void add(shared_ptr<hittable> object) {
objects.push_back(object);
}
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
hit_record temp_rec;
bool hit_anything = false;
auto closest_so_far = ray_t.max;
for (const auto& object : objects) {
if (object->hit(r, interval(ray_t.min, closest_so_far), temp_rec)){
hit_anything = true;
closest_so_far = temp_rec.t;
rec = temp_rec;
}
}
return hit_anything;
}
};
#endif
+37
View File
@@ -0,0 +1,37 @@
#ifndef INTERVAL_H
#define INTERVAL_H
class interval {
public:
double min, max;
interval() : min(+infinity), max(-infinity) {} // Default interval is empty
interval(double min, double max) : min(min), max(max) {}
double size() const {
return max - min;
}
bool contains(double x) const {
return min <= x && x <= max;
}
bool surrounds(double x) const {
return min < x && x < max;
}
double clamp(double x) const {
if (x < min) return min;
if (x > max) return max;
return x;
}
static const interval empty, universe;
};
const interval interval::empty = interval(+infinity, -infinity);
const interval interval::universe = interval(-infinity, +infinity);
#endif
+96
View File
@@ -0,0 +1,96 @@
#ifndef MATERIAL_H
#define MATERIAL_H
#include "hittable.h"
#include "vec3.h"
class material {
public:
virtual ~material() = default;
virtual bool scatter(
const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered
) const {
return false;
}
};
class lambertian : public material {
public:
lambertian(const color& albedo) : albedo(albedo) {}
bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
const override {
auto scatter_direction = rec.normal + random_unit_vector();
// Catch degenerate scatter direction
if (scatter_direction.near_zero())
scatter_direction = rec.normal;
scattered = ray(rec.p, scatter_direction);
attenuation = albedo;
return true;
}
private:
color albedo;
};
class metal : public material {
public:
metal(const color& albedo, double fuzz) : albedo(albedo), fuzz(fuzz<1? fuzz : 1) {}
bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
const override {
vec3 reflected = reflect(r_in.direction(), rec.normal);
reflected = unit_vector(reflected) + (fuzz*random_unit_vector());
scattered = ray(rec.p, reflected);
attenuation = albedo;
return (dot(scattered.direction(), rec.normal)>0);
}
private:
color albedo;
double fuzz;
};
class dielectric : public material {
public:
dielectric(double refraction_index) : refraction_index(refraction_index) {}
bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
const override {
attenuation = color(1.0, 1.0, 1.0);
double ri = rec.front_face ? (1.0/refraction_index) : refraction_index;
vec3 unit_direction = unit_vector(r_in.direction());
double cos_theta = std::fmin(dot(-unit_direction, rec.normal), 1.0);
double sin_theta = std::sqrt(1.0 - cos_theta*cos_theta);
bool cannot_refract = ri * sin_theta > 1.0;
vec3 direction;
if (cannot_refract || reflectance(cos_theta, ri)> random_double())
direction = reflect(unit_direction, rec.normal);
else
direction = refract(unit_direction, rec.normal, ri);
scattered = ray(rec.p, direction);
return true;
}
private:
// Refractive index in vacuum or air, or the ratio of the material's refractive index over
// the refractive index of the enclosing media
double refraction_index;
static double reflectance(double cosine, double refraction_index) {
// Use Schlick's approximation for reflectance.
auto r0 = (1 - refraction_index) / (1 + refraction_index);
r0 = r0*r0;
return r0 + (1-r0)*std::pow((1 - cosine),5);
}
};
#endif
+24
View File
@@ -0,0 +1,24 @@
#ifndef RAY_H
#define RAY_H
#include "vec3.h"
class ray {
public:
ray() {}
ray(const point3& origin, const vec3& direction) : orig(origin), dir(direction) {}
const point3& origin() const { return orig; }
const vec3& direction() const { return dir; }
point3 at(double t) const {
return orig + t*dir;
}
private:
point3 orig;
vec3 dir;
};
#endif
+45
View File
@@ -0,0 +1,45 @@
#ifndef RTWEEKEND_H
#define RTWEEKEND_H
#include <cmath>
#include <random>
#include <iostream>
#include <limits>
#include <memory>
// C++ Std Usings
using std::make_shared;
using std::shared_ptr;
// Constants
const double infinity = std::numeric_limits<double>::infinity();
const double pi = 3.1415926535897932385;
// Utility Functions
inline double degrees_to_radians(double degrees) {
return degrees * pi / 180.0;
}
inline double random_double() {
thread_local static std::mt19937 generator(std::random_device{}());
thread_local static std::uniform_real_distribution<double> distribution(0.0, 1.0);
return distribution(generator);
}
inline double random_double(double min, double max) {
// Returns a random real in [min,max).
return min + (max-min)*random_double();
}
// Common Headers
#include "color.h"
#include "interval.h"
#include "ray.h"
#include "vec3.h"
#endif
+46
View File
@@ -0,0 +1,46 @@
#ifndef SPHERE_H
#define SPHERE_H
#include "hittable.h"
#include "vec3.h"
class sphere : public hittable {
public:
sphere(const point3& center, double radius, shared_ptr<material> mat) : center(center), radius(std::fmax(0,radius)), mat(mat) {}
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
vec3 oc = center - r.origin();
auto a = r.direction().length_squared();
auto h = dot(r.direction(), oc);
auto c = oc.length_squared() - radius*radius;
auto discriminant = h*h - a*c;
if (discriminant < 0)
return false;
auto sqrtd = std::sqrt(discriminant);
// Find the nearest root that lies in the acceptable range.
auto root = (h - sqrtd) / a;
if (!ray_t.surrounds(root)){
root = (h + sqrtd) / a;
if (!ray_t.surrounds(root))
return false;
}
rec.t = root;
rec.p = r.at(rec.t);
vec3 outward_normal = (rec.p - center) / radius;
rec.set_face_normal(r, outward_normal);
rec.mat = mat;
return true;
}
private:
point3 center;
double radius;
shared_ptr<material>mat;
};
#endif
+149
View File
@@ -0,0 +1,149 @@
#ifndef VEC3_H
#define VEC3_H
#include <cmath>
#include <iostream>
class vec3 {
public:
double e[3];
vec3() : e{0,0,0} {}
vec3(double e0, double e1, double e2) : e{e0, e1, e2} {}
double x() const { return e[0]; }
double y() const { return e[1]; }
double z() const { return e[2]; }
vec3 operator-() const { return vec3(-e[0], -e[1], -e[2]); }
double operator[](int i) const { return e[i]; }
double& operator[](int i) { return e[i]; }
vec3& operator+=(const vec3& v) {
e[0] += v.e[0];
e[1] += v.e[1];
e[2] += v.e[2];
return *this;
}
vec3& operator*=(double t) {
e[0] *= t;
e[1] *= t;
e[2] *= t;
return *this;
}
vec3& operator/=(double t) {
return *this *= 1/t;
}
double length() const {
return std::sqrt(length_squared());
}
double length_squared() const {
return e[0]*e[0] + e[1]*e[1] + e[2]*e[2];
}
bool near_zero() const {
// Return true if the vector is close to zero in all dimensions.
auto s = 1e-8;
return (std::fabs(e[0]) < s) && (std::fabs(e[1]) < s) && (std::fabs(e[2]) < s);
}
static vec3 random() {
return vec3(random_double(), random_double(), random_double());
}
static vec3 random(double min, double max) {
return vec3(random_double(min,max), random_double(min,max), random_double(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) {
return out << v.e[0] << ' ' << v.e[1] << ' ' << v.e[2];
}
inline vec3 operator+(const vec3& u, const vec3& v) {
return vec3(u.e[0] + v.e[0], u.e[1] + v.e[1], u.e[2] + v.e[2]);
}
inline vec3 operator-(const vec3& u, const vec3& v) {
return vec3(u.e[0] - v.e[0], u.e[1] - v.e[1], u.e[2] - v.e[2]);
}
inline vec3 operator*(const vec3& u, const vec3& v) {
return vec3(u.e[0] * v.e[0], u.e[1] * v.e[1], u.e[2] * v.e[2]);
}
inline vec3 operator*(double t, const vec3& v) {
return vec3(t*v.e[0], t*v.e[1], t*v.e[2]);
}
inline vec3 operator*(const vec3& v, double t) {
return t * v;
}
inline vec3 operator/(const vec3& v, double t) {
return (1/t) * v;
}
inline double dot(const vec3& u, const vec3& v) {
return u.e[0] * v.e[0]
+ u.e[1] * v.e[1]
+ u.e[2] * v.e[2];
}
inline 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],
u.e[0] * v.e[1] - u.e[1] * v.e[0]);
}
inline vec3 unit_vector(const vec3& v) {
return v / v.length();
}
inline vec3 random_in_unit_disk() {
while (true) {
auto p = vec3(random_double(-1,1), random_double(-1,1), 0);
if (p.length_squared() < 1)
return p;
}
}
inline vec3 random_unit_vector() {
while (true) {
auto p = vec3::random(-1,1);
auto lensq = p.length_squared();
if (1e-160 < lensq && lensq <= 1)
return p / sqrt(lensq);
}
}
inline vec3 random_on_hemisphere(const vec3& normal) {
vec3 on_unit_sphere = random_unit_vector();
if (dot(on_unit_sphere, normal) > 0.0) // In the same hemisphere as the normal
return on_unit_sphere;
else
return -on_unit_sphere;
}
inline vec3 reflect(const vec3& v, const vec3& n) {
return v - 2*dot(v,n)*n;
}
inline vec3 refract(const vec3& uv, const vec3& n, double etai_over_etat){
auto cos_theta = std::fmin(dot(-uv,n),1.0);
vec3 r_out_perp = etai_over_etat*(uv+cos_theta*n);
vec3 r_out_parallel = -std::sqrt(std::fabs(1.0 - r_out_perp.length_squared())) * n;
return r_out_perp + r_out_parallel;
}
#endif
+69
View File
@@ -0,0 +1,69 @@
#include<rtweekend.h>
#include<camera.h>
#include "hittable.h"
#include "hittable_list.h"
#include "sphere.h"
int main() {
hittable_list world;
auto ground_material = make_shared<lambertian>(color(0.5, 0.5, 0.5));
world.add(make_shared<sphere>(point3(0,-1000,0), 1000, ground_material));
for (int a = -11; a < 11; a++) {
for (int b = -11; b < 11; b++) {
auto choose_mat = random_double();
point3 center(a + 0.9*random_double(), 0.2, b + 0.9*random_double());
if ((center - point3(4, 0.2, 0)).length() > 0.9) {
shared_ptr<material> sphere_material;
if (choose_mat < 0.8) {
// diffuse
auto albedo = color::random() * color::random();
sphere_material = make_shared<lambertian>(albedo);
world.add(make_shared<sphere>(center, 0.2, sphere_material));
} else if (choose_mat < 0.95) {
// metal
auto albedo = color::random(0.5, 1);
auto fuzz = random_double(0, 0.5);
sphere_material = make_shared<metal>(albedo, fuzz);
world.add(make_shared<sphere>(center, 0.2, sphere_material));
} else {
// glass
sphere_material = make_shared<dielectric>(1.5);
world.add(make_shared<sphere>(center, 0.2, sphere_material));
}
}
}
}
auto material1 = make_shared<dielectric>(1.5);
world.add(make_shared<sphere>(point3(0, 1, 0), 1.0, material1));
auto material2 = make_shared<lambertian>(color(0.4, 0.2, 0.1));
world.add(make_shared<sphere>(point3(-4, 1, 0), 1.0, material2));
auto material3 = make_shared<metal>(color(0.7, 0.6, 0.5), 0.0);
world.add(make_shared<sphere>(point3(4, 1, 0), 1.0, material3));
camera cam;
cam.aspect_ratio = 16.0 / 9.0;
cam.image_width = 1200;
cam.samples_per_pixel = 500;
cam.max_depth = 50;
cam.vfov = 20;
cam.lookfrom = point3(13,2,3);
cam.lookat = point3(0,0,0);
cam.vup = vec3(0,1,0);
cam.defocus_angle = 0.6;
cam.focus_dist = 10.0;
cam.render(world);
}