1.解決方案
在網上參考了一些資料,使用OSG創建地形最簡單的辦法就是使用OSG::HeightField類,它是描述類似於DEM網格的四角面片。首先給出具體實現代碼:
#include <iostream>
#include <Windows.h>
#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/Texture2D>
#include <osg/ShapeDrawable>
#include <gdal_priv.h>
using namespace std;
using namespace osg;
using namespace osgViewer;
//實現函數:從高程圖創建地形
osg::Node* createHeightField(std::string heightFile, std::string texFile)
{
//讀取高度文件
GDALAllRegister(); //GDAL所有操作都需要先注冊格式
CPLSetConfigOption("GDAL_FILENAME_IS_UTF8", "NO"); //支持中文路徑
GDALDataset* img = (GDALDataset *)GDALOpen(heightFile.c_str(), GA_ReadOnly);
if (!img)
{
return nullptr;
}
//讀取基本參數
int imgWidth = img->GetRasterXSize(); //圖像寬度
int imgHeight = img->GetRasterYSize(); //圖像高度
int bandNum = img->GetRasterCount(); //波段數
int depth = GDALGetDataTypeSize(img->GetRasterBand(1)->GetRasterDataType()) / 8; //圖像深度
//獲取地理坐標信息
double padfTransform[6];
if (img->GetGeoTransform(padfTransform) == CE_Failure)
{
return nullptr;
}
double startX = padfTransform[0] + 0.5 * padfTransform[1]; //左上角點坐標X
double dX = padfTransform[1]; //X方向的分辨率
double startY = padfTransform[3] + padfTransform[5] * imgHeight - 0.5 * padfTransform[5]; //左下角點坐標Y
double dY = -padfTransform[5]; //Y方向的分辨率
//申請buf
int bufWidth = imgWidth;
int bufHeight = imgHeight;
size_t imgBufNum = (size_t)bufWidth * bufHeight * bandNum;
float *imgBuf = new float[imgBufNum];
//讀取
size_t imgBufOffset = (size_t)bufWidth * (bufHeight - 1) * bandNum;
img->RasterIO(GF_Read, 0, 0, bufWidth, bufHeight, imgBuf + imgBufOffset, bufWidth, bufHeight,
GDT_Float32, bandNum, nullptr, bandNum*depth, -bufWidth*bandNum*depth, depth);
//定義並設置高度文件
osg::ref_ptr<osg::HeightField> heightField = new osg::HeightField();
heightField->allocate(imgWidth, imgHeight); //申請空間
heightField->setOrigin(osg::Vec3(startX, startY, 0)); //起始位置
heightField->setXInterval(dX); //間距X
heightField->setYInterval(dY); //間距Y
heightField->setSkirtHeight(1.0f);
//填充高度值
for (int r = 0; r < imgHeight; r++)
{
for (int c = 0; c < imgWidth; c++)
{
size_t m = (size_t)r * imgWidth + c;
heightField->setHeight(c, r, imgBuf[m]);
}
}
//釋放
delete[] imgBuf;
imgBuf = nullptr;
//節點
osg::Geode* geode = new osg::Geode();
osg::ref_ptr<osg::ShapeDrawable> heightShape = new osg::ShapeDrawable(heightField.get());
geode->addDrawable(heightShape);
//設置紋理
osg::ref_ptr<osg::Image> texImage = osgDB::readImageFile(texFile);
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
tex->setImage(texImage);
tex->setDataVariance(osg::Object::DYNAMIC);
//渲染狀態
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset->setTextureAttributeAndModes(0, tex.get(), osg::StateAttribute::ON);
geode->setStateSet(stateset.get());
return geode;
}
int main()
{
osgViewer::Viewer viewer;
osg::ref_ptr<osg::Group> group = new osg::Group;
std::string heightFile = "D:\\Data\\dst.tif";
std::string texFile = "D:\\Data\\dom3_Level_19.jpg";
group->addChild(createHeightField(heightFile, texFile));
viewer.setSceneData(group);
viewer.setUpViewInWindow(100, 100, 800, 600);
return viewer.run();
}
其運行結果如下,顯示的是美國大峽谷(Grand Canyon)中的一小塊:
1) 使用TIF格式的DEM
因為不太清楚別的網上資料里面地形文件是jpg格式的,要知道jpg格式只能8位且沒有地理信息,所以在這里我直接使用的是GTiff格式的DEM。很奇怪我這里用osgDB讀取TIF文件失敗了,所以直接采用了GDAL讀取。
2) 描述HeightField
使用GDAL打開高程文件(DEM),能夠獲取地形的起點位置和間距,將其填充到HeightField中,這樣OSG就確定了高程點的XY位置。在使用GDAL讀取高程文件(DEM)存儲的高程值到內存中之后,依次填充到HeightField,就確定了地形的Z位置。最后繪制到節點,地形圖也就繪制出來了。
2.存在問題
可以看到我這里采用的紋理文件是一個處理好的,范圍剛剛好能夠覆蓋的jpg文件。其紋理是自動貼到四個角點的。其實我最初的設想是采用一個DOM(正射影像圖)來實現,通過其地理位置確定紋理坐標,最終無視范圍大小,實現一個DEM(高程)與DOM(影像)的自動疊加。
問題就在於HeightField的點是內部繪制的,我給其賦予的紋理坐標總是不正確。我初步嘗試發現一個網格點需要2個紋理坐標才能把整個紋理填滿。在這里希望大家批評指正下,究竟如何給HeightField的點設置紋理位置。