您当前的位置: 首页 >  3d

龚建波

暂无认证

  • 2浏览

    0关注

    313博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Qt使用QPainter绘制一个简单的3D风车

龚建波 发布时间:2020-07-26 22:51:30 ,浏览量:2

最近无聊,用QPainter画了个简单的风车,效果如下:

 可以看到很多地方都穿摸了,因为绘制时每个填充路径的顺序没法很好的确定,特别是如果出现两个面交叉更没法处理,我能想到的就是拆分成多个小的三角来计算,不过这样CPU的负担就太大了。

整体思路就是先定义对象树结构体,一个绘制对象可以有多个面和子节点。绘制的时候先根据当前角度和位置计算出所有节点的位置和角度,然后通过矩阵运算得到最终的坐标值。最后,根据所有面的z值进行排序,从最远的开始填充。手稿:

(2021-11-17)最近将之前的欧拉角旋转改成了四元数,这样交互的编码更简单点。参照荷兰风车,改为逆时针旋转,扇叶往塔身一侧偏。

代码的github地址(MySimple3D类):https://github.com/gongjianbo/EasyQPainter

主要代码:

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//图元结构体
struct WindMeta
{
    //顶点,可以是任意个
    QList vertex;
    //颜色
    QColor color;
    //QBrush brush;
    //图元顶点中z值最小者,单独作为成员便于排序
    double z;
    //根据定点计算出的路径,便于绘制
    QPainterPath path;

    //构造函数
    WindMeta(const QList &vtx, const QColor &clr);
};

//物体实体结构体
struct WindItem
{
    //相对于场景或者父节点的坐标位置
    QVector3D position;
    //相对于场景或者父节点的方向
    QVector3D rotation;
    //包含的图元
    QList surfaceMetas;
    //子节点物体列表
    QList subItems;
    //旋转动画因子(根据全局的定时器步进值计算对应分量动画效果)
    QVector3D animationFactor;

    //构造函数
    WindItem(const QVector3D &pos = QVector3D(0, 0, 0),
             const QVector3D &rotate = QVector3D(0, 0, 0),
             const QList &metas = QList(),
             const QList &subs = QList(),
             const QVector3D &factor = QVector3D(0, 0, 0));

    //根据当前位置和角度计算出顶点列表
    //position取出后直接叠加到顶点的坐标上:vertex+position+this->position
    //rotation目前只计算了x和y的旋转,作用于item的顶点上,目前只能旋转item
    //step为定时器动画的步进,每个item根据自身的动画因子成员来计算
    QList calcSurfaceMetas(
            const QVector3D &position, const QQuaternion &rotation, float step, float fovy);
};

//绘制一个3D风车
//(目前按大块平面来计算堆叠顺序效果不太好,两个物体交叉时会有一部分被覆盖)
class Windmill3D : public QWidget
{
    Q_OBJECT
public:
    explicit Windmill3D(QWidget *parent = nullptr);
    ~Windmill3D();

protected:
    //显示时才启动定时动画
    void showEvent(QShowEvent *event) override;
    void hideEvent(QHideEvent *event) override;
    //绘制
    void paintEvent(QPaintEvent *event) override;
    //鼠标操作
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;
    //改变窗口大小
    void resizeEvent(QResizeEvent *event) override;

private:
    //初始化操作
    void initWindmill();
    //绘制
    void drawImage(int width, int height);

private:
    //根实体(这个变量绘制时在线程访问)
    WindItem rootItem;
    //FPS统计,paintEvent累计temp,达到一秒后赋值给counter
    int fpsCounter{0};
    int fpsTemp{0};
    //FPS计时
    QTime fpsTime;

    //鼠标位置
    QPoint mousePos;
    //鼠标按下标志位
    bool mousePressed{false};

    //定时动画
    QTimer timer;
    //定时器旋转步进值
    float animationStep{0.0f};
    //观察矩阵旋转
    QVector3D rotationAxis;
    QQuaternion rotationQuat;
    //透视投影的fovy参数,视野范围
    float projectionFovy{30.0f};

    //多线程异步watcher
    QFutureWatcher watcher;
    //绘制好的image
    QImage image;
};
#include "Windmill3D.h"

#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 

WindMeta::WindMeta(const QList &vtx, const QColor &clr)
    : vertex(vtx), color(clr)
{
}

WindItem::WindItem(const QVector3D &pos, const QVector3D &rotate,
                   const QList &metas,
                   const QList &subs,
                   const QVector3D &factor)
    : position(pos), rotation(rotate), surfaceMetas(metas), subItems(subs), animationFactor(factor)
{
}

