Last active
December 6, 2016 13:17
-
-
Save BichengLUO/7e4c32fc11925dcb91f66fe5a0ebcff2 to your computer and use it in GitHub Desktop.
A simple ray tracer demo in 100 lines
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 <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