Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Last active February 4, 2018 21:02
Show Gist options
  • Save mathdoodle/5c70bee3c68b2b7a4572 to your computer and use it in GitHub Desktop.
Save mathdoodle/5c70bee3c68b2b7a4572 to your computer and use it in GitHub Desktop.
Mandelbrot Set

Mandelbrot Set

Overview

A graphical representation of the Mandelbrot Set. The inner black region is the mandelbrot Set. The colors surrounding this region illustrate the direction of complex numbers that have "escaped" the Mandelbrot Set after a finite number of applications of a transformation.

Mathematics

The Mandelbrot Set is the set of complex numbers that stays bounded after repeated application of the transformation

\[z_{n+1} = z_{n}^2 + c\]

Computing

The program performs most of processing on the Graphics Processor Unit (GPU) using the WebGL API to set things up from JavaScript. Using the GPU makes the graphics generation extremely fast.

<!DOCTYPE html>
<html>
<head>
<base href='/'>
<script src='https://jspm.io/[email protected]'></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id='canvas' width='500' height='500'>
Your browser does not support the HTML5 canvas element.
</canvas>
<script>
System.defaultJSExtensions = true
System.import('./index')
</script>
</body>
</html>
import initWebGL from './initWebGL'
import makeProgram from './makeProgram'
// Use DomReady because we need to use the canvas element.
DomReady.ready(function() {
// Initialize the WebGL rendering context.
const canvas = <HTMLCanvasElement> document.getElementById('canvas')
const gl: WebGLRenderingContext = initWebGL(canvas)
// Compile and link the shader programs.
const vsSource = (<HTMLElement> document.getElementById('shader-vs')).innerText
const fsSource = (<HTMLElement> document.getElementById('shader-fs')).innerText
const program = makeProgram(gl, vsSource, fsSource)
gl.useProgram(program)
// Create some buffers for the WebGL program.
const side = 2
const vertices: number[] = [
0, 0, 0.0, // 0 center
-side / 2, -side / 2, 0.0, // 1 lower left
+side / 2, -side / 2, 0.0, // 2 lower right
+side / 2, +side / 2, 0.0, // 3 upper right
-side / 2, +side / 2, 0.0 // 4 upper left
]
const vbo = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, vbo)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
// The triangle indices are [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1].
const indices = [0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 1]
const ibo = gl.createBuffer()
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STREAM_DRAW)
// Set rendering context parameters.
gl.clearColor(0.5, 0.5, 0.6, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.viewport(0, 0, canvas.width, canvas.height)
// Send the buffers to the WebGL program.
const aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition')
gl.enableVertexAttribArray(aVertexPosition)
// The size parameter satisfy vertices.length() / 3 = number of vertices, 5.
gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0)
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0)
})
/**
* Initializes the WebGL rendering context from a canvas element.
*/
export default function initWebGL(canvas: HTMLCanvasElement): WebGLRenderingContext {
// Try to grab the standard context. If it fails, fallback to experimental.
const context = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
if (context) {
return context
}
else {
throw new Error("Unable to initialize WebGL. Your browser may not support it.")
}
}
/**
* Creates a WebGLProgram with compiled and linked shaders.
*/
export default function makeProgram(gl: WebGLRenderingContext, vertexShader: string, fragmentShader: string): WebGLProgram {
// TODO: Proper cleanup if we throw an error at any point.
const vs = <WebGLShader> gl.createShader(gl.VERTEX_SHADER)
gl.shaderSource(vs, vertexShader)
gl.compileShader(vs)
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
throw new Error(<string> gl.getShaderInfoLog(vs))
}
const fs = <WebGLShader> gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fs, fragmentShader)
gl.compileShader(fs)
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
throw new Error(<string> gl.getShaderInfoLog(fs))
}
const program = <WebGLProgram> gl.createProgram()
gl.attachShader(program, vs)
gl.attachShader(program, fs)
gl.linkProgram(program)
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(<string> gl.getProgramInfoLog(program))
}
return program
}
{
"description": "Mandelbrot Set",
"dependencies": {
"DomReady": "1.0.0",
"jasmine": "2.5.2"
},
"name": "mandelbrot-set",
"version": "0.1.0",
"author": "David Geo Holmes",
"keywords": [
"Fractals",
"Complex Numbers",
"GPU",
"WebGL",
"shaders",
"GLSL"
],
"linting": true,
"hideConfigFiles": true
}
import initWebGL from './initWebGL'
import makeProgram from './makeProgram'
DomReady.ready(main)
function main() {
const canvas = <HTMLCanvasElement> document.getElementById('doodle-canvas')
const gl: WebGLRenderingContext = initWebGL(canvas)
const vs_source = (<HTMLElement> document.getElementById('shader-vs')).innerText
const fs_source = (<HTMLElement> document.getElementById('shader-fs')).innerText
const program = makeProgram(gl, vs_source, fs_source)
gl.useProgram(program)
const size = 2.0
const vertices: number[] = [
0.0, 0.0, 0.0, // 0 center
-size / 2, -size / 2, 0.0, // 1 lower left
+size / 2, -size / 2, 0.0, // 2 lower right
+size / 2, +size / 2, 0.0, // 3 upper right
-size / 2, +size / 2, 0.0 // 4 upper left
]
const vbo = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, vbo)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
const indices = [0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 1]
const ibo = gl.createBuffer()
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STREAM_DRAW)
gl.clearColor(0.5, 0.5, 0.6, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.viewport(0, 0, canvas.width, canvas.height)
const aVertexPosition = gl.getAttribLocation(program, 'aVertexPosition')
gl.enableVertexAttribArray(aVertexPosition)
gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0)
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0)
}
varying highp vec2 position;
const int MAX_ITERATIONS = 250;
const highp float LIGHTNESS_FACTOR = 5.0;
highp vec3 rgb2hsv(in highp vec3 c);
highp vec3 hsv2rgb(in highp vec3 c);
/**
*
*/
highp vec3 hsv2rgb(in highp vec3 c)
{
highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
/**
*
*/
highp vec3 rgb2hsv(in highp vec3 c)
{
highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
highp float d = q.x - min(q.w, q.y);
highp float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
/**
*
*/
void main(void) {
highp vec2 c = vec2(position.x-0.5, position.y);
highp vec2 z = c;
highp vec4 color = vec4(0.0,0.0,0.0,1.0);
for (int i=0; i < MAX_ITERATIONS; i++) {
z = vec2(z.x*z.x -z.y * z.y, 2.0*z.x*z.y) + c;
if (dot(z,z) > 4.0) {
highp float f = LIGHTNESS_FACTOR * float(i) / float(MAX_ITERATIONS);
highp vec2 zcopy = normalize(vec2(z.x, z.y));
highp float theta = atan(zcopy.y, zcopy.x) / (2.0 * 3.14159);
color = vec4(hsv2rgb(vec3(theta,1.0,1.0))*f, 1.0);
break;
}
}
gl_FragColor = color;
}
attribute vec3 aVertexPosition;
varying vec2 position;
void main(void) {
position = vec2(aVertexPosition);
gl_Position = vec4(position, 0.0, 1.0);
}
body {
background-color: blue;
}
canvas {
background-color: black;
position: absolute;
left: 0px;
top: 0px;
}
{
"allowJs": false,
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"jsx": "react",
"module": "system",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": false,
"sourceMap": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5",
"traceResolution": true
}
{
"rules": {
"array-type": [
true,
"array"
],
"curly": false,
"comment-format": [
true,
"check-space"
],
"eofline": true,
"forin": true,
"jsdoc-format": true,
"no-conditional-assignment": false,
"no-consecutive-blank-lines": true,
"no-construct": true,
"no-for-in-array": true,
"no-magic-numbers": false,
"no-shadowed-variable": true,
"no-string-throw": true,
"no-trailing-whitespace": [
true,
"ignore-jsdoc"
],
"no-var-keyword": true,
"one-variable-per-declaration": [
true,
"ignore-for-loop"
],
"prefer-const": true,
"prefer-for-of": true,
"prefer-function-over-method": false,
"radix": true,
"semicolon": [
true,
"never"
],
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": true,
"use-isnan": true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment