I've been playing with HLSL shaders recently, and it occurred to me that the horsepower of the GPU could probably be harnessed to render fractals like the Mandelbrot Set. It turns out to be a perfect task for a shader, since it involves lots of completely localized calculations - exactly what graphics cards are good at.
A bit of web searching turned up the not surprising fact that I'm not the first person to have this realization. I found some nice code examples here and here. My version takes inspiration from both of these approaches, and adds a few tweaks of my own.
The image above is of the Julia set, rendered completely by the GPU. Below is, of course, the Mandelbrot Set.
And another view of the Mandelbrot set zoomed in a bit:
On my GeForce 8800GT, I can zoom and pan around at a rock-solid 60 frames per second. Here is a video exploring the Julia set. The warping you see is me transforming the seed used to render the set.
Here is the shader code I ended up with. It is pretty simple. I'm using the Normalized Iteration Count Algorithm to get nicely smoothed coloring. The parameters are set to sensible default values, but you will want to pass them in from your application and map them to some sort of input device. For rendering, simply draw a full screen quad using the shader. In my application I also gild the lily a bit and apply a bloom effect.
int Iterations = 128; float2 Pan = float2(0.5, 0); float Zoom = 3; float Aspect = 1; float2 JuliaSeed = float2(0.39, -0.2); float3 ColorScale = float3(4, 5, 6); float ComputeValue(float2 v, float2 offset) { float vxsquare = 0; float vysquare = 0; int iteration = 0; int lastIteration = Iterations; do { vxsquare = v.x * v.x; vysquare = v.y * v.y; v = float2(vxsquare - vysquare, v.x * v.y * 2) + offset; iteration++; if ((lastIteration == Iterations) && (vxsquare + vysquare) > 4.0) { lastIteration = iteration + 1; } } while (iteration < lastIteration); return (float(iteration) - (log(log(sqrt(vxsquare + vysquare))) / log(2.0))) / float(Iterations); } float4 Mandelbrot_PixelShader(float2 texCoord : TEXCOORD0) : COLOR0 { float2 v = (texCoord - 0.5) * Zoom * float2(1, Aspect) - Pan; float val = ComputeValue(v, v); return float4(sin(val * ColorScale.x), sin(val * ColorScale.y), sin(val * ColorScale.z), 1); } float4 Julia_PixelShader(float2 texCoord : TEXCOORD0) : COLOR0 { float2 v = (texCoord - 0.5) * Zoom * float2(1, Aspect) - Pan; float val = ComputeValue(v, JuliaSeed); return float4(sin(val * ColorScale.x), sin(val * ColorScale.y), sin(val * ColorScale.z), 1); } technique Mandelbrot { pass { PixelShader = compile ps_3_0 Mandelbrot_PixelShader(); } } technique Julia { pass { PixelShader = compile ps_3_0 Julia_PixelShader(); } }