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:


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));


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

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Old stuff

January 2017

Oh, btw…

%d bloggers like this: