QT+opengl实现3D点云和三维坐标系

    最近利用opengl+QT实现了一个3D点云,并且点击点云的时候可以画出一个以选中点为原点的三维坐标系,可以实现移动和旋转,大概效果如下:

                                                                        (图中的小黄圆是截屏软件导致的,并非实际效果。)

在这里分享几个关键函数。

    将屏幕坐标转换为opengl的世界坐标:

QVector3D GlDisplayWidget::mousePosToWorldPos(QPoint _pos)
{

    double  modelview[ 16 ], projection[ 16 ];
    int  viewport[ 4 ];
    float  x = _pos.x();
    float  y = _pos.y();
    GLfloat  z =  0 ;
    double  objx, objy, objz;

     /*Read the projection, modelview and viewport matrices using the glGet functions.*/
    glGetIntegerv( GL_VIEWPORT, viewport );
    glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
    glGetDoublev( GL_PROJECTION_MATRIX, projection );

     //Read the window z value from the z-buffer

    glReadBuffer(GL_FRONT);
    glReadPixels( x, viewport[ 3 ]-y,  1 ,  1 , GL_DEPTH_COMPONENT, GL_FLOAT, &z );

     //Use the gluUnProject to get the world co-ordinates of
     //the point the user clicked and save in objx, objy, objz.
    gluUnProject( x, viewport[ 3 ]-y, z, modelview, projection, viewport, &objx, &objy, &objz );


    return QVector3D(objx,objy,objz);
}

    我们知道,在opengl中,从世界坐标到屏幕坐标的顺序是:

    所以我们只要使用glReadPixels获取该屏幕坐标的深度值,然后读取最后进行的变换矩阵,就可以通过gluUnProject获取屏幕坐标对应的世界坐标,就可以实现点云的选中功能了。由于QT屏幕坐标系与opengl上下相反,所以调用的时候需要将y改成viewport[3]-y。

    将世界坐标转换为屏幕坐标:

QPointF GlDisplayWidget::worldPosToScreenPos(QVector3D pos)
{
    double  modelview[ 16 ], projection[ 16 ];
    int  viewport[ 4 ];
    //double  objx, objy, objz;

    double x,y,z;

    /*Read the projection, modelview and viewport matrices using the glGet functions.*/
   glGetIntegerv( GL_VIEWPORT, viewport );
   glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
   glGetDoublev( GL_PROJECTION_MATRIX, projection );

   gluProject(pos.x(),pos.y(),pos.z(),modelview, projection, viewport,&x,&y,&z);

   return QPointF(x,viewport[ 3 ]-y);
}

    类似的,我们可以使用gluProject将世界坐标转换为屏幕坐标。

    接下来是坐标系的绘制,由于我使用的是,z-y-x欧拉角变换,所以旋转都是先绕z轴旋转,再绕y轴,最后绕x轴。

    坐标轴渲染的接口:

