1. 程式人生 > 程式設計 >Qt使用QPainter繪製3D立方體

Qt使用QPainter繪製3D立方體

本文例項為大家分享了使用QPainter繪製3D立方體的具體程式碼,供大家參考,具體內容如下

1.實現思路

(網上有另一篇類似的,不過他不是用的 Qt 自帶的矩陣運算類)

實現思路有點類似使用 OpenGL 畫立方體,先準備頂點資料:

//立方體前後四個頂點,從右上角開始順時針
vertexArr=QVector<QVector3D>{
 QVector3D{1,1,1},QVector3D{1,-1,QVector3D{-1,-1},-1} };
 
 //六個面,一個麵包含四個頂點
 elementArr=QVector<QVector<int>>{
 {0,2,3},{4,5,6,7},{0,4,{1,2},{2,7,{3,0} };

然後再和旋轉矩陣、透視矩陣進行運算,得到 3D 頂點座標在 2D 平面上的 xy 值。根據頂點 xy 值,得到每個面的路徑,然後繪製表面的路徑。

這裡面比較麻煩的是判斷哪些是表面,單個立方體還好,可以遍歷比較 z 值,如果是多個物體運算量就大了,還是直接 OpenGL 吧,畢竟我這個只是畫著玩的。

2.實現程式碼

程式碼 github 連結

實現效果 GIF 動圖:

Qt使用QPainter繪製3D立方體

主要程式碼:

#ifndef MYCUBE_H
#define MYCUBE_H
 
#include <QWidget>
#include <QMouseEvent>
 
#include <QVector3D>
#include <QMatrix4x4>
 
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<QVector3D> vertexArr;   //八個頂點
  QVector<QVector<int>> elementArr; //六個面
  QMatrix4x4 rotateMat;   //旋轉矩陣
  QPoint mousePos;      //滑鼠位置
  bool mousePressed=false;  //滑鼠按下標誌位
};
 
#endif // MYCUBE_H
#include "MyCube.h"
 
#include <QPainter>
#include <QtMath>
#include <QDebug>
 
MyCube::MyCube(QWidget *parent)
  : QWidget(parent)
{
  //     7------------------4
  //    /         / |
  //   3------------------0  |
  //   |         |  |
  //   |         |  |
  //   |         |  |
  //   |         |  |
  //   |  6       |  5
  //   |         | /
  //   2------------------1
  //立方體前後四個頂點,從右上角開始順時針
  vertexArr=QVector<QVector3D>{
      QVector3D{1,-1} };
 
  //六個面,一個麵包含四個頂點
  elementArr=QVector<QVector<int>>{
  {0,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,-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<QVector3D> vertex_list; //和矩陣運算後的頂點
  QList<int> vertex_max_list; //top頂點在arr的位置
  float vertex_max_value;   //top值
  //根據旋轉矩陣計算每個頂點
  for(int i=0;i<vertexArr.count();i++)
  {
    QVector3D vertex=vertexArr.at(i)*rotateMat*perspective_mat;
    vertex_list.push_back(vertex);
    //找出z值max的頂點
    if(i==0){
      vertex_max_list.push_back(0);
      vertex_max_value=vertex.z();
    }else{
      if(vertex.z()>vertex_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<QPainterPath> element_path_list; //每個面路徑
  QList<float> element_z_values;  //每個面中心點的z值
  QList<QPointF> element_z_points; //每個面中心點在平面對應xy值
  QList<int> element_front_list;  //elementArr中表面的index
  for(int i=0;i<elementArr.count();i++)
  {
 
    const QVector3D vt0=vertex_list.at(elementArr.at(i).at(0));
    const QVector3D vt1=vertex_list.at(elementArr.at(i).at(1));
    const QVector3D vt2=vertex_list.at(elementArr.at(i).at(2));
    const QVector3D vt3=vertex_list.at(elementArr.at(i).at(3));
 
    //單個面的路徑
    QPainterPath element_path;
    element_path.moveTo(getPoint(vt0,cube_width));
    element_path.lineTo(getPoint(vt1,cube_width));
    element_path.lineTo(getPoint(vt2,cube_width));
    element_path.lineTo(getPoint(vt3,cube_width));
    element_path.closeSubpath();
 
    //包含zmax點的就是正交表面可見的
    bool is_front=true;
    for(int vertex_index:vertex_max_list){
      if(!elementArr.at(i).contains(vertex_index)){
        is_front=false;
        break;
      }
    }
    if(is_front){
      element_front_list.push_back(i);
    }
    element_path_list.push_back(element_path);
    element_z_values.push_back((vt0.z()+vt2.z())/2);
    element_z_points.push_back((getPoint(vt0,cube_width)+getPoint(vt2,cube_width))/2);
  }
 
  //遠小近大,還要把包含max但是被近大遮蓋的去掉
  QList<int> element_front_remove;
  for(int i=0;i<element_front_list.count();i++)
  {
    for(int j=0;j<element_front_list.count();j++)
    {
      if(i==j)
        continue;
      const int index_i=element_front_list.at(i);
      const int index_j=element_front_list.at(j);
      if(element_z_values.at(index_i)>element_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,Qt::DashLine));
  for(int i=0;i<element_path_list.count();i++){
    if(element_front_list.contains(i))
      continue;
    painter.drawPath(element_path_list.at(i));
  }
  //畫表面邊框
  painter.setPen(QPen(Qt::black,2));
  for(auto index:element_front_list){
    painter.drawPath(element_path_list.at(index));
  }
  painter.restore();
 
  painter.drawText(20,30,"Drag Moving");
}
 
void MyCube::mousePressEvent(QMouseEvent *event)
{
  mousePressed=true;
  mousePos=event->pos();
  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));
    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 };
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。