Skip to content

Instantly share code, notes, and snippets.

@Flexi23
Created May 13, 2018 09:31
Show Gist options
  • Save Flexi23/0fa1d2c405af308c0b254a304c246a2b to your computer and use it in GitHub Desktop.
Save Flexi23/0fa1d2c405af308c0b254a304c246a2b to your computer and use it in GitHub Desktop.
<html>
<head>
<title>Lemniscate Panorama Configurator</title>
<script type="text/javascript" src="dat.gui.min.js"></script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aPos;
attribute vec2 aTexCoord;
varying vec2 uv;
void main(void) {
gl_Position = vec4(aPos, 1.);
uv = aTexCoord;
}
</script>
<script id="shader-fs-pano" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 uv;
uniform sampler2D sampler_pano;
uniform vec2 aspect;
uniform float mirrorSize;
uniform vec2 rotation;
uniform float zoom;
uniform float offsetX;
uniform vec2 flip;
uniform float reverse;
vec2 factorA, factorB, product;
#define pi 3.141592653589793238462643383279
#define pi_inv 0.318309886183790671537767526745
#define pi2_inv 0.159154943091895335768883763372
float atan2(float y, float x){
if(x>0.) return atan(y/x);
if(y>=0. && x<0.) return atan(y/x) + pi;
if(y<0. && x<0.) return atan(y/x) - pi;
if(y>0. && x==0.) return pi/2.;
if(y<0. && x==0.) return -pi/2.;
if(y==0. && x==0.) return pi/2.; // undefined usually
return pi/2.;
}
vec2 applyMirror(vec2 uv){
uv.y = 1.- uv.y; // flipud
uv.y = mix( uv.y / (1. - mirrorSize), (1.-uv.y) / mirrorSize, float(uv.y > 1.- mirrorSize));
uv.y = 1.- uv.y; // flipud
return uv;
}
void main(void) {
vec2 uv = (uv-0.5)*aspect * zoom;
uv = 0.5 + vec2( uv.x*rotation.x - uv.y*rotation.y, uv.x*rotation.y + uv.y*rotation.x);
// polar coordinates for the left-hand side
vec2 c1 = vec2(0.25 - mirrorSize*0.25, 0.5);
float a1 = atan2(uv.y - c1.y, uv.x - c1.x) + offsetX; // angle
float d1 = distance(uv, c1) / 0.5; // dist
float m1 = float(d1 < 1.); // mask
vec2 uv1 = applyMirror(vec2(a1*pi2_inv,d1));
float mm = float(d1 < mirrorSize && flip.y == -1.); // mirror mask
uv1 = mix(uv1, 1.-applyMirror(1.-uv1), mm);
// polar coordinates for the right-hand side
vec2 c2 = vec2(0.75 + mirrorSize*0.25, 0.5);
float a2 = -atan2(uv.y - c2.y, uv.x - c2.x) + offsetX + pi; // angle
float d2 = distance(uv, c2) / 0.5; // dist
float m2 = float(d2 < 1.); // mask
vec2 uv2 = applyMirror(vec2(a2*pi2_inv,d2)); uv2.y = 1.-uv2.y;
mm = float(d2 < mirrorSize && flip.y == 1.); // mirror mask
uv2 = mix(uv2, applyMirror(uv2), mm);
vec4 leftBall = texture2D(sampler_pano, uv1) * m1;
vec4 rightBall = texture2D(sampler_pano, uv2) * m2;
float mh = float(0.5 + (uv.y-0.5) * reverse < 0.5); // mask horizontal half
vec2 mixUv = mix(uv1, uv2, m2);
mixUv = mix(mixUv, uv1, mh * m1);
mixUv = 0.5 + (mixUv-0.5)*flip;
float mixMask = max(m1, m2);
vec4 mixBall = texture2D(sampler_pano, mixUv) * mixMask;
gl_FragColor = mixBall;
gl_FragColor.a = 1.;
}
</script>
<script type="text/javascript">
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
var str = "";
var k = shaderScript.firstChild;
while (k) {
if (k.nodeType == 3)
str += k.textContent;
k = k.nextSibling;
}
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex")
shader = gl.createShader(gl.VERTEX_SHADER);
else
return null;
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) == 0)
alert("error compiling shader '" + id + "'\n\n" + gl.getShaderInfoLog(shader));
return shader;
}
window.requestAnimFrame = (function () {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame
|| window.msRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 10); // don't really need 100fps anyway
};
})();
var $ = function (d) {
return document.getElementById(d);
};
var gl;
var ext;
var prog_pano;
var texture_pano;
var texture_pano2;
var texture_pano3;
var sizeX = 1024; // must be powers of 2
var sizeY = 512;
var frame = 0; // frame counter to be resetted every 1000ms
var framecount = 0; // not resetted
var fps, fpsDisplayUpdateTimer;
var time, starttime = new Date().getTime();
var pointerX = 0.5;
var pointerY = 0.5;
// geometry
var squareBuffer;
function load() {
clearInterval(fpsDisplayUpdateTimer);
var c = document.getElementById("c");
try {
gl = c.getContext("experimental-webgl", {
depth: false
});
} catch (e) {
}
if (!gl) {
alert("Meh! Y u no support experimental WebGL !?!");
return;
}
["OES_texture_float", "OES_standard_derivatives", "OES_texture_float_linear", "OES_texture_float_linear"].forEach(function (name) {
console.log("check " + name);
try {
ext = gl.getExtension(name);
} catch (e) {
alert(e);
}
if (!ext) {
alert("Meh! Y u no support " + name + " !?!\n(Chrome 29 or Firefox 24 will do fine)");
return;
}
ext = false;
});
document.onmousemove = function (evt) {
pointerX = evt.pageX / sizeX;
pointerY = 1 - evt.pageY / sizeY;
};
window.onresize = function (a) {
c.style.width = innerWidth + 'px';
c.style.height = innerHeight + 'px';
};
sizeX = window.innerWidth;
sizeY = window.innerHeight;
c.width = sizeX;
c.height = sizeY;
prog_pano = createAndLinkProgram("shader-fs-pano");
triangleStripGeometry = {
vertices: new Float32Array([-1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0]),
texCoords: new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
vertexSize: 3,
vertexCount: 4,
type: gl.TRIANGLE_STRIP
};
createTexturedGeometryBuffer(triangleStripGeometry);
squareBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareBuffer);
var aPosLoc = gl.getAttribLocation(prog_pano, "aPos");
var aTexLoc = gl.getAttribLocation(prog_pano, "aTexCoord");
gl.enableVertexAttribArray(aPosLoc);
gl.enableVertexAttribArray(aTexLoc);
var verticesAndTexCoords = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1, // one square of a quad!
0, 0, 1, 0, 0, 1, 1, 1] // hello texture, you be full
);
gl.bufferData(gl.ARRAY_BUFFER, verticesAndTexCoords, gl.STATIC_DRAW);
gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, gl.FALSE, 8, 0);
gl.vertexAttribPointer(aTexLoc, 2, gl.FLOAT, gl.FALSE, 8, 32);
FBO_pano = gl.createFramebuffer();
texture_pano = createAndBindImageTexture($("pano"), FBO_pano);
FBO_pano2 = gl.createFramebuffer();
//texture_pano2 = createAndBindImageTexture($("pano2"), FBO_pano2);
FBO_pano3 = gl.createFramebuffer();
//texture_pano3 = createAndBindImageTexture($("pano3"), FBO_pano3);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, texture_pano);
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, texture_pano2);
gl.activeTexture(gl.TEXTURE3);
gl.bindTexture(gl.TEXTURE_2D, texture_pano3);
time = new Date().getTime() - starttime;
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
gl.clearColor(0, 0, 0, 1);
var gui = new dat.GUI();
gui.add(configuration, 'zoom', 0.4, 2.5);
gui.add(configuration, 'mirrorSize', 0.001, 1);
gui.add(configuration, 'rotation', -180, 180);
gui.add(configuration, 'offsetX', -180, 180);
gui.add(configuration, 'flipX');
gui.add(configuration, 'flipY');
gui.add(configuration, 'reverse');
anim();
}
function createTexturedGeometryBuffer(geometry) {
geometry.buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, geometry.buffer);
geometry.aPosLoc = gl.getAttribLocation(prog_pano, "aPos");
gl.enableVertexAttribArray(geometry.aPosLoc);
geometry.aTexLoc = gl.getAttribLocation(prog_pano, "aTexCoord");
gl.enableVertexAttribArray(geometry.aTexLoc);
geometry.texCoordOffset = geometry.vertices.byteLength;
gl.bufferData(gl.ARRAY_BUFFER, geometry.texCoordOffset + geometry.texCoords.byteLength, gl.STATIC_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, geometry.vertices);
gl.bufferSubData(gl.ARRAY_BUFFER, geometry.texCoordOffset, geometry.texCoords);
setGeometryVertexAttribPointers(geometry);
}
function setGeometryVertexAttribPointers(geometry) {
gl.vertexAttribPointer(geometry.aPosLoc, geometry.vertexSize, gl.FLOAT, gl.FALSE, 0, 0);
gl.vertexAttribPointer(geometry.aTexLoc, 2, gl.FLOAT, gl.FALSE, 0, geometry.texCoordOffset);
}
function createAndLinkProgram(fsId) {
var program = gl.createProgram();
gl.attachShader(program, getShader(gl, "shader-vs"));
gl.attachShader(program, getShader(gl, fsId));
gl.linkProgram(program);
return program;
}
function createAndBindImageTexture(img, fbo) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
//gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2048, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
return texture;
}
function setUniforms(program) {
gl.uniform2f(gl.getUniformLocation(program, "aspect"), Math.max(1, innerWidth / innerHeight), Math.max(1, innerHeight / innerWidth));
gl.uniform1i(gl.getUniformLocation(program, "sampler_pano"), 1);
gl.uniform1f(gl.getUniformLocation(program, "mirrorSize"), configuration.mirrorSize);
gl.uniform1f(gl.getUniformLocation(program, "offsetX"), configuration.offsetX / 180 * Math.PI);
gl.uniform2f(gl.getUniformLocation(program, "rotation"), Math.cos(configuration.rotation / 180 * Math.PI), Math.sin(configuration.rotation / 180 * Math.PI));
gl.uniform1f(gl.getUniformLocation(program, "zoom"), 1 / configuration.zoom);
gl.uniform2f(gl.getUniformLocation(program, "flip"), configuration.flipX ? -1 : 1, configuration.flipY ? -1 : 1);
gl.uniform1f(gl.getUniformLocation(program, "reverse"), configuration.reverse ? -1 : 1);
}
function useGeometry(geometry) {
gl.bindBuffer(gl.ARRAY_BUFFER, geometry.buffer);
setGeometryVertexAttribPointers(geometry);
}
function renderGeometry(geometry, targetFBO) {
useGeometry(geometry);
gl.bindFramebuffer(gl.FRAMEBUFFER, targetFBO);
gl.drawArrays(geometry.type, 0, geometry.vertexCount);
gl.flush();
}
function renderAsTriangleStrip(targetFBO) {
renderGeometry(triangleStripGeometry, targetFBO);
}
function render() {
gl.viewport(0, 0, sizeX, sizeY);
gl.useProgram(prog_pano);
setUniforms(prog_pano);
renderAsTriangleStrip(null);
}
function anim() {
requestAnimationFrame(anim);
render();
}
var Configuration = function () {
this.zoom = 1.;
this.mirrorSize = 0.001;
this.rotation = 0;
this.offsetX = 110;
this.flipX = true;
this.flipY = false;
this.reverse = true;
};
var configuration = new Configuration();
</script>
<style type="text/css">
body {
background-color: #000000;
color: #FFFFFF;
overflow: hidden;
}
#imgs {
position: absolute;
top: 2048;
left: 0;
z-index: -1;
}
#c {
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
a {
color: #FFFFFF;
font-weight: bold;
}
#desc {
background-color: rgba(0, 0, 0, 0.2);
width: 4096;
}
</style>
</head>
<body onload="load()" ondblclick="hide()">
<canvas id="c" width="4096" height="4096"></canvas>
<br>
<div id="imgs">
<img id="pano" src="your_equirectangular_image_here.jpg"><br>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment