By MulinB
最近項目需要類比一個3D情境飛機飛行的簡單示範,主要功能就是提供一個比較大的地形高程圖和一個飛機飛行路線,能在三維下顯示示範飛機飛行。感覺以前自己見過不少類似的遊戲,這無非是第一個人稱射擊類或者類比飛行類的遊戲的簡化版,所以從避免重複製造輪子的角度決定查一下開源遊戲引擎,看看有沒有類似的可以很快開發出第一人稱射擊類遊戲的引擎。經過查看了一些網上的評論和對比文章決定使用三維圖形渲染引擎OGRE來開發。
OGRE的示範Demo裡有個Terrain的示範,直接是一個由地形高程圖產生三維地形情境的例子,其使用的是OGRE內建的TerrainSceneManager(下面簡稱TSM)情境管理器,《Pro OGRE 3D Programming》裡面對這個情境管理器的使用介紹的相當詳細,無非就是如何配置一個terrain.cfg的設定檔進行映射。更詳細的介紹可以從官方wiki得到:http://www.ogre3d.org/wiki/index.php/Terrain_Scene_Manager 。用這個情境管理器載入比較小的地形高程圖時效果還比較理想,不過載入比較大一點的地形高程圖時,速度就明顯慢了下來,開啟程式時的等待時間變得很長,幀頻也變得比較低。
到網上查了一下關於支援大地形的情境管理器的資料,發現OGRE的一個AddOn外掛程式PLSM2(Paging Landscape SceneManager)可以將大的地形高程圖分成小的page載入,無奈關於這個情境管理器的用法網上資料並不多,使用起來有些困難。經過一番痛苦的折磨調試,終於還算成功的在OGRE1.6版本下把PLSM2使用了起來,效果還不錯,載入速度明顯快了很多,只是仍然有些問題,就是在地形漫遊時,地形會自己變形,鬱悶……這個暫留作TODO吧,估計是有些設定沒設定好,或者是這個情境管理器依然不完善。下面把使用PLSM2的過程記錄一下備忘。
環境:Windows XP, MS Visual Studio 2005 + SP1。
//--------------------------------------------------------------------------
關於PLSM2的使用的官方wiki:http://www.ogre3d.org/wiki/index.php/Paging_Scene_Manager 。
其中有關於PLSM2的安裝、使用、設定檔如何配置的詳細說明。但是按照這些官方wiki一步一步進行並不能讓Demo程式順利跑起來,你需要根據報錯的提示資訊將可執行檔相對於media檔案夾的路徑。而且這其中比較讓人疑惑的是,會有兩套media檔案夾,一套是mapsplitter用,另一套才是使用PLSM2的Demo程式使用。需要注意的是,設定檔的填寫很重要,也很耗時。需要mapsplitter的設定檔(用於將大地形高程圖分割成小塊),還需要配置運行時將小塊地形圖映射成三維情境下的全局座標系的參數。主要的設定檔可以參考wiki:http://www.ogre3d.org/wiki/index.php/Paging_Scene_Manager_config_files 。
關於設定檔,需要說明的是,用於紋理自動產生的效果不是太好,如果沒有合適的紋理,不妨使用Matlab+photoshop將地形高程圖產生偽彩圖作為地形紋理,使用TextureFormat=ImagePaging將紋理也分割成小塊,從實驗效果來看,如果偽彩圖產生的合適,那麼三維情境效果還比較漂亮。還有一點是配置Terrain map specification裡的Map definition參數時,width和height的值是之前分割好的小塊地形圖在兩個方向的個數,比如如果大地形圖是16385×16385的話(注意地形圖的大小按PLSM2的要求應該是(2^n)+1),小塊的大小是PageSize=513的話(這裡的pagesize也應該是(2^n)+1),那麼width和height的大小應該是(16385-1) / (513-1) = 32。還有參數ScaleX, ScaleZ是將一個小塊(pagesize大小的地形圖)映射到三維情境後的長和寬,而不是將整個大圖映射到三維情境的長和寬,經測試,這個ScaleX=PageSize×400的時候,三維地形效果比較理想,地形比較平滑,而且地形起伏明顯。ScaleY是映射到三維情境下的高度範圍(0~ScaleY)。其他的很多參數可以參照wiki說明去設定。
當你把Demo程式跑起來之後,你會發現使用PLSM2情境管理器載入大地形時的時間明顯比TSM縮短,不過你會發現有個眼中的問題是當你在三維情境裡漫遊時,地形像是有生命一樣,會動,當拉近鏡頭時,很多山會長高,當拉遠鏡頭時,很多山會縮回去,⊙﹏⊙b汗……這個問題我調了很多設定參數也沒搞定,哪位大俠搞定希望指點迷津,不過可以肯定的是,顯卡比較好的時候這個效果不是太嚴重。
好吧,現在需要從Demo程式跳出來,把PLSM2用到自己的工程項目中了,你會發現之前從官方wiki介紹的地址 http://tuan.kuranes.free.fr/Ogre.html 下載的編譯好的Demo程式、MapSplitter程式、PLSM2 DLL和Lib檔案,是用OGRE1.2編譯的,而如果你用的是最新版的OGRE SDK的話,或許是像我一樣使用了OGRE1.6.1,那麼你最好像我一樣直接從SVN下載最新代碼(最好是跟你使用的SDK版本相同的代碼),自己編譯出一套MapSplitter程式和PLSM2 DLL和Lib檔案。
然後就是使用PLSM2的三維情境下的全局座標系的問題了。在PLSM2的全局座標系裡,原點(x,z)=(0,0)點(注意在OGRE的三維全局座標系裡,x,z是長寬兩個方向,y是高度方向)是在情境的中間,對應著地形高程圖的中心位置,而不是地形高程圖的左上方。例如:
大地形高程圖大小: 16385×16385
塊大小(PageSize): 513
Width, Height: 32
ScaleX, ScaleZ: 204800 (204800=(513-1)×400)
那麼,PLSM2三維全局座標系的大小是(SizeX, SizeZ) = 32×512×400 = (6553600, 6553600)
那麼,PLSM2三維全局座標系的左上方的座標是(MinX, MinZ) = -6553600/2 = (-3276800, -3276800)
好在這些資訊其實不用自己計算,PLSM2的情境管理器有相應的介面可以得到這些參數,而在代碼裡需要做的是,將一個SceneManager的指標mSceneMgr轉換為PagingLandScapeSceneManager指標,然後用PagingLandScapeSceneManager提供的介面獲得這些參數。在寫代碼之前,需要將PLSM2的標頭檔拷貝到當前工程中,因為PLSM2裡標頭檔相互引用,想挑出這些標頭檔還要一番功夫,我拷貝的標頭檔清單如下:
OgrePagingLandScapeData2DManager.h
OgrePagingLandScapeOcclusion.h
OgrePagingLandScapeOcclusionElement.h
OgrePagingLandScapeOcclusionQuerySet.h
OgrePagingLandScapeOcclusionTraversal.h
OgrePagingLandScapeOctree.h
OgrePagingLandScapeOctreeCamera.h
OgrePagingLandScapeOctreeNode.h
OgrePagingLandScapeOctreeSceneManager.h
OgrePagingLandScapeOptions.h
OgrePagingLandScapePoolSet.h
OgrePagingLandScapePrerequisites.h
OgrePagingLandScapeSceneManager.h
之後,就可以使用PagingLandScapeSceneManager介面獲得PLSM2特有的參數了,程式碼片段如下:
view plaincopy to clipboardprint?
PagingLandScapeSceneManager* G_pPLSceneMgr = NULL;
Real worldSizeX, worldSizeZ;
G_pPLSceneMgr = static_cast<PagingLandScapeSceneManager*>(mSceneMgr);
G_pPLSceneMgr->getWorldSize(&worldSizeX, & worldSizeZ);
PagingLandScapeOptions* pPLSM2Options = G_pPLSceneMgr->getOptions();
Real xScaleFactor = pPLSM2Options->scale.x;
//x方向放大因子, ScaleX/(PageSize-1) , 這裡是400
Real zScaleFactor = pPLSM2Options->scale.z;
//z方向放大因子, ScaleZ/(PageSize-1) , 這裡是400
Real maxY = pPLSM2Options->scale.y; //高度最大值, 這裡是ScaleY
PagingLandScapeSceneManager* G_pPLSceneMgr = NULL;
Real worldSizeX, worldSizeZ;
G_pPLSceneMgr = static_cast<PagingLandScapeSceneManager*>(mSceneMgr);
G_pPLSceneMgr->getWorldSize(&worldSizeX, & worldSizeZ);
PagingLandScapeOptions* pPLSM2Options = G_pPLSceneMgr->getOptions();
Real xScaleFactor = pPLSM2Options->scale.x;
//x方向放大因子, ScaleX/(PageSize-1) , 這裡是400
Real zScaleFactor = pPLSM2Options->scale.z;
//z方向放大因子, ScaleZ/(PageSize-1) , 這裡是400
Real maxY = pPLSM2Options->scale.y; //高度最大值, 這裡是ScaleY
上面的代碼中的變數pPLSM2Options,在偵錯模式觀察之,其實可以觀察到很多PLSM2的參數,是使用PLSM2的很好的一個幫手。
其他的按照官方wiki做應該沒有什麼問題。
//--------------------------------------------------------------------------
附本人做的小程式的一張(紋理貼圖使用Matlab+Photoshop產生):
附Matlab產生偽彩圖代碼(之後可以採用photoshop進行拼接、調色):
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
clear all;close;
%讀取未經過處理的地形高程圖,高度值範圍(0~5000),png為16位灰階(0~65535)
dxgc_data=imread('dxgct.png');
%定義用於貼圖的偽彩顯示的colormap,是本人經過手工調色事先用matlab調好的顏色表
dxcolor=[0 0 0.5625;0.1 0.2 0.7813;0.2 0.4 1;0.3333 0.5333 1;0.4667 0.6667 1;0.6 0.8 1;0.5 0.8 0.75;0.4 0.8 0.5;0.3 0.8 0.25;0.2 0.8 0;0.24 0.78 0;0.28 0.76 0;0.32 0.74 0;0.36 0.72 0;0.4 0.7 0;0.44 0.68 0;0.48 0.66 0;0.52 0.64 0;0.56 0.62 0;0.6 0.6 0;0.5926 0.5852 0;0.5852 0.5704 0;0.5778 0.5556 0;0.5704 0.5407 0;0.563 0.5259 0;0.5556 0.5111 0;0.5481 0.4963 0;0.5407 0.4815 0;0.5333 0.4667 0;0.5259 0.4519 0;0.5185 0.437 0;0.5111 0.4222 0;0.5037 0.4074 0;0.4963 0.3926 0;0.4889 0.3778 0;0.4815 0.363 0;0.4741 0.3481 0;0.4667 0.3333 0;0.4593 0.3185 0;0.4519 0.3037 0;0.4444 0.2889 0;0.437 0.2741 0;0.4296 0.2593 0;0.4222 0.2444 0;0.4148 0.2296 0;0.4074 0.2148 0;0.4 0.2 0;0.4 0.1882 0.01176;0.4 0.1765 0.02353;0.4 0.1647 0.03529;0.4 0.1529 0.04706;0.4 0.1412 0.05882;0.4 0.1294 0.07059;0.4 0.1176 0.08235;0.4 0.1059 0.09412;0.4 0.09412 0.1059;0.4 0.08235 0.1176;0.4 0.07059 0.1294;0.4 0.05882 0.1412;0.4 0.04706 0.1529;0.4 0.03529 0.1647;0.4 0.02353 0.1765;0.4 0.01176 0.1882;0.4 0 0.2];
%高程圖轉化為索引圖
dxgct_ind=gray2ind(dxgc_data, 1000); %經實踐,1000對應著60個索引灰階級
%索引圖轉化為偽彩圖,本來這一行就可以產生,但是地形圖太大時matlab會記憶體不足,所以改為分塊
%dxgct_rgb=ind2rgb(dxgct_ind, colormap(dxcolor)); %使用之前調整好的colormap
%分塊輸出
dxgct_rgb0=ind2rgb(dxgct_ind(1:1500,1:1500), colormap(dxcolor));
dxgct_rgb1=ind2rgb(dxgct_ind(1:1500,1501:3000), colormap(dxcolor));
dxgct_rgb2=ind2rgb(dxgct_ind(1501:3000,1:1500), colormap(dxcolor));
dxgct_rgb3=ind2rgb(dxgct_ind(1501:3000,1501:3000), colormap(dxcolor));
%輸出為png,matlab記憶體不足,改為分塊
%imwrite(dxgct_rgb, 'dxgct_texture.png', 'PNG');
%分塊輸出
imwrite(dxgct_rgb0, 'dxgct_texture0.png', 'PNG');
imwrite(dxgct_rgb1, 'dxgct_texture1.png', 'PNG');
imwrite(dxgct_rgb2, 'dxgct_texture2.png', 'PNG');
imwrite(dxgct_rgb3, 'dxgct_texture3.png', 'PNG');
<完>