void GlDisplayWidget::draw3DCoord(customPoint point)
{

    double x,y,z,rx,ry,rz;
    x = point.x;
    y = point.y;
    z = point.z;
    rx = point.rotateX;
    ry = point.rotateY;
    rz = point.rotateZ;

    const double len = 2.0;
    const double k = 0.75;
    const double vlen = len*(1-k);

    const double step =0.707/25.0;

    GLUquadric* obj = gluNewQuadric();

    glColor3f(0.745,0.745,0.745);
    glPushMatrix();
    glTranslated(x,y,z);
    gluSphere(obj,0.1,20,20);
    glPopMatrix();

    glPushMatrix();

    //x轴
    glTranslated(x,y,z);
    glRotated(rz,0.0,0.0,1.0);
    glRotated(ry,0.0,1.0,0.0);
    glRotated(rx,1.0,0.0,0.0);

    if(curClickType == rxCenter)
    {
      glRotated(rotateAngel,1.0,0.0,0.0);
    }
    else if(curClickType == ryCenter)
    {
        glRotated(rotateAngel,0.0,1.0,0.0);
    }
    else if(curClickType == rzCenter)
    {
        glRotated(rotateAngel,0.0,0.0,1.0);
    }

    {
        //圆柱
        if(curClickType == txCenter)
        {
           glColor3f(1.0,0.0,1.0);
        }
        else
        {
            glColor3f(1.0,0.0,0.0);
        }

        glPushMatrix();
        glRotatef(90, 0.0, 1.0, 0.0);
        //glTranslated(x,y,z);
        gluCylinder(obj,0.02,0.02,len*k,30,30);

        glTranslated(0.0,0.0,len-vlen);
        gluCylinder(obj,0.1,0.00,0.5,30,30);

        //z旋转球
        glRotatef(-90, 0.0, 1.0, 0.0);
        glTranslated(vlen-len+0.5,0.5,0.0);

        if(curClickType == rzCenter)
        {
           glColor3f(1.0,0.0,1.0);
        }
        else
        {
            glColor3f(0.0,0.0,1.0);
        }
        gluSphere(obj,0.12,20,20);
        glPopMatrix();
        //绘制弧线x-y
        glLineWidth(1);
        glBegin(GL_LINE_STRIP);

        for(double x1 = 0.0;x1<=0.707;x1 += step)
        {
            double y1 = sqrt(0.50 - x1*x1);
            glVertex3d(x1,y1,0.0);
        }
        glEnd();


    }
    //y轴
    {
        //圆柱
        if(curClickType == tyCenter)
        {
           glColor3f(1.0,0.0,1.0);
        }
        else
        {
            glColor3f(0.0,1.0,0.0);
        }
        glPushMatrix();
        glRotatef(90, -1.0, 0.0, 0.0);
        //glTranslated(x,y,z);
        gluCylinder(obj,0.02,0.02,1.8,30,30);

        glTranslated(0.0,0.0,len-vlen);
        gluCylinder(obj,0.1,0.00,0.5,30,30);


        //x旋转球
        glRotatef(-90, -1.0, 0.0, 0.0);
        glTranslated(0.0,vlen-len+0.5,0.5);
        if(curClickType == rxCenter)
        {
           glColor3f(1.0,0.0,1.0);
        }
        else
        {
            glColor3f(1.0,0.0,0.0);
        }
        gluSphere(obj,0.12,20,20);
        glPopMatrix();

        //绘制弧线y-z
        glLineWidth(1);
        glBegin(GL_LINE_STRIP);

        for(double y1 = 0.0;y1<=0.707;y1 += step)
        {
            double z1 = sqrt(0.50 - y1*y1);
            glVertex3d(0.0,y1,z1);
        }
        glEnd();

    }

    //z轴
    {
        //圆柱
        if(curClickType == tzCenter)
        {
           glColor3f(1.0,0.0,1.0);
        }
        else
        {
            glColor3f(0.0,0.0,1.0);
        }
        glPushMatrix();
        //glTranslated(x,y,z);
        gluCylinder(obj,0.02,0.02,1.8,30,30);

        glTranslated(0.0,0.0,len-vlen);
        gluCylinder(obj,0.1,0.00,0.5,30,30);

        //y旋转球
        glTranslated(0.5,0.0,vlen-len+0.5);
        if(curClickType == ryCenter)
        {
           glColor3f(1.0,0.0,1.0);
        }
        else
        {
            glColor3f(0.0,1.0,0.0);
        }
        gluSphere(obj,0.12,20,20);

        glPopMatrix();

        //绘制弧线x-z
        glLineWidth(1);
        glBegin(GL_LINE_STRIP);

        for(double x1 = 0.0;x1<=0.707;x1 += step)
        {
            double z1 = sqrt(0.50 - x1*x1);
            glVertex3d(x1,0.0,z1);
        }
        glEnd();

    }
    glPopMatrix();
}

    在函数中,我们使用了opengl绘制图元的方式画作标轴,使用gluSphere绘制球体,gluCylinder绘制圆柱来当坐标轴,gluCylinder来绘制圆锥。绘制圆弧的时候,计算一部分圆弧上的点,再将这些点连起来,就是弧线,效果如下图:

   坐标轴的移动比较简单,但是旋转因为改变旋转顺序会导致结果不同,在这里,我才用了欧拉角旋转公式和逆转公式实现了旋转。

    点绕指定轴旋转a的函数:

