Fast Calculation of the Distance to Cubic Bezier Curves on the GPU
Key topics
The post discusses a method for calculating the distance to cubic Bezier curves on the GPU, sparking a discussion on the trade-offs between analytical and numerical methods, as well as potential applications and future research directions.
Snapshot generated from the HN discussion
Discussion Activity
Moderate engagementFirst comment
3h
Peak period
8
8-10h
Avg / period
4.1
Based on 33 loaded comments
Key moments
- 01Story posted
Oct 18, 2025 at 5:25 AM EDT
3 months ago
Step 01 - 02First comment
Oct 18, 2025 at 8:18 AM EDT
3h after posting
Step 02 - 03Peak activity
8 comments in 8-10h
Hottest window of the conversation
Step 03 - 04Latest activity
Oct 19, 2025 at 8:55 AM EDT
3 months ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
If you only want to fill a path of bezier curves (e.g. for text rendering) you can do without the "distance" part from "signed distance field" [0], leaving you with a "signed field" aka. an implicit curve [1].
Meaning not having to calculate the exact distance but only the sign (inside or outside) can be done without all the crazy iterative root finding in an actually cheap manner with only four multiplications and one addition per pixel / fragment / sample for a rational cubic curve [3].
[0]: https://en.wikipedia.org/wiki/Signed_distance_function
[1]: https://en.wikipedia.org/wiki/Implicit_curve
[2]: https://github.com/Lichtso/contrast_renderer/blob/a189d64a13...
The winding number logic is usually super involved, especially when multiple sub-shapes start overlap and subtracting each other. Is this covered or orthogonal to what you are talking about?
These are the coefficients of the implicit curve, finding them can be done once upfront.
For integral quadratic bezier curves that is trivial as they are constant, see: https://www.shadertoy.com/view/fsXcDj
For rational cubic bezier curves it is more involved, see: https://www.shadertoy.com/view/ttVfzh
And for the full complexity of dealing with self intersecting loops and cusps see: https://github.com/Lichtso/contrast_renderer/blob/main/src/f...
> The winding number logic is usually super involved, especially when multiple sub-shapes start overlap and subtracting each other. Is this covered or orthogonal to what you are talking about?
Orthogonal: The implicit curve only tells you if you are inside or outside (the sign of the SDF), so that is technically sufficient, but usually you want more things: Some kind of anti-aliasing, composite shapes of more than one bezier curve and boolean operators for masking / clipping. Using the stencil buffer for counting the winding number allows to do all of that very easily without tessellation or decomposition at path intersections.
> Can you elaborate on it or provide resources?
If you are interested in the theory behind implicit curve rendering and how to handle the edge cases of cubic bezier curves checkout these papers:
Loop, Charles, and Jim Blinn. "Resolution independent curve rendering using programmable graphics hardware." https://www.microsoft.com/en-us/research/wp-content/uploads/...
BARROWCLOUGH, Oliver JD. "A basis for the implicit representation of planar rational cubic Bézier curves." https://arxiv.org/abs/1605.08669
Yes, as I said it is relevant for text rendering, but not necessarily 2D. It can also be embedded in a 3D perspective as long as the text itself is planar. Meaning you can directly render text in a 3D scene this way without rendering to a texture first.
> But real shadows and lighting would require the distance aspect, no?
I think the difference is in stroke vs fill, not the illumination (as you could still use shadow mapping / projection). In stroking you need to calculate an offset curve either explicitly or implicitly sample it from a signed distance field. Thus the exact distance matters for stroking, for filling it does not.
Though it is very unpractical because the offset curve of a cubic bezier curve is not a cubic bezier curve anymore, instead it is an analytic curve of degree 10. Thus, in practice the offset curves for stroking are either approximated by polygons or implicitly sampled from signed distance fields.
Raph Levien has a good blog post about it:
https://raphlinus.github.io/curves/2022/09/09/parallel-bezie...
One more thing: Offset curves are different form classical scaling from a center point in all but the most trivial cases where there exists such a center; namely regular polygons. And cubic bezier curves can be concave, even have a self intersecting loop or form a cusp.
> Geometry such as hair, fur, and vegetation can have thousands or even millions of primitives. These are typically modeled as fine, smooth curves. Instead of using triangles to approximate these curves, you can use Metal's new curve primitives. These curves will remain smooth even as the camera zooms in. And compared to triangles, curves have a more compact memory footprint and allow faster acceleration structure builds.
> A full curve is made of a series of connected curve segments. Every segment on a curve is its own primitive, and Metal assigns each segment a unique primitive ID. Each of these segments is defined by a series of control points, which control the shape of the curve. These control points are interpolated using a set of basis functions. Depending on the basis function, each curve segment can have 2, 3, or 4 control points. Metal offers four different curve basis functions: Bezier, Catmull-Rom, B-Spline, and Linear. (...)
1: https://developer.apple.com/videos/play/wwdc2023/10128/?time...
[1] https://github.com/Long0x0/distance-to-bezier
The naive generic way of finding distances from point P to curve C([0,1]) is a procedure quite standard for global minimization : repeat "find a local minimum on the space constrained to be better than any previous minimum"
- Find a point P0 local minimum of d(P,P0) subject to the constraint P0=C(t0) (aka P0 is on C)
- define d0 = d(P,P0)
- Bisect the curve at t1 into two curves C1 = C([0,t0]) and C2([t0,1])
- Find a point P1 local minimum of d(P,P1) subject to the constraint P1=C(t1) with 0< t1 < t0 (aka P1 on c1) and the additional constraint d(P,P1) < d0 (note here the inequality is strict so that we won't find P0 again) if it exist.
- define d1 = d(P,P1)
- Find a point P2 local minimum of d(P,P2) subject to the constraint P2=C(t2) with t0 < t2 < 1 (aka P2 on c2) and the additional constraint d(P,P2) < d0 (note here the inequality is strict so that we won't find P0 again) if it exist.
- define d2 = d(P,P2)
Here in the case of cubic Bezier the curve have only one loop so you don't need to bissect anymore. If curves where higher order like spirals, you would need to cascade the problems with a shrinking distance constraint (aka looking only for points in R^2 inside the circle
So the distance is min over all local minimum found = min( d0,d1,d2)
Here the local minimization problems are in R^n (and inside disks centered around P) with n the dimension of the space, and because this is numerical approximation, you can either use slack (dual) variables to find the tx which express the on Curve constraint or barrier methods to express the disk constraints once a t parametrization has been chosen.
Multivariate Newton is guaranteed to work because distances are positive so the Sequential Quadratic Programming problems are convex (scipy minimize "SLSQP"). (Whereas the author needed 5 polynomials root, you can only need to solve for 3 points because each solve solves for two coordinates).
A local minimum is a point which satisfy the KKT conditions.
This procedure is quite standard : it's for example use to find the eigenvalues of matrices iteratively. Or finding all solutions to a minimization problem.
Where this procedure shines, is when you have multiple splines, and you want to find the minimal distance to them : you can partition the space efficiently and not compute distances to part of curves which are to far away to have a chance to be a minimum. This will scale independently of the resolution of your chain spline. (imagine a spiral the number of local minimum you need to encounter are proportional to the complexity of the scene and not the resolution of the spiral)
But when you are speaking about computing the whole signed distance field, you should often take the step of solving the Eikonal equation over the space instead of computing individual distances.
Here there is a small number of local minimum, the idea is to iterate over them in increasing order.
Can't remember the exact name but here is a more recent paper proposing "Sequential Gradient Descent" https://arxiv.org/abs/2011.04866 which features a similar idea.
Sequential convex programming : http://web.stanford.edu/class/ee364b/lectures/seq_notes.pdf
There is not really something special to it, it just standard local non linear minimization techniques with constraints Sequential Least Squares Quadratic Programming (SLSQP).
It's just about framing it as an optimization problem looking for "Points" with constraints and applying standard optimization toolbox, and recognizing which type of problem your specific problem is. You can write it as basic gradient descent if you don't care about performance.
The problem of finding a minimum of a quadratic function inside a disk is commonly known as the "Trust Region SubProblem" https://cran.r-project.org/web/packages/trust/vignettes/trus... but in this specific case of distances to curve we are on the easy case of Positive Definite.
https://gist.github.com/unrealwill/1ad0e50e8505fd191b617903b...
Point 33 "intersection between bezier curve with a circle" may be useful to find the feasible regions of the subproblems https://pomax.github.io/bezierinfo/#introduction
The approach I suggest will need more work, and there are probably problematic edge cases to consider and numerical stability issues. Proper proofs have not been done. It's typically some high work-low reward situation.
It's mostly interesting because it highlight the link between roots and local mimimum. And because it respect the structure of the problem more.
To find roots we can find a first root then divide the polynomial by (x-root). And find a root again.
If you are not mathematically literate, it'll probably be hard to do the details necessary to make it performant. But if you use a standard black-box optimizer with constraints it should be able to do it in few iterations.
You can simplify the problem by considering piece-wise segments instead of splines. The extension to chains of segment is roughly the same, and the spatial acceleration structure based on branch-and-bound are easier.
Since you’re interested in doing this on GPU, an approach that might be interesting to you (although not necessarily more efficient) would be to leverage the intrinsic properties of Bezier curves to feed a near-optimal initial guess to Newton. Some useful facts about Bezier curves: i) Bezier control points form a convex hull of the curve they define ii) Bezier curves defined on [0,1] can be split into two bezier curves, each defined on [0,t] and [t,1] that define the same curve, with a tighter control polygon. iii) This Bezier curve splitting can be done using repeated linear combinations of Bezier control points, so you can skip evaluating Bernstein polynomials directly. iv) there is a mapping from Bezier control points to their corresponding value in the [0,1] parameter space (the term for this for B-Splines is greville abcissae, I’m not sure that there is an explicit name for the equivalent for Bezier curves, but basically the preimage of control point b_i of a degree d curve is i/d, i=0,…,d+1).
These things together sort of imply an algorithm: 1. Subdivide the Bezier curve c into 2 or 3 curves c_1, c_2, c_3 2. Find the closest control point b_j to the target point x 3. Choose the curve c_i corresponding to b_j: this subcurve contains the closest point to x 4. Go to step 1 and repeat this loop several times with c = c_i 5. Then, compute the preimage of the closest control point b_j to x on c (j/d plus some shift and rescaling). This value, t’, will be the initial guess to Newton’s method. 6. Solve for the closest point on the selected subcurve c to x with Newton’s method; this should converge in very few steps because your initial guess is so good, quadratic convergence, blah, blah blah.
The break-even point for this kind of algorithm vs. a derivative based algorithm is very unclear on CPU. But, for GPU, I think the computation can be structured in an architecture friendly way; since computing the euclidean distance between x with all control points and the bezier curve splitting can written in a vectorizable manner, you will probably see a decent speed up. I’ve only really worked with CUDA though, so I’m not sure if this idea maps very cleanly to GLSL.
Here’s an example of the algorithm above for CPU if you are interested: https://github.com/qnzhou/nanospline/commit/5ac97722414dbc75...
I think this is the main reference for this algorithm (should be able to search for the pdf): https://www.sciencedirect.com/science/article/abs/pii/S01678...
I am happy to see other people use the approach I consider more natural.
It's a generic global optimization approach and geometry is always full of pathological edge cases, so it's hard to tell if you miss any. Getting to work in the average case is usually easy, but to be sure it always work is much harder.
Yes the real trouble is true optimality guarantees. I remember that there were edge cases of the above approach needed that a lot of subdivision steps to succeed for general degree curves, so it might end up worse than a more rigorously justified approach in these cases.
[1]: https://crates.io/crates/polycool
Below is a series of writings on this topic that I enjoyed, by James Blinn, of "Blinn-Phong reflection" fame (and more). Not state-of-the-art, but an interesting read. It's just for cubics, which is what you need to solve the distance formula for quadratic bezier curves (my particular case), rather than the harder cubic curves of the linked article.
* https://courses.cs.washington.edu/courses/cse590b/13au/lectu...
* https://courses.cs.washington.edu/courses/cse590b/13au/lectu...
* https://courses.cs.washington.edu/courses/cse590b/13au/lectu...
* https://courses.cs.washington.edu/courses/cse590b/13au/lectu...
* https://courses.cs.washington.edu/courses/cse590b/13au/lectu...
1. Find the derivative, and use an analytic formula for the roots of a quartic equation.
2. Then evaluate the value at the zeros to look for the intervals that cross the [0,1] segment.