WebGL - Panorama Viewer

From NoskeWiki
Jump to navigation Jump to search

About

NOTE: This page is a daughter page of: WebGL


On this page is a tutorial to setup a panorama (aka. photosphere) viewer using three.js library for WebGL.


Warning: If you try to run locally, you will most likely get an error: "Image from origin 'file://' has been blocked from loading by Cross-Origin Resource Sharing policy: ...". Unless you completely disable your browsers web security, anything from your desktop isn't accessible from webGL - hence you'll need web space somewhere to upload it, and make sure the panorama (.jpg) images are in the same dir as your .html pages.


Easiest Version for a PhotoSphere Viewer

Three.js has a wonderful SphereGeometry class which you can use to display panorama with Equirectangular projection... you just have to make sure to invert the material so you can see it from the inside. This first version has the very basics and spins, but with no user interaction.

See live at: http://andrewnoske.com/student/downloads/webgl/pano_viewer/pano_viewer_simple.html


<!doctype html>
 
<html lang="en">
<head>
  <title>Pano Viewer</title>
  <style>
canvas { 
  width: 100%;
  height: 100%;
}
  </style>
</head>

<body>
  <!-- The 'three.min.js' is a great library for webGL... download from http://threejs.org/ -->
  <script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js">  </script>
  <script>
// Global variables:
var scene;     // Our scene.
var camera;    // Camera (centered inside sphere).
var renderer;  // Renderer.
var sphere;    // Sphere, with inverted material so we can see from inside.

// Setup scene and animation:
init();     // Initializes scene and all global variables (above).
animate();  // Animates sphere and renders scene.


/**
 * Initialize scene and global variables. 
 */
function init() {
  // Create a new scene:
  scene = new THREE.Scene();
  var width = window.innerWidth;
  var height = window.innerHeight;
  
  // Create camera:
  var fov = 50;                 // Field of view.
  var aspect = width / height;  // Aspect ratio.
  var near = 0.1;               // Front clipping plane
  var far  = 1000;              // Back clipping plane
  camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 0;
  camera.target = new THREE.Vector3(0, 0, 0);
  
  // Create a new WebGLRenderer object:
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(width, height);
  document.body.appendChild(renderer.domElement);  // Add renderer to page.
  
  // Setup material:
  var material = new THREE.MeshBasicMaterial({
    map: THREE.ImageUtils.loadTexture('Pano_example.jpg')
  });
  
  // Setup sphere:
  var radius = 2;
  var widthSegments = 100;
  var heightSegments = 100;
  var geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);
  geometry.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1));  // Invert sphere to see inside.
  sphere = new THREE.Mesh(geometry, material);
  scene.add(sphere);  // Add to scene.
}

/**
 * Function to render and animate sphere rotating. 
 */
function animate(){
  requestAnimationFrame(animate);  // Keep calling animate function.
  sphere.rotation.y += 0.002;      // Rotate sphere.
  renderer.render(scene, camera);
}
  </script>

</body>
</html>


Adding Mouse Interaction

Now let's build on this and add some mouse interaction..... allowing the user to pan around (mouseup, mousedown, mousemove) and zoom in (mousewheel).

See live at: http://andrewnoske.com/student/downloads/webgl/pano_viewer/pano_viewer.html

<!doctype html>
 
<html lang="en">
<head>
  <title>Pano Viewer Simple</title>
  <style>
canvas { 
  width: 600px;
  height: 400px;
}
  </style>
</head>

<body>
  <!-- The 'three.min.js' is a great library for webGL... download from http://threejs.org/ -->
  <script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js">  </script>
  <script>
// Global variables:
var scene;     // Our scene.
var camera;    // Camera (centered inside sphere).
var renderer;  // Renderer.
var sphere;    // Sphere, with inverted material so we can see from inside.
var sphere2;   // Rotated sphere in background.

var isMouseDown = false;  // Says when mouse is down.
var lat = 0, lng = 0;
var onMouseDownX = 0, onMouseDownY = 0;
var onMouseDownLat = 0, onMouseDownLng = 0;

//var controls;  // Mouse controls.

// Setup scene and animation:
init();     // Initializes scene and all global variables (above).
animate();  // Animates sphere and renders scene.

