Skip to content

Instantly share code, notes, and snippets.

@Emanuele-Spatola
Forked from fieldOfView/WebGL360Video.html
Created August 7, 2018 14:27
Show Gist options
  • Save Emanuele-Spatola/1e7b442cc1c4f08a0e0ccc716432cd7d to your computer and use it in GitHub Desktop.
Save Emanuele-Spatola/1e7b442cc1c4f08a0e0ccc716432cd7d to your computer and use it in GitHub Desktop.
Simple WebGL/GLSL video panorama viewer
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Simple WebGL/GLSL video panorama viewer</title>
<style>
body {
background-color: #000000;
margin: 0px;
overflow: hidden;
}
video {
display: none;
}
</style>
</head>
<body>
<canvas></canvas>
<video></video>
<script id="vs" type="x-shader/vertex">
attribute vec3 position;
void main() {
gl_Position = vec4( position, 1.0 );
}
</script>
<script id="fs" type="x-shader/fragment">
uniform vec2 resolution;
uniform sampler2D texture;
uniform float fov;
uniform float yaw;
uniform float pitch;
const float M_PI = 3.141592653589793238462643;
const float M_TWOPI = 6.283185307179586476925286;
mat3 rotationMatrix(vec2 euler)
{
vec2 se = sin(euler);
vec2 ce = cos(euler);
return mat3(ce.x, 0, -se.x, 0, 1, 0, se.x, 0, ce.x) *
mat3(1, 0, 0, 0, ce.y, -se.y, 0, se.y, ce.y);
}
vec3 toCartesian( vec2 st )
{
return normalize(vec3(st.x, st.y, 0.5 / tan(0.5 * radians(fov))));
}
vec2 toSpherical(vec3 cartesianCoord)
{
vec2 st = vec2(
atan(cartesianCoord.x, cartesianCoord.z),
acos(cartesianCoord.y)
);
if(st.x < 0.0)
st.x += M_TWOPI;
return st;
}
void main(void)
{
vec2 sphericalCoord = gl_FragCoord.xy / resolution - vec2(0.5);
sphericalCoord.y *= -resolution.y / resolution.x;
vec3 cartesianCoord = rotationMatrix(radians(vec2(yaw + 180., -pitch))) * toCartesian(sphericalCoord);
gl_FragColor = texture2D(texture, toSpherical( cartesianCoord )/vec2(M_TWOPI, M_PI));
}
</script>
<script>
/**
* Provides requestAnimationFrame in a cross browser way.
* paulirish.com/2011/requestanimationframe-for-smart-animating/
*/
window.requestAnimationFrame = window.requestAnimationFrame || ( function() {
return window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback, element ) {
window.setTimeout( callback, 1000 / 60 );
};
})();
var canvas,
gl,
video, video_texture, video_started = false,
buffer,
vertex_shader, fragment_shader, currentProgram,
vertex_position,
parameters = { start_time : new Date().getTime(),
time : 0,
screenWidth : 0,
screenHeight: 0 },
currentlyDragging = false,
currentDragX = 0, currentDragY = 0,
startDragX = 0, startDragY = 0,
xSpeed = 0, ySpeed = 0,
yaw = 0, pitch = 0, fov = 80;
init("video.ogv", "preview.jpg");
animate();
function init( video_url, preview_url ) {
video = document.querySelector( 'video' );
video.addEventListener("canplaythrough", startVideo, true);
video.preload = "auto";
video.loop = true;
video.src = video_url;
canvas = document.querySelector( 'canvas' );
vertex_shader = document.getElementById('vs').textContent;
fragment_shader = document.getElementById('fs').textContent;
// Initialise WebGL
try {
gl = canvas.getContext( 'experimental-webgl' );
} catch( error ) { }
if ( !gl ) {
throw "cannot create webgl context";
}
// Create Vertex buffer (2 triangles)
buffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ - 1.0, - 1.0, 1.0, - 1.0, - 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, - 1.0, 1.0 ] ), gl.STATIC_DRAW );
// Create texture for video
createTexture( preview_url );
// Create Program
currentProgram = createProgram( vertex_shader, fragment_shader );
handleResize();
window.addEventListener( 'resize', handleResize, false );
// Initialise interaction
canvas.onmousedown = handleMouseDown;
document.onmouseup = handleMouseUp;
canvas.onmousemove = handleMouseMove;
canvas.ondblclick = handleDoubleClick;
}
function animate() {
handleMouse();
render();
requestAnimationFrame( animate );
}
function render() {
if ( !currentProgram ) return;
if( video_started )
updateTexture();
parameters.time = new Date().getTime() - parameters.start_time;
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
// Load program into GPU
gl.useProgram( currentProgram );
gl.bindTexture(gl.TEXTURE_2D, video_texture);
// Set values to program variables
gl.uniform2f( gl.getUniformLocation( currentProgram, 'resolution' ), parameters.screenWidth, parameters.screenHeight );
gl.uniform1i( gl.getUniformLocation( currentProgram, 'video' ), 0 );
gl.uniform1f( gl.getUniformLocation( currentProgram, 'fov' ), 80 );
gl.uniform1f( gl.getUniformLocation( currentProgram, 'yaw' ), yaw );
gl.uniform1f( gl.getUniformLocation( currentProgram, 'pitch' ), -pitch );
// Render geometry
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.vertexAttribPointer( vertex_position, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( vertex_position );
gl.drawArrays( gl.TRIANGLES, 0, 6 );
gl.disableVertexAttribArray( vertex_position );
}
function createShader( src, type ) {
var shader = gl.createShader( type );
gl.shaderSource( shader, src );
gl.compileShader( shader );
if ( !gl.getShaderParameter( shader, gl.COMPILE_STATUS ) ) {
alert( ( type == gl.VERTEX_SHADER ? "VERTEX" : "FRAGMENT" ) + " SHADER:\n" + gl.getShaderInfoLog( shader ) );
return null;
}
return shader;
}
function createProgram( vertex, fragment ) {
var program = gl.createProgram();
var vs = createShader( vertex, gl.VERTEX_SHADER );
var fs = createShader( '#ifdef GL_ES\nprecision highp float;\n#endif\n\n' + fragment, gl.FRAGMENT_SHADER );
if ( vs == null || fs == null ) return null;
gl.attachShader( program, vs );
gl.attachShader( program, fs );
gl.deleteShader( vs );
gl.deleteShader( fs );
gl.linkProgram( program );
if ( !gl.getProgramParameter( program, gl.LINK_STATUS ) ) {
alert( "ERROR:\n" +
"VALIDATE_STATUS: " + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + "\n" +
"ERROR: " + gl.getError() + "\n\n" +
"- Vertex Shader -\n" + vertex + "\n\n" +
"- Fragment Shader -\n" + fragment );
return null;
}
return program;
}
function createTexture( image_src ) {
video_texture = gl.createTexture();
video_texture.image = new Image();
video_texture.image.onload = function() {
gl.bindTexture(gl.TEXTURE_2D, video_texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video_texture.image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
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.bindTexture(gl.TEXTURE_2D, null);
}
video_texture.image.src = image_src;
}
function updateTexture() {
gl.bindTexture(gl.TEXTURE_2D, video_texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
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.bindTexture(gl.TEXTURE_2D, null);
}
function startVideo() {
video.play();
video_started = true;
}
function handleResize( event ) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
parameters.screenWidth = canvas.width;
parameters.screenHeight = canvas.height;
gl.viewport( 0, 0, canvas.width, canvas.height );
}
function handleDoubleClick(event) {
var isInFullScreen = (document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen);
if(!isInFullScreen) {
if (canvas.requestFullscreen) {
canvas.requestFullscreen();
} else if (canvas.mozRequestFullScreen) {
canvas.mozRequestFullScreen();
} else if (canvas.webkitRequestFullscreen) {
canvas.webkitRequestFullscreen();
}
} else {
if(document.cancelFullScreen) {
document.cancelFullScreen();
} else if(document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if(document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
}
}
}
function handleMouseDown(event) {
startDragX = event.clientX;
startDragY = event.clientY;
currentDragX = event.clientX;
currentDragY = event.clientY;
currentlyDragging = true;
return false;
}
function handleMouseUp(event) {
currentlyDragging = false;
return false;
}
function handleMouseMove(event) {
if(currentlyDragging) {
currentDragX = event.clientX;
currentDragY = event.clientY;
}
return false;
}
function handleMouse() {
if (currentlyDragging) {
xSpeed = (currentDragX - startDragX) / 100;
ySpeed = (currentDragY - startDragY) / 100;
}else {
xSpeed *= 0.8;
ySpeed *= 0.8;
}
yaw += xSpeed;
pitch += ySpeed;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment