Introduction to Ray Tracing

Ray tracing is a rendering technique that simulates the path of light as it bounces around a scene. Unlike rasterization, which projects 3D objects onto a 2D screen, ray tracing follows individual rays of light to create photorealistic images with accurate reflections, refractions, and shadows.

The Basic Algorithm

At its core, ray tracing follows a simple principle: for each pixel on the screen, cast a ray from the camera through that pixel into the scene, and see what it hits.

Ray-Sphere Intersection

One of the fundamental operations in ray tracing is determining if a ray intersects with a sphere. Given a ray with origin $\mathbf{O}$ and direction $\mathbf{D}$, and a sphere with center $\mathbf{C}$ and radius $r$, we can solve for the intersection using the quadratic formula.

The ray equation is:

$$\mathbf{P}(t) = \mathbf{O} + t\mathbf{D}$$

For a sphere, we need to find where:

$$|\mathbf{P}(t) - \mathbf{C}|^2 = r^2$$

Implementation Example

Here's a simple C++ implementation of ray-sphere intersection:

struct Ray {
    Vec3 origin;
    Vec3 direction;
    
    Ray(const Vec3& o, const Vec3& d) : origin(o), direction(d) {}
    
    Vec3 at(float t) const {
        return origin + t * direction;
    }
};

struct Sphere {
    Vec3 center;
    float radius;
    
    Sphere(const Vec3& c, float r) : center(c), radius(r) {}
};

bool intersect(const Ray& ray, const Sphere& sphere, float& t) {
    Vec3 L = ray.origin - sphere.center;
    float a = dot(ray.direction, ray.direction);
    float b = 2.0f * dot(L, ray.direction);
    float c = dot(L, L) - sphere.radius * sphere.radius;
    
    float discriminant = b * b - 4 * a * c;
    
    if (discriminant < 0) {
        return false;
    }
    
    float t1 = (-b - sqrt(discriminant)) / (2 * a);
    float t2 = (-b + sqrt(discriminant)) / (2 * a);
    
    if (t1 > 0) {
        t = t1;
        return true;
    } else if (t2 > 0) {
        t = t2;
        return true;
    }
    
    return false;
}
"The best way to understand a rendering algorithm is to implement it yourself."

Surface Normals and Lighting

Once we find an intersection, we need to calculate the surface normal for lighting calculations. For a sphere, the normal at any point on the surface is simply the vector from the sphere center to that point, normalized:

Vec3 calculateNormal(const Vec3& point, const Sphere& sphere) {
    return normalize(point - sphere.center);
}

Basic Phong Lighting Model

The Phong lighting model combines three components:

  1. Ambient: Constant lighting to simulate global illumination
  2. Diffuse: Lambertian reflection based on the surface normal and light direction
  3. Specular: Mirror-like reflection for shiny surfaces

The combined lighting equation is:

$$I = I_a k_a + I_d k_d (\mathbf{N} \cdot \mathbf{L}) + I_s k_s (\mathbf{R} \cdot \mathbf{V})^n$$

Where:

  • $I_a, I_d, I_s$ are ambient, diffuse, and specular light intensities
  • $k_a, k_d, k_s$ are ambient, diffuse, and specular material coefficients
  • $\mathbf{N}$ is the surface normal
  • $\mathbf{L}$ is the light direction
  • $\mathbf{R}$ is the reflection vector
  • $\mathbf{V}$ is the view direction
  • $n$ is the shininess exponent