Monday 6 January 2014

Photo spheres with three.js

Ever wanted to create those fancy-looking 360° panoramas? If you mix a mobile phone with three.js and some knowledge about (map) projections, it's quite easy. 

I love travelling, especially to mountainous areas of our planet. Taking photos of vast landscapes can be challenging. This drew my interest in spherical panoramas, photographs that captures everything around you - up, down and all around - 360 x 180 degrees. 

Google calls it a photo sphere, and if you own a mobile phone with Android 4.2 or higher, you can easily create one yourself. I captured this winter scene with my phone a few days ago. But what if you want to get rid of the Google wrapping and show your panorama in your own viewer? Let's try to do it with three.js.

If you download the image from your phone it looks like this:


This is called an equirectangular photograph, which will be entirely seamless when wrapped around a sphere. It has the same projection as most of the world maps you download on the web. You'll find several funny-looking equirectangular photographs on Flickr.

Wrapping such images around a sphere is easy with three.js (read the details here):

var sphere = new THREE.Mesh(
  new THREE.SphereGeometry(100, 32, 32),
  new THREE.MeshBasicMaterial({
    map: THREE.ImageUtils.loadTexture('bergsjostolen.jpg')
  })
);

You simply create a mesh of a geometric sphere and a material containing your image as a texture.


This will create a christmas ball effect your photo. Next we need to place the camera inside the sphere. You won't see much unless you invert the mesh "inside-out" by setting:

sphere.scale.x = -1;

I'm using the orbit controls to rotate the camera and allow the user to look around inside the sphere.


var controls = new THREE.OrbitControls(camera);
controls.noPan = true;
controls.noZoom = true; 
controls.autoRotate = true;
controls.autoRotateSpeed = 0.5;

Pan and zoom are disabled, as they're not fit for purpose. Instead I'm allowing the user to change the field of view (code from this three.js example):

function onMouseWheel(event) {
  if (event.wheelDeltaY) { // WebKit
    camera.fov -= event.wheelDeltaY * 0.05;
  } else if (event.wheelDelta) { // Opera / IE9
    camera.fov -= event.wheelDelta * 0.05;
  } else if (event.detail) { // Firefox
    camera.fov += event.detail * 1.0;
  }

  camera.fov = Math.max(40, Math.min(100, camera.fov));
  camera.updateProjectionMatrix();
}

document.addEventListener('mousewheel', onMouseWheel, false);
document.addEventListener('DOMMouseScroll', onMouseWheel, false);

I'm also using the detector script to use the Canvas renderer when WebGL is not supported:

var renderer = Detector.webgl ? new THREE.WebGLRenderer() : new THREE.CanvasRenderer();

Now we have our own photo sphere viewer:

[ Fullscreen ]

Feel free to improve it on GitHub.


Bergsjøstølen at dawn.

Towards Reineskarvet.

Hesthovdstølen shielding at 1155 m. 

16 comments:

Mano Marks said...

Cool! How does it handle tiling?

Bjørn Sandvik said...

No tiling support yet! :-)

Sean de Basti said...

really awesome, thank you! now, I dont need the several plattforms to visualize my 360 panoramas

Jake Parker said...

Hi! I can't seem to get the github download to run. I'm pretty new to HTML5, but I come from a C# background and I am just beginning to understand how js hooks into HTML5 stuff. My steps were as follows:
1. Downloaded the github zip.
2. Opened index.html in Chrome (just a black screen)
3. Opened gaustatoppen.html in Chome with same black screen result.

There's probably some super simple step I'm missing just because of my own ignorance with these systems. Any help you can offer is very much appreciated, and I'm a great researcher so if you just want to point me in the right direction that's perfectly fine too!

Thanks!

Jake Parker said...

As and experiment, I just saved your fullscreen photosphere page and found it also gave me a black screen. Maybe these things cannot be run locally? I'm going to throw this up on a server and find out.

Unknown said...

It cannot be run locally because images cannot be loaded into the webGL scene (blocked by X-Origin policy / tainted canvas).

Locally different files act like they are from completely different domains, even if in same folder.

Stefan said...

Hi.

Nice tutorial.

It is a way to get the orientation of what I see? Something like north arrow.

Thank you,

Stefan

NN said...

Hi Bjørn, nice tool for panoramas,
i work years ago with pano 360 takes from iphone and js reel library (http://jquery.vostrel.cz/reel)
to leafletjs maps.

examples in http://www.mesenken.com/demos/demo06.html

Regards!

Anonymous said...

How do you reverse the controls?

TD Mimi Fortunae Brno said...

Hi Bjørn,
I would like to kindly ask you for advice / code modification. On a small project for friends of the theater (http://indie2014foto.cebreus.eu/) have to displaying photos by Colorbox. I need your great feature to include in the Colorbox. Classic photo identification and a spherical can be done simply on the side of PHP by adding the required class.

Anonymous said...

how can I export a 360 degree frame from THREE.JS scene to be viewable as a photosphere texture?

http://stackoverflow.com/questions/29048161/how-to-export-a-three-js-scene-into-a-360-texture-for-photosphere

Kuan2001 said...

Can you step by step, how to post photosphere on the blogger?
Thanks.
I try to copy the js folder and html. but I couldn't make iframe to show the photo.
where should I host the js and photo and html?

Thanks.
kuan

Unknown said...

@GregGrissom :
To reverse the controls, edit the following line in js/OrbitControls.js
- thetaDelta -= angle;
- phiDelta -= angle;
to
- thetaDelta += angle;
- phiDelta += angle;

Jarodium said...

Is there any way to replace orbit controls with trackball controls?
This will allow pinch to zoom.

Regards

Karan Checker said...

Hey Bjørn

Great work.

Thanks for sharing this.
I'm trying to build a panoramic virtual tour and need to be able to create clickable areas within a panorama which would take me to a hyperlink.

Could you please tell me what is a good way to getting around that?

I'm guessing we'd use Rays and would need to create a plane for marking the selectable area and connect the plane transform to the sphere transform. But I'm really new to Threejs and would really appreciate your help.

Thanks,
Karan

skywalker said...

Hey Karan, just use threejs plane or objects as "links" (threejs objects can have click events on them!).