Raytracing demonstration for Pokitto

Dependencies:   PokittoLib

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 // [header]
00002 // A very basic raytracer example.
00003 // [/header]
00004 // [compile]
00005 // c++ -o raytracer -O3 -Wall raytracer.cpp
00006 // [/compile]
00007 // [ignore]
00008 // Copyright (C) 2012  www.scratchapixel.com
00009 //
00010 // This program is free software: you can redistribute it and/or modify
00011 // it under the terms of the GNU General Public License as published by
00012 // the Free Software Foundation, either version 3 of the License, or
00013 // (at your option) any later version.
00014 //
00015 // This program is distributed in the hope that it will be useful,
00016 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00017 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018 // GNU General Public License for more details.
00019 //
00020 // You should have received a copy of the GNU General Public License
00021 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
00022 // [/ignore]
00023 #include "Pokitto.h"
00024 #include <cstdlib>
00025 #include <cstdio>
00026 #include <cmath>
00027 //#include <fstream>
00028 #include <vector>
00029 //#include <iostream>
00030 //#include <cassert>
00031 
00032 Pokitto::Core g;
00033 
00034 #if defined __linux__ || defined __APPLE__
00035 // "Compiled for Linux
00036 #else
00037 // Windows doesn't define these values by default, Linux does
00038 #define M_PI 3.141592653589793
00039 #define INFINITY 1e8
00040 #endif
00041 
00042 template<typename T>
00043 class Vec3
00044 {
00045 public:
00046     T x, y, z;
00047     Vec3() : x(T(0)), y(T(0)), z(T(0)) {}
00048     Vec3(T xx) : x(xx), y(xx), z(xx) {}
00049     Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
00050     Vec3& normalize()
00051     {
00052         T nor2 = length2();
00053         if (nor2 > 0) {
00054             T invNor = 1 / sqrt(nor2);
00055             x *= invNor, y *= invNor, z *= invNor;
00056         }
00057         return *this;
00058     }
00059     Vec3<T> operator * (const T &f) const { return Vec3<T>(x * f, y * f, z * f); }
00060     Vec3<T> operator * (const Vec3<T> &v) const { return Vec3<T>(x * v.x, y * v.y, z * v.z); }
00061     T dot(const Vec3<T> &v) const { return x * v.x + y * v.y + z * v.z; }
00062     Vec3<T> operator - (const Vec3<T> &v) const { return Vec3<T>(x - v.x, y - v.y, z - v.z); }
00063     Vec3<T> operator + (const Vec3<T> &v) const { return Vec3<T>(x + v.x, y + v.y, z + v.z); }
00064     Vec3<T>& operator += (const Vec3<T> &v) { x += v.x, y += v.y, z += v.z; return *this; }
00065     Vec3<T>& operator *= (const Vec3<T> &v) { x *= v.x, y *= v.y, z *= v.z; return *this; }
00066     Vec3<T> operator - () const { return Vec3<T>(-x, -y, -z); }
00067     T length2() const { return x * x + y * y + z * z; }
00068     T length() const { return sqrt(length2()); }
00069 
00070 };
00071 
00072 typedef Vec3<float> Vec3f;
00073 
00074 class Sphere
00075 {
00076 public:
00077     Vec3f center;                           /// position of the sphere
00078     float radius, radius2;                  /// sphere radius and radius^2
00079     Vec3f surfaceColor, emissionColor;      /// surface color and emission (light)
00080     float transparency, reflection;         /// surface transparency and reflectivity
00081     Sphere(
00082         const Vec3f &c,
00083         const float &r,
00084         const Vec3f &sc,
00085         const float &refl = 0,
00086         const float &transp = 0,
00087         const Vec3f &ec = 0) :
00088         center(c), radius(r), radius2(r * r), surfaceColor(sc), emissionColor(ec),
00089         transparency(transp), reflection(refl)
00090     { /* empty */ }
00091     //[comment]
00092     // Compute a ray-sphere intersection using the geometric solution
00093     //[/comment]
00094     bool intersect(const Vec3f &rayorig, const Vec3f &raydir, float &t0, float &t1) const
00095     {
00096         Vec3f l = center - rayorig;
00097         float tca = l.dot(raydir);
00098         if (tca < 0) return false;
00099         float d2 = l.dot(l) - tca * tca;
00100         if (d2 > radius2) return false;
00101         float thc = sqrt(radius2 - d2);
00102         t0 = tca - thc;
00103         t1 = tca + thc;
00104 
00105         return true;
00106     }
00107 };
00108 
00109 //[comment]
00110 // This variable controls the maximum recursion depth
00111 //[/comment]
00112 #define MAX_RAY_DEPTH 5
00113 
00114 float mix(const float &a, const float &b, const float &mix)
00115 {
00116     return b * mix + a * (1 - mix);
00117 }
00118 
00119 //[comment]
00120 // This is the main trace function. It takes a ray as argument (defined by its origin
00121 // and direction). We test if this ray intersects any of the geometry in the scene.
00122 // If the ray intersects an object, we compute the intersection point, the normal
00123 // at the intersection point, and shade this point using this information.
00124 // Shading depends on the surface property (is it transparent, reflective, diffuse).
00125 // The function returns a color for the ray. If the ray intersects an object that
00126 // is the color of the object at the intersection point, otherwise it returns
00127 // the background color.
00128 //[/comment]
00129 Vec3f trace(
00130     const Vec3f &rayorig,
00131     const Vec3f &raydir,
00132     const std::vector<Sphere> &spheres,
00133     const int &depth)
00134 {
00135     //if (raydir.length() != 1) std::cerr << "Error " << raydir << std::endl;
00136     float tnear = INFINITY;
00137     const Sphere* sphere = NULL;
00138     // find intersection of this ray with the sphere in the scene
00139     for (unsigned i = 0; i < spheres.size(); ++i) {
00140         float t0 = INFINITY, t1 = INFINITY;
00141         if (spheres[i].intersect(rayorig, raydir, t0, t1)) {
00142             if (t0 < 0) t0 = t1;
00143             if (t0 < tnear) {
00144                 tnear = t0;
00145                 sphere = &spheres[i];
00146             }
00147         }
00148     }
00149     // if there's no intersection return black or background color
00150     if (!sphere) return Vec3f(2);
00151     Vec3f surfaceColor = 0; // color of the ray/surfaceof the object intersected by the ray
00152     Vec3f phit = rayorig + raydir * tnear; // point of intersection
00153     Vec3f nhit = phit - sphere->center; // normal at the intersection point
00154     nhit.normalize(); // normalize normal direction
00155     // If the normal and the view direction are not opposite to each other
00156     // reverse the normal direction. That also means we are inside the sphere so set
00157     // the inside bool to true. Finally reverse the sign of IdotN which we want
00158     // positive.
00159     float bias = 1e-4; // add some bias to the point from which we will be tracing
00160     bool inside = false;
00161     if (raydir.dot(nhit) > 0) nhit = -nhit, inside = true;
00162     if ((sphere->transparency > 0 || sphere->reflection > 0) && depth < MAX_RAY_DEPTH) {
00163         float facingratio = -raydir.dot(nhit);
00164         // change the mix value to tweak the effect
00165         float fresneleffect = mix(pow(1 - facingratio, 3), 1, 0.1);
00166         // compute reflection direction (not need to normalize because all vectors
00167         // are already normalized)
00168         Vec3f refldir = raydir - nhit * 2 * raydir.dot(nhit);
00169         refldir.normalize();
00170         Vec3f reflection = trace(phit + nhit * bias, refldir, spheres, depth + 1);
00171         Vec3f refraction = 0;
00172         // if the sphere is also transparent compute refraction ray (transmission)
00173         if (sphere->transparency) {
00174             float ior = 1.1, eta = (inside) ? ior : 1 / ior; // are we inside or outside the surface?
00175             float cosi = -nhit.dot(raydir);
00176             float k = 1 - eta * eta * (1 - cosi * cosi);
00177             Vec3f refrdir = raydir * eta + nhit * (eta *  cosi - sqrt(k));
00178             refrdir.normalize();
00179             refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1);
00180         }
00181         // the result is a mix of reflection and refraction (if the sphere is transparent)
00182         surfaceColor = (
00183             reflection * fresneleffect +
00184             refraction * (1 - fresneleffect) * sphere->transparency) * sphere->surfaceColor;
00185     }
00186     else {
00187         // it's a diffuse object, no need to raytrace any further
00188         for (unsigned i = 0; i < spheres.size(); ++i) {
00189             if (spheres[i].emissionColor.x > 0) {
00190                 // this is a light
00191                 Vec3f transmission = 1;
00192                 Vec3f lightDirection = spheres[i].center - phit;
00193                 lightDirection.normalize();
00194                 for (unsigned j = 0; j < spheres.size(); ++j) {
00195                     if (i != j) {
00196                         float t0, t1;
00197                         if (spheres[j].intersect(phit + nhit * bias, lightDirection, t0, t1)) {
00198                             transmission = 0;
00199                             break;
00200                         }
00201                     }
00202                 }
00203                 surfaceColor += sphere->surfaceColor * transmission *
00204                 std::max(float(0), nhit.dot(lightDirection)) * spheres[i].emissionColor;
00205             }
00206         }
00207     }
00208 
00209     return surfaceColor + sphere->emissionColor;
00210 }
00211 
00212 //[comment]
00213 // Main rendering function. We compute a camera ray for each pixel of the image
00214 // trace it and return a color. If the ray hits a sphere, we return the color of the
00215 // sphere at the intersection point, else we return the background color.
00216 //[/comment]
00217 void render(const std::vector<Sphere> &spheres, int rscale)
00218 {
00219     unsigned width = 220, height = 176;
00220     //Vec3f *image = new Vec3f[width * height], *pixel = image;
00221     Vec3f pixel; //jonne
00222     float invWidth = 1 / float(width), invHeight = 1 / float(height);
00223     float fov = 30, aspectratio = width / float(height);
00224     float angle = tan(M_PI * 0.5 * fov / 180.);
00225     // Trace rays
00226     for (unsigned y = 1; y < height+1; y+=rscale) {
00227         for (unsigned x = 1; x < width+1; x+=rscale) { //, ++pixel) {
00228             float xx = (2 * ((x + 0.5*rscale) * invWidth) - 1) * angle * aspectratio;
00229             float yy = (1 - 2 * ((y + 0.5*rscale) * invHeight)) * angle;
00230             Vec3f raydir(xx, yy, -1);
00231             raydir.normalize();
00232             //*pixel = trace(Vec3f(0), raydir, spheres, 0);
00233             pixel = trace(Vec3f(0), raydir, spheres, 0);
00234             //g.display.directPixel(x,y,g.display.RGBto565(pixel.x*255,pixel.y*255,pixel.z*255));
00235             g.display.directRectangle(x,y,x+rscale,y+rscale,g.display.RGBto565(pixel.x*255,pixel.y*255,pixel.z*255));
00236             //g.wait(100);
00237         }
00238     }
00239     // Save result to a PPM image (keep these flags if you compile under Windows)
00240     /*std::ofstream ofs("./untitled.ppm", std::ios::out | std::ios::binary);
00241     ofs << "P6\n" << width << " " << height << "\n255\n";
00242     for (unsigned i = 0; i < width * height; ++i) {
00243         ofs << (unsigned char)(std::min(float(1), image[i].x) * 255) <<
00244                (unsigned char)(std::min(float(1), image[i].y) * 255) <<
00245                (unsigned char)(std::min(float(1), image[i].z) * 255);
00246     }
00247     ofs.close();*/
00248     //delete [] image;
00249 }
00250 
00251 //[comment]
00252 // In the main function, we will create the scene which is composed of 5 spheres
00253 // and 1 light (which is also a sphere). Then, once the scene description is complete
00254 // we render that scene, by calling the render() function.
00255 //[/comment]
00256 int main()
00257 {
00258     g.begin();
00259     //srand48(13);
00260     std::vector<Sphere> spheres;
00261     // position, radius, surface color, reflectivity, transparency, emission color
00262     spheres.push_back(Sphere(Vec3f( 0.0, -10004, -20), 10000, Vec3f(0.20, 0.20, 0.20), 0, 0.0));
00263     spheres.push_back(Sphere(Vec3f( 0.0,      0, -20),     4, Vec3f(0.32, 1.00, 0.16), 1, 0.5)); //green middle sphere
00264     spheres.push_back(Sphere(Vec3f( 5.0,     -1, -15),     2, Vec3f(1.00, 0.16, 1.00), 1, 0.5)); //magenta
00265     spheres.push_back(Sphere(Vec3f( 5.0,      0, -25),     3, Vec3f(0.36, 0.16, 0.97), 1, 0.5)); //blue
00266     spheres.push_back(Sphere(Vec3f(-5.5,      0, -15),     3, Vec3f(1.00, 0.65, 0.30), 1, 0.5)); //orange
00267     // light
00268     spheres.push_back(Sphere(Vec3f( 0.0,     20, -30),     3, Vec3f(0.00, 0.00, 0.00), 0, 0.0, Vec3f(3)));
00269     render(spheres,32);
00270     render(spheres,16);
00271     render(spheres,8);
00272     render(spheres,4);
00273     render(spheres,2);
00274     render(spheres,1);
00275     while (g.isRunning()) {
00276         if(g.update(true)) {
00277 
00278         }
00279     }
00280     return 0;
00281 }