// Add event listenters:
document.addEventListener('mousedown', onDocumentMouseDown, false);
document.addEventListener('mousemove', onDocumentMouseMove, false);
document.addEventListener('mouseup',   onDocumentMouseUp,   false);
document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );

/**
 * Initialize scene and global variables. 
 */
function init() {
  // Create a new scene:
  scene = new THREE.Scene();
  var width = 600;
  var height = 400;
  
  // Create camera:
  var fov = 50;                 // Field of view.
  var aspect = width / height;  // Aspect ratio.
  var near = 0.1;               // Front clipping plane
  var far  = 1000;              // Back clipping plane
  camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 0;
  camera.target = new THREE.Vector3(0, 0, 0);
  
  // Create a new WebGLRenderer object:
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(width, height);
  document.body.appendChild(renderer.domElement);  // Add renderer to page.
  
  // Setup materials:
  var material = new THREE.MeshBasicMaterial({
    map: THREE.ImageUtils.loadTexture('Pano_example.jpg')
  });
  material.opacity = 0.5;
  
  var material2 = new THREE.MeshBasicMaterial({
    map: THREE.ImageUtils.loadTexture('Pano_example_dark.jpg')
  });
  material2.opacity = 0.5;
  
  // Setup spheres:
  var radius = 20;
  var widthSegments = 100;
  var heightSegments = 100;
  var geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);
  // var geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments, Math.PI/2, Math.PI*2, 0, Math.PI);  // Half sphere.
  geometry.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1));  // Invert sphere to see inside.
  sphere = new THREE.Mesh(geometry, material);
  scene.add(sphere);  // Add to scene.

  var geometry2 = new THREE.SphereGeometry(radius - 1, widthSegments, heightSegments);
  geometry2.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1));  // Invert sphere to see inside.
  sphere2 = new THREE.Mesh(geometry2, material2);
  scene.add(sphere2);  // Add to scene.
  sphere2.rotation.y = sphere.rotation.y + 0.01;
}

/**
 * Function to render and animate. 
 */
function animate(){
  requestAnimationFrame(animate);  // Keep calling animate function.
  if (isMouseDown === false) {
    lng += 0.01;  // Rotate by itself.
  }
  
  sphere2.visible = isMouseDown;

  lat = Math.max(- 85, Math.min(85, lat));
  phi = THREE.Math.degToRad(90 - lat);
  theta = THREE.Math.degToRad(lng);

  camera.target.x = 500 * Math.sin(phi) * Math.cos(theta);
  camera.target.y = 500 * Math.cos(phi);
  camera.target.z = 500 * Math.sin(phi) * Math.sin(theta);

  camera.lookAt(camera.target);

  renderer.render(scene, camera);
}

/**
 * Handle mouse down event - record click position and lat/lng. 
 */
function onDocumentMouseDown(event) {
  isMouseDown = true;
  onMouseDownX = event.clientX;
  onMouseDownY = event.clientY;
  onMouseDownLat = lat;
  onMouseDownLng = lng;
}

/**
 * Handle mouse down event. 
 */
function onDocumentMouseUp(event) {
  isMouseDown = false;
}

/**
 * Handle mouse move event. 
 */
function onDocumentMouseMove(event) {
  if (isMouseDown) {
    lat = (event.clientY - onMouseDownY) * 0.1 + onMouseDownLat;
    lng = (onMouseDownX - event.clientX) * 0.1 + onMouseDownLng;
  }
}

/**
 * Handle mouse wheel event. 
 */
function onDocumentMouseWheel( event ) {
  if (event.wheelDeltaY) {  // WebKit
    camera.fov -= event.wheelDeltaY * 0.05;
  } else if ( event.wheelDelta ) {  // Opera / Explorer 9
    camera.fov -= event.wheelDelta * 0.05;
  } else if ( event.detail ) {  // Firefox
    camera.fov += event.detail * 1.0;
  }
  if (camera.fov > 140) { camera.fov = 140; }
  if (camera.fov < 5)   { camera.fov = 5;  }
  console.log(toString(camera.fov));
  camera.updateProjectionMatrix();
}

  </script>

</body>
</html>


Acknowledgements: Thanks to three.js for some great code on which this was based.... in particular webgl_panorama_equirectangular


Links