JMU
Ray Tracing
An Introduction with Examples in C++


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Review
Designing and Implementing a Ray Tracer
The RayTracingMaterial Interface
cppexamples/graphics3d/raytracing/RayTracingMaterial.h
        #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
        
The Matte Class

The Specification

cppexamples/graphics3d/raytracing/Matte.h
        #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
        
The Matte Class (cont.)

The Implementation

cppexamples/graphics3d/raytracing/Matte.cpp
        #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;
}
        
The Light Intensity

For Multiple Possible Light Sources (Even Though There Is Only One)

cppexamples/graphics3d/graphics3dUtilities.cpp (Fragment: lightIntensityAt)
        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;
}
        
The Glossy Class

The Specification

cppexamples/graphics3d/raytracing/Glossy.h
        #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
        
The Glossy Class (cont.)

The Implementation

cppexamples/graphics3d/raytracing/Glossy.cpp
        #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;
}
        
Reflections
cppexamples/graphics3d/graphics3dUtilities.cpp (Fragment: reflect)
        /**
 * 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;
}
        
The Transparent Class

The Specification

cppexamples/graphics3d/raytracing/Transparent.h
        #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
        
The Transparent Class (cont.)

The Implementation

cppexamples/graphics3d/raytracing/Transparent.cpp
        #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;
}

        
Refraction
cppexamples/graphics3d/graphics3dUtilities.cpp (Fragment: refract)
        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;
  }
}
        
Reflection/Transmission Proportion
cppexamples/graphics3d/graphics3dUtilities.cpp (Fragment: fresnel)
        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);
}

        
The RayTracer Class

The Specification

cppexamples/graphics3d/raytracing/RayTracer.h
        #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
        
The RayTracer Class (cont.)

The Implementation

cppexamples/graphics3d/raytracing/RayTracer.cpp
        #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";
  }
}

        
An Example
images/ray-tracing_spheres.png