using UnityEngine; using System; using MindPowerSdk; public static class TerrainGeneratorWithAtlas { /// /// 利用 MPMap 数据生成 Terrain,采用原生 Terrain 的 splat map 混合方式。 /// 参数 terrainLayers 必须包含 4 个层,每个层用打包好的图集贴图 /// public static void GenTerrain(MPMap map, int chunkSize, Transform parent) { // 计算全局高度范围(归一化用) (float globalMin, float globalMax) = ComputeGlobalHeightRange(map); int xCnt = Mathf.CeilToInt((float)map.Width / chunkSize); int yCnt = Mathf.CeilToInt((float)map.Height / chunkSize); for (int i = 0; i < yCnt; i++) { for (int j = 0; j < xCnt; j++) { int startX = j * chunkSize; int startY = i * chunkSize; // 为保证边界内不越界,右侧和上侧减1 int endX = Mathf.Min(startX + chunkSize, map.Width - 1); int endY = Mathf.Min(startY + chunkSize, map.Height - 1); int resX = (endX - startX) + 1; // 横向瓦片数 int resY = (endY - startY) + 1; // 纵向瓦片数 // 为了保证生成的 Terrain 与原始地图高度一致,并且位置正确,需要使用固定分辨率 int newRes = 33; // 创建 TerrainData,设置高度图和混合图分辨率以及尺寸 TerrainData terrainData = new TerrainData(); terrainData.heightmapResolution = newRes; terrainData.alphamapResolution = newRes; terrainData.size = new Vector3((resX - 1), globalMax - globalMin, (resY - 1)); // 生成高度图(归一化到 [0,1]) float[,] heights = GenerateHeightMap(map, startX, startY, resX, resY, newRes, globalMin, globalMax); terrainData.SetHeights(0, 0, heights); // 生成 Terrain 游戏对象并设置位置 GameObject terrainGO = Terrain.CreateTerrainGameObject(terrainData); terrainGO.name = $"terrain_{i}_{j}"; terrainGO.transform.parent = parent; // 将 Terrain 的 Y 坐标设置为 globalMin,使高度值正确偏移 terrainGO.transform.position = new Vector3(startX, globalMin, startY); } } } private static float[,] GenerateHeightMap(MPMap map, int startX, int startY, int resX, int resY, int newRes, float globalMin, float globalMax) { // 采集原始高度数据,并归一化到 [0,1] float[,] fullHeights = new float[resY, resX]; for (int z = 0; z < resY; z++) { for (int x = 0; x < resX; x++) { int worldX = startX + x; int worldY = startY + z; MPTile tile = map.GetTile(worldX, worldY); float norm = (globalMax - globalMin) > 0 ? (tile.Height - globalMin) / (globalMax - globalMin) : 0f; fullHeights[z, x] = norm; } } // 利用双三次插值生成新分辨率高度图 float[,] resampled = new float[newRes, newRes]; float scaleX = (float)(resX - 1) / (newRes - 1); float scaleY = (float)(resY - 1) / (newRes - 1); for (int z = 0; z < newRes; z++) { float origZ = z * scaleY; for (int x = 0; x < newRes; x++) { float origX = x * scaleX; resampled[z, x] = BicubicInterpolate(fullHeights, origX, origZ, resX, resY); } } return resampled; float[,] smoothed = ApplyGaussianBlur(resampled, newRes, newRes); return smoothed; } private static float BicubicInterpolate(float[,] data, float x, float z, int resX, int resY) { int xInt = Mathf.FloorToInt(x); int zInt = Mathf.FloorToInt(z); float s = x - xInt; float t = z - zInt; float[] arr = new float[4]; for (int m = -1; m <= 2; m++) { int sampleZ = Mathf.Clamp(zInt + m, 0, resY - 1); float p0 = data[sampleZ, Mathf.Clamp(xInt - 1, 0, resX - 1)]; float p1 = data[sampleZ, Mathf.Clamp(xInt, 0, resX - 1)]; float p2 = data[sampleZ, Mathf.Clamp(xInt + 1, 0, resX - 1)]; float p3 = data[sampleZ, Mathf.Clamp(xInt + 2, 0, resX - 1)]; arr[m + 1] = CubicInterpolate(p0, p1, p2, p3, s); } return CubicInterpolate(arr[0], arr[1], arr[2], arr[3], t); } private static float CubicInterpolate(float p0, float p1, float p2, float p3, float t) { float a0 = p3 - p2 - p0 + p1; float a1 = p0 - p1 - a0; float a2 = p2 - p0; float a3 = p1; return ((a0 * t + a1) * t + a2) * t + a3; } private static float[,] ApplyGaussianBlur(float[,] data, int width, int height) { float[,] result = new float[height, width]; int kernelSize = 3; int kernelRadius = kernelSize / 2; float[,] kernel = new float[,] { { 1f, 2f, 1f }, { 2f, 4f, 2f }, { 1f, 2f, 1f } }; float kernelSum = 16f; for (int z = 0; z < height; z++) { for (int x = 0; x < width; x++) { float sum = 0f; for (int kz = -kernelRadius; kz <= kernelRadius; kz++) { int sampleZ = Mathf.Clamp(z + kz, 0, height - 1); for (int kx = -kernelRadius; kx <= kernelRadius; kx++) { int sampleX = Mathf.Clamp(x + kx, 0, width - 1); float weight = kernel[kz + kernelRadius, kx + kernelRadius]; sum += data[sampleZ, sampleX] * weight; } } result[z, x] = sum / kernelSum; } } return result; } private static (float, float) ComputeGlobalHeightRange(MPMap map) { float min = float.MaxValue; float max = float.MinValue; for (int y = 0; y < map.Height; y++) { for (int x = 0; x < map.Width; x++) { float h = map.GetTile(x, y).Height; if (h < min) min = h; if (h > max) max = h; } } return (min, max); } }