|
Ray Tracing
An Introduction with Examples in C++ |
|
Prof. David Bernstein |
| Computer Science Department |
| bernstdh@jmu.edu |
Ray class, an empty (for semantics
only) Material interface, a Shape3D
interface, an Intersection class,
a CompositeShape (in the sense of the composite
pattern) class, some classes that implement
the Shape3D interface, and a Light
classRayTracingMaterial interfaceRayTracingMaterial
interfaceRayTracer
Light
RayTracingMaterial Interface#ifndef edu_jmu_cs_RayTracingMaterial_h
#define edu_jmu_cs_RayTracingMaterial_h
#include <glm/glm.hpp>
#include "../Intersection.h"
#include "../Material.h"
#include "../Ray.h"
#include "RayTracer.h"
/**
* Requirements of a RayTracingMaterial.
*
* @author Prof. David Bernstein, James Madison University
*/
class RayTracingMaterial: public Material {
public:
/**
* Trace a Ray from an Intersection.
*
* @param ray The Ray
* @param intersection Information about the Intersection
* @param tracer The Tracer to use
* @param steps The number of steps taken so far
* @return The color resulting from the trace
*/
virtual glm::vec3 trace(const Ray& ray, const Intersection& intersection,
const RayTracer* tracer, int steps) const = 0;
};
#endif
Matte Class
#ifndef edu_jmu_cs_Matte_h
#define edu_jmu_cs_Matte_h
#include "RayTracingMaterial.h"
/**
* A Material that is a perfect diffuser and, hence, appears matte.
* It will have the same appearance from any angle.
*
* @author Prof. David Bernstein, James Madison University
*/
class Matte: public RayTracingMaterial {
private:
float kA, kD;
glm::vec3 diffuseColor;
public:
/**
* Explicit Value Constructor.
*
* @param kappaA The ambient parameter
* @param kappaD The diffuse parameter
* @param color The color of the Material
*/
Matte(float kappaA, float kappaD, glm::vec3 color);
virtual glm::vec3 trace(const Ray& ray, const Intersection& intersection,
const RayTracer* tracer, int steps) const;
};
#endif
Matte Class (cont.)
#include "Matte.h"
#include "../graphics3dUtilities.h"
Matte::Matte(float kappaA, float kappaD, glm::vec3 color) :
kA(kappaA), kD(kappaD), diffuseColor(color) {
}
glm::vec3 Matte::trace(const Ray& ray, const Intersection& intersection,
const RayTracer* tracer, int steps) const {
glm::vec3 lightAmt = lightIntensityAt(intersection.p, intersection.n,
tracer);
// Ambient Diffuse
return kA * glm::vec3(1.0f) + lightAmt * diffuseColor * kD;
}
glm::vec3 lightIntensityAt(const glm::vec3 &p, const glm::vec3 n,
const Tracer* tracer) {
glm::vec3 lightAmt(0.0f);
glm::vec3 oShadow = p;
for (int i = 0; i < tracer->lightsSize; ++i) {
glm::vec3 l = tracer->light[i]->location - p;
float lightDistanceSquared = dot(l, l);
l = normalize(l);
float cosl = std::max(0.0f, dot(l, n));
// Determine if the point is in the shadow of a relevant object
Intersection shadowIntersection;
bool inShadow = tracer->scene->intersectsWith(Ray(oShadow, l),
0.001, MAXFLOAT, &shadowIntersection)
&& shadowIntersection.t * shadowIntersection.t < lightDistanceSquared;
if (!inShadow)
lightAmt += glm::vec3(1.0f) * tracer->light[i]->color * cosl;
}
return lightAmt;
}
Glossy Class
#ifndef edu_jmu_cs_Glossy_h
#define edu_jmu_cs_Glossy_h
#include "RayTracingMaterial.h"
/**
* A Material that has a shine. That is, a material that has both
* diffuse reflection and specular reflection.
*
* @author Prof. David Bernstein, James Madison University
*/
class Glossy: public RayTracingMaterial {
private:
float kA, kD, kS;
glm::vec3 diffuseColor;
float specularExponent;
public:
/**
* Explicit Value Constructor.
*
* @param kappaA The ambient parameter
* @param kappaD The diffuse parameter
* @param kappaS The specular parameter
* @param color The color of the Material
* @param exp The specular exponent
*/
Glossy(float kappaA, float kappaD, float kappaS, glm::vec3 color, float exp);
virtual glm::vec3 trace(const Ray& ray, const Intersection& intersection,
const RayTracer* tracer, int steps) const;
};
#endif
Glossy Class (cont.)
#include "Glossy.h"
#include "../graphics3dUtilities.h"
Glossy::Glossy(float kappaA, float kappaD, float kappaS, glm::vec3 color,
float exp) :
kA(kappaA), kD(kappaD), kS(kappaS),
diffuseColor(color), specularExponent(exp) {
}
glm::vec3 Glossy::trace(const Ray& ray, const Intersection& intersection,
const RayTracer* tracer, int steps) const {
glm::vec3 lightAmt = lightIntensityAt(intersection.p, intersection.n,
tracer);
glm::vec3 reflected = reflect(normalize(ray.d),
intersection.n);
Ray propagated = Ray(intersection.p, reflected);
glm::vec3 specularColor(0.0f);
if (dot(propagated.d, intersection.n) > 0) {
specularColor = tracer->trace(propagated, steps+1);
}
// Ambient Diffuse Specular
return kA*glm::vec3(1.0f) + lightAmt*diffuseColor * kD+specularColor*kS;
}
Transparent Class
#ifndef edu_jmu_cs_Transparent_h
#define edu_jmu_cs_Transparent_h
#include "RayTracingMaterial.h"
/**
* A Material that is Transparent. It both reflects the incoming
* array and transmits it (after refraction).
*
* @author Prof. David Bernstein, James Madison University
*/
class Transparent: public RayTracingMaterial {
private:
float ior;
public:
/**
* Explicit Value Constructor.
*
* @param indexOfRefraction The refraction index
*/
explicit Transparent(float indexOfRefraction);
virtual glm::vec3 trace(const Ray& ray, const Intersection& intersection,
const RayTracer* tracer, int steps) const;
};
#endif
Transparent Class (cont.)
#include "Transparent.h"
#include "../graphics3dUtilities.h"
Transparent::Transparent(float indexOfRefraction): ior(indexOfRefraction) {
}
/**
* This method must trace both the reflected and transmitted ray.
*/
glm::vec3 Transparent::trace(const Ray& ray, const Intersection& intersection,
const RayTracer* tracer, int steps) const {
glm::vec3 refracted; // The refracted direction
float kR; // The proportion of the energy that is reflected
if (refract(ray.d, intersection.n, ior, &refracted)) {
fresnel(ray.d, intersection.n, ior, &kR);
} else {
kR = 1.0f;
}
kR = clamp(0.0f, 1.0f, kR);
glm::vec3 reflectedColor = glm::vec3(0.0f);
glm::vec3 refractedColor = glm::vec3(0.0f);
glm::vec3 reflected = reflect(ray.d, intersection.n);
Ray propagated;
// The reflected Ray
propagated = Ray(intersection.p, reflected);
reflectedColor = tracer->trace(propagated, steps+1);
// The transmitted (and refracted) Ray
if (kR < 1.0f) {
propagated = Ray(intersection.p, refracted);
refractedColor = tracer->trace(propagated, steps+1);
}
// Note: By conservation of energy, kT = (1 - kR)
return kR * reflectedColor + (1.0f - kR) * refractedColor;
}
bool refract(const glm::vec3 &I, const glm::vec3 &N, const float &ior,
glm::vec3* refracted) {
glm::vec3 i = normalize(I);
glm::vec3 n = N;
float etai = 1; // Assuming the ray comes from air
float etat = ior;
float cosi = clamp(-1, 1, dot(i, n));
if (cosi < 0) { // The ray is from the air to the object
cosi = -cosi;
} else { // The Ray is from the object to the air
std::swap(etai, etat);
n = -N;
}
float ratio = etai / etat;
float discriminant = 1 - ratio*ratio * (1 - cosi * cosi);
if (discriminant > 0.0f) {
*refracted = ratio * (i - cosi*n) - n * sqrtf(discriminant);
return true;
} else {
return false;
}
}
void fresnel(const glm::vec3 &I, const glm::vec3 &N,
const float &ior, float* kR) {
float cosi = clamp(-1, 1, dot(I, N));
float etai = 1, etat = ior;
if (cosi > 0) {
std::swap(etai, etat);
}
// Compute sini using Snell's law
float sint = etai / etat * sqrtf(std::max(0.f, 1 - cosi * cosi));
if (sint >= 1) { // Total internal reflection
*kR = 1;
} else {
float cost = sqrtf(std::max(0.f, 1 - sint * sint));
cosi = fabsf(cosi);
float Rs = ((etat * cosi) - (etai * cost))
/ ((etat * cosi) + (etai * cost));
float Rp = ((etai * cosi) - (etat * cost))
/ ((etai * cosi) + (etat * cost));
*kR = (Rs * Rs + Rp * Rp) / 2;
*kR = clamp(0.0f, 1.0f, *kR);
}
// kT = 1 - kR because of the conservation of energy
}
glm::vec3 lightIntensityAt(const glm::vec3 &p, const glm::vec3 n,
const Tracer* tracer) {
glm::vec3 lightAmt(0.0f);
glm::vec3 oShadow = p;
for (int i = 0; i < tracer->lightsSize; ++i) {
glm::vec3 l = tracer->light[i]->location - p;
float lightDistanceSquared = dot(l, l);
l = normalize(l);
float cosl = std::max(0.0f, dot(l, n));
// Determine if the point is in the shadow of a relevant object
Intersection shadowIntersection;
bool inShadow = tracer->scene->intersectsWith(Ray(oShadow, l),
0.001, MAXFLOAT, &shadowIntersection)
&& shadowIntersection.t * shadowIntersection.t < lightDistanceSquared;
if (!inShadow)
lightAmt += glm::vec3(1.0f) * tracer->light[i]->color * cosl;
}
return lightAmt;
}
glm::vec3 randomPointInUnitDisk() {
//Generate a point at random and see if it is in the unit disk.
// If not, try again.
// Another approach is to use the spherical Fibonacci sequence
// as discussed in Sanger et al.
glm::vec3 p;
do {
p = 2.0f * (glm::vec3(static_cast<float>(drand48()),
static_cast<float>(drand48()), 0.0f)
- glm::vec3(1.0f, 1.0f, 0.0f));
} while (dot(p, p) >= 1.0f);
return p;
}
/**
* Note: The orientation of l is reversed from some presentations.
*/
glm::vec3 reflect(const glm::vec3& l, const glm::vec3& n) {
return l - 2*dot(l, n)*n;
}
bool refract(const glm::vec3 &I, const glm::vec3 &N, const float &ior,
glm::vec3* refracted) {
glm::vec3 i = normalize(I);
glm::vec3 n = N;
float etai = 1; // Assuming the ray comes from air
float etat = ior;
float cosi = clamp(-1, 1, dot(i, n));
if (cosi < 0) { // The ray is from the air to the object
cosi = -cosi;
} else { // The Ray is from the object to the air
std::swap(etai, etat);
n = -N;
}
float ratio = etai / etat;
float discriminant = 1 - ratio*ratio * (1 - cosi * cosi);
if (discriminant > 0.0f) {
*refracted = ratio * (i - cosi*n) - n * sqrtf(discriminant);
return true;
} else {
return false;
}
}
float schlick(float cosine, float etat) {
// Assume the other material is air (with an IOR of 1)
float r0 = (1.0f - etat) / (1.0f + etat);
r0 = r0 * r0;
return r0 + (1.0f - r0)*pow((1.0f - cosine), 5.0f);
}
RayTracer Class
#ifndef edu_jmu_cs_RayTracer_h
#define edu_jmu_cs_RayTracer_h
#include <glm/glm.hpp>
#include "../Tracer.h"
/**
* An encapsulation of a simple RayTracer.
*
* @author Prof. David Bernstein, James Madison University
*/
class RayTracer: public Tracer {
private:
float bias;
glm::vec3 backgroundColor;
public:
/**
* Explicit Value Constructor.
*
* @param s The scene
* @param l The lights
* @param ls The number of lights (only one is used)
* @param ms The maximum number of steps/propagations
* @param bg The background color
*/
RayTracer(Shape3D* scene, Light** light, int ls, int ms,
glm::vec3 bg);
virtual glm::vec3 trace(const Ray& r, int steps) const;
virtual void render(Camera cam, FrameBuffer *fb, int width, int height) const;
};
#endif
RayTracer Class (cont.)
#include "../graphics3dUtilities.h"
#include "RayTracer.h"
#include "RayTracingMaterial.h"
#include <iostream>
RayTracer::RayTracer(Shape3D* s, Light** l, int ls, int ms,
glm::vec3 bg) {
scene = s;
light = l;
lightsSize = ls;
maxSteps = ms;
backgroundColor = bg;
}
glm::vec3 RayTracer::trace(const Ray& r, int steps) const {
Intersection intersection;
if (scene->intersectsWith(r, 0.001, MAXFLOAT, &intersection)) {
if (steps < maxSteps) { // Recurse
RayTracingMaterial* material =
(RayTracingMaterial*)(intersection.material);
return material->trace(r, intersection, this, steps);
} else {
// Done with the recursion so return black
return glm::vec3(0.0f, 0.0f, 0.0f);
}
} else {
// Return the color of the Ray from the Light
glm::vec3 d = normalize(r.d);
float t = 0.5*(d.y + 1.0);
return (1.0f - t) * glm::vec3(1.0f, 1.0f, 1.0f) + t * light[0]->color;
}
}
void RayTracer::render(Camera cam, FrameBuffer *fb,
int width, int height) const {
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
glm::vec3 col(0.0f, 0.0f, 0.0f);
float s = static_cast<float>(i) / static_cast<float>(width);
float t = static_cast<float>(j) / static_cast<float>(height);
Ray r = cam.getRay(s, t);
col = trace(r, 0);
int ir = clamp(0, 255, static_cast<int>(255.99*col[0]));
int ig = clamp(0, 255, static_cast<int>(255.99*col[1]));
int ib = clamp(0, 255, static_cast<int>(255.99*col[2]));
fb->setPixel(i, j, ir, ig, ib);
}
std::cout << "Row " << j << " of " << height << "\n";
}
}