I’m trying to calculate usable normals for a parametric mesh surface using the functions below in a Vertex Shader.

uniform float A, B, C, D; // 0 > 1 range
// The parametric mesh function itself
vec4 heartSphere(in vec4 point)
{
/*
Adapted from Mike Williams' equation, found at
http://www.econym.demon.co.uk/isotut/real.htm#heart1
*/
float u = point.x * PI;
float v = point.y * TWOPI;
vec4 outPos;
outPos.x = (cos(u) * sin(v)) - pow(abs(sin(u) * sin(v)), A) * B;
outPos.y = cos(v) * C;
outPos.z = sin(u) * sin(v);
outPos.w = 1.0;
return outPos;
}
// Structure to hold vertex position and normal values
struct posNorm {
vec4 pos;
vec3 norm;
};
// Calculate vertex position and normal
posNorm surface(in vec4 point)
{
// Initialise output variable
posNorm result;
// Calculate vertex position
result.pos = heartSphere(point);
// Calculate normal
float offset = 0.00001;
vec4 tangent = heartSphere(vec4(point.x, point.y + offset, point.z,1.0)) -
heartSphere(vec4(point.x, point.y - offset, point.z,1.0));
vec4 bitangent = heartSphere(vec4(point.x + offset, point.yz,1.0)) -
heartSphere(vec4(point.x - offset, point.yz,1.0));
result.norm = cross(tangent.xyz, bitangent.xyz);
// Return vertex position and normal
return result;
}

I get a heart-shaped mesh, as I should, but the normals are incorrect, being flipped across the top half of the model. Does anyone know if this method of calculating normals is usable for this kind of parametric surface (basically a modulated sphere form), and if so, what I’ve done wrong?

I’m multiplying the normal by gl_NormalMatrix, then normalizing it before sending it to the Fragment Shader as a vec3 varying.

One solution if to flip the normal when the dot product between the view vector and the surface normal is positive. I know that this work well for a raytracer.

Yes I think this should work. Or, to do it just one time (in a precomputation step) and because you say that your volumes are “a modulated sphere form”; if you have the volume center, just compute the dot product between vertices normal and the vector from center to vertices. If it is negative then flip the normal.
Of course, it works only of a convex volume. But after you don’t have to take care of this in the vertex shader.

Incidentally, is there any way of tweaking the code so both convex and concave surfaces can be lit convincingly? For example, if I scale U and V before the parameterization, I can control the completion of the full closed form. However, when I do this, I can see the back faces of parts of the mesh, and their normals appear flipped (though of course, they’re technically correct). The way it looks now is actually quite nice (see below), but not really very ‘realistic’, so I’d be interested to know if it’s possible to tweak the shader to look more visually correct.

Anyway, thanks very much once again for all your replies, guys!!

It’s lovely, isn’t it…
And the rim-lighting just adds to the ‘ahhhhhhhh-value’

Well, I’ve been working on a GLSL/Quartz Composer port of a set of 55+ parametric surfaces, including all the ones in this document http://www.vtk.org/pdf/ParametricSurfaces.pdf
plus a load more that Desaxismundi put together into an HLSL shader for VVVV. The heart one I discovered recently, and I thought I’d add it to the collection.

Many of the surfaces are quite complex, and I haven’t yet attempted to light them (I’ve just used a simple distance-based shading techinique), but I plan to try at some point, so it would be nice to have a robust technique for generating normals for different kinds of parametric surfaces. Also, the technique of only rendering part of the complete closed form (if the surface has one) is something I plan to use a lot, so being able to light both back and front sides of the mesh would be really useful.

Here are a couple of examples of surfaces rendered with my simple ‘fake’ shading:

I quite like the simplicity of the shading effect, actually (and it’s fast, too, since no normals are calculated at all), but it would be nice to be able to apply more realistic lighting also.

On a more general note, is it worth trying to implement something along the lines outlined here in order to calculate the normals for parametric surfaces:

I’m not entirely sure what jacobian matrices are, or, more importantly, how to calculate the necessary coefficients for a given surface formula, but was just wondering if, theoretically, this might be possible to do with an arbitrary parametric surface…

In a parametric function plotter I wrote, I use the forward and backward derivative formulas to compute the derivatives, which basically amounts to executing the user-defined function four times with a small offset along both of the input axes and then finding a cross product.

Another route is to use the derivative functions available to the fragment shader, although the results are not as good.

I’ve never tried the Fragment Shader route, though. Would that be using dFdx and dFdy, perhaps? You couldn’t give me a very brief rundown of how you might do that, could you?

Yes using jacobian matrices suit very well to the computation of surface normals. As they say, you can find more information in the GPU Gems Chapter 42.

As you have parametric surfaces, precompute Jacobian matrix for each of them and pass it to the vertex shader to find the new tangent and binormals and then the new normal.

I just tried this for creating normals from a spherical heightfield (a sphere with radius modulated by a heightmap) I worked on a while back. It sort-of works, but the normal seems to only vary per-triangle.

Yep, I’m afraid there’s not much you can do about it. This is what I meant when I said the quality wouldn’t be as good.

Really though, what you could do is just compute the forward derivatives only (or backward), instead of both forward and backward. That way, you only execute the function a total of 3 times instead of 5: once for the vertex position calculation, twice for the derivative calculation.

That’s what I ended-up doing, in the end. It does look slightly different though.

I’m still interested in the jacobian matrix route though. That way, I can precompute the matrix coefficients beforehand, which should speed things up quite a bit, since I’ll just be running the parametric formula once, then applying a matrix multiply to create the new normal. I may have to seek advice from a mathematical genius on how to actually work out the coefficients though…

Since you’re essentially transforming from 2D to 3D, you end up with a 3x2 Jacobian matrix. The first column contains the partial derivatives with respect to u of your x(u,v), y(u,v), and z(u,v) functions, and the second column with respect to v.

Since you’re transforming from a 2D plane, your tangent is <1,0> and your binormal is <0,1> at every vertex. Multiplying the Jacobian matrix by these vectors simply yields the first and second columns, which is what you are computing right now.

If you want “exact” derivatives in the matrix (as far as finite word-length architectures go), I recommend looking into using the analytical derivatives instead of the estimations you are using right now.