/** @type {HTMLCanvasElement} */ const canvas = document.getElementById("canvas"); const gl = canvas.getContext("webgl"); const iterations = 1000; const startingX = -.566; const startingY = .525; let smallRes; const vShaderSource = ` attribute vec4 a_position; uniform vec2 u_unitSize; varying vec2 v_position; void main() { gl_Position = a_position; v_position = u_unitSize * a_position.xy; } `; const fShaderSource = ` precision highp float; uniform vec2 u_constant; varying vec2 v_position; float calcJulia(vec2 pos) { float newRE = pos.x; float newIM = pos.y; for (int i = 0; i < ${iterations}; i++) { float oldRE = newRE; float oldIM = newIM; newRE = oldRE * oldRE - oldIM * oldIM + u_constant.x; newIM = 2.0 * oldRE * oldIM + u_constant.y; float dist = newRE * newRE + newIM * newIM; if (dist >= 4.0) { return float(i + 1) - log(log(dist))/log(2.0); } } return -1.0; } void main() { float calced = calcJulia(v_position); float t = mod(calced / 100.0, 2.0); if (t > 1.0) { t = 2.0 - t; } if (calced == -1.0) { gl_FragColor = vec4(.0, .0, .0, 1.0); } else if (t <= 1.0 / 6.0) { gl_FragColor = vec4(1.0, t * 6.0, .0, 1.0); } else if (t <= 1.0 / 3.0) { gl_FragColor = vec4(1.0 - t * 6.0 + 1.0, 1.0, .0, 1.0); } else if (t <= 1.0 / 2.0) { gl_FragColor = vec4(.0, 1.0, t * 6.0 - 2.0, 1.0); } else if (t <= 2.0 / 3.0) { gl_FragColor = vec4(.0, 1.0 - t * 6.0 + 3.0, 1.0, 1.0); } else if (t <= 5.0 / 6.0) { gl_FragColor = vec4(t * 6.0 - 4.0, .0, 1.0, 1.0); } else { gl_FragColor = vec4(1.0, .0, 1.0 - t * 6.0 + 5.0, 1.0); } // else { // VIRIDIS COLOR SCHEME (maybe) // gl_FragColor = vec4( // .280268003 + .230519207 * t, // .165368639 + .836977008 * t - .271554750 * t*t, // .476688822 - .290407016 * t + .009943850 * t*t, // 1.0 // ); // } } `; function createShader(type, source) { const shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (success) { return shader; } console.log(gl.getShaderInfoLog(shader)); gl.deleteShader(shader); } const vertexShader = createShader(gl.VERTEX_SHADER, vShaderSource); const fragmentShader = createShader(gl.FRAGMENT_SHADER, fShaderSource); function createProgram(vShader, fShader) { const program = gl.createProgram(); gl.attachShader(program, vShader); gl.attachShader(program, fShader); gl.linkProgram(program); const success = gl.getProgramParameter(program, gl.LINK_STATUS); if (success) { return program; } console.log(gl.getProgramInfoLog(program)); gl.deleteProgram(program); } const program = createProgram(vertexShader, fragmentShader); gl.useProgram(program); const posBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer); const constLocation = gl.getUniformLocation(program, "u_constant"); const uSizeLocation = gl.getUniformLocation(program, "u_unitSize"); let toRedraw = false; function resizeCanvas() { canvas.width = window.innerWidth * window.devicePixelRatio; canvas.height = window.innerHeight * window.devicePixelRatio; smallRes = Math.min(canvas.width, canvas.height); gl.viewport(0, 0, canvas.width, canvas.height); gl.uniform2f(uSizeLocation, canvas.width / smallRes, canvas.height / smallRes); toRedraw = true; } window.addEventListener("resize", resizeCanvas); resizeCanvas(); const posAttribLocation = gl.getAttribLocation(program, "a_position"); const positions = [ -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); gl.enableVertexAttribArray(posAttribLocation); gl.vertexAttribPointer(posAttribLocation, 2, gl.FLOAT, false, 0, 0); gl.drawArrays(gl.TRIANGLES, 0, 6); function drawJulia(constPosX, constPosY) { gl.uniform2f(constLocation, constPosX, constPosY); gl.drawArrays(gl.TRIANGLES, 0, 6); } let posX = 0; let posY = 0; document.addEventListener("mousemove", e => { posX = e.clientX * window.devicePixelRatio; posY = e.clientY * window.devicePixelRatio; }); document.addEventListener("touchmove", e => { e.preventDefault(); posX = e.touches[0].clientX * window.devicePixelRatio; posY = e.touches[0].clientY * window.devicePixelRatio; }); let currentPosX = 0; let currentPosY = 0; const normalRate = .2; const lowEaseRate = .01 * normalRate; let easeRate = normalRate; document.addEventListener("keydown", e => { if (e.key == "Shift") { easeRate = lowEaseRate; } }); document.addEventListener("keyup", e => { if (e.key == "Shift") { easeRate = normalRate; } }); function main() { const deltaPosX = posX - currentPosX; const deltaPosY = posY - currentPosY; currentPosX += easeRate * deltaPosX; currentPosY += easeRate * deltaPosY; const actualPosX = 2 * (currentPosX - canvas.width / 2) / smallRes; const actualPosY = 2 * (currentPosY - canvas.height / 2) / smallRes; if (toRedraw || deltaPosX**2 + deltaPosY**2 > .005**2) { drawJulia(actualPosX, actualPosY); toRedraw = false; } requestAnimationFrame(main) } // Initiating... drawJulia(startingX, startingY); toRedraw = false; main(); // setInterval(main)