因为项目需要,要用Qt+OpenGL显示三维地形,业务代码涉及保密,但是这种纯技术上的东西还是可以分享的。
话不多说,先看效果
这里我介绍一个简单的使用QT中的OpenGL实现三维地形显示的demo,可以实现第一人称的前后左右以及左右旋转上升下降等动作。
opengl的程序大多是三步式操作。主要实现三个函数就可以了。(当然还有着色器这些其他的)
这三个函数分别是
void initializeGL();
void resizeGL(int w, int h);
void paintGL();
首先介绍
void initializeGL();
void OpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
// vertex shader
QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
vshader->compileSourceFile("://shader/vert.vert");
// fragment shader
QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
fshader->compileSourceFile("://shader/frag.frag");
loadterrian();
// shader program
program = new QOpenGLShaderProgram;
program->addShader(vshader);
program->addShader(fshader);
program->link();
program->bind();
view.setToIdentity();
view.lookAt(QVector3D( 6, 0.5, 0), QVector3D( 6, 0.5, -6), QVector3D( 0, 1, 0));
//1是眼睛的中心位置,2是眼睛看向的位置,3是眼睛的朝向位置
// set color used to clear background
glClearColor(0.5f, 0.0f, 1.0f, 1.0f);
glEnable(GL_DEPTH_TEST);
xtrans=0;ytrans=0;ztrans=0;//初始化坐标轴转动值
glGenBuffers(3, handle);//在handle数组中返回当前n个未使用的名称,表示缓冲区对象
glBindBuffer(GL_ARRAY_BUFFER, handle[0]);//激活缓冲区对象,指定当前活动缓冲区的对象
glBufferData(GL_ARRAY_BUFFER, sizeof(terrian_pos), terrian_pos, GL_STATIC_DRAW);//用数据分配和初始化缓冲区对象
//使设置在着色器中生效
GLuint vPosition = program->attributeLocation("VertexPosition");
glEnableVertexAttribArray(vPosition);
glBindBuffer(GL_ARRAY_BUFFER, handle[0]);
glVertexAttribPointer( (GLuint)0, 3, GL_INT, GL_FALSE, 3*sizeof(GLint), 0 );//指定位置和偏移
//glVertexAttribPointer( (GLuint)1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (const void *)(3*sizeof(GLfloat)) );//备用
model.setToIdentity();
glClearDepthf(1.0);
glEnable(GL_TEXTURE_2D);
//glEnable(GL_CULL_FACE);//是否使能正反面
glDepthFunc(GL_LEQUAL);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
}
这段程序的注释比较完整,就不介绍了。主要其中有一个 loadterrian()函数,这个函数是从图片文件中读取地形数据的。举一个地形数据的图像如下:
每个像素点的灰度值代表该点的高度。
loadterrian()的具体实现如下:
void OpenGLWidget::loadterrian()
{
QImage heightmap;
GLint terrian_pos1[img_width*img_height][3];
GLint terrian_index1[img_width*img_height*3];
GLfloat terrian_texture1[img_width*img_height*2][2];
heightmap.load("heightmap.png");
//索引
int index1=0;
for(int i=0;i<img_width*img_height*2;i++)
{
terrian_index1[index1++]=i;
terrian_index1[index1++]=i+img_width;
}
for(int i=0;i<index1;i++)
{
terrian_index[i]=terrian_index1[i];
}
//顶点
for(int j=img_width-1;j>=0;j--)
{
for(int i=0;i<=img_width-1;i++)
{
terrian_pos1[img_height*(img_width-1-j)+i][0]=i;
QColor color=heightmap.pixel(i,j);
terrian_pos1[img_height*(img_width-1-j)+i][1]=color.red()/25;
terrian_pos1[img_height*(img_width-1-j)+i][2]=-(img_width-1-j);//z是负数
}
}
for(int i=0;i<=img_width-1;i++)
{
for(int j=0;j<=img_width-1;j++)
{
terrian_pos[(img_height-1)*i+j][0]=terrian_pos1[(img_height-1)*i+j][0];
terrian_pos[(img_height-1)*i+j][1]=terrian_pos1[(img_height-1)*i+j][1];
terrian_pos[(img_height-1)*i+j][2]=terrian_pos1[(img_height-1)*i+j][2];
}
}
//纹理
for(int i=0;i<img_width*img_height*2;i++)
{
terrian_texture1[i][0]=(float)terrian_pos1[i][0];
terrian_texture1[i][1]=(float)terrian_pos1[i][2];
}
for(int i=0;i<img_width*img_height*2;i++)
{
terrian_texture[i][0]=terrian_texture1[i][0];
terrian_texture[i][1]=terrian_texture1[i][1];
}
}
这个函数读取了每个点的高度数据,建立了纹理坐标,并且还建立了索引数组。
下面实现
void resizeGL(int w, int h);
void OpenGLWidget::resizeGL(int w, int h)
{
glViewport(0,0,w/2,h/2);
projection.setToIdentity();
projection.perspective(60.0f, (GLfloat)w/(GLfloat)h, 0.001f, 100.0f);
}
这个就简单了
下面直接看
void paintGL();
void OpenGLWidget::paintGL()
{
if(flag==0)//平移变化
{
//model.rotate(0, 0.0, 1.0, 0.0);
model.translate(xtrans, ytrans, ztrans);//模型平移
add_xtrans+=xtrans;
add_ytrans+=ytrans;
add_ztrans+=ztrans;
model.rotate(yrot, 0.0, 1.0, 0.0);
}else//旋转变化
{
model.setToIdentity();
model.translate(6, 0.5, 0);//模型平移
add_yrot+=yrot;
model.rotate(add_yrot, 0.0, 1.0, 0.0);
model.translate(add_xtrans-errorx, add_ytrans-errory, add_ztrans);//模型平移
}
flag=0;
xtrans=0;
ytrans=0;
ztrans=0;
yrot=0;
program->setUniformValue("view", view);
program->setUniformValue("projection", projection);
program->setUniformValue("model", model);
program->bind();
//设置纹理
QImage image(":/shader/texture_img.png");
image = image.convertToFormat(QImage::Format_RGB888);
image = image.mirrored();
GLuint texture;
glGenTextures(1, &texture);//glGenTextures的第一个参数是要创建的纹理数量,后面的参数就是保存这么多数量的整型数数组。
glBindTexture(GL_TEXTURE_2D, texture);//绑定到OpenGL的环境里
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(),
0, GL_RGB, GL_UNSIGNED_BYTE, image.bits());//把加载的图片数据放到纹理中
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //横坐标
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); //纵坐标
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//缩小时的过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//放大时的过滤方式
glGenerateMipmap(GL_TEXTURE_2D);//创建mipmaps,更流畅?
glBindBuffer(GL_ARRAY_BUFFER, handle[1]);//激活缓冲区对象,指定当前活动缓冲区的对象
glBufferData(GL_ARRAY_BUFFER, sizeof(terrian_texture), terrian_texture, GL_STATIC_DRAW);//用数据分配和初始化缓冲区对象
GLuint m_texCoordAttr = program->attributeLocation("tempTextCoord");
glEnableVertexAttribArray(m_texCoordAttr);
//glBindBuffer(GL_ARRAY_BUFFER, handle[0]);
glVertexAttribPointer( (GLuint)2, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), 0 );//指定位置和偏移
program->bind();
//绘制,数目应该是一个面的顶点数x面数
for(int s=0; s<img_width-2; s++)//z小于数据宽度w
{
glDrawElements(GL_TRIANGLE_STRIP, img_width*2, GL_UNSIGNED_INT, &terrian_index[img_width*s*2]);//索引应为数据宽度w,绘图数量应该是一层图像中面数x顶点数
}
qDebug()<<"paintGL";
}
这个函数中主要是纹理的加载和初始化,坐标的变化,以及glDrawElements绘图
这样子opengl的三维地形图就创建完成了。但是现在的三维地图还无法移动,三维地图按照第一人称视角移动是有一点点麻烦的,因为opengl中的移动和旋转都是参照原点坐标系,所以我们需要先做坐标变化。
地图移动的代码如下所示:
void MainWindow::on_forward_clicked()
{
openGLWidget->xtrans+=0.25*sin(-openGLWidget->add_yrot/180.0*3.1415926);
openGLWidget->ztrans+=0.25*cos(-openGLWidget->add_yrot/180.0*3.1415926);
qDebug()<<openGLWidget->add_yrot;
//openGLWidget->ztrans=0.25f;
openGLWidget->update();
}
void MainWindow::on_back_clicked()
{
openGLWidget->ztrans=-0.25f;
openGLWidget->update();
}
void MainWindow::on_left_shift_clicked()
{
openGLWidget->xtrans=0.25f;
openGLWidget->update();
}
void MainWindow::on_right_shift_clicked()
{
openGLWidget->xtrans=-0.25f;
openGLWidget->update();
}
void MainWindow::on_left_handed_clicked()
{
openGLWidget->yrot=-2.0f;
openGLWidget->flag=1;
openGLWidget->update();
}
void MainWindow::on_right_handed_clicked()
{
openGLWidget->yrot=2.0f;
openGLWidget->flag=1;
openGLWidget->update();
}
void MainWindow::on_rise_clicked()
{
openGLWidget->ytrans=-0.25f;
openGLWidget->update();
}
void MainWindow::on_fall_clicked()
{
openGLWidget->ytrans=0.25f;
openGLWidget->update();
}
源码我已经上传了,链接如下:
https://download.csdn.net/download/weixin_42521239/11225126
如果没有积分的小伙伴可以在评论中留下你的邮箱,我稍后发给你。