Ray Infinite Cylinder Intersection in GLSL
Calculating the analytical intersection between a 3D ray and an infinite cylinder involves solving a quadratic equation derived from the geometric definition of a cylinder. In GLSL, this process projects the ray's origin and direction vectors onto the plane perpendicular to the cylinder's axis, reducing the 3D problem into a 2D circle-ray intersection to analytically solve for the ray distance parameter \(t\).
Mathematical Formulation
A 3D ray is defined as:
\[\mathbf{r}(t) = \mathbf{o} + t\mathbf{d}\]
Where:
- \(\mathbf{o}\) is the ray origin.
- \(\mathbf{d}\) is the normalized ray direction.
- \(t \ge 0\) is the distance along the ray.
An infinite cylinder can be defined by a point on its axis \(\mathbf{p}\), a normalized axis vector \(\mathbf{a}\), and a radius \(r\). Any point \(\mathbf{x}\) lies on the cylinder's surface if its perpendicular distance to the cylinder's axis equals \(r\):
\[\|(\mathbf{x} - \mathbf{p}) - ((\mathbf{x} - \mathbf{p}) \cdot \mathbf{a})\mathbf{a}\|^2 = r^2\]
Substituting the ray equation \(\mathbf{x} = \mathbf{o} + t\mathbf{d}\) and defining \(\Delta\mathbf{p} = \mathbf{o} - \mathbf{p}\) yields a quadratic equation in terms of \(t\):
\[A t^2 + 2B t + C = 0\]
Where:
- \(\mathbf{d}_\perp = \mathbf{d} - (\mathbf{d} \cdot \mathbf{a})\mathbf{a}\)
- \(\Delta\mathbf{p}_\perp = \Delta\mathbf{p} - (\Delta\mathbf{p} \cdot \mathbf{a})\mathbf{a}\)
- \(A = \mathbf{d}_\perp \cdot \mathbf{d}_\perp\)
- \(B = \mathbf{d}_\perp \cdot \Delta\mathbf{p}_\perp\)
- \(C = \Delta\mathbf{p}_\perp \cdot \Delta\mathbf{p}_\perp - r^2\)
The discriminant is:
\[\Delta = B^2 - A C\]
- If \(\Delta < 0\), the ray misses the cylinder completely.
- If \(\Delta \ge 0\), the intersections are given by \(t = \frac{-B \pm \sqrt{\Delta}}{A}\).
GLSL Implementation
The following GLSL function calculates the entry and exit
intersection distances. It returns vec2(t_near, t_far). If
the ray does not intersect the cylinder, it returns
vec2(-1.0).
// Intersects a ray with an infinite cylinder
// ro: Ray origin
// rd: Ray direction (must be normalized)
// pa: Point on cylinder axis
// axis: Normalized axis direction of the cylinder
// radius: Cylinder radius
vec2 intersectInfiniteCylinder(vec3 ro, vec3 rd, vec3 pa, vec3 axis, float radius) {
vec3 dp = ro - pa;
// Project direction and origin onto plane perpendicular to cylinder axis
vec3 d_proj = rd - axis * dot(rd, axis);
vec3 dp_proj = dp - axis * dot(dp, axis);
float a = dot(d_proj, d_proj);
float b = dot(d_proj, dp_proj);
float c = dot(dp_proj, dp_proj) - radius * radius;
float discriminant = b * b - a * c;
if (discriminant < 0.0) {
return vec2(-1.0); // No intersection
}
float sqrt_d = sqrt(discriminant);
float t0 = (-b - sqrt_d) / a;
float t1 = (-b + sqrt_d) / a;
return vec2(t0, t1);
}Computing the Surface Normal
To shade the cylinder at the intersection point \(\mathbf{hit} = \mathbf{o} + t\mathbf{d}\), project the vector from the axis to the hit point to get the surface normal:
vec3 getCylinderNormal(vec3 hitPoint, vec3 pa, vec3 axis) {
vec3 d = hitPoint - pa;
return normalize(d - axis * dot(d, axis));
}