Skip to content

Instantly share code, notes, and snippets.

@cfoch
Created November 24, 2017 22:53
Show Gist options
  • Save cfoch/996d65fbc07700ec1fd0491587059518 to your computer and use it in GitHub Desktop.
Save cfoch/996d65fbc07700ec1fd0491587059518 to your computer and use it in GitHub Desktop.
// 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 <cstdlib>
#include <cstdio>
#include <cmath>
#include <fstream>
#include <vector>
#include <iostream>
#include <cassert>
#include <vector>
#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> cross(const Vec3<T> &v) const {
return Vec3<T>(y * v.z - z * v.y, -x * v.z + z * v.x, x * v.y - y * v.x);
}
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()); }
friend std::ostream & operator << (std::ostream &os, const Vec3<T> &v)
{
os << "[" << v.x << " " << v.y << " " << v.z << "]";
return os;
}
};
typedef Vec3<float> Vec3f;
#define EPSILON 0.004
class Primitive {
public:
// surface color and emission (light)
Vec3f surfaceColor, emissionColor;
// urface transparency and reflectivity
float transparency, reflection;
Primitive(Vec3f surfaceColor, Vec3f emissionColor, float transparency,
float reflection)
: surfaceColor(surfaceColor),
emissionColor(emissionColor),
transparency(transparency),
reflection(reflection)
{
}
bool intersect(const Vec3f &rayOrigin, const Vec3f &rayDirection,
std::vector<float> &distances) const
{
std::cout << "hereInSphere\n";
return false;
}
virtual Vec3f getCenter() const {
// FIXME
return Vec3f(0, 0, 0);
}
};
class Triangle : public Primitive {
std::vector<Vec3f> vertices;
Triangle(const std::vector<Vec3f> &vertices, const Vec3f &surfaceColor,
const Vec3f emissionColor, const float transparency,
const float reflection)
: Primitive(surfaceColor, emissionColor, transparency, reflection),
vertices(vertices)
{
// Empty.
}
Vec3f getCenter() const {
float x, y, z;
x = vertices[0].x + vertices[1].x + vertices[2].x;
y = vertices[0].y + vertices[1].y + vertices[2].y;
z = vertices[0].z + vertices[1].z + vertices[2].z;
return Vec3f(x, y, z);
}
bool intersect(const Vec3f &rayOrigin, const Vec3f &rayDirection,
std::vector<float> &distances) const
{
Vec3f p0p1, p1p2, p2p0, normal, intp, p0intp, p1intp, p2intp, c;
float d, normal_dot_rayDirection;
float t;
//distances = new std::vector<float>();
p0p1 = vertices[1] - vertices[0];
p1p2 = vertices[2] - vertices[1];
p2p0 = vertices[0] - vertices[2];
normal = p0p1.cross(p1p2);
normal_dot_rayDirection = normal.dot(rayDirection);
if (abs(normal_dot_rayDirection) < M_PI)
// Ray and plane are parallels.
return false;
d = normal.dot(vertices[0]);
t = (normal.dot(rayOrigin) + d) / normal_dot_rayDirection;
distances.push_back(t);
if (t < 0)
// The triangle is behind.
return false;
// The intersection point.
intp = rayOrigin + rayDirection * t;
// Inside-outside test.
p0intp = intp - vertices[0];
p1intp = intp - vertices[1];
p2intp = intp - vertices[2];
// Test first edge.
c = p0p1.cross(p0intp);
if (normal.dot(c) < 0)
return false;
// Test second edge.
c = p1p2.cross(p1intp);
if (normal.dot(c) < 0)
return false;
// Test third edge.
c = p2p0.cross(p2intp);
if (normal.dot(c) < 0)
return false;
return true;
}
};
class Sphere : public Primitive
{
public:
Vec3f center; /// position of the sphere
float radius, radius2; /// sphere radius and radius^2
Sphere(
const Vec3f &c,
const float &r,
const Vec3f &sc,
const float &refl = 0,
const float &transp = 0,
const Vec3f &ec = 0) :
Primitive(sc, ec, transp, refl),
center(c), radius(r), radius2(r * r)
{ /* empty */ }
//[comment]
// Compute a ray-sphere intersection using the geometric solution
//[/comment]
bool intersect(const Vec3f &rayorig, const Vec3f &raydir,
std::vector<float> &distances) const
{
std::cout << "hereInSphere\n";
Vec3f l = center - rayorig;
//distances = new std::vector<float>();
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);
float t0 = tca - thc;
float t1 = tca + thc;
distances.push_back(t0);
distances.push_back(t1);
return true;
}
Vec3f getCenter() const {
return Vec3f(center.x, center.y, center.z);
}
};
//[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<Primitive> &spheres,
const int &depth)
{
//if (raydir.length() != 1) std::cerr << "Error " << raydir << std::endl;
float tnear = INFINITY;
const Primitive* 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;
std::vector<float> distances;
if (spheres[i].intersect(rayorig, raydir, distances)) {
std::cout << "bar" << std::endl;
float t0 = distances[0], t1 = distances[1];
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->getCenter(); // normal at the intersection point
std::cout << "center: " << std::endl;
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].getCenter() - phit;
lightDirection.normalize();
for (unsigned j = 0; j < spheres.size(); ++j) {
if (i != j) {
std::vector<float> distances;
if (spheres[j].intersect(phit + nhit * bias, lightDirection, distances)) {
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<Primitive> &spheres)
{
unsigned width = 640, height = 480;
Vec3f *image = new Vec3f[width * height], *pixel = image;
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 = 0; y < height; ++y) {
for (unsigned x = 0; x < width; ++x, ++pixel) {
float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
Vec3f raydir(xx, yy, -1);
raydir.normalize();
*pixel = trace(Vec3f(0), raydir, spheres, 0);
}
}
// 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(int argc, char **argv)
{
//srand48(13);
std::vector<Primitive> 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(1.00, 0.32, 0.36), 1, 0.5));
spheres.push_back(Sphere(Vec3f( 5.0, -1, -15), 2, Vec3f(0.90, 0.76, 0.46), 1, 0.0));
spheres.push_back(Sphere(Vec3f( 5.0, 0, -25), 3, Vec3f(0.65, 0.77, 0.97), 1, 0.0));
spheres.push_back(Sphere(Vec3f(-5.5, 0, -15), 3, Vec3f(0.90, 0.90, 0.90), 1, 0.0));
// 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);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment