Skip to content

Instantly share code, notes, and snippets.

@Flexi23
Created May 28, 2018 21:17
Show Gist options
  • Save Flexi23/5052c673aba333a241bdbbc12540da78 to your computer and use it in GitHub Desktop.
Save Flexi23/5052c673aba333a241bdbbc12540da78 to your computer and use it in GitHub Desktop.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Polar Coordinate Montage for Equirectangulars</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 vec3 angles;
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;
}
//Licence: tilt Correction shader code kindly provided by https://www.shadertoy.com/view/4tjGW1
float FOVX = 360.0; //Max 360 deg
float FOVY = 180.0; //Max 180 deg
const float PI = 3.1415926;
mat3 rotX(float theta){
float s = sin(theta);
float c = cos(theta);
mat3 m =
mat3( 1, 0, 0,
0, c, -s,
0, s, c);
return m;
}
mat3 rotY(float theta){
float s = sin(theta);
float c = cos(theta);
mat3 m =
mat3( c, 0, -s,
0, 1, 0,
s, 0, c);
return m;
}
mat3 rotZ(float theta){
float s = sin(theta);
float c = cos(theta);
mat3 m =
mat3( c, -s, 0,
s, c, 0,
0, 0, 1);
return m;
}
float deg2rad(float deg){
return deg*PI / 180.0;
}
vec2 tiltEquirect(vec2 uv){
float fovX = deg2rad(FOVX);
float fovY = deg2rad(FOVY);
float hAngle = uv.x * fovX;
float vAngle = uv.y * fovY;
vec3 p; // point on the sphere
p.x = sin(vAngle) * sin(hAngle);
p.y = cos(vAngle);
p.z = sin(vAngle) * cos(hAngle);
// rotate sphere
p = rotX(angles.z) * rotZ(angles.y) * p;
uv = vec2(atan2(p.x, p.z), acos(p.y))*1./vec2(fovX, fovY);
return uv;
}
void main(void) {
vec2 uv_orig = uv;
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) + angles.x; // 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) + angles.x + 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;
vec4 mixBall = texture2D(sampler_pano, tiltEquirect(mixUv)) * mixMask;
vec2 tiltCorrected = tiltEquirect(uv_orig);
//vec4 equirect = texture2D(sampler_pano, tiltCorrected);
gl_FragColor = mixBall;
//gl_FragColor = equirect;
//gl_FragColor = vec4(tiltCorrected,0,0);
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 $ = id => document.getElementById(id);
var gl;
var ext;
var prog_pano;
var fbo1; // framebuffer for the primary equirectangular texture
var sizeX = 1024;
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("webgl2", {
depth: false
});
} catch (e) {
}
if (!gl) {
alert("Meh! Y u no support WebGL 2 !?!");
return;
}
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);
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, 'flipX');
gui.add(configuration, 'flipY');
gui.add(configuration, 'reverse');
gui.add(configuration, 'angle1', -180, 180);
gui.add(configuration, 'angle2', -180, 180);
gui.add(configuration, 'angle3', -180, 180);
loadTexture("your-equirectangular-image-here.JPG", img => {
fbo1 = createAndBindImageTexture(img, gl.TEXTURE1);
});
anim();
}
var loaderImg;
function loadTexture(pathToTexture, callback){
if(!loaderImg)
loaderImg = document.createElement("img");
loaderImg.onload = () => callback(loaderImg);
loaderImg.src = pathToTexture;
}
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, activeUnit) {
var fbo = gl.createFramebuffer();
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.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);
gl.activeTexture(activeUnit);
gl.bindTexture(gl.TEXTURE_2D, texture);
fbo.activeUnit = activeUnit;
fbo.texture = texture;
return fbo;
}
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.uniform2f(gl.getUniformLocation(program, "rotation"), Math.cos(configuration.rotation / 180 * Math.PI), Math.sin(configuration.rotation / 180 * Math.PI));
gl.uniform3f(gl.getUniformLocation(program, "angles"), configuration.angle1 / 180 * Math.PI, configuration.angle2 / 180 * Math.PI, configuration.angle3 / 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() {
if(!fbo1)
return;
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 = -180;
this.flipX = false;
this.flipY = true;
this.reverse = false;
this.angle1 = -60;
this.angle2 = 60;
this.angle3 = 135;
};
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>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment