继续我们的地形shader之旅。
我们的阿泰被染色成了下面的丫子:
是的,我们多了一个层,这个层的贴图这个样子:
有两个图,就需要进行混合。
这里即涉及到三个图的处理:
1、贴图1(金泰熙)
2、贴图2(上面的那个泥土地)
3、混合图(权重图)
贴图1和贴图2好处理,就是美术给的素材。
混合图是啥?
当我们用笔刷,刷好地形之后,再地形数据的下面,就会有一个或者多个混合图出现。上面出现了一张,为啥时一张?
因为我们有两个图进行混合,一个图可以支持四个图(因为有rgba四个通道)。
加入有5个图,那么则会有2个混合图出现。这里不展开讲述。
ok,有了混合图之后,这个混合图是一个整体,其分辨率是多少呢?
这个控制图,就是混合图的分辨率。
ok,我们要将地形分成的是5x5块,上一篇文章已经讲过了,请参考上一篇。
那么混合图也需要分成5x5这么多块,ok,分割代码如下:
private static void CreateSplitAlphamap(Texture2D alphamap, int blockX, int blockZ)
{
Texture2D tex = new Texture2D(m_blockAlphamapWidth, m_blockAlphamapHeight, TextureFormat.ARGB32, false);
for (int i = 0; i < m_blockAlphamapHeight; ++i)
{
for (int j = 0; j < m_blockAlphamapWidth; ++j)
{
int x = j + blockX * m_blockAlphamapWidth;
int y = i + blockZ * m_blockAlphamapHeight;
Color color = alphamap.GetPixel(x, y);
tex.SetPixel(j, i, color);
}
}
//
string texName = m_sceneData.sceneName + "_alphamap_" + blockX + "_" + blockZ;
string assetPath = PathManager.m_splitTexesDir + "/" + texName + ".png";
if (File.Exists(assetPath)) File.Delete(assetPath);
byte[] data = tex.EncodeToPNG();
File.WriteAllBytes(assetPath, data);
AssetDatabase.Refresh();
}
ok,分割之后的丫子:
最后一步,是如何使用这个混合图,我们有25块分割之后的mesh,那么就需要有25个材质,每个材质有三个属性,如下:
将其每个材质,拖拽到左边的mesh上去,对应起来,不要乱,同时混合图要一个一个拖到每个材质球上去:
混合图的uv怎么弄?
我这里只给出思想,具体实现,就要看下面的代码了:
1、我们将地形分成了25块
2、每块加入是分成64x64个小格子
3、每块有65x65个顶点,那么每个顶点的uv,应该等于所在的坐标(x,z)/64,x和z从0~64
4、用这个uv去采样每块的混合图就可以了
private static void SplitTerrainMesh(int x, int z, int step)
{
BlockData blockData = new BlockData();
int id = x + z * m_sceneData.splitX;
blockData.id = id;
blockData.x = x;
blockData.z = z;
//采样,创建mesh,保存mesh
Mesh mesh = new Mesh();
int verticesCount = (m_blockHeightmapWidth / step + 1) * (m_blockHeightmapHeight / step + 1);
Vector3[] vertices = new Vector3[verticesCount];
int[] triangleIndices = new int[m_blockHeightmapWidth * m_blockHeightmapHeight * 2 * 3];
Vector2[] uvs = new Vector2[verticesCount];
int vertexIndex = 0;
int triangleIndex = 0;
float[,] heights = m_terrain.terrainData.GetHeights(0, 0, m_heightmapWidth, m_heightmapHeight);
for (int i = 0; i <= m_blockHeightmapHeight; i += step)
{
for (int j = 0; j <= m_blockHeightmapWidth; j += step)
{
int indexX = x * m_blockHeightmapWidth + j;
int indexZ = z * m_blockHeightmapHeight + i;
float height = heights[indexZ, indexX];
float posX = indexX / m_scaleX;
float posZ = indexZ / m_scaleZ;
float posY = height * m_terrainY; //GetHeights存储的是0~1的值,需要乘以高度的最大值,才能得到实际的地形高度,并且第一维存储的是z值,第二维存储的是x值
//参考:https://docs.unity3d.com/ScriptReference/TerrainData.GetHeights.html
//float h = m_terrain.terrainData.GetHeight((int)posX, (int)posZ);
//https://docs.unity3d.com/ScriptReference/TerrainData.GetHeight.html GetHeight得到的是地形的实际高度
Vector3 scale = m_terrain.terrainData.heightmapScale;
vertices[vertexIndex] = new Vector3(posX, posY, posZ);
float u = j * 1.0f / m_blockHeightmapWidth;
float v = i * 1.0f / m_blockHeightmapHeight;
uvs[vertexIndex] = new Vector2(u, v);
if (j < m_blockHeightmapWidth && i < m_blockHeightmapHeight)
{
triangleIndices[triangleIndex++] = vertexIndex;
triangleIndices[triangleIndex++] = vertexIndex + m_blockHeightmapWidth + 1;
triangleIndices[triangleIndex++] = vertexIndex + m_blockHeightmapWidth + 2;
triangleIndices[triangleIndex++] = vertexIndex;
triangleIndices[triangleIndex++] = vertexIndex + m_blockHeightmapWidth + 2;
triangleIndices[triangleIndex++] = vertexIndex + 1;
}
vertexIndex++;
}
}
mesh.vertices = vertices;
mesh.triangles = triangleIndices;
mesh.uv = uvs;
string meshName = SceneManager.GetActiveScene().name + "_mesh_" + x + "_" + z;
string meshPath = PathManager.m_splitMeshesDir + "/" + meshName + ".asset";
if (File.Exists(meshPath))
{
File.Delete(meshName);
}
AssetDatabase.CreateAsset(mesh, meshPath);
AssetDatabase.Refresh();
}
最后,我们给出混合了两个贴图的地形shader:
Shader "Unlit/TerrainShader"
{
Properties
{
_BlendTex("_BlendTex", 2D) = "white"{}
_SplatTex0("_SplatTex0", 2D) = "white" {}
_SplatTex1("_SplatTex1", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 worldPosition:TEXCOORD2;
float2 uv : TEXCOORD0;
};
sampler2D _BlendTex;
sampler2D _SplatTex0;
sampler2D _SplatTex1;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldPosition = mul(unity_ObjectToWorld, v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float4 blendColor = tex2D(_BlendTex, i.uv);
float2 uv0 = i.worldPosition.xz * (1.0f / 320); //这个是上节的知识你还记得吗?
float4 color0 = tex2D(_SplatTex0, uv0);
float4 color1 = tex2D(_SplatTex1, uv0);
float4 color = blendColor.r * color0 + blendColor.g * color1;
return color;
}
ENDCG
}
}
}
最后的结果: