A Minimal Ray-Tracer: Rendering Simple Shapes (Sphere, Cube, Disk, Plane, etc.)

## Ray-Plane Intersection

Figure 1: ray-plane intersection.

In this chapter we will learn how to compute the intersection of a ray with a plane and a disk. We know from the lesson on Geometry that the dot (or scalar) product of two vectors which are perpendicular to each other is always equal to 0:

$$A \cdot B = 0$$

Again this is only true if A and B are perpendicular. A plane can be defined as a point representing how far the plane is from the world origin and a normal (defining the orientation of the plane). Let's call this point $p_0$ and the plane normal $n$. A vector can be computed from any point on the plane by subtracting $p_0$ from this point which we will call $p$. Since the vector resulting from this subtraction lies in the plane, it should be perpendicular to the plane's normal, thus using the property that the dot product of two perpendicular vectors is equal to 0, we can write (equation 1):

$$(p - p_0) \cdot n = 0$$

Similarly, a ray can be defined using the following parametric form (equation 2):

$$l_0 + l * t = p$$

where $l_0$ is the origin of the ray and $l$ is the ray direction. This only means that we can compute the position of any point along the ray from the ray's origin, its direction and the term $t$ which is a positive real number (which as usual, is the parametric distance from the origin of the ray to the point of interest along the ray). If the ray and the plane intersect, then they share a point, the point where the line intersects the plane. If this point is $p$, we can insert equation 2 in equation 1, and we get:

$$(l_0 + l * t - p_0) \cdot n = 0$$

What we are interested in here, is to compute a value for $t$ from which we can compute the position of this intersection point using the ray parametric equation. Solving for $t$ we get:

$$\begin{array}{l}l * t \cdot n + (l_0 - p_o) \cdot n = 0\\t = -{\dfrac{(l_0-p_0)\cdot n}{l \cdot n}} = {\dfrac{(p_0 - l_0) \cdot n}{l \cdot n}}\end{array}$$

Note that the plane and the ray are parallel when the denominator (the term $l \cdot n$ gets close to 0. Either the plane and the ray perfectly coincide in which case there is an infinity of solutions or the ray is away from the plane in which case there is no intersection. Generally in a C++ implementation, when the denominator is lower than a very small value, we simply return false (no intersection was found).

bool intersectPlane(const Vec3f &n, const Vec3f &p0, const Vec3f &l0, const Vec3f &l, float &t) { // assuming vectors are all normalized float denom = dotProduct(n, l); if (denom > 1e-6) { Vec3f p0l0 = p0 - l0; t = dotProduct(p0l0, n) / denom; return (t >= 0); } return false; }

## Ray-Disk Intersection

Figure 2: ray-disk intersection.

The ray-disk intersection routine is very simple. A disk is generally defined by a position (the disk center's position), a normal and a radius. First we can test if the ray intersects the plane in which lies the disk. For the ray-plane intersection step, we can simply use the code we have developed for the ray-plane intersection test. If the ray intersects this plane, all we have to do is to compute the intersection point, then compute the distance from this point to this disk's center. If this distance is lower or equal to the disk radius, then the ray intersects the disk. Note that as an optimisation, you can test the square of the distance against the square of the disk's radius. The square distance can be computed from the dot product of this vector (v in the code) with itself. Technically, computing the distance would require to take the square root of this dot product. However we can also test the result of this dot product directly against the square of the radius (which is generally precomputed) to avoid using a square root operation which is expensive.

bool intersectDisk(const Vec3f &n, const Vec3f &p0, const float &radius, const Vec3f &l0, const Vec3 &l) { float t = 0; if (intersectPlane(n, p0, l0, l, t)) { Vec3f p = l0 + l * t; Vec3f v = p - p0; float d2 = dot(v, v); return (sqrtf(d2) <= radius); // or you can use the following optimisation (and precompute radius^2) // return d2 <= radius2; // where radius2 = radius * radius } return false; }