在对地形数据分块处理后,接下来就是对影像纹理进行分块处理。因为影像数据占用较大的存储空间,而且一般计算机的图形渲染设备限制了单次装载影像的大小,如目前的OpenGL渲染设备支持的单张影像的最大范围为2048×2048,而在多数实际情况中地形影像的范围远远大于这个限制数量的大小。因此需要对大的影像纹理进行分块处理,即在平面空间上将影像分割成一系列规则的影像块。
影像分块分为规则分块(一个影像块对应一个或多个DEM子块)和不规则分块(一个影像块并不对应整数个的DEM子块)。在实际应用效率上,不规则分块将造成大量纹理数据冗余,而且在纹理影像块调度时计算烦琐,因此,这里采用规则分块中的一个影像块对应一个DEM子块,漫游时可立即确定所应调入的纹理影像块,如图5-10所示。
对于分块后的影像纹理,为了方便以后进行纹理映射和保证在三维交互显示时纹理数据调度的高效性,我们设计了如下的数据结构用于管理这些纹理数据,如表5-4所示。
表5-4 数据库存储的纹理模型数据结构
序 号 |
空间位置(角点坐标) |
影 像 大 小 |
影 像 数 据 |
1 |
x1,y1,x2,y2 |
W1×h1 |
01010101… |
2 |
x1,y1,x2,y2 |
W2×h2 |
01010101… |
… |
… |
… |
… |
n |
x1,y1,x2,y2 |
Wn×hn |
01010101… |
从表5-4中可以看出,纹理数据是由一些子块影像数据构成的,其中每一子块影像具有一定的属性特征。空间位置表示该影像被映射的区域范围,影像的大小表示该子块影像的高和宽,用于计算纹理坐标。影像数据用于存储该子块影像数据,以BLOB数据类型存储在Oracle数据库中。
程序设计实现如下所述。
1.界面设计
首先在工程“数模与纹理(D)”菜单下添加子菜单,名称为“纹理影像入库(T)”,ID=ID_MENU_TEXTUREIMAGE,菜单如图5-11所示。
然后在T3DSystem工程中添加一个对话框和相应控件,如图5-12所示,详细的信息如表5-5所示。
图5-11 添加纹理入库菜单 图5-12 界面设计
表5-5 控件详细的信息
序 号 |
ID |
名 称 |
变 量 |
1 |
IDC_STATIC |
纹理影像 |
--- |
2 |
IDC_BUTTON_BROWSE |
浏览 |
--- |
3 |
IDC_LIST_FILES |
--- |
CListBox m_listfiles |
4 |
IDC_PROGRESS |
--- |
CProgressCtrl m_progress |
5 |
IDOK |
确定 |
CcomboBox m_subCombolblockSize |
6 |
IDCANCEL |
取消 |
--- |
2.程序设计
1)添加变量和函数
为该对话框添加类,名称为CTextureLoad,在头文件(TextureLoad.h)中添加对"ImageObject.h"的引用和私有变量:
1)添加变量和函数
为该对话框添加类,名称为CTextureLoad,在头文件(TextureLoad.h)中添加对"ImageObject.h"的引用和私有变量:
#include "ImageObject.h" //影像分块处理头文件
private:
CImageObject*m_pImageObject; //CImageObject类型变量,用于实现对影像纹理
的分块处理
int m_totalRows,m_totalCols; //存储纹理影像分块的总行数和总列数
为类CdemLoad添加私有函数,代码如下。
private:
BOOL GetTextureRange(CString tcrPathname)
BOOL WriteImageToDB(CString strFile,int m_RowIndex,int m_ColIndex,int m_height,int m_width,int m_phramidLayer,int m_fg_width,int m_fg_height);
void SeperateImage(CString mfilename,int m_phramidLayer,CString tempDi- rctory);
并为IDC_BUTTON_BROWSE和IDOK分别添加OnButtonBrowse()和OnOK()响应函数。
2)函数实现
在TextureLoad.cpp中最后实现的代码如下。
在TextureLoad.cpp中最后实现的代码如下。
(1)为IDC_BUTTON_BROWSE按钮添加响应函数。
void CTextureLoad::OnButtonBrowse() //浏览外部纹理文件
{
CString strFile,strFilter="Tif files(*.Tif)|*.Tif|"
BMP files(*.BMP)|*.BMP|"
Jpeg files(*.JPG)|*.JPG|"
GIF files(*.GIF)|*.GIF|"
PCX files(*.PCX)|*.PCX|"
Targa files(*.TGA)|*.TGA||";
//定制打开文件对话框,使支持多项选择(OFN_ALLOWMULTISELECT)
CFileDialog fdlg(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_ALLOWMULTISELECT,
strFilter);
char *pBuf=new char[BUFFERLEN]; //申请缓冲区
fdlg.m_ofn.lpstrFile=pBuf; //让pBuf代替CFileDialog缓冲区
fdlg.m_ofn.lpstrFile[0]='"0';
fdlg.m_ofn.nMaxFile=BUFFERLEN;
int nCount=0; //初始选择的文件数为0
//如果成功,则获取对话框多项选择的文件,并依次添加到列表框里
if(fdlg.DoModal()==IDOK)
{
//GetStartPosition():返回指示遍历映射起始位置的POSITION位置,如果映射为
//空则返回NULL
POSITION pos=fdlg.GetStartPosition();
m_listfiles.ResetContent(); //清空列表框
while(pos) //如果映射不为空
{
nCount++;
strFile=fdlg.GetNextPathName(pos); //得到文件名
m_listfiles.AddString(strFile); //将文件名加入到列表框
}
}
delete[] pBuf; //回收缓冲区
}
函数说明:
OnButtonBrowse()函数通过设置打开文件对话框的多项选择功能,实现从外部一次性选择多个影像纹理文件,并将影像纹理文件名填写到纹理列表框中。
(2)初始化对话框。
BOOL CTextureLoad::OnInitDialog()
{
CDialog::OnInitDialog();
myOci.Init_OCI(); //初始化OCI
return TRUE;
}
(3)为IDOK按钮添加响应函数。
void CTextureLoad::OnOK()//对各级纹理影像逐个分块处理,并写入Oracle数据库中
{
CString mfilename,stt,tt;
BeginWaitCursor();
CString tempDirctory="c:""tempRailwayBmp";//存放纹理影像子块的临时文件夹
DWORD dwAttr=GetFileAttributes(tempDirctory); //获取文件夹属性
if(dwAttr==0xFFFFFFFF) //如果该文件夹不存在则创建
CreateDirectory(tempDirctory,NULL); //创建临时文件夹
☆程序第Ⅰ部分☆《对各级纹理影像逐个分块处理,并写入Oracle数据库中》
//对各级纹理影像逐个分块处理,并写入Oracle数据库中
//对各级纹理影像逐个分块处理,并写入Oracle数据库中
for(int i=0;i
{
m_listfiles.SetCurSel(i); //设置列表框当前索引
//得到当前包含全路径的影像文件名
m_listfiles.GetText(m_listfiles.GetCurSel(),mfilename);
int nPos=mfilename.ReverseFind('""');
tt=mfilename.Mid(nPos+1,mfilename.GetLength()-nPos);//得到影像文件名
stt.Format("正在处理:%s",tt);
this->SetWindowText(stt); //设置对话框标题
//对影像纹理进行分块处理,并写入Oracle数据库中
SeperateImage(mfilename,i,tempDirctory);
}
☆程序第Ⅱ部分☆《将影像纹理总体信息写入影像信息表中》
//将影像纹理总体信息写入影像信息表中
//将影像纹理总体信息写入影像信息表中
tt.Format("INSERT INTO IMAGERECT_INFO VALUES(%.3f,%.3f,%.3f,%.3f,%d,"
%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f)",theApp.m_TexturLeftDown_x,theApp.m_TexturLeftDown_y,theApp.m_TexturRightUp_x,theApp.m_TexturRightUp_y,m_listfiles.GetCount(),theApp.m_ImageResolution[1],theApp.m_ImageResolution[2],theApp.m_ImageResolution[3],theApp.m_ImageResolution[4],theApp.m_ImageResolution[5],theApp.m_ImageResolution[6],theApp.m_ImageResolution[7]);
theApp.m_pConnection->Execute(_bstr_t(tt),NULL,adCmdText);//执行SQL语句
☆程序第Ⅲ部分☆《删除临时文件夹,恢复进度条初始设置》
EndWaitCursor();
EndWaitCursor();
m_progress.ShowWindow(SW_HIDE); //隐藏进度条
m_progress.SetPos(0); //恢复初始位置0值
MessageBox("影像纹理文件写入完成!","影像纹理入库",MB_OK);
RemoveDirectory(tempDirctory); //删除临时文件夹
theApp.bLoadImage=TRUE; //加载影像成功
CDialog::OnOK();
}
函数说明:
OnOK()函数实现对各级纹理影像逐个分块处理,然后写入Oracle数据库中,主要由3部分组成。
l 第1部分是对各级纹理影像逐个分块处理,并写入Oracle数据库中。
l 第2部分是将影像纹理总体信息写入影像信息表中。
l 第3部分是删除临时文件夹,恢复进度条初始设置,并给出影像纹理文件写入完成提示信息。
(4)将纹理影像进行分块处理,并写入Oracle数据库中。
void CTextureLoad::SeperateImage(CString mfilename,int m_phramidLayer, CString tempDirctory)
{
CString stt,strfile;
☆程序第Ⅰ部分☆《加载影像纹理,计算纹理高度、宽度等信息,为分块做准备》
m_pImageObject->Load(mfilename,NULL,-1,-1); //加载影像纹理
m_pImageObject->Load(mfilename,NULL,-1,-1); //加载影像纹理
long m_height=m_pImageObject->GetHeight(); //得到纹理高度
long m_width=m_pImageObject->GetWidth(); //得到纹理宽度
//当前LOD级别的纹理分辨率
theApp.m_ImageResolution[m_phramidLayer]=(theApp.m_TexturRightUp_x-
theApp.m_TexturLeftDown_x)/m_width;
//纹理影像子块的宽度
int m_fg_width=theApp.m_Dem_BlockWidth/theApp.m_ImageResolution
[m_phramidLayer];
//纹理影像子块的高度
int m_fg_height=theApp.m_Dem_BlockWidth/theApp.m_ImageResolution
[m_phramidLayer];
//计算当前LOD级的纹理影像分块的总行数
if(m_height%m_fg_height==0)
m_totalRows=m_height/m_fg_height;
else
{
m_totalRows=m_height/m_fg_height+1;
}
//计算当前LOD级的纹理影像分块的总列数
if(m_width%m_fg_width==0)
m_totalCols=m_width/m_fg_width;
else
{
m_totalCols=m_width/m_fg_width+1;
}
☆程序第Ⅱ部分☆《根据计算的影像纹理信息,对影像纹理进行分块处理》
int nPos=mfilename.ReverseFind('""');
int nPos=mfilename.ReverseFind('""');
strfile=mfilename.Mid(nPos+1,mfilename.GetLength()-nPos-5);
for(int i=0;i
{
for(int j=0;j
{
//设置进度条值
m_progress.SetPos((i*m_totalCols+j+1)*100.0/(m_totalRows*m_totalCols));
int mleftx=(j-1)*m_fg_width; //影像子块左下角x坐标
int mlefty=(m_totalRows-i)*m_fg_height; //影像子块左下角y坐标
int mrightx=mleftx+m_fg_width-1; //影像子块右上角x坐标
int mrighty=mlefty+m_fg_height-1; //影像子块右上角y坐标
//读取由mleftx、mlefty、mrightx和mrighty所确定的影像子块
m_pImageObject->Crop(mleftx,mlefty,mrightx,mrigty);
stt.Format("%s""%s@%d_%d.bmp",tempDirctory,strfile,i,j);
m_pImageObject->Save(stt,1);//将读取的影像子块存储到临时文件中
int m_subImageWidth=m_pImageObject->GetWidth(); //得到影像子块的宽度
int m_subImageHeight=m_pImageObject->GetHeight(); //得到影像子块的高度
//将影像子块以BLOB数据类型写入Oracle数据库中
WriteImageToDB(stt,i+1,j+1,m_subImageHeight,m_subImageWidth,m_phrami-
dLayer,m_fg_width,m_fg_height);
m_pImageObject->Load(mfilename,NULL,-1,-1);//重新加载原始影像
}
}
}
函数说明:
SeperateImage()函数实现对加载的纹理影像进行分块处理,主要由以下两部分组成。
l 第1部分是加载影像纹理,计算纹理高度、宽度,计算当前LOD级别的纹理影像分块的总行数、总行数,以及纹理分辨率等信息,为分块做准备。
l 第2部分是根据计算的影像纹理信息,对影像纹理进行分块处理,并将分块后的影像子块以BLOB数据类型写入Oracle数据库中。
(5)将影像子块以BLOB数据类型写入Oracle数据库中。
BOOL CTextureLoad::WriteImageToDB(CString strFile,int m_RowIndex,int m_Col- Index,int m_height,int m_width,int m_phramidLayer,int m_fg_width,int m_fg_height)
{
☆程序第Ⅰ部分☆《将影像子块常规数据类型的数据写入Oracle数据库中》
//计算影像子块的左下角和左上角x、y大地坐标
//计算影像子块的左下角和左上角x、y大地坐标
double m_leftdownx=(m_ColIndex-1)*m_fg_width*theApp.m_ImageResolution
[m_phramidLayer]+theApp.m_TexturLeftDown_x;
double m_leftdowny=(m_RowIndex-1)*m_fg_height*theApp.m_ImageResolution
[m_phramidLayer]+theApp.m_TexturLeftDown_y;
double m_rightUpx=m_leftdownx+m_width*theApp.m_ImageResolution
[m_phramidLayer];
double m_rightUpy=m_leftdowny+m_height*theApp.m_ImageResolution
[m_phramidLayer];
int m_ID=(m_RowIndex-1)*m_totalCols+m_ColIndex;//影像子块的ID号
CString tt;
tt.Format("INSERT INTO texture VALUES(%d,%d,%d,%d,%d,EMPTY_BLOB(),"
%ld,%.3f,%.3f,%.3f,%.3f)","
m_RowIndex,m_ColIndex,m_height,m_width,m_phramidLayer,m_ID,"
m_leftdownx,m_leftdowny,m_rightUpx,m_rightUpy);
//调用OCI公共类的AddNormalDataToDB函数,将常规数据类型的数据写入Oracle数据库中
myOci.AddNormalDataToDB(tt);
☆程序第Ⅱ部分☆《将影像子块的影像数据以BLOB数据类型写入Oracle数据库中》
//调用OCI公共类的AddBOLBDataToDB函数,将BLOB类型的数据写入Oracle数据库中
//调用OCI公共类的AddBOLBDataToDB函数,将BLOB类型的数据写入Oracle数据库中
tt.Format("SELECT纹理数据FROM texture WHERE行号=%d AND 列号=%d AND纹理
金子塔层号=%d AND编号=:%d FOR UPDATE",m_RowIndex,m_ColIndex,
m_phramidLayer,m_ID);
myOci.AddBOLBDataToDB(strFile,tt,m_ID);
return TRUE;
}
函数说明:
WriteImageToDB()函数实现将分块后的纹理子块存储到Oracle数据库中,主要由以下两部分组成。
l 第1部分是调用OCI公共类的AddNormalDataToDB函数,将常规数据类型的数据(如影像子块的左下角和左上角x、y大地坐标、ID号等)写入Oracle数据库中。
l 第2部分是调用OCI公共类的AddBOLBDataToDB函数,将影像子块的影像数据以BLOB数据类型写入Oracle数据库中。
(6)得到影像纹理的左下角和右上角x、y大地坐标。
BOOL CTextureLoad::GetTextureRange(CString tcrPathname)
{
/*
影像纹理坐标文件是以项目名称同名的,是以扩展名为.tod的文件存储的,其格式为:
lb:781395.000 1869975.000
rt:797995.000 1876275.000
其中 第1行的lb:表示影像纹理的左下角x、y坐标
第2行的rt:表示影像纹理的右上角x、y坐标
*/
CString tt,strpath;
int pos=tcrPathname.ReverseFind('""');
strpath=tcrPathname.Left(pos);
pos=strpath.ReverseFind('""');
tt=strpath.Right(strpath.GetLength()-pos-1);
FILE*fp;
tt=strpath+""""+tt+".tod";//得到影像范围文件名
if((fp=fopen(tt,"r"))==NULL)//如果文件打开失败
{
MessageBox("影像范围文件"+tt+"不存在!","读取影像范围文件",
MB_ICONINFORMATION);
fclose(fp); //关闭文件
return FALSE;//返回False
}
else
{
fscanf(fp,"%s",tt);//得到lb字符串
//纹理的左下角x坐标
fscanf(fp,"%s",tt);theApp.m_TexturLeftDown_x=atof(tt);
//纹理的左下角y坐标
fscanf(fp,"%s"n",tt);theApp.m_TexturLeftDown_y=atof(tt);
fscanf(fp,"%s",tt);//得到rt字符串
//纹理的右上角x坐标
fscanf(fp,"%s",tt);theApp.m_TexturRightUp_x=atof(tt);
//纹理的右上角y坐标
fscanf(fp,"%s"n",tt);theApp.m_TexturRightUp_y=atof(tt);
fclose(fp);//关闭文件
return TRUE;//返回True
}
}
函数说明:
GetTextureRange()函数计算影像纹理的左下角和右上角x,y大地坐标,并存储到全局变量中。
(7)响应销毁对话框消息。
void CTextureLoad::OnDestroy()
{
CDialog::OnDestroy();
m_pImageObject=NULL;
delete m_pImageObject; //删除pImageObject,释放内存
}
最后程序运行界面如图5-13所示。单击“浏览”按钮同时选择各LOD级影像纹理文件,然后单击“确定”按钮,将各级原始大的影像纹理根据DEM的分块大小进行分块处理并写入Oracle数据库中,如图5-14所示。
图5-13 纹理影像分块入库运行效果图 图5-14 纹理影像分块入库过程与完成后的提示信息
本文出自《实战OpenGL三维可视化系统开发与源码精解》一书