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.