QList WindItem::calcSurfaceMetas(
        const QVector3D &position, const QQuaternion &rotation, float step, float fovy)
{
    QVector3D cur_position = position + this->position;
    //这里没验证,因为目前只为0,可能有误
    QQuaternion cur_rotation = QQuaternion::fromEulerAngles(this->rotation) * rotation;
    //平移做裁剪,缩放拉近距离
    QMatrix4x4 perspective_mat;
    perspective_mat.scale(100);
    perspective_mat.perspective(fovy, 1.0f, 0.1f, 2000.0f);
    QMatrix4x4 view_mat;
    view_mat.translate(0.0f, 0.0f, -1000.0f);
    //先跟随父节点转动和位移,再以自身的转动步进进行转动
    QMatrix4x4 model_mat;
    model_mat.rotate(cur_rotation);
    model_mat.translate(cur_position);
    model_mat.rotate(QQuaternion::fromEulerAngles(step * this->animationFactor));
    for (QSharedPointer meta : surfaceMetas)
    {
        QPainterPath path;
        double z;
        bool is_first = true;
        for (const QVector3D &vertex : meta->vertex)
        {
            QVector3D calc_vertex= perspective_mat * view_mat * model_mat * vertex;
            calc_vertex.setY(-calc_vertex.y());
            calc_vertex.setZ(-calc_vertex.z());
            //qDebug()rect(), Qt::black);

    if (image.size().isValid())
        painter.drawImage(0, 0, image);

    //fps统计
    if (fpsTime.elapsed() > 1000)
    {
        fpsTime.restart();
        fpsCounter = fpsTemp;
        fpsTemp = 0;
    }
    else
    {
        fpsTemp++;
    }
    painter.setPen(QPen(Qt::white));
    painter.drawText(10, 30, "FPS:" + QString::number(fpsCounter));
    painter.drawText(10, 50, "Drag Moving ... ...");
}

void Windmill3D::mousePressEvent(QMouseEvent *event)
{
    mousePressed = true;
    mousePos = event->pos();
    QWidget::mousePressEvent(event);
}

void Windmill3D::mouseMoveEvent(QMouseEvent *event)
{
    if (mousePressed)
    {
        QVector2D diff = QVector2D(event->pos()) - QVector2D(mousePos);
        mousePos = event->pos();
        QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
        rotationAxis = (rotationAxis + n).normalized();
        //不能对换乘的顺序
        rotationQuat = QQuaternion::fromAxisAndAngle(rotationAxis, 2.0f) * rotationQuat;

        //update();
        drawImage(width(), height());
    }
    QWidget::mouseMoveEvent(event);
}

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

void Windmill3D::wheelEvent(QWheelEvent *event)
{
    event->accept();
    //fovy越小,模型看起来越大
    if (event->delta() < 0)
    {
        //鼠标向下滑动为-,这里作为zoom out
        projectionFovy += 0.5f;
        if (projectionFovy > 90)
            projectionFovy = 90;
    }
    else
    {
        //鼠标向上滑动为+,这里作为zoom in
        projectionFovy -= 0.5f;
        if (projectionFovy < 1)
            projectionFovy = 1;
    }
    //update();
    drawImage(width(), height());
}

void Windmill3D::resizeEvent(QResizeEvent *event)
{
    if (event->size().isValid())
    {
        const int width = event->size().width();
        const int height = event->size().height();
        drawImage(width, height);
    }
    QWidget::resizeEvent(event);
}

void Windmill3D::initWindmill()
{
    //参照荷兰风车,逆时针旋转,帆布往塔身一侧倾斜
    //四个扇叶
    WindMeta *sub_fan1 = new WindMeta{{QVector3D(0, 0, 0), QVector3D(-250, -250, 0),
            QVector3D(-300, -200, -10), QVector3D(-100, 0, -10)},
            QColor(110, 250, 250, 200)};
    WindMeta *sub_fan2 = new WindMeta{{QVector3D(0, 0, 0), QVector3D(-250, 250, 0),
            QVector3D(-200, 300, -10), QVector3D(0, 100, -10)},
            QColor(130, 250, 250, 200)};
    WindMeta *sub_fan3 = new WindMeta{{QVector3D(0, 0, 0), QVector3D(250, 250, 0),
            QVector3D(300, 200, -10), QVector3D(100, 0, -10)},
            QColor(110, 250, 250, 200)};
    WindMeta *sub_fan4 = new WindMeta{{QVector3D(0, 0, 0), QVector3D(250, -250, 0),
            QVector3D(200, -300, -10), QVector3D(0, -100, -10)},
            QColor(130, 250, 250, 200)};
    auto sub_fanmetas = QList{QSharedPointer(sub_fan1),
                                                       QSharedPointer(sub_fan2),
                                                       QSharedPointer(sub_fan3),
                                                       QSharedPointer(sub_fan4)};
    auto sub_fansubs = QList{};
    WindItem *sub_fanitem = new WindItem{
            QVector3D(0, 400, 150), //相对位置,y400放到顶部,z150贴在墙上
            QVector3D(0, 0, 0), //相对方向
            sub_fanmetas,
            sub_fansubs,
            QVector3D(0, 0, 1)}; //给z加了动画因子,即扇叶在xy平面转

    //风车主干,共9个面,顶部尖塔4+主干4+底面
    //顶部4
    WindMeta *sub_main1 = new WindMeta{{QVector3D(100, 400, 100), QVector3D(-100, 400, 100), QVector3D(0, 500, 0)},
            QColor(250, 0, 0)};
    WindMeta *sub_main2 = new WindMeta{{QVector3D(-100, 400, 100), QVector3D(-100, 400, -100), QVector3D(0, 500, 0)},
            QColor(0, 250, 0)};
    WindMeta *sub_main3 = new WindMeta{{QVector3D(-100, 400, -100), QVector3D(100, 400, -100), QVector3D(0, 500, 0)},
            QColor(0, 0, 250)};
    WindMeta *sub_main4 = new WindMeta{{QVector3D(100, 400, -100), QVector3D(100, 400, 100), QVector3D(0, 500, 0)},
            QColor(250, 250, 0)};
    //主体4
    WindMeta *sub_main5 = new WindMeta{{QVector3D(100, 400, 100), QVector3D(-100, 400, 100),
            QVector3D(-120, 0, 120), QVector3D(120, 0, 120)},
            QColor(205, 150, 100)};
    WindMeta *sub_main6 = new WindMeta{{QVector3D(-100, 400, 100), QVector3D(-100, 400, -100),
            QVector3D(-120, 0, -120), QVector3D(-120, 0, 120)},
            QColor(220, 150, 100)};
    WindMeta *sub_main7 = new WindMeta{{QVector3D(-100, 400, -100), QVector3D(100, 400, -100),
            QVector3D(120, 0, -120), QVector3D(-120, 0, -120)},
            QColor(235, 150, 100)};
    WindMeta *sub_main8 = new WindMeta{{QVector3D(100, 400, -100), QVector3D(100, 400, 100),
            QVector3D(120, 0, 120), QVector3D(120, 0, -120)},
            QColor(250, 150, 100)};
    //底部1
    WindMeta *sub_main9 = new WindMeta{{QVector3D(-120, 0, 120), QVector3D(-120, 0, -120),
            QVector3D(120, 0, -120), QVector3D(120, 0, 120)},
            QColor(200, 150, 0)};

    auto sub_mainmetas = QList{QSharedPointer(sub_main1),
                                                        QSharedPointer(sub_main2),
                                                        QSharedPointer(sub_main3),
                                                        QSharedPointer(sub_main4),
                                                        QSharedPointer(sub_main5),
                                                        QSharedPointer(sub_main6),
                                                        QSharedPointer(sub_main7),
                                                        QSharedPointer(sub_main8),
                                                        QSharedPointer(sub_main9)};
    auto sub_mainsubs = QList{QSharedPointer(sub_fanitem)};
    WindItem *sub_mainitem = new WindItem{
            QVector3D(0, 0, 0), //相对位置
            QVector3D(0, 0, 0), //相对方向
            sub_mainmetas,
            sub_mainsubs};

    //根节点,一个平面,(平面用半透明是为了穿模时看起来没那么别扭)
    WindMeta *root_meta = new WindMeta{{QVector3D(-200, 0, 200), QVector3D(200, 0, 200),
            QVector3D(200, 0, -200), QVector3D(-200, 0, -200)},
            QColor(255, 255, 255, 100)};
    auto root_metas = QList{QSharedPointer(root_meta)};
    auto root_subs = QList{QSharedPointer(sub_mainitem)};
    rootItem = WindItem{
            QVector3D(0, -300, 0), //相对位置,y轴-300相当于放到了底部
            QVector3D(0, 0, 0), //相对方向
            root_metas,
            root_subs,
            QVector3D(0, -0.1f, 0)}; //给y加了动画因子,即柱子在xz平面转
}

void Windmill3D::drawImage(int width, int height)
{
    if (width > 10 && height > 10 && watcher.isFinished())
    {
        QQuaternion rotate = rotationQuat;
        float step = animationStep;
        float fovy = projectionFovy;

        //多线程绘制到image上,绘制完后返回image并绘制到窗口上
        QFuture futures = QtConcurrent::run([this, width, height, rotate, step, fovy]()
        {
            QImage img(width, height, QImage::Format_ARGB32);
            img.fill(Qt::transparent);
            QPainter painter(&img);
            if (!painter.isActive())
                return img;
            painter.fillRect(img.rect(), Qt::black);

            //painter.save();
            //坐标原点移动到中心
            painter.translate(width / 2, height / 2);
            //抗锯齿
            painter.setRenderHint(QPainter::Antialiasing);

            //计算所有的图元顶点路径
            QList surface_metas = rootItem.calcSurfaceMetas(
                        QVector3D(0, 0, 0), rotate, step, fovy);
            //根据z轴排序
            std::sort(surface_metas.begin(), surface_metas.end(),
                      [](const QSharedPointer &left, const QSharedPointer &right)
            {
                return left->z < right->z;
            });
            //根据z值从远处开始绘制图元路径
            for (QSharedPointer meta : surface_metas)
            {
                painter.fillPath(meta->path, meta->color);
            }

            //painter.restore();
            return img;
        });
        watcher.setFuture(futures);
    }
}

关注
打赏
1655829268
查看更多评论
立即登录/注册

微信扫码登录

0.2550s