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

时间:2016-09-07 21:54:07

标签: opengl unity3d shader fragment-shader

我正在尝试使用下一个代码复制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;
}

我以为我正在使用正确的算法,因为这是我在那里看到的双线性唯一的算法。但我尝试使用具有相同纹理重复的统一:

  • 1º纹理:处于点过滤并使用自定义双线性着色器(使用默认的精灵着色器进行处理)。
  • 2º纹理:使用默认的精灵着色器
  • 在双线性滤镜中

这就是结果:

enter image description here

您可以看到它们不同,并且我的自定义着色器中存在一些位移,使得精灵在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;
}

结果的图像测试:

enter image description here

为什么不完全减去0.5?

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

1 个答案:

答案 0 :(得分:7)

让我们仔细看看你实际在做什么。我将坚持一维案例,因为它更容易可视化。

你有一个像素数组和一个纹理位置。我假设,_MainTex_TexelSize.z以某种方式设置,以便它给出像素坐标。这就是你得到的(方框表示像素,框中的数字表示像素数和像素空间坐标下方的数字):

Pixels

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

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

Pixels

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

解决方案应该非常简单:在对它做任何事情之前从纹理坐标减去一半像素宽度,这可能只是以下(假设你的变量包含我认为的东西):

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

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