One way to play with tessellation shaders in OpenGL is by evaluating various parametric functions on the GPU. You can find plenty of parametric equations on the web, but I thought it would be fun to derive some interesting equations from scratch using a symbolic math package.
I didn’t want to shell out the cash for Mathematica so I looked around for open source solutions and settled on SymPy. Overall it feels much less mature than commercial packages – I can hang or crash it pretty easily, especially when I call simplify. But, it has a nice Pythonic API.
The parametric equation for a standard Torus is easy enough to find on the interwebs, but it’s a good starting point for our exercise. Let’s derive the parametric equation for a torus that has ridges, like this:
In SymPy, the Matrix class is an all-encompassing class that can represent matrices, vectors, and vector-valued functions. I found it convenient to create some helper functions with terse names:
One advantage of deriving the torus equation from scratch is that we can easily come up with an analytic solution for computing the normal vector at an arbitrary point:
To compute the parametric equation of a torus, we need a general way to sweep a circle along an arbitrary curve. We can use the Gram-Schmidt orthogonalization to formulate the Frenet frame:
We can now easily the derive the parametric equation for the ridged torus. The sweep curve is just a circle with radius R, and the cross section is a circle with radius r. To achieve the ridges, we’ll deform r using a sine wave:
The output should look something like this:
The equation for the surface itself isn’t a surpising result, but it’s pretty cool to see an analytic solution for the normal vectors as well!
Superellipse Cross Section
For the cross-section curve, let’s try something more interesting than a circle. How about a superellipse:
Since this “squircle torus” is just another swept surface, it’s just as easy to derive as the ridged torus:
The resulting formulae are reasonably-sized:
If we squish the superellipse and gradually rotate it, we can generate a nice Möbius Tube. Here’s the result, cut in half for clarity:
We can re-use our existing SuperellipseYZ function and add a rotation function to make this easier. Here’s one way of doing it:
And, here’s the result:
SymPy had trouble computing the partial derivatives in this case. Unfortunately, I found it pretty easy to run against its limitations, especially when I tried complex sweep curves.
It’s okay that we weren’t able to compute a one-shot formula for the Mobius tube’s normals, because the shader can use forward differencing, as we’ll see in the next section.
Rendering With Zero Vertex Attributes
If you’re using core profile, you can render one of these surfaces using one small patch, and no enabled vertex attributes whatsoever. Just make sure that you’re not using VAO zero. For example, on the C side:
And, on the GLSL side:
I left out the geometry shader and fragment shader since they’re pretty standard. The full blown code used to generate these images can be found in my github project. Enjoy!