Jump to content
  • Advertisement
Sign in to follow this  
maxmaxmax

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

Recommended Posts

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

Share this post


Link to post
Share on other sites
Advertisement

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.

 

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites

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

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!