using System; using System.Collections; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Windows; namespace XGame { class TerrainBlock { public Color32 m_BaseMapIndex; public Color32[] m_MapIndex; public Color[] m_MapMix; public float[] m_Height; } class TerrainObject { //m_BlockGridSize = 32 //lv0 512*512 = (32*32单块) * 16 * 16 (总共256块) //lv1 256*256 = (32*32单块) * 8 * 8 (64块) //lv2 128*128 = (32*32单块) * 4 * 4 (16块) //lv3 64*64 = (32*32单块) * 2 * 2 (4块) class MeshObj { public GameObject m_Object; public Mesh m_Mesh; public MeshObj[] m_MeshSubObj;//四叉分割 32*32 64*64 128*128 256*256 512*512 (1024*1024) } string[] m_strBaseMapList; // 预先定义 16 种颜色 static Color[] m_Colors = new Color[] { new Color(255, 0, 0), // 红色 new Color(255, 128, 0), // 橙色 new Color(255, 255, 0), // 黄色 new Color(128, 255, 0), // 淡绿色 new Color(0, 255, 0), // 绿色 new Color(0, 255, 128), // 青色 new Color(0, 255, 255), // 蓝绿色 new Color(0, 128, 255), // 淡蓝色 new Color(0, 0, 255), // 蓝色 new Color(128, 0, 255), // 紫色 new Color(255, 0, 255), // 粉红色 new Color(255, 0, 128), // 桃红色 new Color(128, 128, 128), // 灰色 new Color(192, 192, 192), // 淡灰色 new Color(255, 255, 255), // 白色 new Color(0, 0, 0), // 黑色 }; GameObject m_GameObject; Transform m_Transform; MeshObj m_Top; MeshObj[] m_BlockMeshObjList; TerrainBlock[] m_TerrainBlock; Texture2D[] m_BaseMapTexture; Texture2D[] m_NormalMapTexture; Texture2D m_ControlTexture; Material m_mtlTerrain; string m_Name;//xxxzzz.grd static Color32 m_Index;// = new int[4]; static Color m_Degreed;// = new float[4]; static Color[] m_nearestColors = new Color[4]; public static float m_HeightScale = 20.0f; public static float m_UVScale = 4.0f;//1-32-贴图大小 public static int m_BlockGridSize = 32;//32*32 void SetCorlorBase(Color[] colors) { m_Colors = colors; } //通过heightMap生成 public IEnumerator CreateGroundObjectFromPic(int xPos, int zPos, string heightMap, string mixMap, string[] baseMap, string[] normalMap, Transform trParent ) { Debug.Log("CreateGroundObjectFromPic"); if (baseMap.Length != normalMap.Length) { Debug.Log("baseMap Count Must = normalMap Count"); yield break; } if (baseMap.Length < 4) { Debug.Log("baseMap Must >= 4"); yield break; } DateTime oriTime = DateTime.Now; yield return XResLoader.coLoadAllRes(new string[] { "res/scene/ground/mat/Terrain.mat" }, typeof(Material), objs => { Debug.Log("coLoadAllRes Terrain.mat finish"); if (objs != null && objs[0] != null) { m_mtlTerrain = objs[0] as Material; } }); if(m_mtlTerrain == null) { Debug.Log("Terrian Mtl Load Failed : res/scene/ground/mat/Terrain.mat"); yield break; } m_BaseMapTexture = new Texture2D[baseMap.Length]; m_NormalMapTexture = new Texture2D[normalMap.Length]; Texture2D texture = null; Texture2D mixTexture = null; //byte[] imageData; //texture = new Texture2D(2, 2); //imageData = File.ReadAllBytes(mixMap); //yield return XResLoader.LoadRes(mixMap, typeof(Texture2D), objs => yield return XResLoader.coLoadAllRes(new string[] { mixMap }, typeof(Texture2D), objs => { if (objs != null) { mixTexture = objs[0] as Texture2D; if (mixTexture.width != mixTexture.height) { Debug.Log("Height Must == Width"); } if (!mixTexture.isReadable) { Debug.Log("mixTexture.isReadable false"); #if UNITY_EDITOR string texturePath = UnityEditor.AssetDatabase.GetAssetPath(mixTexture); UnityEditor.TextureImporter textureImporter = (UnityEditor.TextureImporter)UnityEditor.AssetImporter.GetAtPath(texturePath); textureImporter.isReadable = true; UnityEditor.AssetDatabase.ImportAsset(texturePath); #endif } } }); if(mixTexture == null) yield break; //texture.LoadImage(imageData); Texture2D texHeight = null;// = new Texture2D(2, 2); //imageData = File.ReadAllBytes(heightMap); //texHeight.LoadImage(imageData); //yield return XResLoader.LoadRes(heightMap, typeof(Texture2D), objs => yield return XResLoader.coLoadAllRes(new string[] { heightMap }, typeof(Texture2D), objs => { if (objs != null) { texHeight = objs[0] as Texture2D; if (texHeight.width != mixTexture.width || texHeight.height != mixTexture.height) {//比较两个图的大小 Debug.Log("HeightMap Size != MixMap Size"); } if (!texHeight.isReadable) { Debug.Log("texHeight.isReadable false"); #if UNITY_EDITOR string texturePath = UnityEditor.AssetDatabase.GetAssetPath(texHeight); UnityEditor.TextureImporter textureImporter = (UnityEditor.TextureImporter)UnityEditor.AssetImporter.GetAtPath(texturePath); textureImporter.isReadable = true; UnityEditor.AssetDatabase.ImportAsset(texturePath); #endif } } }); if (texHeight == null) yield break; m_GameObject = new GameObject($"Terrain{xPos}{zPos}"); m_Transform = m_GameObject.transform; m_Transform.SetParent(trParent); m_Transform.position = new Vector3(xPos, 0, zPos); m_strBaseMapList = baseMap; //baseMap列表生成m_Colors列表 m_Colors = new Color[baseMap.Length]; Color totalColor = Color.black; int pixelCount = 0; Debug.Log("baseMap Load"); //XResLoader.LoadRes(baseMap, typeof(Texture2D), objs=> yield return XResLoader.coLoadAllRes(baseMap, typeof(Texture2D), objs => { Debug.Log($"baseMap load {objs.Length} "); for (int i = 0; i < objs.Length; ++i) { if (objs[i] != null) m_BaseMapTexture[i] = objs[i] as Texture2D; else { Debug.Log($"m_BaseMapTexture {normalMap[i]} Failed"); } } }); Debug.Log("normalMap Load"); yield return XResLoader.coLoadAllRes(normalMap, typeof(Texture2D), objs => { Debug.Log($"normalMap load {objs.Length} "); for (int i = 0; i < objs.Length; ++i) { if (objs[i] != null) m_NormalMapTexture[i] = objs[i] as Texture2D; else { Debug.Log($"normalMap {normalMap[i]} Failed"); } } }); Debug.Log($"m_BaseMapTexture Color {m_BaseMapTexture.Length}"); for (int i = 0; i < m_BaseMapTexture.Length; ++i) { texture = m_BaseMapTexture[i]; if (texture == null) { Debug.Log($"texture {i} == null"); continue; } if (!texture.isReadable) { Debug.Log($"texture.isReadable {texture.name} Failed"); #if UNITY_EDITOR string texturePath = UnityEditor.AssetDatabase.GetAssetPath(texture); UnityEditor.TextureImporter textureImporter = (UnityEditor.TextureImporter)UnityEditor.AssetImporter.GetAtPath(texturePath); textureImporter.isReadable = true; UnityEditor.AssetDatabase.ImportAsset(texturePath); #endif } for (int j = 0; j < texture.width; j = j + 4) { for (int k = 0; k < texture.height; k = k + 4) { Color pixelColor = texture.GetPixel(j, k); totalColor += pixelColor; pixelCount++; } } // 计算平均颜色 m_Colors[i] = totalColor / pixelCount; } Debug.Log($"Color List OK {(DateTime.Now - oriTime).TotalMilliseconds}"); //mixMap rgba决定此点是哪四个basemap且分别是多少混合比例 //mix[513*513][4](index, alpha), 32 * 32 缩减到4种地形(g1 g2 g3 g4), //0-32 33-64 (33列需要特殊处理) //相邻区块如果没有相同的就把最小那个改为主块的g1 //读取32*32格子关联边的数据提取两个最大的(g1 g2),然后强设置进去只有两种alpha, //中心点设置为唯一颜色(最大g1) //边界混合问题 int BlockWidth = mixTexture.width / m_BlockGridSize; int BlockHeight = mixTexture.height / m_BlockGridSize; int[] BaseMapCount = new int[baseMap.Length]; m_TerrainBlock = new TerrainBlock[BlockWidth* BlockHeight]; for (int j = 0; j < BlockHeight; j++) { for (int i = 0; i < BlockWidth; ++i) { Color32[] BaseMapIndex = new Color32[m_BlockGridSize * m_BlockGridSize]; Color[] BaseMapMix = new Color[m_BlockGridSize * m_BlockGridSize]; TerrainBlock tBlock = new TerrainBlock(); tBlock.m_MapIndex = BaseMapIndex; tBlock.m_MapMix = BaseMapMix; Array.Clear(BaseMapCount, 0, BaseMapCount.Length); int l, k; for (k = 0; k < m_BlockGridSize; k++) { for (l = 0; l < m_BlockGridSize; l++) { Color pixelColor = mixTexture.GetPixel(i* m_BlockGridSize+k, j* m_BlockGridSize+l); GetNearColorIndex(pixelColor, ref BaseMapIndex[l * m_BlockGridSize + k], ref BaseMapMix[l * m_BlockGridSize + k]); BaseMapCount[BaseMapIndex[l * m_BlockGridSize + k][0]]++; BaseMapCount[BaseMapIndex[l * m_BlockGridSize + k][1]]++; BaseMapCount[BaseMapIndex[l * m_BlockGridSize + k][2]]++; BaseMapCount[BaseMapIndex[l * m_BlockGridSize + k][3]]++; } } //留下最高数量的四个BaseMapCount[0-3] Color32 BaseMapIndexMax = new Color32(0,0,0,0); int[] BaseMapCountMax = new int[4]; for ( k = 0; k < 4; k++) { for ( l = 0; l < BaseMapCount.Length; l++) { if (BaseMapCountMax[k] < BaseMapCount[l]) { BaseMapCountMax[k] = BaseMapCount[l]; BaseMapIndexMax[k] = (byte)l; //(byte)l; } } BaseMapCount[BaseMapIndexMax[k]] = 0;//最终选定的最大数位置 } //全部混合顺序要变为四个统一 byte temp = 0; float fMix; for ( k = 0; k < m_BlockGridSize * m_BlockGridSize; k++) { for( l = 0; l < 4; ++l) { bool bfound = false; if (BaseMapIndexMax[l] != BaseMapIndex[k][l]) { if (l == 3) { bfound = false; } else for (int m = l+1; m < 4; m++) { if (BaseMapIndex[k][m] == BaseMapIndexMax[l]) {//替换 if (l != m) { fMix = BaseMapMix[k][m]; BaseMapMix[k][m] = BaseMapMix[k][l]; BaseMapMix[k][l] = fMix; temp = BaseMapIndex[k][m]; BaseMapIndex[k][m] = BaseMapIndex[k][l]; BaseMapIndex[k][l] = temp; } bfound = true; break; } } if (!bfound) { for (int m = l+1; m < 4; m++) { if (BaseMapIndexMax[m] == BaseMapIndex[k][l]) {//顺序错了而已 bfound = true; fMix = BaseMapMix[k][m]; BaseMapMix[k][m] = BaseMapMix[k][l]; BaseMapMix[k][l] = fMix; temp = BaseMapIndex[k][m]; BaseMapIndex[k][m] = BaseMapIndex[k][l]; BaseMapIndex[k][l] = temp; if (BaseMapIndexMax[l] != BaseMapIndex[k][l]) {//如果换了后仍然不同,则需要重新判断一遍 m = l;//再判断一遍 bfound = false; } } } if (!bfound) {//完全找不到直接替换(不是最大的四个),混合参数BaseMapMix不用改 BaseMapIndex[k][l] = BaseMapIndexMax[l]; } } } } } tBlock.m_BaseMapIndex = BaseMapIndexMax; m_TerrainBlock[j * BlockWidth + i] = tBlock; } } Debug.Log($"Block Create OK{(DateTime.Now - oriTime).TotalMilliseconds}"); //所有区块都初始化好了,但是边缘是可能对不上的,需要修正 //组合出混合控制贴图 m_ControlTexture = new Texture2D(mixTexture.width, mixTexture.height); for (int j = 0; j < BlockHeight; j++) { for (int i = 0; i < BlockWidth; ++i) { TerrainBlock tBlock = m_TerrainBlock[j * BlockWidth + i]; int beginX = i * m_BlockGridSize; int beginY = j * m_BlockGridSize; for (int y = 0; y < m_BlockGridSize; y++) { for (int x = 0; x < m_BlockGridSize; x++) { m_ControlTexture.SetPixel(beginX + x, beginY + y, ChangeColor(tBlock.m_MapMix[x + y * m_BlockGridSize])); } } } } m_ControlTexture.Apply(); Debug.Log($"ControlTexture Create OK{(DateTime.Now - oriTime).TotalMilliseconds}"); //height map 决定地表多边形的高度值 float[] mapHeight = new float[texHeight.width * texHeight.height]; for (int j = 0; j < texHeight.height; ++j) { for (int i = 0; i < texHeight.width; ++i) { Color pixelColor = texHeight.GetPixel(i, j); mapHeight[i + j * texHeight.width] = GetHeight(pixelColor, m_HeightScale); } } Debug.Log($"height map Create OK{(DateTime.Now - oriTime).TotalMilliseconds}"); //构造真正的Mesh m_BlockMeshObjList = new MeshObj[BlockWidth * BlockHeight]; //修改uv缩放为贴图大小,适应control图,其他图按照缩放比例 m_UVScale = mixTexture.width; #if !UNITY_WEBGL Task[] task = new Task[BlockWidth]; for (int i = 0; i < BlockWidth; ++i) { task[i] = Task.Run(() => CalMeshTask(m_TerrainBlock, mapHeight, m_BlockMeshObjList, i, BlockWidth)); } for (int i = 0; i < BlockWidth; ++i) { task[i].Wait(); } #else for (int i = 0; i < BlockWidth; ++i)// //test { CalMeshTask(m_TerrainBlock, mapHeight, m_BlockMeshObjList, i, BlockWidth); } #endif Debug.Log($"Mesh Create OK{(DateTime.Now - oriTime).TotalMilliseconds}"); //区块生成后把所有节点放到总对象下 for (int i = 0; i < m_BlockMeshObjList.Length; ++i) { if(m_BlockMeshObjList[i] != null) m_BlockMeshObjList[i].m_Object.transform.SetParent(m_Transform); } } static float ColorScale = 3f; Color ChangeColor(Color color) { //超过0.66的设置为全色, 0.5以上为增比例,0.5一下为减比例 Color rc = color; int i = 0, index = -1; float cmax = 0; for ( i = 0; i < 4; ++i) { if (color[i] > cmax) { cmax = color[i]; index = i; } //if (color[i] > 0.5f) //{ // rc[i] = (color[i] - 0.5f) * ColorScale + 0.5f; //} //else //{ // rc[i] = color[i] / ColorScale; //} } rc[index] = rc[index] * ColorScale; Vector4 v4 = rc; v4.Normalize(); return v4; } //方便大部分平台分线程计算 int CalMeshTask(TerrainBlock[] BlockList, float[] mapHeight, MeshObj[] BlockMeshObjList, int WidthIndex, int BlockWidth) { int BlockHeight = BlockWidth; int mapWidth = BlockWidth * m_BlockGridSize; for (int j = 0; j < BlockHeight; j++) { TerrainBlock tBlock = BlockList[j * BlockWidth + WidthIndex]; MeshObj mObj = new MeshObj();//需要填充的模型对象 BlockMeshObjList[j * BlockWidth + WidthIndex] = mObj; // 创建一个新的游戏对象 mObj.m_Object = new GameObject($"T{WidthIndex}_{j}"); // 添加MeshFilter组件 MeshFilter meshFilter = mObj.m_Object.AddComponent(); // 创建Mesh Mesh mesh = new Mesh(); mObj.m_Mesh = mesh; // 设置顶点和三角形数据 Vector3[] vertices = new Vector3[(m_BlockGridSize+1)*(m_BlockGridSize+1)]; Vector3[] normals;// = new Vector3[(m_BlockGridSize + 1) * (m_BlockGridSize + 1)]; Vector2[] uvs = new Vector2[(m_BlockGridSize + 1) * (m_BlockGridSize + 1)]; //Vector2[] uvs2 = new Vector2[(m_BlockGridSize + 1) * (m_BlockGridSize + 1)]; int[] triangles = new int[6* m_BlockGridSize* m_BlockGridSize]; float height = 0; int x, y; int beginPos = j * m_BlockGridSize * mapWidth + WidthIndex * m_BlockGridSize; //顶点 uv for (int k = 0; k < m_BlockGridSize+1; ++k) { for (int l = 0; l < m_BlockGridSize+1; ++l) {//每个格子是两个三角形 if (WidthIndex == BlockWidth - 1 && l == m_BlockGridSize) x = l - 1; else x = l; if (j == BlockHeight - 1 && k == m_BlockGridSize) y = k - 1; else y = k; height = mapHeight[beginPos + mapWidth * y + x]; vertices[k * (m_BlockGridSize+1) + l] = new Vector3(l, height, k); //normals[k * (m_BlockGridSize + 1) + l] = Vector3.up;//简化 //Control uv uvs[k * (m_BlockGridSize + 1) + l].x = ((float)l + WidthIndex*m_BlockGridSize) / m_UVScale; uvs[k * (m_BlockGridSize + 1) + l].y = ((float)k + j*m_BlockGridSize) / m_UVScale; //Control uv 不需要, 都用比例 //uvs2[k * (m_BlockGridSize + 1) + l] = new Vector2((float)(WidthIndex* BlockWidth+l) / (float)mapWidth, (float)(j * BlockWidth + k)/ (float)mapWidth); } } //法线 //三角形 for (int k = 0; k < m_BlockGridSize; ++k) { for (int m = 0; m < m_BlockGridSize; ++m) { triangles[6 * (k * m_BlockGridSize + m)] = k * (m_BlockGridSize+1) + m; triangles[6 * (k * m_BlockGridSize + m)+2] = k * (m_BlockGridSize + 1) + m+1; triangles[6 * (k * m_BlockGridSize + m)+1] = (k+1) * (m_BlockGridSize + 1) + m; triangles[6 * (k * m_BlockGridSize + m)+3] = k * (m_BlockGridSize + 1) + m +1; triangles[6 * (k * m_BlockGridSize + m)+4] = (k+1) * (m_BlockGridSize + 1) + m; triangles[6 * (k * m_BlockGridSize + m)+5] = (k+1) * (m_BlockGridSize + 1) + m+1; } } // 这里假设已经按照上面的方法设置了顶点和三角形数据 mesh.vertices = vertices; //mesh.normals = normals; mesh.triangles = triangles; mesh.uv = uvs; //mesh.uv2 = uvs2;//control uv mesh.RecalculateNormals(); normals = mesh.normals; for (int k = 0; k < m_BlockGridSize + 1; ++k) { normals[k] = Vector3.up;//第一行 normals[m_BlockGridSize * (m_BlockGridSize + 1) + k] = Vector3.up;//最后一行 } for (int k = 1; k < m_BlockGridSize; ++k) { normals[k * (m_BlockGridSize+1)] = Vector3.up;//第一列 normals[(k+1) * (m_BlockGridSize + 1) - 1] = Vector3.up;//最后一列 } mesh.normals = normals; // 将Mesh赋值给MeshFilter meshFilter.mesh = mesh; // 添加MeshRenderer组件 MeshRenderer meshRenderer = mObj.m_Object.AddComponent(); meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; // 设置材质和渲染模式 // 这里假设已经有一个名为"Material"的材质 //meshRenderer.material = Resources.Load("Material"); meshRenderer.material = new Material(m_mtlTerrain); meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On; meshRenderer.receiveShadows = true; meshRenderer.material.SetTexture("_Splat0", m_BaseMapTexture[tBlock.m_MapIndex[0][0]]); meshRenderer.material.SetTexture("_Normal0", m_NormalMapTexture[tBlock.m_MapIndex[0][0]]); meshRenderer.material.SetTexture("_Splat1", m_BaseMapTexture[tBlock.m_MapIndex[0][1]]); meshRenderer.material.SetTexture("_Normal1", m_NormalMapTexture[tBlock.m_MapIndex[0][1]]); meshRenderer.material.SetTexture("_Splat2", m_BaseMapTexture[tBlock.m_MapIndex[0][2]]); meshRenderer.material.SetTexture("_Normal2", m_NormalMapTexture[tBlock.m_MapIndex[0][2]]); meshRenderer.material.SetTexture("_Splat3", m_BaseMapTexture[tBlock.m_MapIndex[0][3]]); meshRenderer.material.SetTexture("_Control", m_ControlTexture); Vector4 uvScale = new Vector4(48f, 64f, 48f, 48f); // 假设要设置的值是(1.0, 1.0, 0.0, 0.0) meshRenderer.material.SetVector("_UvScale0", uvScale); //位置 mObj.m_Object.transform.position = new Vector3(WidthIndex * m_BlockGridSize, 0, j * m_BlockGridSize); } return 0; } static float GetHeight(Color color, float HeightScale = 1.0f) { return HeightScale > 0 ? GetGrayScale(color) * HeightScale : GetGrayScale(color) * HeightScale; } static float GetGrayScale(Color color) { return 0.299f * color.r + 0.587f * color.g + 0.114f * color.b; } static void GetNearColorIndex(Color color, ref Color32 index, ref Color degreed) { // 找到与输入颜色最接近的四个颜色 // //double[] distances = new double[4]; for (int i = 0; i < 4; ++i) { m_nearestColors[i] = m_Colors[0]; m_Degreed[i] = 1000; } for (int i = 0; i < m_Colors.Length; i++) { float distance = GetDistance(color, m_Colors[i]); // 将当前颜色与已知的最近的四个颜色进行比较 for (int j = 0; j < m_nearestColors.Length; j++) { if (m_nearestColors[j] == null || distance < m_Degreed[j]) { // 将当前颜色插入到最近的四个颜色中,其他颜色推后 for (int k = m_nearestColors.Length - 2; k >= j; k--) { m_nearestColors[k + 1] = m_nearestColors[k]; m_Degreed[k + 1] = m_Degreed[k]; m_Index[k + 1] = m_Index[k]; } m_nearestColors[j] = m_Colors[i]; m_Degreed[j] = distance; m_Index[j] = (byte)i; break; } } } index = m_Index; degreed = m_Degreed; } // 计算两个颜色之间的距离 static float GetDistance(Color color1, Color color2) { // 使用欧几里得距离公式计算两个颜色在 RGB 空间中的距离 float r = color1.r - color2.r; float g = color1.g - color2.g; float b = color1.b - color2.b; return (float)Math.Sqrt(r * r + g * g + b * b); } } }