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来绘制圆锥。绘制圆弧的时候,计算一部分圆弧上的点,再将这些点连起来,就是弧线,效果如下图:

QT+opengl实现3D点云和三维坐标系_第1张图片

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

    点绕指定轴旋转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,opengl,qt)