├── .gitignore ├── out.jpg ├── trace.png ├── anaglyph.jpg ├── Dockerfile ├── .gitpod.yml ├── CMakeLists.txt ├── Readme.md └── tinyraytracer.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore content built with CMake 2 | build/ 3 | 4 | -------------------------------------------------------------------------------- /out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssloy/tinyraytracer/HEAD/out.jpg -------------------------------------------------------------------------------- /trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssloy/tinyraytracer/HEAD/trace.png -------------------------------------------------------------------------------- /anaglyph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssloy/tinyraytracer/HEAD/anaglyph.jpg -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | USER root 4 | # add your tools here 5 | RUN apt-get update && apt-get install -y \ 6 | netpbm 7 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: Dockerfile 3 | tasks: 4 | - command: > 5 | mkdir --parents build && 6 | cd build && 7 | cmake .. && 8 | make && 9 | ./tinyraytracer && 10 | pnmtopng out.ppm > out.png && 11 | open out.png && 12 | cd .. 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8) 2 | project(tinyraytracer) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | find_package(OpenMP) 8 | if(OPENMP_FOUND) 9 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") 12 | endif() 13 | 14 | if(NOT CMAKE_BUILD_TYPE) 15 | set(CMAKE_BUILD_TYPE Release) 16 | endif() 17 | 18 | file(GLOB SOURCES *.h *.cpp) 19 | add_executable(${PROJECT_NAME} ${SOURCES}) 20 | 21 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Understandable RayTracing in 256 lines of bare C++ 2 | 3 | This repository is a support code for my computer graphics lectures. It is not meant to be the ultimate rendering code or even physically realistic. It is meant to be **simple**. This project is distributed under the [DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE](https://en.wikipedia.org/wiki/WTFPL). 4 | 5 | **Check [the wiki](https://github.com/ssloy/tinyraytracer/wiki) that accompanies the source code. The second raytracing chapter is available [in the tinykaboom repository](https://github.com/ssloy/tinykaboom/wiki). If you are looking for a software rasterizer, check the [other part of the lectures](https://github.com/ssloy/tinyrenderer/wiki).** 6 | 7 | In my lectures I tend to avoid third party libraries as long as it is reasonable, because it forces to understand what is happening under the hood. So, the raytracing 256 lines of plain C++ give us this result: 8 | ![](https://raw.githubusercontent.com/ssloy/tinyraytracer/master/out.jpg) 9 | 10 | ## compilation 11 | ```sh 12 | git clone https://github.com/ssloy/tinyraytracer.git 13 | cd tinyraytracer 14 | mkdir build 15 | cd build 16 | cmake .. 17 | make 18 | ``` 19 | 20 | You can open the project in Gitpod, a free online dev evironment for GitHub: 21 | 22 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ssloy/tinyraytracer) 23 | 24 | On open, the editor will compile & run the program as well as open the resulting image in the editor's preview. 25 | Just change the code in the editor and rerun the script (use the terminal's history) to see updated images. 26 | -------------------------------------------------------------------------------- /tinyraytracer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct vec3 { 8 | float x=0, y=0, z=0; 9 | float& operator[](const int i) { return i==0 ? x : (1==i ? y : z); } 10 | const float& operator[](const int i) const { return i==0 ? x : (1==i ? y : z); } 11 | vec3 operator*(const float v) const { return {x*v, y*v, z*v}; } 12 | float operator*(const vec3& v) const { return x*v.x + y*v.y + z*v.z; } 13 | vec3 operator+(const vec3& v) const { return {x+v.x, y+v.y, z+v.z}; } 14 | vec3 operator-(const vec3& v) const { return {x-v.x, y-v.y, z-v.z}; } 15 | vec3 operator-() const { return {-x, -y, -z}; } 16 | float norm() const { return std::sqrt(x*x+y*y+z*z); } 17 | vec3 normalized() const { return (*this)*(1.f/norm()); } 18 | }; 19 | 20 | vec3 cross(const vec3 v1, const vec3 v2) { 21 | return { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; 22 | } 23 | 24 | struct Material { 25 | float refractive_index = 1; 26 | float albedo[4] = {2,0,0,0}; 27 | vec3 diffuse_color = {0,0,0}; 28 | float specular_exponent = 0; 29 | }; 30 | 31 | struct Sphere { 32 | vec3 center; 33 | float radius; 34 | Material material; 35 | }; 36 | 37 | constexpr Material ivory = {1.0, {0.9, 0.5, 0.1, 0.0}, {0.4, 0.4, 0.3}, 50.}; 38 | constexpr Material glass = {1.5, {0.0, 0.9, 0.1, 0.8}, {0.6, 0.7, 0.8}, 125.}; 39 | constexpr Material red_rubber = {1.0, {1.4, 0.3, 0.0, 0.0}, {0.3, 0.1, 0.1}, 10.}; 40 | constexpr Material mirror = {1.0, {0.0, 16.0, 0.8, 0.0}, {1.0, 1.0, 1.0}, 1425.}; 41 | 42 | constexpr Sphere spheres[] = { 43 | {{-3, 0, -16}, 2, ivory}, 44 | {{-1.0, -1.5, -12}, 2, glass}, 45 | {{ 1.5, -0.5, -18}, 3, red_rubber}, 46 | {{ 7, 5, -18}, 4, mirror} 47 | }; 48 | 49 | constexpr vec3 lights[] = { 50 | {-20, 20, 20}, 51 | { 30, 50, -25}, 52 | { 30, 20, 30} 53 | }; 54 | 55 | vec3 reflect(const vec3 &I, const vec3 &N) { 56 | return I - N*2.f*(I*N); 57 | } 58 | 59 | vec3 refract(const vec3 &I, const vec3 &N, const float eta_t, const float eta_i=1.f) { // Snell's law 60 | float cosi = - std::max(-1.f, std::min(1.f, I*N)); 61 | if (cosi<0) return refract(I, -N, eta_i, eta_t); // if the ray comes from the inside the object, swap the air and the media 62 | float eta = eta_i / eta_t; 63 | float k = 1 - eta*eta*(1 - cosi*cosi); 64 | return k<0 ? vec3{1,0,0} : I*eta + N*(eta*cosi - std::sqrt(k)); // k<0 = total reflection, no ray to refract. I refract it anyways, this has no physical meaning 65 | } 66 | 67 | std::tuple ray_sphere_intersect(const vec3 &orig, const vec3 &dir, const Sphere &s) { // ret value is a pair [intersection found, distance] 68 | vec3 L = s.center - orig; 69 | float tca = L*dir; 70 | float d2 = L*L - tca*tca; 71 | if (d2 > s.radius*s.radius) return {false, 0}; 72 | float thc = std::sqrt(s.radius*s.radius - d2); 73 | float t0 = tca-thc, t1 = tca+thc; 74 | if (t0>.001) return {true, t0}; // offset the original point by .001 to avoid occlusion by the object itself 75 | if (t1>.001) return {true, t1}; 76 | return {false, 0}; 77 | } 78 | 79 | std::tuple scene_intersect(const vec3 &orig, const vec3 &dir) { 80 | vec3 pt, N; 81 | Material material; 82 | 83 | float nearest_dist = 1e10; 84 | if (std::abs(dir.y)>.001) { // intersect the ray with the checkerboard, avoid division by zero 85 | float d = -(orig.y+4)/dir.y; // the checkerboard plane has equation y = -4 86 | vec3 p = orig + dir*d; 87 | if (d>.001 && d-30) { 88 | nearest_dist = d; 89 | pt = p; 90 | N = {0,1,0}; 91 | material.diffuse_color = (int(.5*pt.x+1000) + int(.5*pt.z)) & 1 ? vec3{.3, .3, .3} : vec3{.3, .2, .1}; 92 | } 93 | } 94 | 95 | for (const Sphere &s : spheres) { // intersect the ray with all spheres 96 | auto [intersection, d] = ray_sphere_intersect(orig, dir, s); 97 | if (!intersection || d > nearest_dist) continue; 98 | nearest_dist = d; 99 | pt = orig + dir*nearest_dist; 100 | N = (pt - s.center).normalized(); 101 | material = s.material; 102 | } 103 | return { nearest_dist<1000, pt, N, material }; 104 | } 105 | 106 | vec3 cast_ray(const vec3 &orig, const vec3 &dir, const int depth=0) { 107 | auto [hit, point, N, material] = scene_intersect(orig, dir); 108 | if (depth>4 || !hit) 109 | return {0.2, 0.7, 0.8}; // background color 110 | 111 | vec3 reflect_dir = reflect(dir, N).normalized(); 112 | vec3 refract_dir = refract(dir, N, material.refractive_index).normalized(); 113 | vec3 reflect_color = cast_ray(point, reflect_dir, depth + 1); 114 | vec3 refract_color = cast_ray(point, refract_dir, depth + 1); 115 | 116 | float diffuse_light_intensity = 0, specular_light_intensity = 0; 117 | for (const vec3 &light : lights) { // checking if the point lies in the shadow of the light 118 | vec3 light_dir = (light - point).normalized(); 119 | auto [hit, shadow_pt, trashnrm, trashmat] = scene_intersect(point, light_dir); 120 | if (hit && (shadow_pt-point).norm() < (light-point).norm()) continue; 121 | diffuse_light_intensity += std::max(0.f, light_dir*N); 122 | specular_light_intensity += std::pow(std::max(0.f, -reflect(-light_dir, N)*dir), material.specular_exponent); 123 | } 124 | return material.diffuse_color * diffuse_light_intensity * material.albedo[0] + vec3{1., 1., 1.}*specular_light_intensity * material.albedo[1] + reflect_color*material.albedo[2] + refract_color*material.albedo[3]; 125 | } 126 | 127 | int main() { 128 | constexpr int width = 1024; 129 | constexpr int height = 768; 130 | constexpr float fov = 1.05; // 60 degrees field of view in radians 131 | std::vector framebuffer(width*height); 132 | #pragma omp parallel for 133 | for (int pix = 0; pix