如果不是双线性过滤,这是什么技术?

What is this technique, if not bilinear filtering?

我正在尝试使用下一个代码复制 Unity3D 的自动双线性过滤算法:

fixed4 GetBilinearFilteredColor(float2 texcoord)
{
    fixed4 s1 = SampleSpriteTexture(texcoord + float2(0.0, _MainTex_TexelSize.y));
    fixed4 s2 = SampleSpriteTexture(texcoord + float2(_MainTex_TexelSize.x, 0.0));
    fixed4 s3 = SampleSpriteTexture(texcoord + float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y));
    fixed4 s4 = SampleSpriteTexture(texcoord);

    float2 TexturePosition = float2(texcoord)* _MainTex_TexelSize.z;

    float fu = frac(TexturePosition.x);
    float fv = frac(TexturePosition.y);

    float4 tmp1 = lerp(s4, s2, fu);
    float4 tmp2 = lerp(s1, s3, fu);

    return lerp(tmp1, tmp2, fv);
}

fixed4 frag(v2f IN) : SV_Target
{
    fixed4 c = GetBilinearFilteredColor(IN.texcoord) * IN.color;
    c.rgb *= c.a;
    return c;
}

我认为我使用的是正确的算法,因为这是我见过的唯一一种双线性算法。但我尝试使用具有相同纹理的 unity 复制:

这是结果:


你可以看到它们是不同的,而且我的自定义着色器中有一些位移,这使得精灵在 Z 轴上旋转时偏离中心。

知道我做错了什么吗? 知道 Unity3D 有什么不同吗? 有没有适合Unity3D默认过滤的算法?

解决方案

为其他在这里搜索的人更新了带有 Nico 代码的完整代码解决方案:

fixed4 GetBilinearFilteredColor(float2 texcoord)
{
    fixed4 s1 = SampleSpriteTexture(texcoord + float2(0.0, _MainTex_TexelSize.y));
    fixed4 s2 = SampleSpriteTexture(texcoord + float2(_MainTex_TexelSize.x, 0.0));
    fixed4 s3 = SampleSpriteTexture(texcoord + float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y));
    fixed4 s4 = SampleSpriteTexture(texcoord);

    float2 TexturePosition = float2(texcoord)* _MainTex_TexelSize.z;

    float fu = frac(TexturePosition.x);
    float fv = frac(TexturePosition.y);

    float4 tmp1 = lerp(s4, s2, fu);
    float4 tmp2 = lerp(s1, s3, fu);

    return lerp(tmp1, tmp2, fv);
}

fixed4 frag(v2f IN) : SV_Target
{
    fixed4 c = GetBilinearFilteredColor(IN.texcoord - 0.498 * _MainTex_TexelSize.xy) * IN.color;
    c.rgb *= c.a;
    return c;
}

图像测试结果:

为什么不减去0.5?

如果你测试它,你会看到它跳到 (pixel - 1) 的一些边缘情况。

让我们仔细看看您实际上在做什么。我会坚持使用一维情况,因为它更容易可视化。

你有一个像素数组和一个纹理位置。我假设 _MainTex_TexelSize.z 以某种方式设置,从而提供像素坐标。这就是你得到的(方框代表像素,方框内的数字代表像素编号和像素下方的数字 space 坐标):

通过您的采样(假设最近点采样),您将获得像素 2 和像素 3。但是,您发现 lerp 的插值坐标实际上是错误的。您将传递纹理位置的小数部分(即 0.8),但它应该是 0.3= 0.8 - 0.5)。这背后的原因很简单:如果你落在一个像素的中心,你想使用像素值。如果您恰好落在两个像素之间的中间,您需要使用两个像素值的平均值(即 0.5 的插值)。现在,您基本上向左偏移了半个像素。

当你解决了第一个问题后,还有第二个问题:

在这种情况下,您实际上想要在像素 1 和像素 2 之间混合。但是因为您总是在采样中向右移动,所以您将在 2 和 3 之间混合。同样,插值值错误。

解决方法应该很简单:在对纹理坐标进行任何操作之前,先从纹理坐标中减去像素宽度的一半,这可能就是以下内容(假设您的变量持有我认为的东西):

fixed4 c = GetBilinearFilteredColor(IN.texcoord - 0.5 * _MainTex_TexelSize.xy) * IN.color;

结果不同的另一个原因可能是 Unity 实际上使用了不同的过滤器,例如双三次(但我不知道)。此外,mipmaps 的使用可能会影响结果。