Qt+OpenGL实现三维地形显示

因为项目需要,要用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

如果没有积分的小伙伴可以在评论中留下你的邮箱,我稍后发给你。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页