目前在網上能找到的關於利用高度圖生成地形的文章很多,使用的高度圖有的是通過各大地圖服務獲取,有的是使用各種圖形軟體自行繪製,也有些使用noise紋理來製作隨機地形。
今天這一章來講一點不一樣的,使用mapbox提供的terrain-rgb圖像來生成真實地形。
與我們平時所熟悉的黑白高度圖相比,mapbox提供的rgb圖像通常看起來是藍綠色,在一些臨近像素的顏色可能會出現極大的差別。這是由於terrain-rgb使用了RGB三個通道來共同表示某個區域的海拔高度,比起只使用了一個有效通道的灰度圖包含了更多信息,但可讀性也變差了。
關於terrain-rgb的編碼方式,閱讀 : https://blog.mapbox.com/global-elevation-data-6689f1d0ba65
在獲取了高度圖後,我們首先要做的是將它轉換為高度數據。
首先準備好生成地形的各種參數:
[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; } } } 一個方塊至少要由兩個三角面來組成。只要把頂點按照順時針方向排列,怎麼劃分三角面都能達到差不多的效果。
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這樣,法線就會變成向下的,在使用普通單面材質的情況下只能從下往上看才能看見正常的面。
接下來,如果需要給地形貼圖的話,就需要設定一下每個頂點的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> ();
這樣,一個根據真實地理數據製作的地形就生成出來了,將這塊地形所對應的衛星圖作為材質貼在上面,一塊真實世界的地形就完成了。
這一章的內容出自另一個地形生成插件:
這一篇可能沒有之前的文章那樣有趣,這主要是由於被人打了兩星之後,本月銷量創了歷史新低,作者產生了深深的焦慮,覺得有必要推廣一下。
如果有興趣可以買一份回去玩玩,提供了比較便宜的lite版本供大家試用,先買lite版本再升級可以享有一點微小的折扣。
建議在閱讀使用說明或者看過demo之後理性購買,真的不要買後隨便給差評啊,作者哭超兇的
這章所用到的資源在這裡獲取:
腳本掛在任意物體上即可使用。
如果出現Texture Trr00downloadHeightmap_143 is not readable這樣的提示,將圖片的import setting設為Read/Write Enabled即可。
這一章並沒有講將mesh轉換為unity terrain的內容,但相關的文章很多,如有興趣可以很容易的找到。
ps:
根據google給出的數據,在馬裏亞納海溝的挑戰者深淵處,海拔為-10166m,而terrain-rgb所能給出的最小值是-10000,這可能是在數據精度、計算難度和實際使用之間折中考慮所得出的結果。
另外,由於terrain-rgb所給出的是png格式的文件,所以理論上來說如果把alpha通道也一起利用上的話,就能讓數據的精度更高。然而mapbox並沒有這麼做,而是使用a通道來表示數據缺失(通常發生在海里或較高緯度地區),果然人類對地球還需要更多的探索呢。(當然由於看圖的時候a通道會對其他通道都造成影響,如果把a通道也拿來存高度數據的話,圖的可讀性就完全沒有了:)
建了個客服羣,方便一下有問題諮詢的用戶。店主會耐心解答大家的疑問,有需求可以加_(┐「ε:)_
關於Real Terrain Maker 的使用交流與答疑。關於Dynamic Flat Icon和Slideshow Effects 2D這兩個沒人買的插件的問題也可以聊。