Skip to content

Instantly share code, notes, and snippets.

@BichengLUO
Last active December 6, 2016 13:17
Show Gist options
  • Save BichengLUO/7e4c32fc11925dcb91f66fe5a0ebcff2 to your computer and use it in GitHub Desktop.
Save BichengLUO/7e4c32fc11925dcb91f66fe5a0ebcff2 to your computer and use it in GitHub Desktop.
A simple ray tracer demo in 100 lines
#include <Windows.h>
#include <vector>
#include <opencv2\opencv.hpp>
#define WIDTH 500
#define HEIGHT 500
#define UNIT_PER_PIXEL 0.001
#define PI 3.14159265
#define EPS 0.0001
struct Color { unsigned char r, g, b; };
struct Vec3d { double x, y, z; };
struct Sphere { Vec3d pos; double r; Color c; }; //Information about the color, position and size of a sphere
struct Ray { Vec3d pos, dir; }; //A ray shooting from 'pos' with direction vector 'dir'
const double ambient = 0.2; //Ambient color intensity
const char *title = "Mini Ray Tracer (by Bicheng LUO, Tsinghua University)"; // Title of the window
//Calculate the intersection between a ray and a sphere
bool ray_intersect(Ray ray, Sphere sphere, Vec3d *its, Vec3d *normal, double *depth) {
double a = ray.dir.x * ray.dir.x + ray.dir.y * ray.dir.y + ray.dir.z * ray.dir.z;
double b = 2 * (ray.dir.x * (ray.pos.x - sphere.pos.x) + ray.dir.y * (ray.pos.y - sphere.pos.y) + ray.dir.z * (ray.pos.z - sphere.pos.z));
double c = (ray.pos.x - sphere.pos.x) * (ray.pos.x - sphere.pos.x) + (ray.pos.y - sphere.pos.y) * (ray.pos.y - sphere.pos.y) + (ray.pos.z - sphere.pos.z) * (ray.pos.z - sphere.pos.z) - sphere.r * sphere.r;
double delta = b*b - 4 * a * c;
if (delta < 0) return false;
double t1 = (-b + sqrt(delta)) / (2 * a);
double t2 = (-b - sqrt(delta)) / (2 * a);
double t;
if ((t1 < t2 && t1 > EPS) || (t2 < 0 && t1 > EPS)) t = t1;
else if ((t2 < t1 && t2 > EPS) || (t1 < 0 && t2 > EPS)) t = t2;
else return false;
its->x = ray.pos.x + ray.dir.x * t;
its->y = ray.pos.y + ray.dir.y * t;
its->z = ray.pos.z + ray.dir.z * t;
normal->x = (its->x - sphere.pos.x) / sphere.r;
normal->y = (its->y - sphere.pos.y) / sphere.r;
normal->z = (its->z - sphere.pos.z) / sphere.r;
*depth = t * sqrt(a);
return true;
}
//The entry point of the program
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE, LPSTR, int) {
cv::Mat canvas(HEIGHT, WIDTH, CV_8UC3, cv::Scalar(0, 0, 0));
Vec3d light{10, 10, 10};
std::vector<Sphere*> scene;
scene.push_back(new Sphere{ Vec3d{ 0, 0, 0 }, 0.5, Color{ 100, 255, 100 } }); //Add a green sphere
scene.push_back(new Sphere{ Vec3d{ 0, 0.8, 0.8 }, 0.2, Color{ 255, 100, 100 } }); //Add a red and small sphere
scene.push_back(new Sphere{ Vec3d{ 0.8, 0.8, 0 }, 0.3, Color{ 100, 100, 255 } }); //Add a blue and small sphere
int frame = 0;
while (true) { //The main loop
canvas.setTo(cv::Scalar(0, 0, 0)); //Clear the canvas
scene[1]->pos.x = scene[2]->pos.z = 0.8 * sin(frame / 180.0 * PI);
scene[1]->pos.y = scene[2]->pos.x = 0.8 * cos(frame / 180.0 * PI);
scene[1]->pos.z = scene[2]->pos.y = cos((frame++) / 180.0 * PI); //Rotate the small spheres
Vec3d its, normal;
double depth;
for (int j = 0; j < HEIGHT; j++) {
uchar *ptr = canvas.ptr<uchar>(j);
for (int i = 0; i < WIDTH; i++) {
double pix_y = (i - WIDTH / 2.0) * UNIT_PER_PIXEL;
double pix_x = (j - HEIGHT / 2.0) * UNIT_PER_PIXEL;
Ray ray{ Vec3d{ 0, 0, 5 }, Vec3d{ pix_x, pix_y, -1 } }; //Cast each pixel to the specific ray
Vec3d its_m, normal_m;
double depth_m = std::numeric_limits<double>::max();
Sphere *hit = nullptr;
for (Sphere *sphere : scene) {
if (ray_intersect(ray, *sphere, &its, &normal, &depth) && depth < depth_m) {
its_m = its;
normal_m = normal;
depth_m = depth;
hit = sphere; //Update the nearest hit object with the smaller depth
}
}
bool is_shadow = false;
if (hit != nullptr) {
Ray light_ray{ its_m, Vec3d{ light.x - its_m.x, light.y - its_m.y, light.z - its_m.z } };
for (Sphere *sphere : scene) {
if (ray_intersect(light_ray, *sphere, &its, &normal, &depth)) {
is_shadow = true; //Determine whether the pixel is in the shadow
break;
}
}
double light_ray_len = sqrt(light_ray.dir.x * light_ray.dir.x + light_ray.dir.y * light_ray.dir.y + light_ray.dir.z * light_ray.dir.z);
double diffuse = MAX((normal_m.x * light_ray.dir.x + normal_m.y * light_ray.dir.y + normal_m.z * light_ray.dir.z) / light_ray_len, 0); //Calculate the diffuse color intensity
double intensity = (is_shadow ? diffuse / 3.0 : diffuse) + ambient; //Decrease the color intensity for the pixels in shadow
ptr[3 * i] = MIN(hit->c.b * intensity, 255);
ptr[3 * i + 1] = MIN(hit->c.g * intensity, 255);
ptr[3 * i + 2] = MIN(hit->c.r * intensity, 255);
}
}
}
cv::imshow(title, canvas); //Show the canvas in the window
if (cv::waitKey(1) == 27) break; //Press 'ESC' to quit
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment