https://blog.csdn.net/wodownload2/article/details/106071916
繼續我們的地形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);
}
}
//https://blog.csdn.net/hakukou/article/details/104667576/
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
}
}
}
最後的結果:
左圖是我們的分割之後的mesh效果,右圖是unity原始地形的效果。