{"id":7,"date":"2014-10-04T09:31:21","date_gmt":"2014-10-04T09:31:21","guid":{"rendered":"http:\/\/betelge.wordpress.com\/?p=4"},"modified":"2014-10-04T09:31:21","modified_gmt":"2014-10-04T09:31:21","slug":"generating-the-mandelbrot-set-in-an-opengl-es-shader","status":"publish","type":"post","link":"https:\/\/www.betelge.com\/blog\/2014\/10\/04\/generating-the-mandelbrot-set-in-an-opengl-es-shader\/","title":{"rendered":"Generating the Mandelbrot set in an OpenGL ES shader"},"content":{"rendered":"<p><a href=\"https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-20 size-large\" src=\"https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot2.png?w=660\" alt=\"2014-9-4_mandelbrot2\" width=\"660\" height=\"396\" srcset=\"https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot2.png 800w, https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot2-300x180.png 300w, https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot2-768x461.png 768w\" sizes=\"auto, (max-width: 660px) 100vw, 660px\" \/><\/a><\/p>\n<p>Fractals are easy to parallelize and GPUs are\u00a0insanely fast at parallell tasks. Mobile devices are\u00a0not intended to be used this way so we can forget about OpenCL and other GPGPU techniques and we can expect to find\u00a0obscure vendor specific limitations and bugs. The final app can be found in Google Play, <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=tk.betelge.mandelbrot\">GPU Mandelbrot<\/a>.<\/p>\n<p>My long term goal is to generate entire interactive worlds on the GPU, but we have\u00a0to start with something simpler. The\u00a0Mandelbrot set is defined to be the set of complex numbers <code>c<\/code>, for which the formula <code>z = z^2 + c<\/code> will go towards zero when run over and over again.<\/p>\n<p>All the work here will be done by the fragment shader. The rest of the app will be as simple as possible. We draw a screen filling quad and color it with our shader. How to set this up in a minimal way will be covered in a future post.<a href=\"https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot2.png\"><br \/>\n<\/a><a href=\"https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot2.png\"><br \/>\n<\/a><\/p>\n<h2>The Vertex Shader<\/h2>\n<pre>uniform vec2 offset;\nuniform vec2 scale;\nattribute vec3 position;\nvarying vec2 c;\n\nvoid main() {\n    c = scale*position.xy + offset;\n    gl_Position = vec4(position, 1.);\n}<\/pre>\n<p>Very simple. The fractal coordinates are passed on to the fragment shader as the variable <code>c<\/code> and the vertex position is just converted to the 4-component representation that OpenGL requiers. No need for any matrices or other transforms. The offset and scale uniforms will allow us to pan and zoom the fractal. That&#8217;s all for the vertex shader.<\/p>\n<h2>The Fragment Shader<\/h2>\n<p>Here\u00a0we immediately get reminded that we&#8217;re coding for a mobile device and not a desktop with a full fledged discreet GPU. The shader won&#8217;t compile unless it starts with the following line:<\/p>\n<pre>precision mediump float;<\/pre>\n<p>That is a precision qualifier. OpenGL ES 2 doesn&#8217;t guarantee support for full single float precision in fragment shaders. No big deal. We set the precision of floats to mediump and forget about it. On most devices mediump means 16-bit. One of those bits is used for the sign and another 5 for the exponent, which leaves 10 bits of precision for the mentissa, the actual decimals of the float. A float usually has the form <code>(-1)^sign*1.mentissa*2^exponent<\/code>.<br \/>\nQuerying OpenGL with the following code<\/p>\n<pre>int range[2], precision;\nGLES20.glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_MEDIUM_FLOAT, range, &amp;precision);<\/pre>\n<p>tells me that my device has a mediump float precision of 10.<br \/>\nThe full fragment shader:<\/p>\n<pre>precision mediump float;\n\n\/\/ The fractal coordinate in the complex plane\n\/\/ Passed down from the vertex shader\nvarying vec2 c;\n\n\/\/ The function that defines the Mandelbrot set\nfloat iteration(float z) {\n    return z*z + c;\n}\n\nvoid main() {\n    float z = 0.; \/\/ The decimal point makes the zero a float\n    int i;\n    int MAX_ITER = 40; \/\/ How many iterations to do\n    float ESCAPE_RADIUS = 16;\n    for(i = 0; i &lt; MAX_ITER; i++) {\n        \/\/ If z gets outside of the escape radius\n        \/\/ we know it's not part of the set\n        if(z &gt; ESCAPE_RADIUS) break;\n\n        \/\/ Do the math\n        z = iteration(z);\n    }\n\n    \/\/ If we do all the iterations and still remain inside\n    \/\/ the escape radius, we say that the point belongs\n    \/\/ to the set and color it 0.\n    \/\/\n    \/\/ If the point escapes we color it with a gradient\n    \/\/ depending on how many iterations it took it to\n    \/\/ escape.\n    float speed;\n    if(i == MAX_ITER)\n        speed = 0.;\n    else\n        speed = float(i)\/float(MAX_ITER);\n\n    \/\/ speed 0 -&gt; blue\n    \/\/ slower speed -&gt; more yellow\n    gl_FragColor = vec4(speed, speed, 0.5, 1.);\n}<\/pre>\n<p>That&#8217;s all we need in the shader for a zoomable fractal viewer.<\/p>\n<p>Some devices might fail to compile this fragment shader because of the dynamic for loop in it. They need to be able to unwrap loops and we have to use constants for the start and end points of the loop. How to get around this is left for a future post, but for now a workaround is to write the for loop like this:<\/p>\n<pre>int i;\nfloat ESCAPE_RADIUS = 16;\nfor(int j = 0; j &lt; 40; j++) {\n    if(z &gt; ESCAPE_RADIUS) break;\n    z = iteration(z);\n    i++;\n}<\/pre>\n<p>It doesn&#8217;t look\u00a0like a big difference but it will be as the shader gets more complicated in future posts.<\/p>\n<h2>Precision<\/h2>\n<p>Let&#8217;s see how far we can zoom!<br \/>\n<a href=\"https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot3.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-21 size-large\" src=\"https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot3.png?w=660\" alt=\"2014-9-4_mandelbrot3\" width=\"660\" height=\"396\" srcset=\"https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot3.png 800w, https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot3-300x180.png 300w, https:\/\/www.betelge.com\/blog\/wp-content\/uploads\/2014\/10\/2014-9-4_mandelbrot3-768x461.png 768w\" sizes=\"auto, (max-width: 660px) 100vw, 660px\" \/><\/a><\/p>\n<p>That&#8217;s at 100x zoom level, a scale of just 0.01. Not even remotely\u00a0as good as I had hoped! The floats in our fragment shader just don&#8217;t have enough precision. That <code>mediump<\/code> at the start of the shader turns out to be a problem.<\/p>\n<p>The next post in the series will be about emulating double precision in OpenGL ES shaders.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Fractals are easy to parallelize and GPUs are\u00a0insanely fast at parallell tasks. Mobile devices are\u00a0not intended to be used this way so we can forget about OpenCL and other GPGPU techniques and we can expect to find\u00a0obscure vendor specific limitations &hellip; <a href=\"https:\/\/www.betelge.com\/blog\/2014\/10\/04\/generating-the-mandelbrot-set-in-an-opengl-es-shader\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-7","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.betelge.com\/blog\/wp-json\/wp\/v2\/posts\/7","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.betelge.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.betelge.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.betelge.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.betelge.com\/blog\/wp-json\/wp\/v2\/comments?post=7"}],"version-history":[{"count":0,"href":"https:\/\/www.betelge.com\/blog\/wp-json\/wp\/v2\/posts\/7\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.betelge.com\/blog\/wp-json\/wp\/v2\/media?parent=7"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.betelge.com\/blog\/wp-json\/wp\/v2\/categories?post=7"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.betelge.com\/blog\/wp-json\/wp\/v2\/tags?post=7"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}