<!DOCTYPE html> <html lang="en-us"> <head> <meta charset="utf-8"> <meta name="robots" content="noindex"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <title>Newton Fractal</title> <style> html, body, canvas { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; } </style> </head> <body> <canvas id="canvas"></canvas> <!-- © Adam Murray 2024 https://adammurray.link/ Creative Commons License Attribution-NonCommercial-ShareAlike 4.0 International https://creativecommons.org/licenses/by-nc-sa/4.0/ --> <script id="vertexShader" type="x-shader/x-vertex"> #version 300 es in vec4 vertexPosition; void main() { // no-op vertex shader gl_Position = vertexPosition; } </script> <script id="fragmentShader" type="x-shader/x-fragment"> #version 300 es precision highp float; uniform vec2 canvasSize; uniform float time; // time in seconds out vec4 fragColor; vec2 cmul(vec2 a, vec2 b) { // complex multiplication return vec2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); } vec2 cdiv(vec2 a, vec2 b) { // complex division return vec2(a.x*b.x+a.y*b.y, -a.x*b.y+a.y*b.x) / (b.x*b.x+b.y*b.y); } vec2 fn(vec2 z) { // f(z) = z^3 - 1 return cmul(z,cmul(z,z)) - vec2(1,0); } vec2 dfn(vec2 z) { // f'(z) = 3*z^2 return cmul(vec2(3,0),cmul(z,z)); } void main() { vec2 coord = (2.*gl_FragCoord.xy - canvasSize)/min(canvasSize.x, canvasSize.y); // [-1,1] in smaller dimension float zoom = pow(sin(time/5.+2.)+1.05,2.); vec2 z = coord/zoom; vec2 zPrev = z; float threshold = 0.00001; float i; // iterate: zNext = z - f(z)/f'(z) for (i=0.; i<50.; i++) { z -= cdiv(fn(z),dfn(z)); if (length(z - zPrev) < threshold) break; zPrev = z; } float theta = (z.x == 0. && z.y == 0.) ? 0. : atan(z.y, z.x); float rotation = mod(theta/6.2832 + 1., 1.); // [0,1] vec3 color; if (rotation < 0.33) { color = vec3(1,0,0); } else if (rotation < 0.66) { color = vec3(0,1,0); } else { color = vec3(0,0,1); } // set intensity based on how fast the solution was found float intensity = 1./log(i); fragColor = vec4(color * intensity, 1); } </script> <script> const gl = canvas.getContext("webgl2"); if (!gl) { document.body.innerHTML = '<h2>Error: WebGL2 is <a href="https://get.webgl.org/webgl2/">not supported by your browser</a></h2>'; throw "WebGL2 not supported"; } function createShader(shaderType, sourceCode) { const shader = gl.createShader(shaderType); gl.shaderSource(shader, sourceCode); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw gl.getShaderInfoLog(shader); return shader; } const program = gl.createProgram(); gl.attachShader(program, createShader(gl.VERTEX_SHADER, vertexShader.textContent.trim())); gl.attachShader(program, createShader(gl.FRAGMENT_SHADER, fragmentShader.textContent.trim())); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw gl.getProgramInfoLog(program); gl.useProgram(program); const vertices = [[-1, -1], [1, -1], [-1, 1], [1, 1]]; gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices.flat()), gl.STATIC_DRAW); const vertexPosition = gl.getAttribLocation(program, "vertexPosition"); gl.enableVertexAttribArray(vertexPosition); gl.vertexAttribPointer(vertexPosition, 2, gl.FLOAT, false, 0, 0); const canvasSizeUniform = gl.getUniformLocation(program, 'canvasSize'); const timeUniform = gl.getUniformLocation(program, 'time'); function draw() { const width = canvas.clientWidth; const height = canvas.clientHeight; canvas.width = width; canvas.height = height; gl.viewport(0, 0, width, height); gl.uniform2f(canvasSizeUniform, width, height); gl.uniform1f(timeUniform, performance.now() / 1000); gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length); requestAnimationFrame(draw); } draw(); </script> </body> </html>