QVector3D GlDisplayWidget::Rotate(QVector3D p1, QVector3D p2, double angle)
{
    double old_x,old_y,old_z;
    double vx,vy,vz;

    old_x = p1.x();
    old_y = p1.y();
    old_z = p1.z();

    vx = p2.x();
    vy = p2.y();
    vz = p2.z();

    double c = cos(angle/180.0*3.14);
    double s = sin(angle/180.0*3.14);


    double new_x = (vx*vx*(1 - c) + c) * old_x + (vx*vy*(1 - c) - vz*s) * old_y + (vx*vz*(1 - c) + vy*s) * old_z;
    double new_y = (vy*vx*(1 - c) + vz*s) * old_x + (vy*vy*(1 - c) + c) * old_y + (vy*vz*(1 - c) - vx*s) * old_z;
    double new_z = (vx*vz*(1 - c) - vy*s) * old_x + (vy*vz*(1 - c) + vx*s) * old_y + (vz*vz*(1 - c) + c) * old_z;

    return QVector3D(new_x,new_y,new_z);
}

   已知坐标轴从状态a到状态b,获取从a到b旋转角度的函数:

QVector3D GlDisplayWidget::fromRotate(QVector3D x,QVector3D y,QVector3D z)
{
    double r11,r12,r13,r21,r22,r23,r31,r32,r33;

    r11 = x.x();
    r12 = y.x();
    r13 = z.x();

    r21 = x.y();
    r22 = y.y();
    r23 = z.y();

    r31 = x.z();
    r32 = y.z();
    r33 = z.z();

    QVector3D result;

    double cb = sqrt(r11*r11+r21*r21);
    if(cb != 0)
    {
        result.setY(atan2(0-r31,sqrt(r11*r11+r21*r21))/3.14*180.0);
        result.setZ(atan2(r21,r11)/3.14*180.0);
        result.setX(atan2(r32,r33)/3.14*180.0);
        return result;
    }

    else
    {
        result.setY(90.0);
        result.setZ(0);
        result.setX(atan2(r21,r22)/3.14*180.0);

        return result;
    }
}

    这个接口得出来的角度一定是从z轴旋转c,y轴旋转b,x轴旋转a,得到了角度a,b,c。具体原理大家可以去了解欧拉角和RPY角。

 

    如果想给窗口加一个背景图,可以先切换正交视角,关闭深度测试,加载图片,然后再切换投影视角,开启深度测试,再进行渲染。

您想使用Qt OpenGL进行3D点云的编程,并且希望根据高度使颜色产生渐变。您可以通过以下代码实现这个功能: ``` // 假设您已经拥有了3D点云的数据,存储在vertex数组和height数组中 GLfloat* vertex; // 3D点云的数组 GLfloat* height; // 包含每个点高度的数组 int num_points; // 点云中的点数 // 定义颜色的RGBA值(红、绿、蓝、透明度) GLfloat color[4] = {1.0, 0.0, 0.0, 1.0}; // 红色 // 迭代点云中的每个点 for (int i = 0; i < num_points; i++) { // 计算每个点的颜色 // 根据高度从红色渐变到绿色,由高到低逐渐变化 GLfloat height_ratio = height[i] / (float)max_height; // 计算当前点高度与最大高度的比值 color[0] = 1.0 - height_ratio; // 红色分量从1.0到0.0逐渐减小 color[1] = height_ratio; // 绿色分量从0.0到1.0逐渐增加 // 设置当前点的颜色 glColor4fv(color); // 使用glColor4fv设置颜色,参数为颜色数组 // 绘制当前点 glPointSize(3.0f); // 设置点的大小为3像素 glBegin(GL_POINTS); // 开始绘制点 glVertex3fv(&vertex[i * 3]); // 指定点的位置,vertex数组中的每个点是由3个坐标值构成的,所以要乘以3 glEnd(); // 结束绘制 } ``` 在上述代码中,我们使用OpenGL函数glColor4fv设置当前点的颜色,颜色的值根据当前点的高度值计算得出,从红色渐变到绿色,由高到低逐渐变化。需要注意的是,在绘制3D点云时可以设置点的大小。在这个示例中,使用glPointSize设置点的大小为3个像素。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值