Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| <html> | |
| <head> | |
| <title>Will's quick mandelbulb GLSL</title> | |
| <script id="vs_perf" type="x-shader/x-vertex"> | |
| uniform mat4 mvp; | |
| attribute vec2 vertex; | |
| void main() { | |
| gl_Position = mvp * vec4(vertex,0.5,1); | |
| } | |
| </script> | |
| <script id="fs_perf" type="x-shader/x-fragment"> | |
| precision mediump float; | |
| uniform vec4 colour; | |
| void main() { | |
| gl_FragColor = colour; | |
| } | |
| </script> | |
| <script id="vs_mandelbulb_1" type="x-shader/x-vertex"> | |
| uniform mat4 inv_mvp; | |
| attribute vec2 vertex; | |
| attribute vec2 tex_coord; | |
| varying vec2 pos; | |
| varying vec3 near, far; | |
| void main() { | |
| gl_Position = vec4(vertex,0,1); | |
| pos = tex_coord; | |
| vec4 p = inv_mvp * vec4(vertex,-1,1); | |
| near = p.xyz / p.w; | |
| p = inv_mvp * vec4(vertex,1,1); | |
| far = (p.xyz / p.w); | |
| } | |
| </script> | |
| <script id="fs_mandelbulb_1" type="x-shader/x-vertex"> | |
| precision mediump float; | |
| uniform sampler2D distance_tex; | |
| uniform float epsilon; | |
| varying vec2 pos; | |
| varying vec3 near, far; | |
| const float Power = 8.0; | |
| const float BailOut = 2.0; | |
| const int Iterations = 5; | |
| float DE(vec3 pos) { | |
| vec3 z = pos; | |
| float dr = 1.0; | |
| float r = 0.0; | |
| for(int i = 0; i < Iterations; i++) { | |
| r = length(z); | |
| if (r>BailOut) break; | |
| // convert to polar coordinates | |
| float theta = acos(z.z/r); | |
| float phi = atan(z.y,z.x); | |
| float r7 = r*r*r*r*r*r*r, r8 = r7*r; | |
| dr = r7*Power*dr + 1.0; | |
| // scale and rotate the point | |
| float zr = r8; | |
| theta = theta*Power; | |
| phi = phi*Power; | |
| // convert back to cartesian coordinates | |
| z = zr*vec3(sin(theta)*cos(phi), sin(phi)*sin(theta), cos(theta)); | |
| z+=pos; | |
| } | |
| return 0.5*log(r)*r/dr; | |
| } | |
| const int MaxRaySteps = $MaxRaySteps$; | |
| void main() { | |
| vec3 rayDir = far-near; | |
| float rayLen = length(rayDir), | |
| g = 1.0, | |
| b = texture2D(distance_tex,pos).r, | |
| totalDistance = b * rayLen, | |
| distance = 1.0; | |
| if(b != 1.0) { | |
| g = 0.0; | |
| rayDir /= rayLen; | |
| for(int i=0; i<MaxRaySteps; i++) { | |
| distance = DE(near + totalDistance * rayDir); | |
| totalDistance += distance; | |
| if(totalDistance > rayLen) { | |
| totalDistance = rayLen; | |
| break; | |
| } | |
| if(distance < epsilon) break; | |
| } | |
| distance = totalDistance/rayLen; | |
| } | |
| gl_FragColor = vec4(distance,g,b,1.0); | |
| } | |
| </script> | |
| <script type="application/javascript"> | |
| var canvas, gl, steps; | |
| var perf = { | |
| data: new Float32Array(2*6*60*3), | |
| slot: 0, | |
| now: function() { return Date.now()/1000.0; }, | |
| init: function(target) { | |
| this.start = this.now(); | |
| this.program = createProgram( | |
| document.getElementById("vs_perf").textContent, | |
| document.getElementById("fs_perf").textContent); | |
| this.program.mvp = gl.getUniformLocation(this.program,"mvp"); | |
| this.program.colour = gl.getUniformLocation(this.program,"colour"); | |
| this.program.vertex = gl.getAttribLocation(this.program,"vertex"); | |
| this.program.vertexBuf = gl.createBuffer(); | |
| }, | |
| draw: function() { | |
| // tick | |
| var t = this.now() - this.start, w = 1.0/60; | |
| var pts = [t,1,t+w,1,t,0,t+w,1,t,0,t+w,0]; // 2 triangles | |
| this.data.set(pts,this.slot); | |
| this.slot += pts.length; | |
| if(this.slot >= this.data.length) this.slot = 0; | |
| // draw | |
| gl.useProgram(this.program); | |
| var mvp = createOrtho2D(t+w-canvas.width/120.0,t+w,-5,1); | |
| gl.uniformMatrix4fv(this.program.mvp,false,new Float32Array(mvp)); | |
| gl.uniform4f(this.program.colour,0.5,0.2,0.2,1.0); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,this.program.vertexBuf); | |
| gl.bufferData(gl.ARRAY_BUFFER,this.data,gl.DYNAMIC_DRAW); | |
| gl.enableVertexAttribArray(this.program.vertex); | |
| gl.vertexAttribPointer(this.program.vertex,2,gl.FLOAT,false,0,0); | |
| gl.drawArrays(gl.TRIANGLES,0,this.data.length/2); | |
| gl.disableVertexAttribArray(this.program.vertex); | |
| }, | |
| }; | |
| function start() { | |
| window.onerror = function(msg, url, lineno) { | |
| alert(url + '(' + lineno + '): ' + msg); | |
| }; | |
| canvas = document.getElementById("game-canvas"); | |
| try { | |
| gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); | |
| } catch(e) {} | |
| if(!gl) { | |
| alert("Unable to initialize WebGL. Your browser may not support it."); | |
| return; | |
| } | |
| gl.activeTexture(gl.TEXTURE0); | |
| gl.clearColor(0,0,0,1); | |
| window.onresize = function() { | |
| // setting its OpenGL canvas size to the window size | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| gl.clear(gl.COLOR_BUFFER_BIT); | |
| }; | |
| window.onresize(); | |
| var vs = document.getElementById("vs_mandelbulb_1").textContent; | |
| var fs = document.getElementById("fs_mandelbulb_1").textContent; | |
| steps = [{ | |
| program: createProgram(vs,fs.replace("$MaxRaySteps$","10")), | |
| framebuffer: gl.createFramebuffer(), | |
| distance_tex: createFramebufferTexture(128,64), | |
| },{ | |
| program: createProgram(vs,fs.replace("$MaxRaySteps$","10")), | |
| framebuffer: gl.createFramebuffer(), | |
| distance_tex: createFramebufferTexture(256,256), | |
| },{ | |
| program: createProgram(vs,fs.replace("$MaxRaySteps$","40")), | |
| framebuffer: null, | |
| distance_tex: null, | |
| }]; | |
| var vertices = [-1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, 1], // two triangles that fill the screen, promise | |
| tex_coords = [0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1]; // corresponding tex coordinates | |
| for(var step in steps) { | |
| step = steps[step]; | |
| step.program.vertexBuf = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,step.program.vertexBuf); | |
| gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(vertices),gl.STATIC_DRAW); | |
| step.program.vertex = gl.getAttribLocation(step.program,"vertex"); | |
| step.program.tex_coordBuf = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,step.program.tex_coordBuf); | |
| gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(tex_coords),gl.STATIC_DRAW); | |
| step.program.tex_coord = gl.getAttribLocation(step.program,"tex_coord"); | |
| step.program.epsilon = gl.getUniformLocation(step.program,"epsilon"); | |
| step.program.inv_mvp = gl.getUniformLocation(step.program,"inv_mvp"); | |
| step.program.distance_tex = gl.getUniformLocation(step.program,"distance_tex"); | |
| } | |
| perf.init(); | |
| window.requestAnimFrame(render); | |
| } | |
| var start_time = (new Date()).getTime(); | |
| function render() { | |
| var elapsed = (((new Date()).getTime() - start_time) / 1000) / 2; | |
| // we swing and spin a bit | |
| var distance = 2, | |
| modelview = createLookAt([Math.cos(elapsed)*Math.sin(elapsed)*distance,Math.sin(elapsed)*distance,Math.cos(elapsed)*1.1],[0,0,0],[0,0,1]); | |
| var distance_tex = null; | |
| for(var step in steps) { | |
| step = steps[step]; | |
| gl.useProgram(step.program); | |
| var target = step.distance_tex || canvas, | |
| perspective = createPerspective(60.0,canvas.width/canvas.height,0.1,distance+2), | |
| inv_mvp = mat4_inverse(mat4_multiply(perspective,modelview)); | |
| gl.bindFramebuffer(gl.FRAMEBUFFER,step.framebuffer); | |
| if(step.distance_tex) | |
| gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, step.distance_tex, 0); | |
| gl.viewport(0,0,target.width,target.height); | |
| gl.activeTexture(gl.TEXTURE0); | |
| gl.bindTexture(gl.TEXTURE_2D, distance_tex); | |
| gl.uniform1f(step.program.epsilon,(1.0/Math.min(canvas.width,canvas.height))/2.0); | |
| gl.uniform1i(step.program.distance_tex,0); | |
| gl.uniformMatrix4fv(step.program.inv_mvp,false,new Float32Array(inv_mvp)); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,step.program.vertexBuf); | |
| gl.enableVertexAttribArray(step.program.vertex); | |
| gl.vertexAttribPointer(step.program.vertex,2,gl.FLOAT,false,0,0); | |
| gl.bindBuffer(gl.ARRAY_BUFFER,step.program.tex_coordBuf); | |
| gl.enableVertexAttribArray(step.program.tex_coord); | |
| gl.vertexAttribPointer(step.program.tex_coord,2,gl.FLOAT,false,0,0); | |
| gl.drawArrays(gl.TRIANGLES,0,6); | |
| gl.disableVertexAttribArray(step.program.vertex); | |
| gl.disableVertexAttribArray(step.program.tex_coord); | |
| gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, null, 0); | |
| gl.bindTexture(gl.TEXTURE_2D, null); | |
| distance_tex = step.distance_tex; | |
| } | |
| perf.draw(); | |
| window.requestAnimFrame(render); | |
| } | |
| function createShader(str,type) { | |
| if(!window.x_shaders) window.x_shaders = []; | |
| var shader = window.x_shaders[[str,type]]; | |
| if(!shader) { | |
| shader = gl.createShader(type); | |
| gl.shaderSource(shader,str); | |
| gl.compileShader(shader); | |
| if (!gl.getShaderParameter(shader,gl.COMPILE_STATUS)) | |
| throw gl.getShaderInfoLog(shader); | |
| window.x_shaders[[str,type]] = shader; | |
| } | |
| return shader; | |
| } | |
| function createProgram(vstr,fstr) { | |
| if(!window.x_programs) window.x_programs = []; | |
| var program = window.x_programs[[vstr,fstr]]; | |
| if(!program) { | |
| program = gl.createProgram(); | |
| var vshader = createShader(vstr,gl.VERTEX_SHADER); | |
| var fshader = createShader(fstr,gl.FRAGMENT_SHADER); | |
| gl.attachShader(program,vshader); | |
| gl.attachShader(program,fshader); | |
| gl.linkProgram(program); | |
| window.x_programs[[vstr,fstr]] = program; | |
| } | |
| return program; | |
| } | |
| function createFramebufferTexture(width,height) { | |
| var tex = gl.createTexture(); | |
| gl.bindTexture(gl.TEXTURE_2D, tex); | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
| gl.bindTexture(gl.TEXTURE_2D, null); | |
| tex.width = width; | |
| tex.height = height; | |
| return tex; | |
| } | |
| function createLookAt(eye,centre,up) { | |
| if (eye[0] == centre[0] && eye[1] == centre[1] && eye[2] == centre[2]) | |
| return [1, 0, 0, 0, | |
| 0, 1, 0, 0, | |
| 0, 0, 1, 0, | |
| 0, 0, 0, 1]; | |
| var z0,z1,z2,x0,x1,x2,y0,y1,y2,len; | |
| //vec3.direction(eye, center, z); | |
| z0 = eye[0] - centre[0]; | |
| z1 = eye[1] - centre[1]; | |
| z2 = eye[2] - centre[2]; | |
| // normalize (no check needed for 0 because of early return) | |
| len = 1/Math.sqrt(z0*z0 + z1*z1 + z2*z2); | |
| z0 *= len; | |
| z1 *= len; | |
| z2 *= len; | |
| //vec3.normalize(vec3.cross(up, z, x)); | |
| x0 = up[1]*z2 - up[2]*z1; | |
| x1 = up[2]*z0 - up[0]*z2; | |
| x2 = up[0]*z1 - up[1]*z0; | |
| len = Math.sqrt(x0*x0 + x1*x1 + x2*x2); | |
| if(len) len = 1/len; else len = 0; | |
| x0 *= len; | |
| x1 *= len; | |
| x2 *= len; | |
| //vec3.normalize(vec3.cross(z, x, y)); | |
| y0 = z1*x2 - z2*x1; | |
| y1 = z2*x0 - z0*x2; | |
| y2 = z0*x1 - z1*x0; | |
| len = Math.sqrt(y0*y0 + y1*y1 + y2*y2); | |
| if(len) len = 1/len; else len = 0; | |
| y0 *= len; | |
| y1 *= len; | |
| y2 *= len; | |
| return [x0, y0, z0, 0, | |
| x1, y1, z1, 0, | |
| x2, y2, z2, 0, | |
| -(x0*eye[0] + x1*eye[1] + x2*eye[2]), -(y0*eye[0] + y1*eye[1] + y2*eye[2]), -(z0*eye[0] + z1*eye[1] + z2*eye[2]), 1]; | |
| } | |
| function createOrtho2D(left,right,top,bottom) { | |
| var near = -1, far = 1, rl = right-left, tb = top-bottom, fn = far-near; | |
| return [2/rl,0,0,0, | |
| 0,2/tb,0,0, | |
| 0,0,-2/fn,0, | |
| -((right+left)/rl), -((top+bottom)/tb), -((far+near)/fn),1]; | |
| } | |
| function createPerspective(fovy,aspect,near,far) { | |
| var top = near*Math.tan(fovy*Math.PI/360.0); | |
| var right = top*aspect, left = -right, bottom = -top; | |
| var rl = (right-left); | |
| var tb = (top-bottom); | |
| var fn = (far-near); | |
| return [(near*2)/rl, 0, 0, 0, | |
| 0, (near*2)/tb, 0, 0, | |
| (right+left)/rl, (top+bottom)/tb, -(far+near)/fn, -1, | |
| 0, 0, -(far*near*2)/fn, 0]; | |
| } | |
| function mat4_multiply(a,b) { | |
| return [b[0]*a[0] + b[1]*a[4] + b[2]*a[8] + b[3]*a[12], | |
| b[0]*a[1] + b[1]*a[5] + b[2]*a[9] + b[3]*a[13], | |
| b[0]*a[2] + b[1]*a[6] + b[2]*a[10] + b[3]*a[14], | |
| b[0]*a[3] + b[1]*a[7] + b[2]*a[11] + b[3]*a[15], | |
| b[4]*a[0] + b[5]*a[4] + b[6]*a[8] + b[7]*a[12], | |
| b[4]*a[1] + b[5]*a[5] + b[6]*a[9] + b[7]*a[13], | |
| b[4]*a[2] + b[5]*a[6] + b[6]*a[10] + b[7]*a[14], | |
| b[4]*a[3] + b[5]*a[7] + b[6]*a[11] + b[7]*a[15], | |
| b[8]*a[0] + b[9]*a[4] + b[10]*a[8] + b[11]*a[12], | |
| b[8]*a[1] + b[9]*a[5] + b[10]*a[9] + b[11]*a[13], | |
| b[8]*a[2] + b[9]*a[6] + b[10]*a[10] + b[11]*a[14], | |
| b[8]*a[3] + b[9]*a[7] + b[10]*a[11] + b[11]*a[15], | |
| b[12]*a[0] + b[13]*a[4] + b[14]*a[8] + b[15]*a[12], | |
| b[12]*a[1] + b[13]*a[5] + b[14]*a[9] + b[15]*a[13], | |
| b[12]*a[2] + b[13]*a[6] + b[14]*a[10] + b[15]*a[14], | |
| b[12]*a[3] + b[13]*a[7] + b[14]*a[11] + b[15]*a[15]]; | |
| } | |
| function mat4_inverse(mat) { | |
| var a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3]; | |
| var a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7]; | |
| var a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11]; | |
| var a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15]; | |
| var b00 = a00*a11 - a01*a10; | |
| var b01 = a00*a12 - a02*a10; | |
| var b02 = a00*a13 - a03*a10; | |
| var b03 = a01*a12 - a02*a11; | |
| var b04 = a01*a13 - a03*a11; | |
| var b05 = a02*a13 - a03*a12; | |
| var b06 = a20*a31 - a21*a30; | |
| var b07 = a20*a32 - a22*a30; | |
| var b08 = a20*a33 - a23*a30; | |
| var b09 = a21*a32 - a22*a31; | |
| var b10 = a21*a33 - a23*a31; | |
| var b11 = a22*a33 - a23*a32; | |
| var invDet = 1/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); | |
| return [ | |
| (a11*b11 - a12*b10 + a13*b09)*invDet, | |
| (-a01*b11 + a02*b10 - a03*b09)*invDet, | |
| (a31*b05 - a32*b04 + a33*b03)*invDet, | |
| (-a21*b05 + a22*b04 - a23*b03)*invDet, | |
| (-a10*b11 + a12*b08 - a13*b07)*invDet, | |
| (a00*b11 - a02*b08 + a03*b07)*invDet, | |
| (-a30*b05 + a32*b02 - a33*b01)*invDet, | |
| (a20*b05 - a22*b02 + a23*b01)*invDet, | |
| (a10*b10 - a11*b08 + a13*b06)*invDet, | |
| (-a00*b10 + a01*b08 - a03*b06)*invDet, | |
| (a30*b04 - a31*b02 + a33*b00)*invDet, | |
| (-a20*b04 + a21*b02 - a23*b00)*invDet, | |
| (-a10*b09 + a11*b07 - a12*b06)*invDet, | |
| (a00*b09 - a01*b07 + a02*b06)*invDet, | |
| (-a30*b03 + a31*b01 - a32*b00)*invDet, | |
| (a20*b03 - a21*b01 + a22*b00)*invDet]; | |
| } | |
| function mat4_transpose(f) { | |
| return [ | |
| f[0], f[4], f[8], f[12], | |
| f[1], f[5], f[9], f[13], | |
| f[2], f[6], f[10], f[14], | |
| f[3], f[7], f[11], f[15]]; | |
| } | |
| window.requestAnimFrame = window.requestAnimationFrame || | |
| window.webkitRequestAnimationFrame || | |
| window.mozRequestAnimationFrame || | |
| window.oRequestAnimationFrame || | |
| window.msRequestAnimationFrame || | |
| function(callback) { | |
| window.setTimeout(callback, 0); | |
| }; | |
| </script> | |
| </head> | |
| <body onload="start()" style="border:0px;margin:0px;padding:0px;"> | |
| <canvas id="game-canvas" style="width:100%;height:100%;border:0px;margin:0px;padding:0px;"> | |
| Your browser does not support WebGL :( | |
| </canvas> | |
| </body> | |
| </html> |