Zooming into the Mandelbrot set in a WebGL fragment shader

Writing these tutorials is all good fun, but I decided to take a short break from them today to show something a little more visually interesting that you can do now that WebGL gives web pages access to the graphics hardware: a page that zooms into the Mandelbrot set in realtime, with all of the calculations taking place in a fragment shader. Fragment shaders run on the graphics card itself, so we can get a pretty impressive frame rate even though we completely recalculate the set every time we draw it.

Here’s a video showing what it looks like when run on a browser that supports WebGL:

Click here and you’ll see the live WebGL version, if you’ve got a browser that supports it; here’s how to get one if you don’t.

[UPDATE: looks like not all drivers will support the shader, which is interesting. It's worked on all the desktop machines I've tried it on but doesn't work on the laptop where I have software rendering. I'll be taking a look at that over the weekend.]

[FURTHER UPDATE: Interesting, I was using "%" for modulo, but that's not valid GLSL. The desktop machines' drivers accepted it anyway, but the MESA software renderer used on the laptop was stricter. I've fixed the bug, and it works now on the laptop. Very very slowly.]

The UI is pretty simple — you can enter a “zoom point” in the fields at the bottom, and click the “Reset Zoom” button to start zooming in again with the target you’ve specified.

I won’t explain the details of how the code works here, but if you have been following the tutorial so far then it should be pretty easy to work out from the source, perhaps with reference to the Wikipedia pages for the Mandelbrot set and Hue/Saturation/Value to RGB conversions. If you haven’t been following the tutorial, you might want to check out lesson 1 and lesson 2, or Benjamin DeLillo’s shorter WebGL intro.

The one thing I’m not entirely satisfied with is the fact that it doesn’t quite zoom to where you ask it to because of (I think) Javascript numerical accuracy problems. Oh, and the colour scheme could be improved :-)

Do let me know if you know of any neat tricks for fixing either of those…

You can leave a response, or trackback from your own site.

21 Responses to “Zooming into the Mandelbrot set in a WebGL fragment shader”

  1. I found one compatibility issue is that some GLSL implementations don’t have the overloaded pow(float, int) and you need to use pow(float, float)


    pow(x, 2);
    needs to be
    pow(x, 2.0);

  2. Ilmari Heikkinen says:

    You probably want to add a “#version 110″ (or 120) pragma to the top of the shader, as the current WebGL implementations pass the shader to the driver as-is. So you get some shader compatibility hell between vendors, hardware, operating systems and driver versions… I think the final implementations will do compatibility wrangling in the browser before passing on the shader, but they’re not there yet.

    The software renderer GLSL implementation has some weird quirks too, reporting them on the MESA bug tracker would be awesome.

  3. giles says:

    @Benjamin — thanks for the warning; turned out it was because I was using “%” as modulo — which is not officially part of the GLSL standard — instead of the “mod” function. Odd that the desktop machines’ graphics cards supported it, but never mind — fixed now.

    @Ilmari — that’s good advice, I’ll get into that habit, though I’ll have to work out which version I’m using first :-) Presumably as part of the standardisation process they will work out the minimal spec for WebGL compatibility (eg., do they accept “%” or only mod). Hmmm, I wonder if there are any chances for security problems with just passing shaders to the driver directly? I managed to take crash my machine quite thoroughly by putting a non-terminating loop in a shader (forgot to increment my loop index in a do/while), so if they can’t work out an easy way to block that, people can at least write annoying web pages that crash your machine, even if they can’t break in…

    If I do find any bugs in the MESA shader implementation then I’ll definitely report them.



  4. cmarrin says:

    Note that this demo also uses:

    for each (corner in baseCorners)

    which is Firefox specific. I believe you can do the same thing with for (… in …) and some dereferencing, which would work in all browsers.

  5. giles says:

    @cmarrin — many thanks again! I’ve fixed the “for each” problem. I’ve not been able to install WebKit on my PC yet, but hopefully with that and the clearDepth fix I’ve also put in, these examples will run on it without problems. Should be able to test it out on my wife’s Mac this evening.

  6. giles says:

    @Benjamin DeLillo just tried this one on a Macbook Air and got loads of type errors like the ones you described; fixed them and it works. Thanks for the heads-up!

  7. Ray says:

    Thanks for your lessons.
    But I can’t run on my FireFox 3.7a1pre.
    OS: win xp professional sp3
    graphic card: ati FireGL v3300.

    I can run some of demos, like:


    I hope you could help me. Many thanks.

  8. giles says:

    @Ray — What happens when you look at the demos? Does anything appear in the Firefox Error Console (on the Tools menu)?

  9. Ray says:

    For the lesson 1 demo, only a black background and nothing else.

    No Error Console.

    Thanks for your help.

  10. Ray says:

    The Graphic Card Driver date: 2006.2.7 and version is

    is it too old ?

  11. R4Y says:

    I have just checked the OpenGL version, it is 2.0 .

    I am really new to OpenGL and WebGL. I don’t know what is necessary to support this demo.

    Thanks for your attention and your pretty nice lessons.


  12. giles says:

    Hi Ray,

    Glad you like the demo. The OpenGL version should be OK; I’ve commented with some questions that might help debug the problem at http://learningwebgl.com/blog/?p=28&cpage=1#comment-73



  13. Daniel_Idspispopd says:

    Actually, you should be able to use % for modulus if you go with:

    extension GL_EXT_gpu_shader4 : enable

    In the first section of your shader.

    -Sorry for inconvenience if this response will work like bump somehow.

  14. giles says:

    @Daniel — thanks! I’m a little uncertain about using extensions, though. I’m sure they’ll work in most desktop PCs, but not so sure about mobile devices…

    (Oh, and don’t worry — I’m just using Wordpress, comments don’t bump posts up.)

  15. Evgeny says:

    1. You could use colors similar to M-set animation in
    2. There are a few Julia sets animations here (Java based out of dated :-) and one could transfer them into WebGL
    3. Unfortunately on Radeon HD 3650 (120 shaders 750MHz) RivaTuner shows me GPU load only 30% in both WebGL animations.

  16. giles says:

    @Evgeny — thanks for the link, those are nice examples. I’d not heard of RivaTuner, sounds like a useful tool. I wonder why neither of our examples are using all of the GPU?

  17. giles says:

    Thanks, Evgeny! Another one for the next roundup.

  18. RCrock says:

    The easy way to do different coloration is to specify a gradient then load from the values in the gradient. This can be done either in code using the built in gradient tools (I am not sure how / whether Javascript supports this) or by loading an image and just looking at the pixel values along the image.

  19. giles says:

    Nice idea, thanks!

Leave a Reply

Subscribe to RSS Feed Follow Learning WebGL on Twitter