Unity - Use Texture2DArray In Terrain System

效果

问题

最近要制作一个2D的Terrain System,遇到的第一个问题就是 Texture Atlas Tiling.

为了性能考虑 所以多张纹理会最终打成一张Texture Atlas来进行绘制, 如图:

因为纹理是Tiled平铺,所以直接用6个顶点表示两个Quad

在单纹理模式下只要将纹理的Warp Modle设置为Repeat,然后顶点2和顶点5的uv直接用 uv=(2,0) 和 uv=(2,1) 即可

但是改成纹理集以后就无法使用Repeat模式了,必须使用Clamp模式,并且手动设置uv

根据如上纹理位置,uv设置为:

1
2
3
4
5
6
7
var uv = new Vector2[6];
uv[0] = new Vector2(0.25f, 0.5f);
uv[1] = new Vector2(0.5f, 0.5f);
uv[2] = new Vector2(0.25f, 0.5f);
uv[3] = new Vector2(0.25f, 0.75f);
uv[4] = new Vector2(0.5f, 0.75f);
uv[5] = new Vector2(0.25f, 0.75f);

得到的结果如图:

图中有两个错误,一个是图像水平翻转了,另外一个两个Tile之间有裂缝

无法共享顶点

第一个问题很好解决,因为uv无法Repeat,所以每个Quad都需要4个顶点来实现. 如图:

这个问题我还特意查看了一下 UGUI中Image的Tiled的实现

Image.cs line 501

1
2
3
4
5
6
7
8
9
10
11
...
double nVertices = 0;
if (hasBorder)
{
nVertices = (nTilesW + 2.0) * (nTilesH + 2.0) * 4.0; // 4 vertices per tile
}
else
{
nVertices = nTilesW * nTilesH * 4.0; // 4 vertices per tile
}
...

Image也是用4顶点实现的Repeat

采样错误

第二个黑线问题其实是一个old toptic,很多人都给了分析和解答.

Unity Shader-地形纹理合并

在文章里面说的很清楚了. 裂缝问题是由于Shadertex2D采样时候是从周围点进行采集的,所以出现里图集越界问题.

并且这个问题会随着Mip Level的变化越来越明显

具体的原因及解决方法参考我的另外一篇文章 Unity Shader 关于tex2D中 dx dy 的猜想

TextureArrays

在OpenGL ES3.0 以后加入了TextureArrays的支持.

根据Wiki OpenGL_ES中的描述

Android since version 4.3, on devices with appropriate hardware and drivers, including:

iOS since version 7, on devices including:

  • iPhone 5S[39]
  • iPad Air
  • iPad mini with Retina display

目前大部分手机机型都应该支持了3.0. 所以直接使用TextureArrays无疑是最佳的解决方案

其优点在于

  • 两个Quad之间可以共享顶点,这样对于大面积平铺的Terrain来说还是很有诱惑的
  • 纹理直接使用Repeat模式,去除烦人的Tiling Issue

创建纹理

How To Use Texture Arrays In Unity
Texture Arrays in Unity

创建纹理集直接参考上面两篇文章,一个是直接在运行时构建,一个是可以直接存为assets,用到时候直接加载.

存为assets时要区分平台,构建不同压缩格式的纹理集.

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SaveTextureArrayMT : MonoBehaviour
{
[LabelText("纹理格式")]
public TextureFormat saveFormat;

[LabelText("纹理名称")]
public string saveName;

[LabelText("保存路径")]
public string savePath;

[LabelText("纹理集")]
public Texture2D[] ordinaryTextures;

[Button("保存", ButtonSizes.Large), GUIColor(0.066667f, 0.843137f, 0.435294f)]
private void SaveTextureArray()
{
var output = new Texture2DArray(ordinaryTextures[0].width, ordinaryTextures[0].height, ordinaryTextures.Length, saveFormat, true, false)
{
filterMode = FilterMode.Bilinear,
wrapMode = TextureWrapMode.Repeat
};

for (var i = 0; i < ordinaryTextures.Length; i++)
{
output.SetPixels(ordinaryTextures[i].GetPixels(0), i, 0);
}

output.Apply();

AssetDatabase.CreateAsset(output, "Assets/" + savePath + saveName + ".asset");
}
}

构建后本地得到 TArray01.asset Unity自动识别为TextureArrays

Mesh中写入Index信息

index信息可以直接写到uv的z参数中去,但是注意设置uv时候就必须使用SetUVs才行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void DoGenerateMesh()
{
...
var uv = new List<Vector3>
{
new Vector3(0f, 0f, 4f),
new Vector3(1f, 0f, 4f),
new Vector3(2f, 0f, 4f),
new Vector3(0f, 1f, 4f),
new Vector3(1f, 1f, 4f),
new Vector3(2f, 1f, 4f)
};

mCurrentMesh.SetUVs(0, uv);
...
}

构建Shader

Unity Doc SL-TextureArrays

Shader中直接使用宏UNITY_SAMPLE_TEX2DARRAY进行采样即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Shader "Eran/Sample2DArrayTexture"
{
Properties
{
_MyArr ("Tex", 2DArray) = "" {}
_Index ("Index",Range (0, 4)) = 4
}
SubShader
{
Pass
{
CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#pragma require 2darray

#include "UnityCG.cginc"

UNITY_DECLARE_TEX2DARRAY(_MyArr);

struct a2v {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
};

struct v2f {
float4 position : SV_POSITION;
float3 uv : TEXCOORD0;
};

float _Index;


v2f vert (a2v v)
{
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}



half4 frag (v2f i) : SV_Target
{
return UNITY_SAMPLE_TEX2DARRAY(_MyArr, float3(i.uv.x,i.uv.y,_Index));
}

ENDCG
}
}
}

效率

坚持原创技术分享,您的支持将鼓励我继续创作!