Raytracing demonstration for Pokitto

Dependencies:   PokittoLib

Revision:
0:398e251490fd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Fri Oct 13 23:37:15 2017 +0000
@@ -0,0 +1,281 @@
+// [header]
+// A very basic raytracer example.
+// [/header]
+// [compile]
+// c++ -o raytracer -O3 -Wall raytracer.cpp
+// [/compile]
+// [ignore]
+// Copyright (C) 2012  www.scratchapixel.com
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+// [/ignore]
+#include "Pokitto.h"
+#include <cstdlib>
+#include <cstdio>
+#include <cmath>
+//#include <fstream>
+#include <vector>
+//#include <iostream>
+//#include <cassert>
+
+Pokitto::Core g;
+
+#if defined __linux__ || defined __APPLE__
+// "Compiled for Linux
+#else
+// Windows doesn't define these values by default, Linux does
+#define M_PI 3.141592653589793
+#define INFINITY 1e8
+#endif
+
+template<typename T>
+class Vec3
+{
+public:
+    T x, y, z;
+    Vec3() : x(T(0)), y(T(0)), z(T(0)) {}
+    Vec3(T xx) : x(xx), y(xx), z(xx) {}
+    Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {}
+    Vec3& normalize()
+    {
+        T nor2 = length2();
+        if (nor2 > 0) {
+            T invNor = 1 / sqrt(nor2);
+            x *= invNor, y *= invNor, z *= invNor;
+        }
+        return *this;
+    }
+    Vec3<T> operator * (const T &f) const { return Vec3<T>(x * f, y * f, z * f); }
+    Vec3<T> operator * (const Vec3<T> &v) const { return Vec3<T>(x * v.x, y * v.y, z * v.z); }
+    T dot(const Vec3<T> &v) const { return x * v.x + y * v.y + z * v.z; }
+    Vec3<T> operator - (const Vec3<T> &v) const { return Vec3<T>(x - v.x, y - v.y, z - v.z); }
+    Vec3<T> operator + (const Vec3<T> &v) const { return Vec3<T>(x + v.x, y + v.y, z + v.z); }
+    Vec3<T>& operator += (const Vec3<T> &v) { x += v.x, y += v.y, z += v.z; return *this; }
+    Vec3<T>& operator *= (const Vec3<T> &v) { x *= v.x, y *= v.y, z *= v.z; return *this; }
+    Vec3<T> operator - () const { return Vec3<T>(-x, -y, -z); }
+    T length2() const { return x * x + y * y + z * z; }
+    T length() const { return sqrt(length2()); }
+
+};
+
+typedef Vec3<float> Vec3f;
+
+class Sphere
+{
+public:
+    Vec3f center;                           /// position of the sphere
+    float radius, radius2;                  /// sphere radius and radius^2
+    Vec3f surfaceColor, emissionColor;      /// surface color and emission (light)
+    float transparency, reflection;         /// surface transparency and reflectivity
+    Sphere(
+        const Vec3f &c,
+        const float &r,
+        const Vec3f &sc,
+        const float &refl = 0,
+        const float &transp = 0,
+        const Vec3f &ec = 0) :
+        center(c), radius(r), radius2(r * r), surfaceColor(sc), emissionColor(ec),
+        transparency(transp), reflection(refl)
+    { /* empty */ }
+    //[comment]
+    // Compute a ray-sphere intersection using the geometric solution
+    //[/comment]
+    bool intersect(const Vec3f &rayorig, const Vec3f &raydir, float &t0, float &t1) const
+    {
+        Vec3f l = center - rayorig;
+        float tca = l.dot(raydir);
+        if (tca < 0) return false;
+        float d2 = l.dot(l) - tca * tca;
+        if (d2 > radius2) return false;
+        float thc = sqrt(radius2 - d2);
+        t0 = tca - thc;
+        t1 = tca + thc;
+
+        return true;
+    }
+};
+
+//[comment]
+// This variable controls the maximum recursion depth
+//[/comment]
+#define MAX_RAY_DEPTH 5
+
+float mix(const float &a, const float &b, const float &mix)
+{
+    return b * mix + a * (1 - mix);
+}
+
+//[comment]
+// This is the main trace function. It takes a ray as argument (defined by its origin
+// and direction). We test if this ray intersects any of the geometry in the scene.
+// If the ray intersects an object, we compute the intersection point, the normal
+// at the intersection point, and shade this point using this information.
+// Shading depends on the surface property (is it transparent, reflective, diffuse).
+// The function returns a color for the ray. If the ray intersects an object that
+// is the color of the object at the intersection point, otherwise it returns
+// the background color.
+//[/comment]
+Vec3f trace(
+    const Vec3f &rayorig,
+    const Vec3f &raydir,
+    const std::vector<Sphere> &spheres,
+    const int &depth)
+{
+    //if (raydir.length() != 1) std::cerr << "Error " << raydir << std::endl;
+    float tnear = INFINITY;
+    const Sphere* sphere = NULL;
+    // find intersection of this ray with the sphere in the scene
+    for (unsigned i = 0; i < spheres.size(); ++i) {
+        float t0 = INFINITY, t1 = INFINITY;
+        if (spheres[i].intersect(rayorig, raydir, t0, t1)) {
+            if (t0 < 0) t0 = t1;
+            if (t0 < tnear) {
+                tnear = t0;
+                sphere = &spheres[i];
+            }
+        }
+    }
+    // if there's no intersection return black or background color
+    if (!sphere) return Vec3f(2);
+    Vec3f surfaceColor = 0; // color of the ray/surfaceof the object intersected by the ray
+    Vec3f phit = rayorig + raydir * tnear; // point of intersection
+    Vec3f nhit = phit - sphere->center; // normal at the intersection point
+    nhit.normalize(); // normalize normal direction
+    // If the normal and the view direction are not opposite to each other
+    // reverse the normal direction. That also means we are inside the sphere so set
+    // the inside bool to true. Finally reverse the sign of IdotN which we want
+    // positive.
+    float bias = 1e-4; // add some bias to the point from which we will be tracing
+    bool inside = false;
+    if (raydir.dot(nhit) > 0) nhit = -nhit, inside = true;
+    if ((sphere->transparency > 0 || sphere->reflection > 0) && depth < MAX_RAY_DEPTH) {
+        float facingratio = -raydir.dot(nhit);
+        // change the mix value to tweak the effect
+        float fresneleffect = mix(pow(1 - facingratio, 3), 1, 0.1);
+        // compute reflection direction (not need to normalize because all vectors
+        // are already normalized)
+        Vec3f refldir = raydir - nhit * 2 * raydir.dot(nhit);
+        refldir.normalize();
+        Vec3f reflection = trace(phit + nhit * bias, refldir, spheres, depth + 1);
+        Vec3f refraction = 0;
+        // if the sphere is also transparent compute refraction ray (transmission)
+        if (sphere->transparency) {
+            float ior = 1.1, eta = (inside) ? ior : 1 / ior; // are we inside or outside the surface?
+            float cosi = -nhit.dot(raydir);
+            float k = 1 - eta * eta * (1 - cosi * cosi);
+            Vec3f refrdir = raydir * eta + nhit * (eta *  cosi - sqrt(k));
+            refrdir.normalize();
+            refraction = trace(phit - nhit * bias, refrdir, spheres, depth + 1);
+        }
+        // the result is a mix of reflection and refraction (if the sphere is transparent)
+        surfaceColor = (
+            reflection * fresneleffect +
+            refraction * (1 - fresneleffect) * sphere->transparency) * sphere->surfaceColor;
+    }
+    else {
+        // it's a diffuse object, no need to raytrace any further
+        for (unsigned i = 0; i < spheres.size(); ++i) {
+            if (spheres[i].emissionColor.x > 0) {
+                // this is a light
+                Vec3f transmission = 1;
+                Vec3f lightDirection = spheres[i].center - phit;
+                lightDirection.normalize();
+                for (unsigned j = 0; j < spheres.size(); ++j) {
+                    if (i != j) {
+                        float t0, t1;
+                        if (spheres[j].intersect(phit + nhit * bias, lightDirection, t0, t1)) {
+                            transmission = 0;
+                            break;
+                        }
+                    }
+                }
+                surfaceColor += sphere->surfaceColor * transmission *
+                std::max(float(0), nhit.dot(lightDirection)) * spheres[i].emissionColor;
+            }
+        }
+    }
+
+    return surfaceColor + sphere->emissionColor;
+}
+
+//[comment]
+// Main rendering function. We compute a camera ray for each pixel of the image
+// trace it and return a color. If the ray hits a sphere, we return the color of the
+// sphere at the intersection point, else we return the background color.
+//[/comment]
+void render(const std::vector<Sphere> &spheres, int rscale)
+{
+    unsigned width = 220, height = 176;
+    //Vec3f *image = new Vec3f[width * height], *pixel = image;
+    Vec3f pixel; //jonne
+    float invWidth = 1 / float(width), invHeight = 1 / float(height);
+    float fov = 30, aspectratio = width / float(height);
+    float angle = tan(M_PI * 0.5 * fov / 180.);
+    // Trace rays
+    for (unsigned y = 1; y < height+1; y+=rscale) {
+        for (unsigned x = 1; x < width+1; x+=rscale) { //, ++pixel) {
+            float xx = (2 * ((x + 0.5*rscale) * invWidth) - 1) * angle * aspectratio;
+            float yy = (1 - 2 * ((y + 0.5*rscale) * invHeight)) * angle;
+            Vec3f raydir(xx, yy, -1);
+            raydir.normalize();
+            //*pixel = trace(Vec3f(0), raydir, spheres, 0);
+            pixel = trace(Vec3f(0), raydir, spheres, 0);
+            //g.display.directPixel(x,y,g.display.RGBto565(pixel.x*255,pixel.y*255,pixel.z*255));
+            g.display.directRectangle(x,y,x+rscale,y+rscale,g.display.RGBto565(pixel.x*255,pixel.y*255,pixel.z*255));
+            //g.wait(100);
+        }
+    }
+    // Save result to a PPM image (keep these flags if you compile under Windows)
+    /*std::ofstream ofs("./untitled.ppm", std::ios::out | std::ios::binary);
+    ofs << "P6\n" << width << " " << height << "\n255\n";
+    for (unsigned i = 0; i < width * height; ++i) {
+        ofs << (unsigned char)(std::min(float(1), image[i].x) * 255) <<
+               (unsigned char)(std::min(float(1), image[i].y) * 255) <<
+               (unsigned char)(std::min(float(1), image[i].z) * 255);
+    }
+    ofs.close();*/
+    //delete [] image;
+}
+
+//[comment]
+// In the main function, we will create the scene which is composed of 5 spheres
+// and 1 light (which is also a sphere). Then, once the scene description is complete
+// we render that scene, by calling the render() function.
+//[/comment]
+int main()
+{
+    g.begin();
+    //srand48(13);
+    std::vector<Sphere> spheres;
+    // position, radius, surface color, reflectivity, transparency, emission color
+    spheres.push_back(Sphere(Vec3f( 0.0, -10004, -20), 10000, Vec3f(0.20, 0.20, 0.20), 0, 0.0));
+    spheres.push_back(Sphere(Vec3f( 0.0,      0, -20),     4, Vec3f(0.32, 1.00, 0.16), 1, 0.5)); //green middle sphere
+    spheres.push_back(Sphere(Vec3f( 5.0,     -1, -15),     2, Vec3f(1.00, 0.16, 1.00), 1, 0.5)); //magenta
+    spheres.push_back(Sphere(Vec3f( 5.0,      0, -25),     3, Vec3f(0.36, 0.16, 0.97), 1, 0.5)); //blue
+    spheres.push_back(Sphere(Vec3f(-5.5,      0, -15),     3, Vec3f(1.00, 0.65, 0.30), 1, 0.5)); //orange
+    // light
+    spheres.push_back(Sphere(Vec3f( 0.0,     20, -30),     3, Vec3f(0.00, 0.00, 0.00), 0, 0.0, Vec3f(3)));
+    render(spheres,32);
+    render(spheres,16);
+    render(spheres,8);
+    render(spheres,4);
+    render(spheres,2);
+    render(spheres,1);
+    while (g.isRunning()) {
+        if(g.update(true)) {
+
+        }
+    }
+    return 0;
+}
\ No newline at end of file