Sampling equirectangular textures

Suppose you want to do 360 panorama demo using equirectangular texture. Easy way would be to use sphere mesh, with enough triangles distortions are invisible, and the client is happy. The few of us, who are dealing with “make me another matterport” bullshit, are not so lucky – we have to project images onto complex meshes and, therefore, write the shaders. Like this:

// vertex
varying vec3 worldPosition;
void main () {
   vec4 p = vec4 (position, 1.0);
   worldPosition = (modelMatrix * p).xyz;
   gl_Position = projectionMatrix * modelViewMatrix * p;
}

// fragment
uniform sampler2D map;
uniform vec3 placement;
varying vec3 worldPosition;
void main () {
   vec3 R = worldPosition - placement;
   float r = length (R);
   float theta = acos (-R.y / r);
   float phi = atan (R.x, -R.z);
   gl_FragColor = texture2D (map, vec2 (
      0.5 + phi / 6.2831852,
      theta / 3.1415926
   ));
}

This code kind of does the job, but it has two major problems. The less obvious one is that, if you look under your feet, the image is all blurred:

screen-shot-2017-01-19-at-2-53-13

This happens because texels get further apart down there and smaller mip maps are used. So you have to undo this using sampling bias:

float c = -R.y / r;
...
gl_FragColor = texture2D (map, vec2 (
   0.5 + phi / 6.2831852,
   theta / 3.1415926
), -2.0 * log2 (1.0 + c * c));

screen-shot-2017-01-19-at-3-07-36

Ok, great, it is no longer blurry, but we still have the elephant in the room: the ugly aliased seam. That is where texture coordinate wraps around, and the neighbor pixels map to the texels on the opposite sides of the texture. Which, again, causes the smallest mip map to be used. So we need to add even more bias around the seam.

So far I have not been able to come up with nice expression in terms of phi and theta (nor find it anywhere on the internet), but in terms of cartesian coordinates it is quite easy to calculate decent seam mask:

const float seamWidth = 0.05;
...
float seam =
   // thin circle around x axis
   max (0.0, 1.0 - abs (R.x / r) / seamWidth) *
   // keep the part facing positive z direction
   clamp (1.0 + (R.z / r) / seamWidth, 0.0, 1.0);

With that, we can finally remove the seam. I guess similar method could be applied anywhere you want to use equirectangular maps instead of cube maps, e.g. to fake reflections.

0 Responses to “Sampling equirectangular textures”



  1. Leave a Comment

Ask a Question




Old stuff

January 2017
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
3031