Qt使用QPainter绘制一个3D立方体

1.实现思路

(网上有另一篇类似的,不过他不是用的 Qt 自带的矩阵运算类:https://blog.csdn.net/BIG_C_GOD/article/details/53285152)

实现思路有点类似使用 OpenGL 画立方体,先准备顶点数据:

    //立方体前后四个顶点,从右上角开始顺时针
    vertexArr=QVector{
            QVector3D{1,1,1},
            QVector3D{1,-1,1},
            QVector3D{-1,-1,1},
            QVector3D{-1,1,1},

            QVector3D{1,1,-1},
            QVector3D{1,-1,-1},
            QVector3D{-1,-1,-1},
            QVector3D{-1,1,-1} };

    //六个面,一个面包含四个顶点
    elementArr=QVector>{
    {0,1,2,3},
    {4,5,6,7},
    {0,4,5,1},
    {1,5,6,2},
    {2,6,7,3},
    {3,7,4,0} };

然后再和旋转矩阵、透视矩阵进行运算,得到 3D 顶点坐标在 2D 平面上的 xy 值。根据顶点 xy 值,得到每个面的路径,然后绘制表面的路径。

这里面比较麻烦的是判断哪些是表面,单个立方体还好,可以遍历比较 z 值,如果是多个物体运算量就大了,还是直接 OpenGL 吧,毕竟我这个只是画着玩的。

2.实现代码

代码 github 链接:https://github.com/gongjianbo/EasyQPainter

实现效果 GIF 动图:

Qt使用QPainter绘制一个3D立方体_第1张图片

主要代码:

#ifndef MYCUBE_H
#define MYCUBE_H

#include 
#include 

#include 
#include 

class MyCube : public QWidget
{
    Q_OBJECT
public:
    explicit MyCube(QWidget *parent = nullptr);

protected:
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;

    QPointF getPoint(const QVector3D &vt,int w) const;

private:
    QVector vertexArr;     //八个顶点
    QVector> elementArr; //六个面
    QMatrix4x4 rotateMat;      //旋转矩阵
    QPoint mousePos;           //鼠标位置
    bool mousePressed=false;   //鼠标按下标志位
};

#endif // MYCUBE_H

 

#include "MyCube.h"

#include 
#include 
#include 

MyCube::MyCube(QWidget *parent)
    : QWidget(parent)
{
    //          7------------------4
    //        /                 /  |
    //     3------------------0    |
    //     |                  |    |
    //     |                  |    |
    //     |                  |    |
    //     |                  |    |
    //     |    6             |    5
    //     |                  |  /
    //     2------------------1
    //立方体前后四个顶点,从右上角开始顺时针
    vertexArr=QVector{
            QVector3D{1,1,1},
            QVector3D{1,-1,1},
            QVector3D{-1,-1,1},
            QVector3D{-1,1,1},

            QVector3D{1,1,-1},
            QVector3D{1,-1,-1},
            QVector3D{-1,-1,-1},
            QVector3D{-1,1,-1} };

    //六个面,一个面包含四个顶点
    elementArr=QVector>{
    {0,1,2,3},
    {4,5,6,7},
    {0,4,5,1},
    {1,5,6,2},
    {2,6,7,3},
    {3,7,4,0} };

    setFocusPolicy(Qt::ClickFocus); //Widget默认没有焦点
}

void MyCube::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)
    QPainter painter(this);
    //先画一个白底黑框
    painter.fillRect(this->rect(),Qt::white);
    QPen pen(Qt::black);
    painter.setPen(pen);
    painter.drawRect(this->rect().adjusted(0,0,-1,-1)); //右下角会超出范围

    //思路,找到z值最高的顶点,然后绘制该顶点相邻的面
    // 根据z值计算,近大远小
    //(此外,Qt是屏幕坐标系,原点在左上角)

    //矩形边框参考大小
    const int cube_width=(width()>height()?height():width())/4;

    //投影矩阵
    //(奇怪,为什么只是平移了z轴,没用perspective函数就有远小近大的效果,
    //在我的想象中默认不该是正交投影么)
    QMatrix4x4 perspective_mat;
    perspective_mat.translate(0.0f,0.0f,-0.1f);

    //计算顶点变换后坐标,包含z值max点就是正交表面可见的,
    //再计算下远小近大的透视投影效果齐活了
    QList vertex_list; //和矩阵运算后的顶点
    QList vertex_max_list; //top顶点在arr的位置
    float vertex_max_value;     //top值
    //根据旋转矩阵计算每个顶点
    for(int i=0;ivertex_max_value){
                vertex_max_list.clear();
                vertex_max_list.push_back(i);
                vertex_max_value=vertex.z();
            }else if(abs(vertex.z()-vertex_max_value)<(1E-7)){
                vertex_max_list.push_back(i);
            }
        }
    }

    //把原点移到中间来
    painter.save();
    painter.translate(width()/2,height()/2);
    //绘制front和back六个面,先计算路径再绘制
    QList element_path_list; //每个面路径
    QList element_z_values;   //每个面中心点的z值
    QList element_z_points; //每个面中心点在平面对应xy值
    QList element_front_list;   //elementArr中表面的index
    for(int i=0;i element_front_remove;
    for(int i=0;ielement_z_values.at(index_j)
                    &&element_path_list.at(index_i).contains(element_z_points.at(index_j))){
                element_front_remove.push_back(index_j);
            }
        }
    }
    for(int index:element_front_remove){
        element_front_list.removeOne(index);
    }

    //根据计算好的路径绘制
    painter.setRenderHint(QPainter::Antialiasing,true);
    //画表面
    for(auto index:element_front_list){
        painter.fillPath(element_path_list.at(index),Qt::green);
    }
    //画被遮盖面的边框虚线
    painter.setPen(QPen(Qt::white,1,Qt::DashLine));
    for(int i=0;ipos();
    QWidget::mousePressEvent(event);
}

void MyCube::mouseMoveEvent(QMouseEvent *event)
{
    if(mousePressed){
        const QPoint posOffset=event->pos()-mousePos;
        mousePos=event->pos();
        //旋转矩阵  x和y分量
        //rotateMat.rotate(posOffset.x(),QVector3D(0.0f,-0.5f,0.0f));
        //rotateMat.rotate(posOffset.y(),QVector3D(0.5f,0.0f,0.0f));
        rotateMat.rotate(1.1f,QVector3D(0.5f*posOffset.y(),-0.5f*posOffset.x(),0.0f));
        update();
    }
    QWidget::mouseMoveEvent(event);
}

void MyCube::mouseReleaseEvent(QMouseEvent *event)
{
    mousePressed=false;
    QWidget::mouseReleaseEvent(event);
}

QPointF MyCube::getPoint(const QVector3D &vt,int w) const
{
    //可以用z来手动计算远小近大,也可以矩阵运算
    //const float z_offset=vt.z()*0.1;
    //return QPointF{ vt.x()*w*(1+z_offset), vt.y()*w*(1+z_offset) };
    return QPointF{ vt.x()*w, vt.y()*w };
}

你可能感兴趣的:(Qt,略知一二)