-
-
Save gauravssnl/6dd188732569e6f4e3cd4283d213fe07 to your computer and use it in GitHub Desktop.
Minimal ray tracer for leaning purposes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <array> | |
#include <cmath> | |
#include <fstream> | |
#include <iostream> | |
#include <vector> | |
struct Vec { | |
float x, y, z; | |
Vec(float vx, float vy, float vz) : x(vx), y(vy), z(vz) {} | |
Vec operator+(Vec vec) { return {x + vec.x, y + vec.y, z + vec.z}; } | |
Vec operator-(Vec vec) { return {x - vec.x, y - vec.y, z - vec.z}; } | |
Vec operator*(float n) { return {x * n, y * n, z * n}; } | |
Vec unit() { return Vec(x, y, z) * (1 / this->length()); } | |
// dot product | |
float operator%(Vec vec) { return x * vec.x + y * vec.y + z * vec.z; } | |
float length() { return sqrtf(*this % *this); } | |
}; | |
struct Sphere { | |
Vec center; | |
float color; | |
float radius; | |
float intersect(Vec origin, Vec direction) { | |
Vec p = origin - this->center; | |
float a = direction % direction; | |
float b = (p % direction) * 2; | |
float c = (p % p) - (this->radius * this->radius); | |
float d = b * b - 4 * a * c; | |
if (d < 0) { | |
return NAN; | |
} | |
float sqd = sqrtf(d); | |
float distance = (-b - sqd) / (2.f * a); | |
if (distance > .1f) { | |
return distance; | |
} | |
distance = (-b + sqd) / (2.f * a); | |
if (distance > .1f) { | |
return distance; | |
} | |
return NAN; | |
} | |
}; | |
struct World { | |
std::vector<Sphere> spheres; | |
std::vector<Sphere> lights; | |
}; | |
float trace(World world, Vec origin, Vec direction) { | |
int index = -1; | |
float distance = NAN; | |
for (int i = 0; i < world.spheres.size(); ++i) { | |
float d = world.spheres[i].intersect(origin, direction); | |
if (!std::isnan(d) && (index < 0 || d < distance)) { | |
distance = d; | |
index = i; | |
} | |
} | |
if (index < 0) { | |
return 1.f - direction.y; | |
} | |
Vec p = origin + direction * distance; | |
Vec n = (p - world.spheres[index].center).unit(); | |
float c = world.spheres[index].color * .1f; | |
for (auto light : world.lights) { | |
Vec l = (light.center - p).unit(); | |
int shadow = 0; | |
for (auto sphere : world.spheres) { | |
if (!std::isnan(sphere.intersect(p, l))) { | |
shadow = 1; | |
} | |
} | |
if (!shadow) { | |
float df = std::max(0.f, (l % n) * 0.7f); | |
float sp = powf(fmax(0.f, (l % n)), 70.f) * 0.4f; | |
c = c + world.spheres[index].color * light.color * df + sp; | |
} | |
} | |
return c; | |
} | |
void render_pgm_stereo(World world, std::string filename, int width, | |
int height) { | |
std::ofstream f(filename); | |
f << "P2" << std::endl << (width * 2) << " " << height << " 255" << std::endl; | |
for (int y = 0; y < height; y++) { | |
for (int x = 0; x < width; x++) { | |
float c = trace(world, {0, 1, 5}, | |
Vec(x - width / 2, height / 2 - y, -height).unit()); | |
f << ((int)(c * 255)) << " "; | |
} | |
for (int x = 0; x < width; x++) { | |
float c = trace(world, {0.5, 1, 5}, | |
Vec(x - width / 2, height / 2 - y, -height).unit()); | |
f << ((int)(c * 255)) << " "; | |
} | |
} | |
} | |
void render_pgm(World world, std::string filename, int width, int height) { | |
std::ofstream f(filename); | |
f << "P2" << std::endl << width << " " << height << " 255" << std::endl; | |
for (int y = 0; y < height; y++) { | |
for (int x = 0; x < width; x++) { | |
float c = trace(world, {0, 1, 5}, | |
Vec(x - width / 2, height / 2 - y, -height).unit()); | |
f << ((int)(c * 255)) << " "; | |
} | |
} | |
} | |
void render_tty(World world, int width, int height) { | |
for (int y = 0; y < height; y++) { | |
for (int x = 0; x < width; x++) { | |
float c = trace(world, {0, 1, 5}, | |
Vec(x - width / 2, height / 2 - y, -height).unit()); | |
char pixel = " .:-=+*#%@$"[std::max(std::min((int)(c * 10), 10), 0)]; | |
std::cout << pixel << pixel; | |
} | |
std::cout << std::endl; | |
} | |
} | |
int main() { | |
World world = { | |
// spheres | |
{ | |
{{0, -1000, 0}, 0.001, 1000}, | |
{{-2, 1, -2}, 1, 1}, | |
{{0, 1, 0}, 0.5, 1}, | |
{{2, 1, -1}, 0.1, 1}, | |
}, | |
// lights | |
{ | |
{{0, 100, 0}, .4, 0}, | |
{{100, 100, 200}, .5, 0}, | |
{{-100, 300, 100}, .1, 0}, | |
}, | |
}; | |
render_pgm(world, "ray_mono.pgm", 600, 600); | |
render_pgm_stereo(world, "ray.pgm", 300, 200); | |
render_tty(world, 40, 25); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment