Created
May 28, 2018 21:17
-
-
Save Flexi23/5052c673aba333a241bdbbc12540da78 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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