美文网首页常用查询
拼接贴图接缝问题的解决方案

拼接贴图接缝问题的解决方案

作者: duanoldfive | 来源:发表于2022-07-01 11:28 被阅读0次

    烦人的接缝

    游戏中经常会用到在一个区域平铺某个贴图的需求,比如用碎石铺就的广场,我们通常会根据 顶点/像素 的世界坐标动态的计算UV值,达到贴图密度可调的目的。URP下Shader代码如下:

        float gridScale = 0.5;
        float2 roadUV = abs(frac(input.positionWS * gridScale).xz);
        surfaceData.albedo = SAMPLE_TEXTURE2D(_RoadTex, sampler_RoadTex, roadUV).rgb;
    
    广场

    细心的你一定注意到了,广场整体效果看起来还可以,但是有的地方有接缝的感觉,特别是摄像机移动或者旋转的过程中,尤其明显。(如红色箭头所指的地方)。

    原因和解决方案

    造成这个现象的原因主要由两个,一个是贴图的Mipmap,一个是贴图的过滤方式。我们导入贴图,一般默认是产生Mipmap, 过滤方式默认双线性过滤。
    这种过滤方式会采样距离当前像素最近的四个纹素,然后根据像素到四个纹素点的距离进行插值来确定最终颜色,但是当UV值到达边缘值0或者1的时候,由于边缘像素对应的纹素少了一边或者两边,造成采样到的颜色和中间的像素颜色不一样,从而出现接缝现象。
    Mip map 是根据距离摄像机远近不同,采用不同分辨率的贴图,而确定用哪个级别的贴图,需要用到UV的偏导数,而我们的UV是通过像素的世界坐标frac得到的,会导致偏导数不连续,从而采用了错误的Mipmap等级,使接缝变的更加明显。详见Unity Shader 关于tex2D中 dx dy 的猜想
    知道了原因,我们就去修改一下试试效果,选中贴图,把Generate Mip Maps后面的勾选去掉,Filter Mode改成Point.别忘了点击Apply按钮应用设置。

    设置贴图
    再来看看效果:
    效果
    仔细观察刚才有接缝的地方,现在果然没有接缝了。

    "就这样就好了吗?这也太简单了点吧?"

    你的直觉是对的,事情肯定不是这么简单。
    一般我们的地面会有多种材质,比如有石头路面,沙地,草地等等,怎么做呢?一般我们会把几种贴图合成到一张贴图中,类似Atlas,然后根据Mask贴图做混合,具体的可以研究一下刷地表的功能,这里主要讲接缝,就不展开了,我们为了实验,把两种贴图合成到一块看看会出现什么效果:

    合成图片
    效果
    看起来不错,也没有接缝。以为万事大吉,可是等打包到手机平台,你就会发现还是出现了明显的接缝。
    我分析可能是因为点过滤方式的采样,是取距离像素最近的纹素进行采样,像素落在两张贴图中间的时候(U值0.5的时候),会采样到另一侧的像素,所以接缝处有点土黄色,所以有一个解决方案是把每个贴图都外扩一定的像素,再合成一张贴图,然后对采样UV做一个clamp.这里就不详细介绍了,感兴趣的同学可以参考: 地形纹理合并

    Textrue2DArray

    今天我们要说的是另一种解决方案: Texture2DArray, 贴图数组,可以把它直接传给Shader,采样的时候可以指定index,然后就像采样单张贴图一样,系统会自动根据FilterMode,WrapMode处理采样中的各种问题,不会出现上面的两张贴图接缝处采样到另一边的问题,处理UV也简单很多。
    详细文档地址: Texture2DArray

    支持平台
    可见该技术对平台有一定的要求,可以在运行时通过SystemInfo.supports2DArrayTextures来判断是否支持,不支持的可以按照之前的做法用拼图方式进行处理,不过随着硬件的发展,大部分设备都能够支持了。

    创建资源

    Texture2DArray没有办法通过Potoshop创建,也没有办法通过Unity Create菜单直接创建,只能通过脚本创建,所以需要写一个工具类,放到Editor文件夹下:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using UnityEditor;
    using UnityEngine;
    
    //创建Texture2DArray
    
    public class TextureArray : EditorWindow
    {
        public int PropertyNum = 10;
    
        public List<Texture2D> textures = new List<Texture2D>();
    
        [MenuItem("Tools/Texture2DArray")]
        static void Init()
        {   
            TextureArray window = (TextureArray)EditorWindow.GetWindow(typeof(TextureArray), false, "TextureArray", true);
            window.Show();
        }
        
    
        private float spaceNumber = 10f;
    
        private void OnGUI()
        {   
            GUILayout.Space(spaceNumber);
            EditorGUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            EditorGUILayout.LabelField("要合成的贴图:", GUILayout.Width(100), GUILayout.Height(30));
            GUILayout.FlexibleSpace();
            EditorGUILayout.EndHorizontal();
            for (int i=0; i<textures.Count; i++)
            {
                EditorGUILayout.BeginHorizontal();
                textures[i] = (Texture2D)EditorGUILayout.ObjectField(textures[i], typeof(Texture2D), true, GUILayout.Width(64), GUILayout.Height(64));
                if (GUILayout.Button("-", GUILayout.Width(30), GUILayout.Height(30)))
                {
                    textures.RemoveAt(i);
                    i--;
                }
                EditorGUILayout.EndHorizontal();
            }
            
    
            GUILayout.Space(spaceNumber);
            EditorGUILayout.BeginHorizontal();
            if (GUILayout.Button("+", GUILayout.Height(30)))
            {
                textures.Add(null);
            }
            EditorGUILayout.EndHorizontal();
    
            GUILayout.Space(spaceNumber);
            EditorGUILayout.BeginHorizontal();
            if (GUILayout.Button("创  建", GUILayout.Height(30)))
            {
                CreateTextureArray();
            }
            EditorGUILayout.EndHorizontal();
        }
    
        public void CreateTextureArray()
        {
            //如果没有指定要合成的贴图,或者都为空,则直接返回
            textures.RemoveAll(tex => tex == null);
            if (textures.Count == 0)
            {
                Debug.LogError("Please select textures for combine");
                return;
            }
    
            Texture2D firstTex = textures[0];
    
            //Create texture2DArray
            Texture2DArray texture2DArray = new Texture2DArray(firstTex.width,firstTex.height, textures.Count, firstTex.format, false, false);
            // Apply settings
            
            //texture2DArray.filterMode = firstTex.filterMode;
            //texture2DArray.wrapMode = firstTex.wrapMode;
    
            texture2DArray.filterMode = FilterMode.Point;
            texture2DArray.wrapMode = TextureWrapMode.Clamp;
    
            int index = 0;
            foreach(Texture2D tex in textures)
            {
                for (int m = 0; m < tex.mipmapCount; m++)
                {
                    Graphics.CopyTexture(tex, 0, m, texture2DArray, index, m);
                }
                index++;
            }
    
            
    
            //Save 
            string path = EditorUtility.SaveFilePanel("Save As", "Assets", "texArray", "asset");
            if (path.Length > 0)
            {
                path = path.Substring(Application.dataPath.Length - 6);
    
                AssetDatabase.CreateAsset(texture2DArray, path);
            }
        }
    }
    

    然后Unity菜单中点击 Tools->Textrue2DArray,会弹出一个窗口,点击加号按钮,把要合成的贴图拖到对应的框内,等把所有要合成到一起的贴图全部处理好,点击合成按钮,选择位置,文件名,就会创建出一个Texture2DArray的资源了。
    这里要注意的是,合成在一起的所有贴图要有一样的大小,格式,Import选项。
    另外由于法线贴图都是线性空间的而不像普通贴图的gamma空间,所以要用这个工具处理法线贴图,请在new texture2DArray的时候,最后一个参数传true(表示线性空间)。最好自己加个参数,给用户选择。


    创建窗口

    Texture2DArray的使用

    资源有了,现在就是怎么使用了,给要使用的Shader添加代码:

        Properties
        {
            ...
            _RoadTex("Road texture", 2DArray) = "" {}
         }
        SubShader
        {
          ...
          Pass
          {
             ...
            //声明变量
            TEXTURE2D_ARRAY(_RoadTex);  SAMPLER(sampler_RoadTex);
            ...
            half4 LitPassFragment(Varyings input) : SV_Target 
            {
                int index = 1;  //贴图索引,请根据项目需求自行设置,这里只是演示,固定取索引1
                //计算UV坐标
                float gridScale = 0.25;
                float2 roadUV = abs(frac(input.positionWS * gridScale).xz);
                //采样
                surfaceData.albedo = SAMPLE_TEXTURE2D_ARRAY(_RoadTex, sampler_RoadTex, roadUV, 1).rgb;
                ...
            }
          }
       }
    

    Shader准备好以后,把刚才创建的Array资源拖到材质面板的Road texture字段处,运行项目,看看效果吧:


    效果

    完全看不到接缝了,打包到手机,同样完美。

    感谢您的阅读,如果有什么意见建议欢迎联系我,共同进步。


    最后给出项目地址 接缝项目

    相关文章

      网友评论

        本文标题:拼接贴图接缝问题的解决方案

        本文链接:https://www.haomeiwen.com/subject/diafbrtx.html