主要是利用C++搭建的框架,利用OpenGL、GDAL及Qt进行影像分块显示遥感影像,目前测试显示600M的资源3号卫星影像,仅仅需要15秒左右的时间。
此文章不对OpenGL以及GDAL做解释,如果对OpenGL和GDAL不熟悉,请自行查阅相应的文档。
利用OpenGL显示影像,那么第一步首先是要对OpenGL进行初始化。
void GeoGraphicsView::initializeGL()
{
initializeOpenGLFunctions();
glShadeModel(GL_SMOOTH); glClearDepth(1.0f);
glClearColor(d_ptr->bgcolor().x, d_ptr->bgcolor().y,
d_ptr->bgcolor().z, d_ptr->bgcolor().w);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
createShader(); getLocationId();
QOpenGLWidget::initializeGL();
}
在initializeGL接口中d_ptr是GeoGraphicsView的一个类成员,仅仅保存了一些类的私有成员。这也符合Qt编程规范。
createShader接口是用来初始化着色器。
bool GeoGraphicsView::createShader()
{
NApplication app; QString shader = app.currentShaderVertex();
d_ptr->program()->addShaderFromSourceCode(QOpenGLShader::Vertex, shader);
QString log = d_ptr->program()->log(); if(false == log.isEmpty()) return false;
shader = app.currentShaderFragment();
d_ptr->program()->addShaderFromSourceCode(QOpenGLShader::Fragment, shader);
log = d_ptr->program()->log(); if(false == log.isEmpty()) return false;
d_ptr->program()->link(); log = d_ptr->program()->log(); if(false == log.isEmpty()) return false;
d_ptr->program()->bind(); log = d_ptr->program()->log(); if(false == log.isEmpty()) return false;
return true;
}
getLocationId接口是用来获取在着色器中定义的变量。
void GeoGraphicsView::getLocationId()
{
d_ptr->matrix(d_ptr->program()->uniformLocation("matrix"));
d_ptr->sclaeid(d_ptr->program()->attributeLocation("scale"));
d_ptr->posAttr(d_ptr->program()->attributeLocation("posAttr"));
d_ptr->colAttr(d_ptr->program()->attributeLocation("colAttr"));
}
到此OpenGL初始化完成。
下一步就是渲染了。
void GeoGraphicsView::paintGL()
{
modifyShaderVariable();
renderData();
QOpenGLWidget::paintGL();
}
在渲染接口中,首先是修改着色器中的变量值,因为我们可能对显示的影像进行缩放或者平移操作。这些操作都是需要在着色器中对变量进行修改。
void GeoGraphicsView::modifyShaderVariable()
{
float halfWidth = d_ptr->winwidth() / 2.0f;
float halfHeight = d_ptr->winheight() / 2.0f;
glViewport(0, 0, d_ptr->winwidth(), d_ptr->winheight());
QMatrix4x4 screenProj, rotatex, rotatey, rotate, scale, move;
screenProj.ortho(-halfWidth, halfWidth, halfHeight, -halfHeight, -80.0f, 80.0f);
rotatex.rotate(d_ptr->rotateangle().x, 0, 1); rotatey.rotate(d_ptr->rotateangle().y, 1, 0);
rotate = rotatey *rotatex; screenProj = screenProj * rotate;
screenProj.scale(d_ptr->scale());
screenProj.translate(d_ptr->movedist().x(), d_ptr->movedist().y());
d_ptr->program()->setUniformValue(d_ptr->matrix(), screenProj);
}
这个接口中都是使用OpenGL和Qt的接口,不懂的可以自行查阅文档。
接下来就是对数据进行渲染,而渲染又可以分为两种方式,一种是利用纹理进行贴图,第二种就是将影像数据进行按照实际的栅格格式进行渲染显示。显示DEM数据的时候就是利用纹理贴图的方式进行显示,而遥感影像是使用第二种方式。
void GeoGraphicsView::renderData()
{
glClearColor(d_ptr->bgcolor().x, d_ptr->bgcolor().y,
d_ptr->bgcolor().z, d_ptr->bgcolor().w);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
for(int idx = 0; idx < d_ptr->block(); ++idx)
{
glEnableVertexAttribArray(d_ptr->colAttr());
glEnableVertexAttribArray(d_ptr->posAttr());
glBindBuffer(GL_ARRAY_BUFFER, d_ptr->colorid()[idx]);
glVertexAttribPointer(d_ptr->colAttr(), 3, GL_FLOAT, GL_FALSE, sizeof(float3), 0);
glBindBuffer(GL_ARRAY_BUFFER, d_ptr->dataid()[idx]);
glVertexAttribPointer(d_ptr->posAttr(), 2, GL_FLOAT, GL_FALSE, sizeof(float2), 0);
glDrawArrays(GL_POINTS, 0, d_ptr->verticeslen()[idx]);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(d_ptr->posAttr());
glDisableVertexAttribArray(d_ptr->colAttr());
}
}
在打开软件的时候因为没有数据,所以d_ptr中的block为0,这个block也就是分块的总数。后面有介绍。到此OpenGL初始化以及渲染都完成。接下来就是组织数据,将数据传入着色器中。
前方高能,警报!!!警报!!!
打开影像,AdapterRaster类是对GDAL进行了封装,没有其他特别的。
bool GeoGraphicsView::openFile(const QString &file)
{
if(true == file.isEmpty()) return false;
AdapterRaster raster;
if(false == raster.openImage(file, ReadOnly)) return false;
if(raster->imageZSize() != 1) readImageRGB(raster);
else readImageGray(raster);
return true;
}
接下来就是读取影像。。。。(包括对数据进行分块)
void GeoGraphicsView::readImageRGB(const Object<IAdapterRaster> &raster)
{
glClearColor(d_ptr->bgcolor().x, d_ptr->bgcolor().y,
d_ptr->bgcolor().z, d_ptr->bgcolor().w);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
int bandMap[3] = { 3, 2, 1 };
int w = raster.imageXSize(); d_ptr->width(w);
int h = raster.imageYSize(); d_ptr->height(h);
int xblock = w / 640, yblock = h / 640;
xblock = (w + xblock + 639) / 640;
yblock = (h + yblock + 639) / 640;
int block = xblock * yblock;
d_ptr->block(block);
d_ptr->dataid() = new uint[block];
d_ptr->colorid() = new uint[block];
d_ptr->verticeslen() = new uint[block];
double2 rmm, gmm, bmm;
double rmin, rmax, gmin, gmax, bmin, bmax;
raster.imageMinMax(bandMap[0], rmm.x, rmm.y);
raster.imageMinMax(bandMap[1], gmm.x, gmm.y);
raster.imageMinMax(bandMap[2], bmm.x, bmm.y);
initializeProgressBar(S2Q("正在读取数据..."), 0, block - 1);
for(int yb = 0; yb < yblock; ++yb)
{
int ybuf = yb == yblock - 1 ? h - yb * 640 : 640;
for(int xb = 0; xb < xblock; ++xb)
{
int xbuf = xb == xblock - 1 ? w - xb * 640 : 640;
SYPImageData data; int bk = yb * xblock + xb;
setProgressBarPosition(bk);
data.datatype = raster.imageDataType();
float2 *verticespos = new float2[xbuf * ybuf];
float3 *verticescolor = new float3[xbuf * ybuf];
data.xpos = xb == 0 ? 0 : xb * 640 - xb;
data.ypos = yb == 0 ? 0 : yb * 640 - yb;
data.col = xbuf; data.row = ybuf; data.band = 3;
data.allocator();
raster.readImage(data, bandMap, BSQ);
createRenderData(data, xbuf, ybuf, xb, yb, verticespos, verticescolor, rmm, gmm, bmm);
beginRenderData(bk, xbuf * ybuf, verticespos, verticescolor);
delete verticescolor; delete verticespos;
}
}
restoreProgressBar();
}
这里面需要解释的应该就是createRenderData和beginRenderData两个接口。
void GeoGraphicsView::createRenderData(const SYPImageData &data, int XBuf, int YBuf, int XBlock, int YBlock, float2 *VPos, float3 *VColor, const float2 &rmm, const float2 &gmm, const float2 &bmm)
{
double rval, gval, bval; int bs = XBuf * YBuf;
for(int row = 0; row < YBuf; ++row)
{
for(int col = 0; col < XBuf; ++col)
{
int index = row * XBuf + col;
VPos[index].x = XBlock * 640 + col - d_ptr->width() / 2;
VPos[index].y = YBlock * 640 + row - d_ptr->height() / 2;
switch(data.datatype)
{
case 1: valueFromPointer(((byte *)data.imageData()), index, bs, rval, gval, bval); break;
case 2: valueFromPointer(((ushort *)data.imageData()), index, bs, rval, gval, bval); break;
case 3: valueFromPointer(((short *)data.imageData()), index, bs, rval, gval, bval); break;
case 4: valueFromPointer(((ulong *)data.imageData()), index, bs, rval, gval, bval); break;
case 5: valueFromPointer(((long *)data.imageData()), index, bs, rval, gval, bval); break;
case 6: valueFromPointer(((float *)data.imageData()), index, bs, rval, gval, bval); break;
case 7: valueFromPointer(((double *)data.imageData()), index, bs, rval, gval, bval); break;
default: break;
}
rval = rval < rmm.x ? rmm.x : rval > rmm.y ? rmm.y : rval;
VColor[index].x = double(rval - rmm.x) / (double)(rmm.y - rmm.x);
gval = gval < gmm.x ? gmm.x : gval > gmm.y ? gmm.y : gval;
VColor[index].y = double(gval - gmm.x) / (double)(gmm.y - gmm.x);
bval = bval < bmm.x ? bmm.x : bval > bmm.y ? bmm.y : bval;
VColor[index].z = double(bval - bmm.x) / (double)(bmm.y - bmm.x);
}
}
}
void GeoGraphicsView::beginRenderData(int bk, int bs, const float2 *VPos, const float3 *VColor)
{
d_ptr->verticeslen()[bk] = bs; int len;
glEnableVertexAttribArray(d_ptr->colAttr());
glEnableVertexAttribArray(d_ptr->posAttr());
glGenBuffers(1, &d_ptr->dataid()[bk]);
glVertexAttribPointer(d_ptr->colAttr(), 3, GL_FLOAT, GL_FALSE, sizeof(float3), 0);
glBindBuffer(GL_ARRAY_BUFFER, d_ptr->dataid()[bk]);
len = bs * sizeof(float2);
glBufferData(GL_ARRAY_BUFFER, len, VPos, GL_STATIC_DRAW);
glGenBuffers(1, &d_ptr->colorid()[bk]);
glVertexAttribPointer(d_ptr->posAttr(), 2, GL_FLOAT, GL_FALSE, sizeof(float2), 0);
glBindBuffer(GL_ARRAY_BUFFER, d_ptr->colorid()[bk]);
len = bs * sizeof(float3);
glBufferData(GL_ARRAY_BUFFER, len, VColor, GL_STATIC_DRAW);
glDrawArrays(GL_POINTS, 0, bs);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(d_ptr->posAttr());
glDisableVertexAttribArray(d_ptr->colAttr());
}
到此影像显示结束。
下面是显示效果:
放大之后的效果:
放大之后显示可以看出是一个一个小方块,实际上这并不是小方块,而是我们看到的错觉。再放大之后我们可以看到其实是一个个的点。
到此整个显示完成,后续将优化显示效果。
资源下载地址:https://download.csdn.net/download/yangfahe1/10781676
示例程序运行方式:https://blog.csdn.net/yangfahe1/article/details/84028318
目前支持小数据分块显示,后续将继续更新大数据显示