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"; } }