Files
2026-07-10 10:24:29 +08:00

635 lines
28 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<int>[] task = new Task<int>[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<MeshFilter>();
// 创建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>();
meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
// 设置材质和渲染模式
// 这里假设已经有一个名为"Material"的材质
//meshRenderer.material = Resources.Load<Material>("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);
}
}
}