Unity3D贴图平铺时边界可见的接缝线问题

5

为了我的游戏,我编写了一个着色器,可以使我的纹理在多个对象上良好平铺。我这样做是通过选择基于绝对世界位置的UV而不是基于顶点相对位置。自定义着色器如下所示。基本上它只是在1x1世界单位的网格中平铺纹理。

Shader "MyGame/Tile" 
{
    Properties 
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader 
    { 
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert
        sampler2D _MainTex;

        struct Input 
        {
            float2 uv_MainTex;
            float3 worldPos;
        };

        void surf (Input IN, inout SurfaceOutput o) 
        {
            //adjust UV for tiling
            float2 cell = floor(IN.worldPos.xz);
            float2 offset = IN.worldPos.xz - cell;
            float2 uv = offset;

            float4 mainTex = tex2D(_MainTex, uv);
            o.Albedo = mainTex.rgb;
        }
        ENDCG
    } 
    FallBack "Diffuse"
}
我以前在XNA上使用Cg和HLSL着色器实现过这种方法,一直都很成功。但是,在Unity着色器中,纹理边缘出现了非常明显的接缝。我尝试了Unity表面着色器和顶点/片段着色器,但结果相同。

visible seams

纹理本身如下所示。在我的游戏中,它实际上是一个 .tga 文件,而不是 .png 文件,但这并不会导致问题。无论是在所有纹理过滤设置上还是在重复或夹紧模式上都会出现问题。

texture

现在我看到有人在这里遇到了类似的问题:灯光贴图时平面之间的接缝。 但是,没有确定性的答案来解决这样的问题。此外,我的问题与光照或灯光无关。 在我测试的片段着色器中,没有启用任何光照,但问题仍然存在。

同样的问题也被发布在Unity Answers网站上,但我没有收到答案,也没有得到很多的浏览量,所以我也在这里尝试:平铺纹理边界可见的接缝


1
在你的纹理(http://docs.unity3d.com/Documentation/Manual/Textures.html)中,尝试将“Wrap Mode”更改为“Clamp”。 - Jerdak
1
正如我所说:这个问题在所有纹理过滤设置和重复或夹紧模式下都会发生。 - Thrakbad
哎呀,我用grep查找的是“自动换行模式”,而不是clamp。 - Jerdak
一定要尝试使用MipMaps。看看是否有帮助。 - Nestor Ledon
1
您无需自己实现frac。http://http.developer.nvidia.com/Cg/frac.html - user652038
2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
6
这是您问题的原因描述: http://hacksoflife.blogspot.com/2011/01/derivatives-i-discontinuities-and.html 这是一个很好的视觉示例,和您的相似: http://aras-p.info/blog/2010/01/07/screenspace-vs-mip-mapping/ 除非你要禁用mipmaps,否则我认为这在Unity中无法解决,因为据我所知,它不会让你使用可以在片段着色器中指定使用哪个mip级别的函数(至少在OS X/OpenGL ES上;如果您只针对Windows,则可能不是问题)。 话虽如此,我不知道为什么您正在进行片段级别的uv计算;只需从顶点着色器传递数据就可以正常工作,而且还可以使用平铺纹理。
struct v2f {
    float4 position_clip : SV_POSITION;
    float2 position_world_xz : TEXCOORD;
};

#pragma vertex vert
v2f vert(float4 vertex : POSITION) {
    v2f o;
    o.position_clip = mul(UNITY_MATRIX_MVP, vertex);
    o.position_world_xz = mul(_Object2World, vertex).xz;
    return o;
}

#pragma fragment frag
uniform sampler2D _MainTex;
fixed4 frag(v2f i) : COLOR {
    return tex2D(_MainTex, i.position_world_xz);
}

非常感谢,我不知道为什么我自己没有想到这个。当然,我可以直接从顶点着色器中获取世界位置,而无需进行进一步的计算。这实际上使问题消失了(禁用mip-maps也起作用)。 - Thrakbad

0
现有的答案对于在另一种方式下可以完成的情况是可以的,但实际上Unity确实可以解决这个问题,同时保持现有的坐标。例如,我曾经遇到这样的情况:实际的纹理是从一个带有边框的纹理“图集”中获取的,但它也应该是可重复的,所以我使用了frac自行进行平铺,然后面临了这个问题。 这个现象发生的原因部分是由于像素的渲染方式 - 它们不是单独渲染的,而是以2×2的四边形渲染,这就是为什么你可能会看到它跨越2×2个像素方块。另一半的原因是mipmaps - 通常内存中的一个纹理不仅包含一个图像,还包含多个缩小(减半)版本的图像,称为mipmaps,根据LOD(细节级别)选择使用。作为其魔力的一部分,tex2D会根据屏幕上纹理的本地分辨率自动计算LOD,当纹理的各个像素非常接近时(无论是水平还是垂直方向),它会选择更高的LOD(因此更小的mipmaps)。这被称为各向异性过滤,有助于优化(不需要频繁采样较大的纹理)和视觉效果。 这个魔法的核心是通过沿着两个轴计算uv的导数,这在功能上等同于调用ddx(uv)ddy(uv)。当你调用这两个函数时,你可以自己计算LOD,将其乘以纹理的尺寸,选择最大值,并取其以2为底的对数(每个mipmap的尺寸是前一个尺寸的一半)。然后,你可以手动调用tex2Dlod并传入LOD参数。 然而,你不必自己计算细节级别!tex2D实际上提供了一个重载函数,接受dxdy作为前述的导数,tex2D(s, uv)等同于tex2D(s, uv, ddx(uv), ddy(uv))。这有什么用呢?接缝可见是由于如何计算导数——GPU只需检查相同四边形中(基于使用的轴)相邻像素,并取相邻像素的uv值与当前像素值之间的差异(离散导数就是差异)。现在很明显为什么这些像素看起来是这样的了——尽管导数通常在其他地方都很小,但在接缝处却很大,因为值突然跳动。由于导数很大,GPU认为屏幕空间中的这种微小差异实际上是UV空间中的非常大的差异,因此像素必须真的挤在一起,所以它加载了一个更小的(可能是最小的)mipmap,通常只是原始纹理模糊在一起。 为了以一种非常整洁的方式解决这个问题,我建议使用两组 uv ‒ 一组用于实际采样,另一组用于计算导数。你可以自由地在"真实"的 uv 上执行任何操作,但禁止在第二对中进行任何不连续操作。可以进行缩放(实际上是这里唯一重要的操作)、平移等操作,但不要使用 frac% 等类似的操作。例如(与你原始代码等效):
float2 uvbase = IN.worldPos.xz;
float2 uv = frac(uvbase);
float4 mainTex = tex2D(_MainTex, uv);
可以改为
float2 uvbase = IN.worldPos.xz;
float2 uv = frac(uvbase);
float4 mainTex = tex2D(_MainTex, uv, ddx(uvbase), ddy(uvbase));

frac 不会对 uvbase 进行任何缩放,因此大部分情况下,导数将与 uv 的导数相等,但它们也会在接缝处按预期工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,