手动处理UV偏导数,正确采样MipMap级别

故事的起因是由于一个柱形环绕的特效Shader。

直接上代码和效果:

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = v.vertex;
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    float2 atuv = float2((-atan2(i.uv.x,i.uv.z) * 0.5 + 0.5) / UNITY_PI, i.uv.y) * _MainTex_ST.xy + _MainTex_ST.zw;
    fixed4 col = tex2D(_MainTex, atuv);
    return col;
}

贴图会以环绕Y轴的方式显示在物体表面。

但是当把贴图替换为循环特效的时候,在环绕UV的起始点会产生一个像素接缝。

经过测试,当取消图片的mipmap设置后,该接缝会消失,所以推断这个接缝应该跟mipmap采样有关。

观察代码,当环绕物体Y轴进行贴图采样时,接缝的左侧和右侧分别为0和1,所以接缝处的UV偏导数会出现错误。

这一点我们可以通过一段简单的代码进行验证。

当时我们使用跨图片象限的大UV或者材质的Tiling参数进行贴图循环采样时,因为屏幕像素之间的UV是连续的,所以不会出现任何偏导数错误的问题。

但是下面我们换一种方式,使用fmod手动处理采样循环。

finalColor = tex2D(MainTexture, fmod(i.uv.xy , 1));

那么我们将会在原点象限以外的区域看到很多mipmap偏导数错误导致的接缝像素。

所以要想保证我们的循环结果正确,必须手动计算UV偏导数,以确保采样到正确的mipmap级别。

解决方案如下(仅修改frag片元的采样部分):

fixed4 frag (v2f i) : SV_Target
{
    float2 atuv = float2((-atan2(i.uv.x,i.uv.z) * 0.5 + 0.5) / UNITY_PI, i.uv.y) * _MainTex_ST.xy + _MainTex_ST.zw;
    fixed4 col = tex2D(_MainTex, atuv, frac(ddx(atuv) + 0.5) - 0.5, frac(ddy(atuv) + 0.5) - 0.5);
    return col;
}

然后最终采样的接缝已经可以被完美去除啦~~