# Why is precalculating a distortion different from calculating it in the shader?

I have an application where I simulate a camera distortion. There are two ways to go about it: calculate the distortion per pixel in the shader or precalculate the distortion and store the distortion as a 2D texture. In either case I first render the undistorted image, then calculate different texture coordinates in a new render pass for every pixel and thus read from the original image at a different position.

In the latter case I store tex coords, meaning that for my texture coordinate $$(u_d, v_d)$$ in the shader I read out the texture coordinate $$(u, v)$$ and thus have a distortion from the original point $$(u, v)$$ to my distorted point $$(u_d, v_d)$$.

I can set up my distortion to have no distortion at all (i.e. $$(u, v) = (u_d, v_d)$$). This works fine when I calculate the distortion in the shader, but if I precalculate the distortion and store my new tex coords, read them out in the shader and then apply that "distortion", I get a somewhat blurry image. I have checked my texture with the new texture coordinates, but they seem to store exactly what I want them to (basically their own position).

Here is the same excerpt from my results, first with the shader calculated distortion: And then with the precalculated distortion and blurry result: I'm a bit at a loss as to why I get this blurry image now, especially when there is actually no offset between the original and the distorted tex coords.

The texture is using GL_FLOAT as the data type and GL_RG32F as the internal format, I don't use mip mapping and the image is usually non power of 2 and non square (e.g. in this case 1920*1079), the prerendered image has the same size as my precalculated texture coordinate texture. This effect occurs with filtering set to GL_LINEAR as well as set to GL_NEAREST.

Edit2

I have managed to reproduce the problem in a more minimal example, excluding any distortion calculations. That is, in OpenSceneGraph I have two cameras, one (rttCamera) rendering a scene into a texture and one (displayCamera) rendering this texture to the framebuffer. I precalculate a texture which stores texture coordinates, but basically they should return the same texture coordinates at every pixel as the geometry of the screen quad does:

for (int i = 0; i < res; i++)
{
for (int j = 0; j < res; j++)
{
reinterpret_cast<float*>(img->data(i, j)) = i / res;
reinterpret_cast<float*>(img->data(i, j)) = j / res;
}
}


img->data is the buffer for the texture, i, j are the pixel coordinates, res stores the resolution (1920 * 1080 in this case). The texture itself is again GL_RG32F with data type GL_FLOAT.

The fragment shader code for the displayCamerais the following:

#version 400

uniform sampler2D uRenderedImage;
uniform sampler2D uTexCoordLut;

uniform bool uIsLutUsed;

in vec2 vTexCoord;
out vec4 gl_FragColor;

void main()
{
vec2 texCoord = texture(uTexCoordLut, vTexCoord).rg;
// gl_FragColor = vec4(texCoord, 0, 1);
gl_FragColor = texture(uRenderedImage, vTexCoord);
if(uIsLutUsed)
{
gl_FragColor = texture(uRenderedImage, texCoord);
}
}


where the boolean uniform is for me to switch between the geometry texcoord version and my precalculated texcoord version.

The resulting images are the following (again cropped), first the version without the precalculated texture coordinates and then the version with precalculated texture coordinates Edit3

I tried around a bit, and I found one solution (although that doesn't answer my question of 'why'). Currently my prerendered image and my 2D texture that stores my new texture coordinates both have the same resolution. However, in my minimal example from Edit2, if keep the prerendered image at 1920*1080 pixel and increase the texture with my precalculated coordinates to 10000*10000, the difference between precalculated texture coordinates and geoemtry texture coordinates (or the rendered image with said coordinates) is minimal, barely noticable at all.

I finally found the problem.

My precalculation was done with pixel coordinates from 0 to res - 1 (i.e. the resolution width):

for (int i = 0; i < res; i++)
{
for (int j = 0; j < res; j++)
{
reinterpret_cast<float*>(img->data(i, j)) = i / res;
reinterpret_cast<float*>(img->data(i, j)) = j / res;
}
}


However, pixels in OpenGL are accessed via their center, as can be read in another SO answer or in Real-Time Rendering (3rd and 4th Edition, p.21). Meaning, that if I have a pixel with 0 as its left border and 0.1 as its right border, then 0.05 is its center. That means in my case, I have to offset the precalculated texture coordinate by half a pixel to read out of my prerendered image:

for (int i = 0; i < res; i++)
{
for (int j = 0; j < res; j++)
{
reinterpret_cast<float*>(img->data(i, j)) = i / res + 0.5 * pixelWidth;
reinterpret_cast<float*>(img->data(i, j)) = j / res + 0.5 * pixelHeight;
}
}


It's a bit hard to tell with the images, but I assume the whole viewport is distorted?

My first thought is that there is a difference between the 2 distortion computation methods which might not be obvious. A good first debugging step would be to output only $$(u_d, v_d)$$ as a color with the two methods. This will allow isolating (or not) the problem to this computation since this is likely where the problem is. If this is the problem, it will be much easier to reason about and debug with all other factors removed from the final image.

Could you add a (simplified) version of your distortion computation shader and the matching texture generation code?

• It's a bit hard to tell with the images, but I assume the whole viewport is distorted? Yes, it is over the whole viewport. I will edit my question and add the distortion calculations, but they are pretty much the same. – Tare Dec 5 '18 at 11:31
• As seen in the new edit, I can reproduce the issue with a minimal example that has no camera distortion implemented, just once a rendered image read out and written to the frame buffer, and once the rendered image read out with precalculated texture coordinates per pixel and then written to the frame buffer. – Tare Dec 5 '18 at 12:20