├── .gitpod.yml ├── CMakeLists.txt ├── Dockerfile ├── Readme.md ├── geometry.h ├── kaboom.gif ├── kaboom.mp4 ├── out.jpg └── tinykaboom.cpp /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: Dockerfile 3 | tasks: 4 | - command: > 5 | mkdir --parents build && 6 | cd build && 7 | cmake .. && 8 | make && 9 | ./tinykaboom && 10 | pnmtopng out.ppm > out.png && 11 | open out.png && 12 | cd .. 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8) 2 | project (tinykaboom) 3 | 4 | include(CheckCXXCompilerFlag) 5 | 6 | function(enable_cxx_compiler_flag_if_supported flag) 7 | string(FIND "${CMAKE_CXX_FLAGS}" "${flag}" flag_already_set) 8 | if(flag_already_set EQUAL -1) 9 | check_cxx_compiler_flag("${flag}" flag_supported) 10 | if(flag_supported) 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}" PARENT_SCOPE) 12 | endif() 13 | unset(flag_supported CACHE) 14 | endif() 15 | endfunction() 16 | 17 | enable_cxx_compiler_flag_if_supported("-Wall") 18 | enable_cxx_compiler_flag_if_supported("-Wextra") 19 | enable_cxx_compiler_flag_if_supported("-pedantic") 20 | enable_cxx_compiler_flag_if_supported("-std=c++11") 21 | enable_cxx_compiler_flag_if_supported("-O3") 22 | enable_cxx_compiler_flag_if_supported("-fopenmp") 23 | 24 | file(GLOB SOURCES *.h *.cpp) 25 | 26 | add_executable(${PROJECT_NAME} ${SOURCES}) 27 | 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # KABOOM! in 180 lines of bare C++ 2 | 3 | This repository is a teaching aid for my computer graphics lectures. It is not meant to produce the ultimate or even physically realistic renders. 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 article](https://github.com/ssloy/tinykaboom/wiki) that accompanies the source code.** 6 | 7 | This project is closely related to my [software raytracer](https://github.com/ssloy/tinyraytracer/wiki). If you are looking for a software rasterizer, check the [other part of the lectures](https://github.com/ssloy/tinyrenderer/wiki). 8 | 9 | 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 in 180 lines of plain C++ produces this result: 10 | ![](https://raw.githubusercontent.com/ssloy/tinykaboom/master/out.jpg) 11 | 12 | ## compilation 13 | ```sh 14 | git clone https://github.com/ssloy/tinykaboom.git 15 | cd tinykaboom 16 | mkdir build 17 | cd build 18 | cmake .. 19 | make 20 | ``` 21 | 22 | You can open the project in Gitpod, a free online dev evironment for GitHub: 23 | 24 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ssloy/tinykaboom) 25 | 26 | On open, the editor will compile & run the program as well as open the resulting image in the editor's preview. 27 | Just change the code in the editor and rerun the script (use the terminal's history) to see updated images. 28 | 29 | ## Homework 30 | The possibilities are infinte. For example, you can add the environment map and some transparency: 31 | ![](https://raw.githubusercontent.com/ssloy/tinykaboom/homework_assignment/envmap1.jpg) 32 | 33 | Add other objects and illuminate them: 34 | ![](https://raw.githubusercontent.com/ssloy/tinykaboom/homework_assignment/envmap2.jpg) 35 | -------------------------------------------------------------------------------- /geometry.h: -------------------------------------------------------------------------------- 1 | #ifndef __GEOMETRY_H__ 2 | #define __GEOMETRY_H__ 3 | #include 4 | #include 5 | 6 | template struct vec { 7 | vec() { for (size_t i=DIM; i--; data_[i] = T()); } 8 | T& operator[](const size_t i) { assert(i struct vec<3,T> { 15 | vec() : x(T()), y(T()), z(T()) {} 16 | vec(T X, T Y, T Z) : x(X), y(Y), z(Z) {} 17 | T& operator[](const size_t i) { assert(i<3); return i<=0 ? x : (1==i ? y : z); } 18 | const T& operator[](const size_t i) const { assert(i<3); return i<=0 ? x : (1==i ? y : z); } 19 | float norm() const { return std::sqrt(x*x+y*y+z*z); } 20 | vec<3,T> & normalize(T l=1) { *this = (*this)*(l/norm()); return *this; } 21 | T x,y,z; 22 | }; 23 | 24 | template T operator*(const vec& lhs, const vec& rhs) { 25 | T ret = T(); 26 | for (size_t i=DIM; i--; ret+=lhs[i]*rhs[i]); 27 | return ret; 28 | } 29 | 30 | templatevec operator+(vec lhs, const vec& rhs) { 31 | for (size_t i=DIM; i--; lhs[i]+=rhs[i]); 32 | return lhs; 33 | } 34 | 35 | templatevec operator-(vec lhs, const vec& rhs) { 36 | for (size_t i=DIM; i--; lhs[i]-=rhs[i]); 37 | return lhs; 38 | } 39 | 40 | template vec operator*(const vec &lhs, const U& rhs) { 41 | vec ret; 42 | for (size_t i=DIM; i--; ret[i]=lhs[i]*rhs); 43 | return ret; 44 | } 45 | 46 | template vec operator-(const vec &lhs) { 47 | return lhs*T(-1); 48 | } 49 | 50 | typedef vec<3, float> Vec3f; 51 | 52 | #endif //__GEOMETRY_H__ 53 | 54 | -------------------------------------------------------------------------------- /kaboom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssloy/tinykaboom/80065da77975bf36b26e7c2a564f60ef2a569007/kaboom.gif -------------------------------------------------------------------------------- /kaboom.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssloy/tinykaboom/80065da77975bf36b26e7c2a564f60ef2a569007/kaboom.mp4 -------------------------------------------------------------------------------- /out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssloy/tinykaboom/80065da77975bf36b26e7c2a564f60ef2a569007/out.jpg -------------------------------------------------------------------------------- /tinykaboom.cpp: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "geometry.h" 9 | 10 | const float sphere_radius = 1.5; // all the explosion fits in a sphere with this radius. The center lies in the origin. 11 | const float noise_amplitude = 1.0; // amount of noise applied to the sphere (towards the center) 12 | 13 | template inline T lerp(const T &v0, const T &v1, float t) { 14 | return v0 + (v1-v0)*std::max(0.f, std::min(1.f, t)); 15 | } 16 | 17 | float hash(const float n) { 18 | float x = sin(n)*43758.5453f; 19 | return x-floor(x); 20 | } 21 | 22 | float noise(const Vec3f &x) { 23 | Vec3f p(floor(x.x), floor(x.y), floor(x.z)); 24 | Vec3f f(x.x-p.x, x.y-p.y, x.z-p.z); 25 | f = f*(f*(Vec3f(3.f, 3.f, 3.f)-f*2.f)); 26 | float n = p*Vec3f(1.f, 57.f, 113.f); 27 | return lerp(lerp( 28 | lerp(hash(n + 0.f), hash(n + 1.f), f.x), 29 | lerp(hash(n + 57.f), hash(n + 58.f), f.x), f.y), 30 | lerp( 31 | lerp(hash(n + 113.f), hash(n + 114.f), f.x), 32 | lerp(hash(n + 170.f), hash(n + 171.f), f.x), f.y), f.z); 33 | } 34 | 35 | Vec3f rotate(const Vec3f &v) { 36 | return Vec3f(Vec3f(0.00, 0.80, 0.60)*v, Vec3f(-0.80, 0.36, -0.48)*v, Vec3f(-0.60, -0.48, 0.64)*v); 37 | } 38 | 39 | float fractal_brownian_motion(const Vec3f &x) { // this is a bad noise function with lots of artifacts. TODO: find a better one 40 | Vec3f p = rotate(x); 41 | float f = 0; 42 | f += 0.5000*noise(p); p = p*2.32; 43 | f += 0.2500*noise(p); p = p*3.03; 44 | f += 0.1250*noise(p); p = p*2.61; 45 | f += 0.0625*noise(p); 46 | return f/0.9375; 47 | } 48 | 49 | Vec3f palette_fire(const float d) { // simple linear gradent yellow-orange-red-darkgray-gray. d is supposed to vary from 0 to 1 50 | const Vec3f yellow(1.7, 1.3, 1.0); // note that the color is "hot", i.e. has components >1 51 | const Vec3f orange(1.0, 0.6, 0.0); 52 | const Vec3f red(1.0, 0.0, 0.0); 53 | const Vec3f darkgray(0.2, 0.2, 0.2); 54 | const Vec3f gray(0.4, 0.4, 0.4); 55 | 56 | float x = std::max(0.f, std::min(1.f, d)); 57 | if (x<.25f) 58 | return lerp(gray, darkgray, x*4.f); 59 | else if (x<.5f) 60 | return lerp(darkgray, red, x*4.f-1.f); 61 | else if (x<.75f) 62 | return lerp(red, orange, x*4.f-2.f); 63 | return lerp(orange, yellow, x*4.f-3.f); 64 | } 65 | 66 | float signed_distance(const Vec3f &p) { // this function defines the implicit surface we render 67 | float displacement = -fractal_brownian_motion(p*3.4)*noise_amplitude; 68 | return p.norm() - (sphere_radius + displacement); 69 | } 70 | 71 | bool sphere_trace(const Vec3f &orig, const Vec3f &dir, Vec3f &pos) { // Notice the early discard; in fact I know that the noise() function produces non-negative values, 72 | if (orig*orig - pow(orig*dir, 2) > pow(sphere_radius, 2)) return false; // thus all the explosion fits in the sphere. Thus this early discard is a conservative check. 73 | // It is not necessary, just a small speed-up 74 | pos = orig; 75 | for (size_t i=0; i<128; i++) { 76 | float d = signed_distance(pos); 77 | if (d < 0) return true; 78 | pos = pos + dir*std::max(d*0.1f, .01f); // note that the step depends on the current distance, if we are far from the surface, we can do big steps 79 | } 80 | return false; 81 | } 82 | 83 | Vec3f distance_field_normal(const Vec3f &pos) { // simple finite differences, very sensitive to the choice of the eps constant 84 | const float eps = 0.1; 85 | float d = signed_distance(pos); 86 | float nx = signed_distance(pos + Vec3f(eps, 0, 0)) - d; 87 | float ny = signed_distance(pos + Vec3f(0, eps, 0)) - d; 88 | float nz = signed_distance(pos + Vec3f(0, 0, eps)) - d; 89 | return Vec3f(nx, ny, nz).normalize(); 90 | } 91 | 92 | int main() { 93 | const int width = 640; // image width 94 | const int height = 480; // image height 95 | const float fov = M_PI/3.; // field of view angle 96 | std::vector framebuffer(width*height); 97 | 98 | #pragma omp parallel for 99 | for (size_t j = 0; j(255*framebuffer[i][j])))); 121 | } 122 | } 123 | ofs.close(); 124 | 125 | return 0; 126 | } 127 | 128 | --------------------------------------------------------------------------------