Advertisement

How to manually create bilinear interpolation in HLSL using Gather functions??

Started by April 01, 2019 09:57 AM
3 comments, last by maxmaxmax 5 years, 10 months ago

I am trying to create a bilinear interpolation filter using HLSL and the GatherRed / GatherGreen / GatherBlue functions but I am getting really poor results compared to a proper hardware bilinear filter as you can see in the image attached to this post.

Here is the pixel shader code I am using:


Texture2D g_texture : register(t0);

struct pixel_in
{
    float2 tex_coord : TEXCOORD;
};

float3 bilinear(float2 texcoord, float tex_dimension)
{
    float3 result;

    // red channel
    float4 reds = g_texture.GatherRed(g_sampler, texcoord);
    float r1 = reds.x;
    float r2 = reds.y;
    float r3 = reds.z;
    float r4 = reds.w;

    float2 fract = frac(texcoord.xy * tex_dimension);
    float top_row_red = lerp(r1, r2, fract.x);
    float bottom_row_red = lerp(r3, r4, fract.x);

    float final_red = lerp(bottom_row_red, top_row_red, fract.y);
    result.x = final_red;
            
    // green channel
    float4 greens = g_texture.GatherGreen(g_sampler, texcoord);
    float g1 = greens.x;
    float g2 = greens.y;
    float g3 = greens.z;
    float g4 = greens.w;

    float top_row_green = lerp(g1, g2, fract.x);
    float bottom_row_green = lerp(g3, g4, fract.x);

    float final_green = lerp(bottom_row_green, top_row_green, fract.y);
    result.y = final_green;
            
    // blue channel
    float4 blues = g_texture.GatherBlue(g_sampler, texcoord);
    float b1 = blues.x;
    float b2 = blues.y;
    float b3 = blues.z;
    float b4 = blues.w;

    float top_row_blue = lerp(b1, b2, fract.x);
    float bottom_row_blue = lerp(b3, b4, fract.x);

    float final_blue = lerp(bottom_row_blue, top_row_blue, fract.y);
    result.z = final_blue;

    return result;
}

float4 PS(pixel_input pin) : SV_Target
{
    uint width_texels;
    uint height_texels;
    g_texture.GetDimensions(width_texels, height_texels);
    
    float4 result = float4(0.0f, 0.0f, 0.0f, 1.0f);

    result.xyz = bilinear(pin.tex_coord, width_texels);
    
    return result;
}

I'm really not sure what I'm doing wrong here, there doesn't seem to be much linear interpolation hapening in my version but I'm not sure why.

Untitled-3.jpg

At first glance, the general logic of the filtering code seems ok to me. My first idea would be, that the gather functions return the samples in a different order, than what you expect, something that seems to be backed up by the asm versions documentation: https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/gather4--sm5---asm-
 

Quote

The four samples that would contribute to filtering are placed into xyzw in counter clockwise order starting with the sample to the lower left of the queried location. This is the same as point sampling with (u,v) texture coordinate deltas at the following locations: (-,+),(+,+),(+,-),(-,-), where the magnitude of the deltas are always half a texel.

 

Advertisement
12 hours ago, maxmaxmax said:

float2 fract = frac(texcoord.xy * tex_dimension);

This assumes the first pixel is at coordinate 0, the next at 1, then 2... But, the first pixel is at 0.5, then 1.5, then 2.5...

Yea it turns out both of you mentioned things I was doing incorrectly. I was missing the 0.5 offset for my pixel coordinates. I was also not lerping in the correct order. Thanks!

Here's the final result which works as expected:


Texture2D g_texture : register(t0);

struct pixel_in
{
    float2 tex_coord : TEXCOORD;
};

float3 bilinear(float2 texcoord, float tex_dimension)
{
    float3 result;

    // red channel
    float4 reds = g_texture.GatherRed(g_sampler, texcoord);
    float r1 = reds.x;
    float r2 = reds.y;
    float r3 = reds.z;
    float r4 = reds.w;

    float2 pixel = texcoord * tex_dimension + 0.5;
    float2 fract = frac(pixel)
      
    float top_row_red = lerp(r4, r3, fract.x);
    float bottom_row_red = lerp(r1, r2, fract.x)

    float final_red = lerp(top_row_red, bottom_row_red, fract.y);
    result.x = final_red;
            
    // green channel
    float4 greens = g_texture.GatherGreen(g_sampler, texcoord);
    float g1 = greens.x;
    float g2 = greens.y;
    float g3 = greens.z;
    float g4 = greens.w;

    float top_row_green = lerp(g4, g3, fract.x);
    float bottom_row_green = lerp(g1, g2, fract.x);

    float final_green = lerp(top_row_green, bottom_row_green, fract.y);
    result.y = final_green;
            
    // blue channel
    float4 blues = g_texture.GatherBlue(g_sampler, texcoord);
    float b1 = blues.x;
    float b2 = blues.y;
    float b3 = blues.z;
    float b4 = blues.w;

    float top_row_blue = lerp(b4, b3, fract.x);
    float bottom_row_blue = lerp(b1, b2, fract.x);

    float final_blue = lerp(top_row_blue, bottom_row_blue, fract.y);
    result.z = final_blue;

    return result;
}

float4 PS(pixel_input pin) : SV_Target
{
    uint width_texels;
    uint height_texels;
    g_texture.GetDimensions(width_texels, height_texels);
    
    float4 result = float4(0.0f, 0.0f, 0.0f, 1.0f);

    result.xyz = bilinear(pin.tex_coord, width_texels);
    
    return result;
}

 

This topic is closed to new replies.

Advertisement