OK; this needs some explanation. The background is, ages ago, when I was working on the original Desaxismundi Maths Surfaces QTZ, I found that some of the surfaces didn’t really work as I expected. Some of them also weren’t really that interesting, in the end, so I thought it would be cool to find a few different ones to replace the more boring and/or non-working ones. One of the 3D forms I thought would be fun to do, if I could find a parametric equation for it, was a ‘Valentine’s Day’-style architypal love-heart shape. I managed to find a few different equations to create something along these lines, but didn’t get around to testing any of them, and forgot about it for a few months. Then, I came across this page on Mike Williams’ site again this morning, had a bit of free time, and thought I’d have another go at making ‘Yet Another Heart’ into a parametric GLSL Vertex Shader. I was able to create both the solid heart shape (based on a modulated sphere) and the heart hoop version (based on a torus). Once I’d found the right formulae to create the base Sphere and Torus forms from a flat GLSL Grid patch, both were pretty easy to implement, though the maths could probably be made more efficient.
Here’s the Vertex Shader for the solid version:
///////////////////////////
// CONSTANTS //
///////////////////////////
const float PI = 3.14159265;
const float TWOPI = 6.28318531;
///////////////////////////
// CONTROLS //
///////////////////////////
uniform float A, B, C, D;
///////////////////////////
// PARAMETRIC EQUATION //
// FUNCTIONS //
///////////////////////////
vec4 heartSphere(in vec4 point)
{
/*
Parametric 3D heart equation, based on parametric sphere:
u = point.x * PI;
v = point.y * TWOPI;
sphere.x = radius * cos(u) * sin(v);
sphere.y = radius * sin(u) * sin(v);
sphere.z = radius * cos(v);
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 = point.w;
return outPos;
}
///////////////////////////
// VARYINGS (TO FS) //
///////////////////////////
varying float shade;
///////////////////////////
// MAIN LOOP //
///////////////////////////
void main()
{
// Set vert to parametric formula
// applied to original vertex position
vec4 vert = heartSphere(gl_Vertex);
// Scale
vert.xyz *= D;
// Generate value for 'fake' lighting effect
// based on vertex distance from origin
shade = 1.0 - smoothstep(0.0,0.8,distance(vert.xyz, vec3(0.0)));
//Transform vertex by modelview and projection matrices
gl_Position = gl_ModelViewProjectionMatrix * vert;
//Forward current color and texture coordinates after applying texture matrix
gl_FrontColor = gl_Color;
gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;
}
..and here’s the very simple Fragment Shader:
///////////////////////////
// VARYINGS (FROM FS) //
///////////////////////////
varying float shade;
///////////////////////////
// CONTROLS //
///////////////////////////
uniform vec4 Base_Color;
///////////////////////////
// MAIN LOOP //
///////////////////////////
void main()
{
// Multiply base color by value from Fragment Shader
gl_FragColor = Base_Color * vec4(vec3(shade), 1.0);
}
No normal-calculation here, so it can’t be lit properly. Normals could be calculated quite easily (with a performance hit, of course) using the method outlined by tonfilm. Parameters A B C and D should be in the 0 > 1 range.
I’m definitely going to add this to to the Maths Surfaces collection, and there are a couple of other surfaces on Mike’s site I might have a go at, too. I’ll have to work up to it though, as it always takes ages to update the Surfaces QTZ, because I always think of extra tweaks to make, which then have to be copied to all 50+ shaders.
‘tb Heart Surface 00.qtz’ in the Box.net widget.



Sweet