// [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 .
// [/ignore]
#include
#include
float dot(float *a,float *b){
return (a[0]*b[0]+a[1]*b[1]+a[2]*b[2]);
}
void mul(float *a,float b,float *out){
out[0] = (a[0]*b);
out[1] = (a[1]*b);
out[2] = (a[2]*b);
}
void mul_vecs(float *a,float *b,float *out){
out[0] = (a[0]*b[0]);
out[1] = (a[1]*b[0]);
out[2] = (a[2]*b[0]);
}
void sub(float *a,float *b,float *out){
out[0] = (a[0] - b[0]);
out[1] = (a[1] - b[1]);
out[2] = (a[2] - b[2]);
}
void add(float *a,float *b,float *out){
out[0] = (a[0] + b[0]);
out[1] = (a[1] + b[1]);
out[2] = (a[2] + b[2]);
}
void crossProduct(float *a,float *b,float *out){
out[0] = a[1] * b[2] - a[2] * b[1];
out[1] = a[2] * b[0] - a[0] * b[2];
out[2] = a[0] * b[1] - a[1] * b[0];
}
void normalize(float *a){
float len2 = dot(a,a);
if (len2 > 0) {
float invNor = 1 / sqrt(len2);
a[0] *= invNor; a[1] *= invNor; a[2] *= invNor;
}
}
//[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);
}
bool intersect(
float *rayorig,
float *raydir,
float triangles[1][3][3],
int i,
float *tr)
{
float kEpsilon = 1e-8;
float v0[3];
float v1[3];
float v2[3];
float orig[3] = {0,0,0};
for( auto i=0; i <3 ; i++){
v0[i]=triangles[0][0][i];
v1[i]=triangles[0][1][i];
v2[i]=triangles[0][2][i];
}
float v0v1[3],v0v2[3],pvec[3],tvec[3],qvec[3];
sub(v1,v0,v0v1);
sub(v2,v0,v0v2);
crossProduct(raydir,v0v2,pvec);
float det = dot(v0v1,pvec);
// ray and triangle are parallel if det is close to 0
if (fabs(det) < kEpsilon) return false;
float invDet = 1 / det;
sub(orig , v0 , tvec);
float u = dot(pvec,tvec) * invDet;
if (u < 0 || u > 1) return false;
crossProduct(tvec,v0v1,qvec);
float v = dot(raydir,qvec) * invDet;
if (v < 0 || u + v > 1) return false;
*tr = dot(qvec,v0v2) * invDet;
// printf("%f",t);
return true;
}
//[comment]
// This is the main trace function. It takes a ray as argument (defined by its origin
// and direction). We test if this rarintersection point, otherwise it returns
// the background color.
//[/comment]
void trace(
float *rayorig,
float *raydir,
const int &depth,
float triangles[1][3][3],
float *surface_color,
float *emission_color,
float *reflectivity,
float *transparency,
int n ,
float *pixel
)
{
//if (raydir.length() != 1) std::cerr << "Error " << raydir << std::endl;
float tnear = INFINITY;
int triag = -1;
// find intersection of this ray with the sphere in the scene
for (unsigned i = 0; i < n; ++i) {
float t0 = INFINITY, t1 = INFINITY;
if (intersect(rayorig, raydir, triangles ,i , &t0)) {
// printf("%f" , vab );
if (t0 < 0) t0 = t1;
if (t0 < tnear) {
tnear = t0;
triag = i;
}
}
}
// if there's no intersection return black or background color
if (triag == -1){
pixel[0] = pixel[1] = pixel[2] = 2;
return;
}
float surfaceColor[3] = {0,0,0}; // color of the ray/surfaceof the object intersected by the ray
float phit[3];
mul(raydir, tnear , raydir);
add(rayorig , raydir ,phit); // point of intersection
float v0[3];
float v1[3];
float v2[3];
// float orig[3] = {0,0,0};
for( auto i=0; i <3 ; i++){
v0[i]=triangles[0][0][i];
v1[i]=triangles[0][1][i];
v2[i]=triangles[0][2][i];
}
float v0v1[3],v0v2[3];
sub(v1,v0,v0v1);
sub(v2,v0,v0v2);
float nhit[3];
crossProduct(v0v1,v0v2,nhit); // normal at the intersection point
normalize(nhit); // 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 (dot(raydir,nhit) > 0) mul(nhit,-1,nhit), inside = true;
if ((transparency > 0 || reflectivity > 0) && depth < MAX_RAY_DEPTH) {
float facingratio = -1*dot(raydir,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)
float refldir[3] , tmp[3] , tmp2[3] , tmp3[3];
mul(nhit , 2 * dot(raydir,nhit),tmp);
sub(raydir ,tmp , refldir);
normalize(refldir);
float reflection[3];
mul(nhit , bias,tmp);
add(phit , tmp , tmp);
trace(tmp, refldir, depth + 1 , triangles, surface_color,emission_color,reflectivity,transparency,n,reflection);
float refraction[3] = {};
// if the sphere is also transparent compute refraction ray (transmission)
if (transparency) {
float ior = 1.1, eta = (inside) ? ior : 1 / ior; // are we inside or outside the surface?
float cosi = -1* dot(nhit,raydir);
float k = 1 - eta * eta * (1 - cosi * cosi);
float refrdir[3];
mul(raydir,eta,tmp);
mul( nhit , (eta * cosi - sqrt(k)) , tmp2);
add ( tmp , tmp2 , refrdir);
normalize(refrdir);
mul(nhit , bias,tmp);
sub(phit , tmp , tmp);
trace(tmp, refrdir, depth + 1 , triangles, surface_color,emission_color,reflectivity,transparency,n,refraction);
}
// the result is a mix of reflection and refraction (if the sphere is transparent)
mul( reflection , fresneleffect , tmp);
mul( refraction , (1 - fresneleffect) * (*transparency) , tmp2);
add( tmp , tmp2 , tmp3);
mul_vecs ( tmp3 , surface_color, surfaceColor);
}
// else {
// // it's a diffuse object, no need to raytrace any further
// for (unsigned i = 0; i < n; ++i) {
// if ( emission_color[0] > 0) {
// // this is a light
// float transmission[3] = {1,1,1};
// float lightDirection[3];
// lightDirection[:] = nhit[:];
// normalize(lightDirection);
// for (unsigned j = 0; j < n; ++j) {
// if (i != j) {
// float t0;
// if (intersect(phit + nhit * bias, lightDirection, triangles, i , &t0)) {
// transmission[0] = 0;
// transmission[1] = 0;
// transmission[2] = 0;
// break;
// }
// }
// }
// surfaceColor += surface_color * transmission * std::max(float(0), dot(lightDirection,nhit)) * emission_color;
// }
// }
// }
add(surfaceColor , emission_color,pixel);
}
//[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( float triangles[1][3][3],float *surface_color,float *emission_color,float reflectivity,float transparency, int n)
{
unsigned width = 640, height = 480;
float image[width * height][3] = {};
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.);
float orig[3]= {0,0,0};
// #pragma omp parallel for collapse(2)
for (unsigned y = 0; y < height; ++y) {
for (unsigned x = 0; x < width; ++x) {
float xx = (2 * ((x + 0.5) * invWidth) - 1) * angle * aspectratio;
float yy = (1 - 2 * ((y + 0.5) * invHeight)) * angle;
float raydir[3] ={xx, yy, -1};
normalize(raydir);
trace(orig, raydir, 0 , triangles, surface_color,emission_color,&reflectivity,&transparency,n,image[ y*width + x]);
}
}
// 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][0]) * 255) <<
(unsigned char)(std::min(float(1), image[i][1]) * 255) <<
(unsigned char)(std::min(float(1), image[i][2]) * 255);
}
ofs.close();
}
//[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)
{
int n = 1;
srand48(13);
float triangles[1][3][3] = {{{-1, -1, -5} , { 1, -1, -5} , { 0, 1, -5}}};
float surface_color[3] = {5.0, -1, -15};
float emission_color[3] = {1,1,1};
float reflectivity = 0.5, transparency=0.5;
render(triangles,surface_color,emission_color,reflectivity,transparency,n);
return 0;
}