Point to Line Segment Distance in GLSL
Calculating the shortest distance from a 3D point to a finite line segment requires projecting the point onto the line that contains the segment, clamping that projection to stay within the segment's finite boundaries, and measuring the Euclidean distance between the point and the resulting closest position. This article details the mathematical formulation and provides an optimized GLSL function for use in shaders, raymarching, and Signed Distance Field (SDF) rendering.
The Mathematical Formula
Let the finite line segment be defined by two endpoints in 3D space, \(A\) and \(B\), and let \(P\) be the arbitrary query point.
Construct vectors:
- Segment vector: \(\mathbf{v} = B - A\)
- Vector from start to point: \(\mathbf{u} = P - A\)
Project and normalize parameter \(t\): The scalar projection of \(\mathbf{u}\) onto \(\mathbf{v}\) determines where the perpendicular projection of \(P\) falls along the infinite line: \[t = \frac{\mathbf{u} \cdot \mathbf{v}}{\mathbf{v} \cdot \mathbf{v}} = \frac{(P - A) \cdot (B - A)}{|B - A|^2}\]
Constrain to the finite segment: Because the segment is finite, \(t\) must be clamped between \(0.0\) and \(1.0\): \[h = \text{clamp}(t, 0.0, 1.0)\]
- If \(h = 0.0\), the closest point is endpoint \(A\).
- If \(h = 1.0\), the closest point is endpoint \(B\).
- If \(0.0 < h < 1.0\), the closest point lies strictly along the segment interior.
Calculate distance: The closest point on the segment is \(C = A + h \cdot (B - A)\). The Euclidean distance \(d\) is: \[d = \|P - C\| = \|P - (A + h(B - A))\|\]
GLSL Implementation
GLSL natively implements vector operations like dot products, clamping, and length calculations directly in hardware. The formula translates into the following shader function:
float distanceToSegment(vec3 p, vec3 a, vec3 b) {
vec3 pa = p - a;
vec3 ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h);
}Explanation of the Code
vec3 pa = p - a;shifts the coordinate system so that point \(A\) acts as the origin.dot(pa, ba) / dot(ba, ba)computes the normalized projection \(t\).dot(ba, ba)calculates the squared length \(|B - A|^2\) without needing an expensive square root operation.clamp(..., 0.0, 1.0)enforces the segment boundaries.pa - ba * his algebraically equivalent to \(p - (a + ba \cdot h)\), representing the vector from the closest point on the segment to the query point \(P\).length(...)evaluates the magnitude of that difference vector, returning the final scalar distance.