KopMap/Assets/MindPowerSdk/EditorWindow/TerrainGenerator.cs
2025-05-09 17:39:24 +08:00

226 lines
9.3 KiB
C#
Raw 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.Generic;
using System.IO;
using UnityEngine;
using MindPowerSdk;
public static class TerrainGenerator
{
/// <summary>
/// 利用 MPMap 数据生成 Terrain采用自定义材质实现贴图混合
/// </summary>
/// <param name="map">地图数据</param>
/// <param name="chunkSize">每块区域的瓦片数(例如 64</param>
/// <param name="terrainMaterial">基于自定义Shader例如 Custom/TerrainUVBlendShader的材质</param>
/// <param name="parent">生成的 Terrain 父节点</param>
/// <param name="isSmoothing"></param>
/// <param name="entryMapName"></param>
public static List<Texture> GenTerrain(MPMap map, int chunkSize, Material terrainMaterial, Transform parent,
string entryMapName, bool isSmoothing = true)
{
List<Texture> textureList = new List<Texture>();
// 计算全局高度范围(归一化用)
(float globalMin, float globalMax) = ComputeGlobalHeightRange(map);
int xCnt = Mathf.CeilToInt((float)map.Width / chunkSize);
int yCnt = Mathf.CeilToInt((float)map.Height / chunkSize);
var terrainMap = new GameObject(entryMapName);
terrainMap.transform.parent = parent;
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 = 4097;
// 创建 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,
isSmoothing);
terrainData.SetHeights(0, 0, heights);
// ---------------------- 新增部分 -------------------------
// 生成贴图编号和遮罩贴图(调用你已有的 GenTxtNoTexture 方法)
Debug.LogWarning($"{startX} {startY} {chunkSize}");
(Texture2D texNo, Texture2D maskNo) = map.GenTxtNoTexture((short)startX, (short)startY, chunkSize);
textureList.Add(texNo);
textureList.Add(maskNo);
// 设置每个 tile 在辅助 baked UV 贴图中希望的像素尺寸(建议不小于 4
int cellPixelSize = 7;
// 创建材质实例,并设置贴图
Material matInstance = new Material(terrainMaterial);
matInstance.SetTexture("_TexNo", texNo);
matInstance.SetTexture("_MaskNo", maskNo);
// 计算每个 tile 在 UV 空间内的尺寸(通常为 1/chunkSize
Vector2 mainTileScale = new Vector2(1.0f / chunkSize, 1.0f / chunkSize);
matInstance.SetVector("_MainTileOffset", new Vector4(mainTileScale.x, mainTileScale.y, 0, 0));
// 设置 Terrain 尺寸参数Shader 内部可根据该参数计算全局 UV0X宽度Z高度
matInstance.SetVector("_TerrainSize", new Vector4((resX - 1), (resY - 1), 0, 0));
matInstance.SetFloat("_Repeat", (resX - 1));
// ---------------------- 新增部分结束 -------------------------
// 生成 Terrain 游戏对象并设置位置Y 坐标偏移 globalMin
GameObject terrainGO = Terrain.CreateTerrainGameObject(terrainData);
terrainGO.name = $"terrain_{i}_{j}";
terrainGO.transform.parent = terrainMap.transform;
terrainGO.transform.position = new Vector3(startX, globalMin, startY - chunkSize + 1);
Debug.Log($"terrainGO: {terrainGO.name} | pos: {terrainGO.transform.position}");
// 设置 Terrain 为自定义材质模式,并赋予生成的材质实例
Terrain terrainComponent = terrainGO.GetComponent<Terrain>();
terrainComponent.materialType = Terrain.MaterialType.Custom;
terrainComponent.materialTemplate = matInstance;
}
}
return textureList;
}
// 以下方法与原有代码一致
private static float[,] GenerateHeightMap(MPMap map, int startX, int startY, int resX, int resY, int newRes,
float globalMin, float globalMax, bool isSmoothing)
{
// 采集原始高度数据,并归一化到 [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[resY - z - 1, 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);
}
}
if (isSmoothing)
{
// 后处理:高斯模糊平滑
float[,] smoothed = ApplyGaussianBlur(resampled, newRes, newRes);
return smoothed;
}
else
{
return resampled;
}
}
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);
}
}