Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
407 lines (391 sloc) 14 KB
<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>