Part I – Panorama prototype

So, after all the posts about rendering image planes we are finally getting to our first panorama implementation. You can test version 0.1 on the left, but be sure to read the ‘Issues’ section for an overview of stuff to fix in the next versions.

Basically it is nothing more than just another cube like we had in the previous example, but now the cube is centered around our camera at (0,0,0) and we are looking at the planes on the inside of the cube.

Getting some test images

I tried to find some test images that were not copyrighted, searching google up and down for ‘sample cube faces’ and those kind of search terms. After some long hard searching I realized that searching for ‘equirectangular’ was a better option and I stumbled on this flickr group.

You have to check the source license appended to each image, so that you don’t use copyrighted material. In this post I will be using this image:, which requires attribution, so there it is :).

Converting equirectangular images to cube faces

Converting the test image to cube faces send me on another long google search spree, but as it turned out you can use Pano2VR ( for that. Simply create a new project, select the equirectangular image and select ‘Convert to cubefaces’.
I’ve processed the images afterwards to resize them and am using 600×600 sized images for this first test, which is still way bigger than required for the example, but we might be zooming in later so I’ll probably leave it at that.

The CubicPanorama

In the previous examples I had a couple of classes, but the examples were constantly implemented in a main class. Since we want this thing to be reusable and refactorable, we’ll create a new class called CubicPanorama, and we will keep refactoring this example as we go along.

Specifying corners point and projection

In the previous example I used some arbitrary coordinates for the cube’s corner points. Then I adjusted my focal point and projection plane until I got something that looked acceptable. Since the cube is now going to be centered on the camera (or the other way around, depending how you look at it), and we might be using images of different sizes for the panorama we need to do this a little bit less ad-hoc.

So although we might pick anything, a fairly common choice is to simply use -1 and 1 to base the corner points on, so the points would become:

(-1,-1,-1), (1,-1,-1), (-1,1,-1), (1,1,-1), (-1,-1,1), (1,-1,1), (-1,1,1), (1,1,1).

See the image below for a visual explanation:

And now we have to map the different distorted planes to these points. By referring to the image above this is fairly easy. I’ll get to the projection bit shortly.

Rotating points

In the previous examples I implemented a rotation method on the points, but there is a quicker/better way. Instead of rotating each point individually, we can use one rotated matrix and multiply each point-as-a-vector with that matrix. But which matrix? We need a matrix that preserves the original coordinate system, and there is a very simple matrix that does just that: the identity matrix. So by simple rotating the identity matrix and multiplying that result with all our points-as-vectors, we end up with the rotated points.

Projecting the planes

To project the planes, we need to calculate the projection plane distance. To understand how this works, take a look at:

3 things are related in calculating the field of view:

  • the distance from ‘wall’ (projection plane)
  • the ‘wall size’
  • the field of view

If you look at the explanation in the link given above you can see that:
tan (fieldofview/2) = (wallsize/2)/projectionPlaneDistance

In other words:
projectionPlaneDistance = (wallsize/2)/tan(fieldofview/2);

Our wallsize is the panorama (viewport) size, and the fov needs to be in radians. Now each photo represents one cube side, which corresponds with an fov of 90, so using an fov of 90 here will map those photos exactly to the corner points of the planes. In other words point (-1,-1,-1) should be projected at (-viewportwidth/2, -viewportheight/2):

projectionPlaneDistance = (wallsize/2)/tan(fieldofview/2); =>
projectionPlaneDistance = (wallsize/2)/tan(90/2 (in radians)); =>
projectionPlaneDistance = (200/2)/tan(45 in radians); =>
projectionPlaneDistance = 100/1 = 100

So px and py become (-100/-1)*-1, (-100/-1)*-1 is (-100, -100) which is correct. If we had used for example coordinates like (-2,-2,-2) we would get (-100/-2)*-2, (-100/-2)*-2 which is exactly the same.

Anyway an fov of 50-60 is more realistic, so we’ll be using that.

Issues (Stuff to fix)

In the next version we will look at a number of improvements:

Fluid motion (mass)

The motion is ugly, besides switching to a mouse for the next version, we need something commonly known as ‘mass’, which is used in almost all the panoramas out there I think. In other words when you stop applying force either by keys or mouse, the panorama has a certain momentum while will decrease due to the application of an opposite force called friction. Simply put, it will slowly stop moving instead of abruptly.

Correct rotation

We mistakingly apply a rotation to the cube in it’s currently rotated state, we can see this is wrong, since we can end up lying on our side. What we should do is first rotate the cube left and right (around the y axis) and then up and down (around the x axis). However this requires us to maintain the original coordinates, and store the left-right/up-down angle and apply the whole rotation each time we change our viewing direction.


If you look closely you see seams in the rendered image (for example disable the grid and look up a bit until you see a small discontinuity in the rendering).

Better clipping and positioning

I wanted to show how the clipping and rendering works. Now that we’ve seen it, the next example will use a scrollrect to mask the panorama, which means we can no longer use a centered clip (scrollrect doesn’t work with negative offsets), but will have to a clip with x,y at 0,0.

Here is all the code for this example: 3d Panorama v0.1 (772 downloads) .

Onwards to version 0.2!

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *