<!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>Fibonacci lattice</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 type="x-shader/x-vertex"> #version 300 es uniform float numVertices; uniform vec2 canvasSize; const float TWO_PI_OVER_PHI = 6.2831853072 / 1.61803398875; vec2 polarToCartesian(float radius, float angle) { return vec2(radius * cos(angle), radius * sin(angle)); } void main() { float i = float(gl_VertexID) + 1.; // the point at index 0 looks out of place so start from 1 vec2 point = polarToCartesian( sqrt(i/numVertices), i * TWO_PI_OVER_PHI ); // compensate for aspect ratio: if (canvasSize.x > canvasSize.y) { point.x *= canvasSize.y/canvasSize.x; } else { point.y *= canvasSize.x/canvasSize.y; } gl_PointSize = 3.; gl_Position = vec4(point, 0, 1); } </script> <script id="fragmentShader" type="x-shader/x-fragment"> #version 300 es precision highp float; out vec4 fragColor; void main() { fragColor = vec4(0, 0, 0, 1); } </script> <script> const canvas = document.querySelector("canvas"); const vertexShader = document.querySelector('script[type="x-shader/x-vertex"]'); const fragmentShader = document.querySelector('script[type="x-shader/x-fragment"]'); const gl = canvas.getContext("webgl2"); if (!gl) { main.innerHTML = '<p>Error: WebGL2 is <a href="https://get.webgl.org/webgl2/">not supported by your browser</a></p>'; throw "WebGL2 not supported"; } function createShader(shaderType, sourceCode) { const shader = gl.createShader(shaderType); gl.shaderSource(shader, sourceCode.trim()); 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 numVertices = 4000; const numVerticesUniform = gl.getUniformLocation(program, "numVertices"); gl.uniform1f(numVerticesUniform, numVertices); const canvasSizeUniform = gl.getUniformLocation(program, "canvasSize"); 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); // Since we are determining all vertex positions inside the vertex // shader, we don't actually need to pass in a list of vertices. // Instead we tell it how many vertices to draw and rely on the // vertex index (gl_VertexID) to calculate position. gl.drawArrays(gl.POINTS, 0, numVertices); } window.addEventListener("resize", () => draw()); draw(); </script> <style> canvas { background-color: white; } </style> </body> </html>