Search code examples
openglglsltextures

Downsample and upsample texture offsets, OpenGL GLSL


Let's say that I want to downsample from 4x4 to 2x2 texels texture, do some fancy stuff, and upsample it again from 2x2 to 4x4. How do I calculate the correct neighbor texels offsets? I can't use bilinear filtering or nearest filtering. I need to pick 4 samples for each fragment execution and pick the maximum one before downsampling. The same holds for the upsampling pass, i.e., I need to pick 4 samples for each fragment execution.

Have I calculated the neighbor offsets correctly(I'm using a fullscreen quad)?

//Downsample: 1.0 / 2.0, Upsample: 1.0 / 4.0.
vec2 texelSize = vec2(1.0 / textureWidth, 1.0 / textureHeight);

const vec2 DOWNSAMPLE_OFFSETS[4] = vec2[]
(
    vec2(-0.5, -0.5) * texelSize,
    vec2(-0.5, 0.5) * texelSize,
    vec2(0.5, -0.5) * texelSize,
    vec2(0.5, 0.5) * texelSize
);

const vec2 UPSAMPLE_OFFSETS[4] = vec2[]
(
    vec2(-1.0, -1.0) * texelSize,
    vec2(-1.0, 1.0) * texelSize,
    vec2(1.0, -1.0) * texelSize,
    vec2(1.0, 1.0) * texelSize
);

//Fragment shader.
#version 400 core

uniform sampler2D mainTexture;
in vec2 texCoord;
out vec4 fragColor;

void main(void) 
{
    #if defined(DOWNSAMPLE)
    vec2 uv0 = texCoord + DOWNSAMPLE_OFFSETS[0];
    vec2 uv1 = texCoord + DOWNSAMPLE_OFFSETS[1];
    vec2 uv2 = texCoord + DOWNSAMPLE_OFFSETS[2];
    vec2 uv3 = texCoord + DOWNSAMPLE_OFFSETS[3];
    #else
    vec2 uv0 = texCoord + UPSAMPLE_OFFSETS[0];
    vec2 uv1 = texCoord + UPSAMPLE_OFFSETS[1];
    vec2 uv2 = texCoord + UPSAMPLE_OFFSETS[2];
    vec2 uv3 = texCoord + UPSAMPLE_OFFSETS[3];
    #endif

    float val0 = texture(mainTexture, uv0).r;
    float val1 = texture(mainTexture, uv1).r;
    float val2 = texture(mainTexture, uv2).r;
    float val3 = texture(mainTexture, uv3).r;

    //Do some stuff...

    fragColor = ...;
}

Solution

  • The offsets look correct, assuming texelSize is in both cases the texel size of the render target. That is, twice as big for the downsampling pass than the upsampling pass. In the case of upsampling, you are not hitting the source texel centers exactly, but come close enough that nearest neighbor filtering snaps them to the intended result.

    A more efficient option is to use textureGather instruction, specified in the ARB_texture_gather extension. When used to sample a texture, it returns the same four texels, that would be used for filtering. It only returns a single component of each texel to produce a vec4, but given that you only care about the red component, it's an ideal solution if the extension is available. The code would then be the same for both downsampling and upsampling:

    #define GATHER_RED_COMPONENT 0
    vec4 vals = textureGather(mainTexture, texcoord, GATHER_RED_COMPONENT);
    
    // Output the maximum value
    fragColor = max(max(vals.x, vals.y), max(vals.z, vals.w));