我正在尝试使用下一个代码复制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;
}
我以为我正在使用正确的算法,因为这是我在那里看到的双线性唯一的算法。但我尝试使用具有相同纹理重复的统一:
这就是结果:
您可以看到它们不同,并且我的自定义着色器中存在一些位移,使得精灵在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?
如果你测试它,你会看到一些跳转到(像素-1)的边缘情况。
答案 0 :(得分:7)
让我们仔细看看你实际在做什么。我将坚持一维案例,因为它更容易可视化。
你有一个像素数组和一个纹理位置。我假设,_MainTex_TexelSize.z
以某种方式设置,以便它给出像素坐标。这就是你得到的(方框表示像素,框中的数字表示像素数和像素空间坐标下方的数字):
通过采样(假设最近点采样),您将获得像素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实际上使用了不同的过滤器,例如bicubic(但我不知道)。此外,mipmap的使用可能会影响结果。