Ray-Plane Analytical Intersection in GLSL
This article explains how to compute the analytical intersection distance between a geometric ray and an infinite plane using GLSL. It details the underlying vector mathematics, addresses edge cases such as parallel rays and negative distances, and provides an optimized GLSL function ready for use in ray tracing, ray marching, or compute shaders.
Mathematical Formulation
A ray is defined parametrically by an origin point \(O\) and a normalized direction vector \(D\):
\[R(t) = O + tD\]
where \(t \ge 0\) represents the scalar distance along the ray.
An infinite plane can be defined by a reference point on the plane \(P_0\) and a normalized normal vector \(N\). Any point \(P\) lying on the plane satisfies the equation:
\[(P - P_0) \cdot N = 0\]
To find the intersection, substitute the ray equation into the plane equation:
\[((O + tD) - P_0) \cdot N = 0\]
Expanding and rearranging the terms to isolate the distance \(t\):
\[(O - P_0) \cdot N + t(D \cdot N) = 0\]
\[t = \frac{(P_0 - O) \cdot N}{D \cdot N}\]
If the plane is instead defined by the algebraic equation \(P \cdot N + d = 0\) (where \(d = -P_0 \cdot N\)), the formula simplifies to:
\[t = -\frac{O \cdot N + d}{D \cdot N}\]
Edge Cases and Validity
- Parallel Ray: When the denominator \(D \cdot N = 0\) (or within a small threshold \(\epsilon\)), the ray is parallel to the plane. In this case, there is either no intersection or the ray lies entirely on the plane.
- Intersection Behind the Ray: If \(t < 0\), the intersection occurs behind the ray origin and should typically be discarded.
- One-Sided vs. Two-Sided Planes: If testing for single-sided planes, ensure that \(D \cdot N < 0\) so that the ray only hits the front face.
GLSL Implementation
Below is a GLSL function that calculates the scalar distance \(t\) to an infinite plane defined by a point
and a normal. It returns -1.0 if no valid forward
intersection occurs.
// Calculates the intersection distance 't' from ray (ro, rd) to plane (p0, n).
// Returns -1.0 if there is no valid forward intersection.
float intersectPlane(vec3 ro, vec3 rd, vec3 p0, vec3 n) {
float denom = dot(rd, n);
// Check if ray is roughly parallel to the plane
if (abs(denom) > 1e-6) {
float t = dot(p0 - ro, n) / denom;
if (t >= 0.0) {
return t;
}
}
return -1.0;
}If the plane is represented in the algebraic form
vec4 plane where plane.xyz is the normal \(N\) and plane.w is the offset
\(d\):
float intersectPlaneEquation(vec3 ro, vec3 rd, vec4 plane) {
float denom = dot(rd, plane.xyz);
if (abs(denom) > 1e-6) {
float t = -(dot(ro, plane.xyz) + plane.w) / denom;
if (t >= 0.0) {
return t;
}
}
return -1.0;
}To determine the actual 3D world-space coordinates of the intersection point once \(t\) is calculated, compute:
vec3 hitPoint = ro + rd * t;