KopMap/Assets/MindPowerSdk/TerrainGeneratorWithAtlas.cs

170 lines
6.6 KiB
C#
Raw Normal View History

2025-04-03 02:30:16 +08:00
using UnityEngine;
using System;
using MindPowerSdk;
public static class TerrainGeneratorWithAtlas
{
/// <summary>
/// 利用 MPMap 数据生成 Terrain采用原生 Terrain 的 splat map 混合方式。
/// 参数 terrainLayers 必须包含 4 个层,每个层用打包好的图集贴图
/// </summary>
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);
}
}