在Unity3D中從ASC文本生成地形
簡單說說ASC文件
ASC文件是GIS行業的領導者ESRI為格網數據自己定義的文本文件格式(ASCII),被其他軟件廣泛的支持。ASC文件非常簡單,并且用任何的文本編輯器都能夠編輯。一個ASC文件以簡短的文件頭開始,描述了位置信息以及格網的大小。文件頭后面就是一行一行的高程數據了。看下面這個例子:
NCOLS xxx
NROWS xxx
XLLCENTER xxx | XLLCORNER xxx
YLLCENTER xxx | YLLCORNER xxx
CELLSIZE xxx
NODATA_VALUE xxx
row 1
row 2
...
row n
文件頭包括六行,前兩行("ncols" and "nrows")指定了格網的大小。第三行和第四行指定了格網左下角位置的橫向("xllcorner") 和縱向("yllcorner") 坐標。 "cellsize"是兩個相鄰行列之間的距離。最后一行 ("NODATA_value")是可選的,指定一個認為是認為是“無數值”的值。接著下面就是一行一行的數據了, row 1,row 2.....
從ASC文本生成地形
對于ASC文本的處理可以通過ArcGIS或者GDAL等軟件的處理,生成高度圖(height),然后把高度圖導入到Unity3D里面生成地形。但是仔細想想,其實沒有必要經過這么一層轉換,自己把文本里面的數據讀進數組,賦給Unity3D里面的TerrainData對象就可以了,這樣就省去轉換的步驟了。
思想很簡單,下面看下代碼:
Terrain terrain = null;
TerrainData trnData = null;
private int xDataRes = 731;//我的數據就是這么大
private int yDataRes = 437;
private int xRes = 1024;//按照1024來做吧
private int yRes = 1024;
private const int nodata_trn = 9999;
private float[,] trn_heights = null;
void Start () {
terrain = Terrain.activeTerrain;
trnData = terrain.terrainData;
Vector3 size = new Vector3(xRes, 100, yRes);
trnData.size = size;
trnData.heightmapResolution = 1025;//官方說必須這么做
trn_heights = new float[xRes, yRes];
//dem1.txt文件必須在Resources文件夾下面
TextAsset demdata = (TextAsset)Resources.Load("dem1", typeof(TextAsset));
StringReader reader = new StringReader(demdata.text);
if ( reader == null ){
Debug.Log("dem1.txt not found or not readable");
}else{
string line;
int index = 0;
//terrain data
while((line = reader.ReadLine()) != null){
string[] values = line.Trim().Split(new char[]{' '});
if(values.Length != xDataRes){
continue;
}else{
for(int i = 0; i < xDataRes; i++){
float v = float.Parse(values[i]);
if(Mathf.Approximately(v, nodata_trn)){
v = 0.0f;
}
trn_heights[index, i] = v/trnData.size.y;//這里很重要!
}
index++;
}
}
trnData.SetHeights(0, 0, trn_heights);//設置高程數據
}
reader.Close();
}
這里只能給出工程中的部分代碼,僅供參考。
這里有一個坑
在設置高度的地方著實讓人迷惑了很久,主要是設置高度、獲取高度這幾個接口實在是讓人摸不清頭腦。最后查了好多文檔,還好有人提到過這個問題,在此再記錄一下:
It's quite simple. I've done a small test like you did. This are the results:
* GetHeight returns the height value in the range [0.0f, Terrain Height]
* GetHeights returns the height values in the range of [0.0f,1.0f]
* SetHeights expect the height values in the range of [0.0f,1.0f].
So when using GetHeight you have to manually divide the value by theTerrain.activeTerrain.terrainData.size.y which is the configured height ("Terrain Height") of the terrain.
The behaviour of GetHeight seems a bit strange and does not return what you would expect. This might be a bug / might be a feature, however without a proper documentation it's more likely a bug.
也就是說,GetHeight返回的是0-地形總高度之間的高度值,GetHeights返回的是0.0-1.0之間的浮點數,SetHeights設置的也是0.0-1.0之間的浮點數,知道前面為什么要除以地形總高度了吧!
更奇怪的是,有GetHeight接口卻沒有SetHeight接口......
效果截圖
最后加上洪水,來個效果圖:
參考
http://www.reliefshading.com/analytical/dem_file_formats.html
http://resources.esri.com/help/9.3/arcgisengine/java/GP_ToolRef/spatial_analyst_tools/esri_ascii_raster_format.htm
http://answers.unity3d.com/questions/193780/how-do-i-use-terrain-setheights-and-getheights.html