KopMap/Assets/MindPowerSdk/TerrainGeneratorWithAtlas.cs
2025-04-03 02:30:16 +08:00

170 lines
6.6 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 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);
}
}