目前在網上能找到的關於利用高度圖生成地形的文章很多,使用的高度圖有的是通過各大地圖服務獲取,有的是使用各種圖形軟體自行繪製,也有些使用noise紋理來製作隨機地形。

今天這一章來講一點不一樣的,使用mapbox提供的terrain-rgb圖像來生成真實地形。

通過mapbox提供的API獲取到的一張terrain-rgb,位置是death valley

一張黑白的高度圖,來自https://en.wikipedia.org/wiki/Heightmap

與我們平時所熟悉的黑白高度圖相比,mapbox提供的rgb圖像通常看起來是藍綠色,在一些臨近像素的顏色可能會出現極大的差別。這是由於terrain-rgb使用了RGB三個通道來共同表示某個區域的海拔高度,比起只使用了一個有效通道的灰度圖包含了更多信息,但可讀性也變差了。

關於terrain-rgb的編碼方式,閱讀 : blog.mapbox.com/global-

在獲取了高度圖後,我們首先要做的是將它轉換為高度數據。

首先準備好生成地形的各種參數:

[SerializeField]
Texture2D tex2d;//高度圖
[SerializeField]
int sx=20;//mesh在x和y方向的分段數量
[SerializeField]
int sy=20;
[SerializeField]
float width=100;//生成的mesh的尺寸,這裡表示的是正方向的邊長
[SerializeField]
float heightscale=1; //添加一個係數來調整生成的地形y方向的縮放,如果要製作真實比例的地形,這個係數需要根據實際的距離和mesh的尺寸來計算。

以及生成一個mesh所需的一些數據:

Mesh mesh;
Vector3[] _vertices;
Vector2[] _uvs;
int[] _triangles;

把一張高度圖案中sx*sy的塊數切割,獲得的頂點數量是(sx+1)*(sy+1)。要確保採樣的點在一定在高度圖的邊緣像素上,每個頂點之間相差的距離(tex2d.width-1) / sx和(tex2d.height -1)/sy,之後按照順序依次採樣,並將獲取的顏色換算成高度。

由於mesh的頂點數據保存在vertices中,所以在從高度圖中取樣的同時,順便把vertices也準備好。地形的整體尺寸是width時,每個頂點在x和z方向的跨度就是width/sx和width/sy,而y值就是剛剛通過顏色換算出的高度。與heightscale相乘是為了讓結果比較直觀,與事後調整scale達到的是同樣的效果,實際使用的時候可以酌情添加。

public void generateTerrain(){
float stepx = (tex2d.width-1) /(float) sx;
float stepy = (tex2d.height -1)/ (float)sy;
_vertices=new Vector3[(sx+1)*(sy+1)] ;
for (int yy = 0; yy <= sy; yy++) {
for (int xx = 0; xx <=sx; xx++) {
int self = xx + yy *(sx+1);
_vertices [self].x = xx * width/sx;
_vertices [self].z = yy * width /sy;
Color cc = tex2d.GetPixel (Mathf.FloorToInt( xx*stepx),Mathf.FloorToInt( yy*stepy));
float height = -10000 + (Mathf.Round (cc.r * 256) * 256 * 256 + Mathf.Round (cc.g * 256) * 256 + Mathf.Round (cc.b * 256)) / 10;
//https://blog.mapbox.com/global-elevation-data-6689f1d0ba65
height *= heightscale/width;
_vertices [self].y = height;
}
}
}

現在,每個頂點的信息按照從左下到右上的順序存在了_vertices中。

接下來準備Triangles數組。根據官方文檔中解釋,Mesh.triangles儲存的是vertices中頂點的排列順序(The array is a list of triangles that contains indices into the vertex array. )

將頂點用橫線和豎線連接起來後,整個地形就被劃分成了sx*sy個小方塊,接下來將每個小方塊拆分為兩個三角面,每個三角面對應三個頂點。

void setMeshTriangles()
{
int sum = Mathf.FloorToInt(sx * sy * 6);////每格兩個三角形,6個頂點
_triangles = new int[sum];
int index = 0;
for (int yy = 0; yy < sy; yy++)
{
for (int xx = 0; xx < sx; xx++)
{
int self = xx + (yy * (sx + 1));
int next = xx + (yy + 1) * (sx + 1);
_triangles [index] = self;
_triangles [index + 1] = next + 1;
_triangles [index + 2] = self + 1;
_triangles [index + 3] = self;
_triangles [index + 4] = next;
_triangles [index + 5] = next + 1;
index += 6;
}
}
}

