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

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

  • 23
    点赞
  • 190
    收藏
    觉得还不错? 一键收藏
  • 192
    评论
Qt、Assimp和OpenGL是三种常用的工具,在三维模型设计中发挥着重要的作用。Qt是一个跨平台的C++应用程序开发框架,它为用户提供了GUI和其他一些基本的用户界面组件。Assimp是一个允许用户读取和写入多种3D文件格式的库,包括OBJ、FBX、DAE等。OpenGL是一种用于创建高性能的计算机图形的API。因此,将这三种工具结合使用,可以方便地进行三维模型的开发。 在使用Qt、Assimp和OpenGL进行三维模型开发时,Qt主要负责提供GUI界面和基本的用户界面组件。Assimp则用于读取和写入不同格式的三维模型,然后通过OpenGL进行渲染展示。OpenGL作为一个底层API,提供了强大的图形处理能力,允许用户在计算机上创建复杂的3D场景和动画。 具体地说,使用这三种工具进行三维模型开发的步骤通常如下:首先用Assimp读入各种格式的三维模型文件,然后通过OpenGL进行渲染显示Qt可以用于创建一个GUI界面,方便用户进行模型读取、渲染、缩放、旋转、材质设定等基本的操作。同时,用户可以通过Qt的事件机制来和OpenGL进行交互:比如通过鼠标事件、键盘事件等,实现三维模型的选择、移动等控制。在此基础上,用户还可以通过OpenGL的扩展和着色器功能等,使三维模型的渲染更加复杂和高效。 总之,Qt、Assimp和OpenGL是三种常用的工具,它们的结合使用可以让用户方便地进行三维模型的设计和开发。在实际应用中,用户可以根据实际需求选择不同的工具和技术,实现更加复杂和高效的三维模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 192
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值