|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | + <script id = "shader-vs" type = "x-shader/x-vertex"> |
| 4 | + precision highp float; |
| 5 | + attribute vec2 a_Position; |
| 6 | + void main() { |
| 7 | + gl_Position = vec4(a_Position.x, a_Position.y, 0.0, 1.0); |
| 8 | + } |
| 9 | + </script> |
| 10 | + <script> |
| 11 | + function f(x, c) { |
| 12 | + return [ |
| 13 | + x[0] * x[0] + c[0], |
| 14 | + x[1] * x[1] + c[1] |
| 15 | + ]; |
| 16 | + } |
| 17 | + function palette(t, a, b, c, d) { |
| 18 | + return [ |
| 19 | + a[0] + b[0] * Math.cos(6.28318 * (c[0] * t[0] + d[0])), |
| 20 | + a[1] + b[1] * Math.cos(6.28318 * (c[1] * t[1] + d[1])), |
| 21 | + a[2] + b[2] * Math.cos(6.28318 * (c[2] * t[2] + d[2])) |
| 22 | + ]; |
| 23 | + } |
| 24 | + function vectorLength(vector) { |
| 25 | + return Math.sqrt(vector[0]*vector[0]+vector[1]*vector[1]); |
| 26 | + } |
| 27 | + function kernel(zoom, zoomSize, maxIterations) { |
| 28 | + let x = [0, 0]; |
| 29 | + let escaped = false; |
| 30 | + let iterations = 0; |
| 31 | + for (let i = 0; i < maxIterations; i++) { |
| 32 | + iterations = i; |
| 33 | + x = f(x, c); |
| 34 | + if (vectorLength(x) > 2) { |
| 35 | + escaped = true; |
| 36 | + break; |
| 37 | + } |
| 38 | + } |
| 39 | + escaped |
| 40 | + ? this.color(palette(iterations / maxIterations, [0, 0, 0], [.59, .55, .75], [.1, .2, .3], [.75, .75, .75]), 1) |
| 41 | + : this.color(.85, .99, 1, 1); |
| 42 | + } |
| 43 | + </script> |
| 44 | + <script id = "shader-fs" type = "x-shader/x-fragment"> |
| 45 | + precision highp float; |
| 46 | + |
| 47 | + uniform vec2 u_zoomCenter; |
| 48 | + uniform float u_zoomSize; |
| 49 | + uniform int u_maxIterations; |
| 50 | + |
| 51 | + vec2 f(vec2 x, vec2 c) { |
| 52 | + return mat2(x,-x.y,x.x)*x + c; |
| 53 | + } |
| 54 | + vec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d) { |
| 55 | + return a + b*cos( 6.28318*(c*t+d) ); |
| 56 | + } |
| 57 | + void main() { |
| 58 | + vec2 uv = gl_FragCoord.xy / vec2(800.0, 800.0); |
| 59 | + vec2 c = u_zoomCenter + (uv * 4.0 - vec2(2.0)) * (u_zoomSize / 4.0); |
| 60 | + vec2 x = vec2(0.0); |
| 61 | + bool escaped = false; |
| 62 | + int iterations = 0; |
| 63 | + for (int i = 0; i < 10000; i++) { |
| 64 | + if (i > u_maxIterations) break; |
| 65 | + iterations = i; |
| 66 | + x = f(x, c); |
| 67 | + if (length(x) > 2.0) { |
| 68 | + escaped = true; |
| 69 | + break; |
| 70 | + } |
| 71 | + } |
| 72 | + gl_FragColor = escaped ? vec4(palette(float(iterations)/float(u_maxIterations), vec3(0.0),vec3(0.59,0.55,0.75),vec3(0.1, 0.2, 0.3),vec3(0.75)),1.0) : vec4(vec3(0.85, 0.99, 1.0), 1.0); |
| 73 | + } |
| 74 | + </script> |
| 75 | + <script type="text/javascript"> |
| 76 | + function main() { |
| 77 | + /* locate the canvas element */ |
| 78 | + var canvas_element = document.getElementById("maincanvas"); |
| 79 | + |
| 80 | + /* obtain a webgl rendering context */ |
| 81 | + var gl = canvas_element.getContext("webgl"); |
| 82 | + |
| 83 | + /* get shader code from the <script> tags */ |
| 84 | + var vertex_shader_src = document.getElementById("shader-vs").text; |
| 85 | + var fragment_shader_src = document.getElementById("shader-fs").text; |
| 86 | + |
| 87 | + /* compile and link shaders */ |
| 88 | + var vertex_shader = gl.createShader(gl.VERTEX_SHADER); |
| 89 | + var fragment_shader = gl.createShader(gl.FRAGMENT_SHADER); |
| 90 | + gl.shaderSource(vertex_shader, vertex_shader_src); |
| 91 | + gl.shaderSource(fragment_shader, fragment_shader_src); |
| 92 | + gl.compileShader(vertex_shader); |
| 93 | + console.log(gl.getShaderInfoLog(vertex_shader)); |
| 94 | + gl.compileShader(fragment_shader); |
| 95 | + console.log(gl.getShaderInfoLog(fragment_shader)); |
| 96 | + var mandelbrot_program = gl.createProgram(); |
| 97 | + gl.attachShader(mandelbrot_program, vertex_shader); |
| 98 | + gl.attachShader(mandelbrot_program, fragment_shader); |
| 99 | + gl.linkProgram(mandelbrot_program); |
| 100 | + gl.useProgram(mandelbrot_program); |
| 101 | + |
| 102 | + /* create a vertex buffer for a full-screen triangle */ |
| 103 | + var vertex_buf = gl.createBuffer(gl.ARRAY_BUFFER); |
| 104 | + gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buf); |
| 105 | + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW); |
| 106 | + |
| 107 | + /* set up the position attribute */ |
| 108 | + var position_attrib_location = gl.getAttribLocation(mandelbrot_program, "a_Position"); |
| 109 | + gl.enableVertexAttribArray(position_attrib_location); |
| 110 | + gl.vertexAttribPointer(position_attrib_location, 2, gl.FLOAT, false, 0, 0); |
| 111 | + |
| 112 | + /* find uniform locations */ |
| 113 | + var zoom_center_uniform = gl.getUniformLocation(mandelbrot_program, "u_zoomCenter"); |
| 114 | + var zoom_size_uniform = gl.getUniformLocation(mandelbrot_program, "u_zoomSize"); |
| 115 | + var max_iterations_uniform = gl.getUniformLocation(mandelbrot_program, "u_maxIterations"); |
| 116 | + |
| 117 | + /* these hold the state of zoom operation */ |
| 118 | + var zoom_center = [0.0, 0.0]; |
| 119 | + var target_zoom_center = [0.0, 0.0]; |
| 120 | + var zoom_size = 4.0; |
| 121 | + var stop_zooming = true; |
| 122 | + var zoom_factor = 1.0; |
| 123 | + var max_iterations = 500; |
| 124 | + |
| 125 | + var renderFrame = function () { |
| 126 | + /* bind inputs & render frame */ |
| 127 | + gl.uniform2f(zoom_center_uniform, zoom_center[0], zoom_center[1]); |
| 128 | + gl.uniform1f(zoom_size_uniform, zoom_size); |
| 129 | + gl.uniform1i(max_iterations_uniform, max_iterations); |
| 130 | + gl.clearColor(0.0, 0.0, 0.0, 1.0); |
| 131 | + gl.clear(gl.COLOR_BUFFER_BIT); |
| 132 | + gl.drawArrays(gl.TRIANGLES, 0, 3); |
| 133 | + |
| 134 | + /* handle zoom */ |
| 135 | + if (!stop_zooming) { /* zooming in progress */ |
| 136 | + /* gradually decrease number of iterations, reducing detail, to speed up rendering */ |
| 137 | + max_iterations -= 10; |
| 138 | + if (max_iterations < 50) max_iterations = 50; |
| 139 | + |
| 140 | + /* zoom in */ |
| 141 | + zoom_size *= zoom_factor; |
| 142 | + |
| 143 | + /* move zoom center towards target */ |
| 144 | + zoom_center[0] += 0.1 * (target_zoom_center[0] - zoom_center[0]); |
| 145 | + zoom_center[1] += 0.1 * ( target_zoom_center[1] - zoom_center[1]); |
| 146 | + |
| 147 | + window.requestAnimationFrame(renderFrame); |
| 148 | + } else if (max_iterations < 500) { |
| 149 | + /* once zoom operation is complete, bounce back to normal detail level */ |
| 150 | + max_iterations += 10; |
| 151 | + window.requestAnimationFrame(renderFrame); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + /* input handling */ |
| 156 | + canvas_element.onmousedown = function(e) { |
| 157 | + var x_part = e.offsetX / canvas_element.width; |
| 158 | + var y_part = e.offsetY / canvas_element.height; |
| 159 | + target_zoom_center[0] = zoom_center[0] - zoom_size / 2.0 + x_part * zoom_size; |
| 160 | + target_zoom_center[1] = zoom_center[1] + zoom_size / 2.0 - y_part * zoom_size; |
| 161 | + stop_zooming = false; |
| 162 | + zoom_factor = e.buttons & 1 ? 0.99 : 1.01; |
| 163 | + renderFrame(); |
| 164 | + return true; |
| 165 | + } |
| 166 | + canvas_element.oncontextmenu = function(e){return false;} |
| 167 | + canvas_element.onmouseup = function(e) { stop_zooming = true; } |
| 168 | + |
| 169 | + /* display initial frame */ |
| 170 | + renderFrame(); |
| 171 | + } |
| 172 | + </script> |
| 173 | +</head> |
| 174 | +<body onload="main()"> |
| 175 | +<center> |
| 176 | + <p>Left click to zoom in, right click to zoom out.</p> |
| 177 | + <canvas id="maincanvas" width = "800" height = "800" style="border:1px solid black">canvas not supported</canvas> |
| 178 | +</center> |
| 179 | +</body> |
| 180 | +</html> |
0 commit comments