一個方塊至少要由兩個三角面來組成。只要把頂點按照順時針方向排列,怎麼劃分三角面都能達到差不多的效果。

這裡使用了0~7~1和0~6~7的方式來定義這兩個三角面,頂點的排列順序決定了三角面法線的方向。確定三角面的方式不唯一,只要三個頂點按照順時針的順序排列,用0~6~1,1~6~7的方式也是沒有問題的。如果使用了逆時針的方法,比如0~7~6這樣,法線就會變成向下的,在使用普通單面材質的情況下只能從下往上看才能看見正常的面。

用了0~7~1,0~7~6的方法,畫出了分別能從正反面看見一部分的圖形

接下來,如果需要給地形貼圖的話,就需要設定一下每個頂點的uv,如果什麼也不做的話,每個點都是默認值(0,0)。設定uv很簡單,只要將0~1的值平均分配給每個頂點即可。

void setUV()
{
_uvs = new Vector2[(sx+1)*(sy+1)];
float u = 1.0F / sx;
float v = 1.0F / sy;
for (int yy = 0; yy <= sy; yy++)
{
for (int xx = 0; xx <= sx; xx++)
{
int self = xx + yy *(sx+1);
_uvs[self] = new Vector2(xx * u, yy * v);
}
}
}

最後,生成mesh,並用已經準備好的幾個數組為其賦值。

void DrawMesh()
{
mesh = new Mesh ();
gameObject.AddComponent<MeshFilter> ().sharedMesh=mesh ;
////給mesh 賦值
mesh.Clear();
mesh.vertices = _vertices ;
mesh.uv = _uvs;
mesh.triangles = _triangles;
mesh.RecalculateNormals();////重置法線
mesh.RecalculateBounds();////重置範圍
}

再添加meshrender,讓生成的地形顯示出來。

gameObject.AddComponent<MeshRenderer> ();

這樣,一個根據真實地理數據製作的地形就生成出來了,將這塊地形所對應的衛星圖作為材質貼在上面,一塊真實世界的地形就完成了。


這一章的內容出自另一個地形生成插件:

Real Terrain Maker - Asset Store?

assetstore.unity.com
圖標

這一篇可能沒有之前的文章那樣有趣,這主要是由於被人打了兩星之後,本月銷量創了歷史新低,作者產生了深深的焦慮,覺得有必要推廣一下。

如果有興趣可以買一份回去玩玩,提供了比較便宜的lite版本供大家試用,先買lite版本再升級可以享有一點微小的折扣。

建議在閱讀使用說明或者看過demo之後理性購買,真的不要買後隨便給差評啊,作者哭超兇的

這章所用到的資源在這裡獲取:

sakuraplus/make-terrain-with-google-elevation?

github.com

腳本掛在任意物體上即可使用。

如果出現Texture Trr00downloadHeightmap_143 is not readable這樣的提示,將圖片的import setting設為Read/Write Enabled即可。

這一章並沒有講將mesh轉換為unity terrain的內容,但相關的文章很多,如有興趣可以很容易的找到。


ps:

根據google給出的數據,在馬裏亞納海溝的挑戰者深淵處,海拔為-10166m,而terrain-rgb所能給出的最小值是-10000,這可能是在數據精度、計算難度和實際使用之間折中考慮所得出的結果。

google會給出xml或json格式的數據

另外,由於terrain-rgb所給出的是png格式的文件,所以理論上來說如果把alpha通道也一起利用上的話,就能讓數據的精度更高。然而mapbox並沒有這麼做,而是使用a通道來表示數據缺失(通常發生在海里或較高緯度地區),果然人類對地球還需要更多的探索呢。(當然由於看圖的時候a通道會對其他通道都造成影響,如果把a通道也拿來存高度數據的話,圖的可讀性就完全沒有了:)


建了個客服羣,方便一下有問題諮詢的用戶。店主會耐心解答大家的疑問,有需求可以加_(┐「ε:)_

關於Real Terrain Maker 的使用交流與答疑。關於Dynamic Flat Icon和Slideshow Effects 2D這兩個沒人買的插件的問題也可以聊。


推薦閱讀:
相關文章