Posts tagged with 'curvature'

Rendering tubes with shaders

  • Posted on June 26, 2012 at 11:57 pm

One of the main aims with Functy has always been to allow functions to be rendered using shaders on the GPU and using the function derivative to generate normals. This should be faster than rendering on the CPU. Defining the normals mathematically should also give more accurate results, and since we have the functions to play around with, it just seems like the sensible thing to do.

This presented a bit of a challenge for the new curve functions though. As is so often the case when using shaders, the problem is one of parallelism. If you have a function, the position of each vector in the model should be independent of the others and therefore a prime target for parallelism. With a curve you have the path of the curve and the radius at a particular point determined mathematically as long as you have the position along the curve, s and the rotation around the curve p given to you. However, what isn’t necessarily pre-determined is the orientation of the curve.

To explain this a bit further, consider the curve in the diagram below. Notice how the vectors perpendicular to the curve change direction as you move along the curve. These vectors are used to define the thickness of the curve at a particular point. In two-dimensions this is fine, as there’s no ambiguity about which direction these vectors should be pointing in.

Binormal along a curve in 2D

Normal along a curve in 2D.

However, lets now consider this in 3D. Suddenly these vectors can rotate around the axis of the curve, and while the vector must always lie within the plane perpendicular to the curve, there’s still an infinite number of possible directions that the vector can point.

Binormal along a curve in 3D

Normal along a curve in 3D.

In Functy, we use all of these directions, because the p variable defines a full rotation around the curve, so that it becomes a tube (rather than a line). But we still need to decide which direction the zero angle should point.

Some how or other a choice has to be made for this. There are a number of possibilities. We could set it randomly. However, this means there will be no consistency from one piece of the curve to the next, and if the cross section isn’t a circle, will result in a random twisting of the curve. This would look rubbish, so it’s not an option.

In my 3D Celtic Knot program I came up against exactly the same problem. Since it was essential for the start and end of a curve to match up exactly, I used an iterative approach there. For each piece, the rotation between the previous and next point on the curve is calculated and the perpendicular vector transformed by this in order to establish its new position. At the end of the curve an adjustment is made to ensure pairs of curves will always fit perfectly together. This is possible because the maximum adjustment needed will never be more than 2π/x radians where x is the number of segments that make up the cross section of the curve (called the Radial Accuracy in Functy).

However, unfortunately this technique can’t be easily parallelised since the orientation of each piece depends on the last, meaning that it couldn’t be translated easily into shader code. For Functy I therefore needed a different solution.

Luckily for me this isn’t a new problem, and the solution came in the form of Frenet Frames. A Frenet Frame is an orthogonal set of axes that’s defined based on the curvature and torsion of the curve at a particular point. Since it’s (in general) canonically defined at each point on the curve, it can be calculated independently from the other points, by calculating the derivatives and second-derivatives of the curve. More specifically, it requires that the tangent, normal and binormal vectors of the curve be calculated. There’s a decent explanation in the Wikipedia section “Other expressions of the frame”, and there’s also a neat Wolfram Demonstration too.

Since these three vectors can be calculated using the derivative of the curve, there’s no need to iterate along the curve, which makes it perfect for calculation using shaders. This is now implemented in Functy, and it seems to work pretty well. On my laptop, which has a decent but not mindblowing graphics card, animating a Frenet curve on the GPU using shader code is considerably faster than using the CPU.

The only problem is that this method has a tendency to generate curves with twists in. That is, the axis can make sudden rotations around the direction of the curve. In general this isn’t a problem, but can cause the curve to ‘pinch’ if the resolution of the pieces is too low. Below is a particularly extreme example.

Twist in curve due to Frenet frame rotation.

Twist in curve due to Frenet frame rotation.

Usually it’s not as bad as this, but it’s a shame nonetheless. For the benefit to be had from parallelisation I’m willing to live with it.