如果不是双线性过滤,这是什么技术?
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 复制:
- 1º 纹理:在点过滤中并使用自定义双线性着色器(由默认精灵着色器制成)。
- 2º 纹理:使用默认精灵着色器的双线性过滤器
这是结果:
你可以看到它们是不同的,而且我的自定义着色器中有一些位移,这使得精灵在 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 的使用可能会影响结果。
我正在尝试使用下一个代码复制 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 复制:
- 1º 纹理:在点过滤中并使用自定义双线性着色器(由默认精灵着色器制成)。
- 2º 纹理:使用默认精灵着色器的双线性过滤器
这是结果:
你可以看到它们是不同的,而且我的自定义着色器中有一些位移,这使得精灵在 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 的使用可能会影响结果。