Qt趣味开发之打造一个3D名字渲染小工具

本篇文章讲介绍三个软件:

  • 名字数据生成工具
  • OpenGL名字渲染工具
  • CPU名字渲染工具

这三个小东西一共花了我三天时间,接下来分别看一下这是三个软件的显示效果:

1. 名字数据生成工具 ,点击Create可以生成名字数据(本人不姓李哈^v^)

2. OpenGL名字渲染工具 , 使用Opengl渲染成3D图像,点击Load加载之前生成的数据

3. CPU名字渲染工具 , 接下来是使用CPU实现的渲染同一个数据,显示效果如下:


1. 名字数据生成工具

名字数据生成工具主要的功能就是生成数据,他主要实现了下面几个功能:

  • 鼠标点击创建多个或多边形
  • 将一个或多个多边形分割成三角形
  • 将2D的多边形变成3D的多边形
  • 生成数据(定点数据、法线、颜色数据)

当点击鼠标左键创建多边形的点,点击鼠标右键实现三角形的填充(不知道为什么,gif录制软件会使窗口丢掉移动时候的效果)

其中鼠标点击和绘制相关的代码,如下:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPolygon>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();

    void getCurrentPolygons(QVector<QPolygon>& points);

    void cleanAll(void);

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

    void paintEvent(QPaintEvent* event) override;

private:
    QPoint m_startPos;
    bool m_isDrawing = false;

    QVector<QPolygon> m_points;
    QVector<QPolygon> m_points2;
};

#endif // WIDGET_H
#include "widget.h"
#include <QMouseEvent>
#include <QPainter>
#include <QDebug>
#include "PolygonTool.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    this->setMouseTracking(true);
}

Widget::~Widget()
{

}

void Widget::mousePressEvent(QMouseEvent* event)
{
    QPoint pos = event->pos();

    if (event->button() == Qt::LeftButton)
    {
        if (!m_isDrawing)
        {
            QPolygon polygon;
            m_isDrawing = true;

            polygon.append(pos);
            polygon.append(pos);
            m_points.append(polygon);
        }
        else {
            int nCount = m_points.size();
            int nSize = m_points[nCount - 1].count();

            m_points[nCount - 1].replace(nSize - 1, pos);
            m_points[nCount - 1].append(pos);
        }

    }
    else if (event->button() == Qt::RightButton)
    {
        m_isDrawing = false;

        int nCount = m_points.size();
        int nSize = m_points[nCount - 1].count();

        m_points[nCount - 1].replace(nSize - 1, pos);

        // 更新填充数据
        m_points2 += g_PolygonTool->getTrianglePolygons(m_points[nCount - 1]);
        this->update();
    }


    qDebug() << m_points.size() << m_points;

    return QWidget::mousePressEvent(event);
}

void Widget::mouseMoveEvent(QMouseEvent* event)
{
    if (m_isDrawing)
    {
        int nCount = m_points.count();

        QPoint pos = event->pos();

        int nSize = m_points[nCount - 1].count();
        m_points[nCount - 1].replace(nSize - 1, pos);

        this->update();
    }

    return QWidget::mouseMoveEvent(event);
}

void Widget::mouseReleaseEvent(QMouseEvent* event)
{
    return QWidget::mouseReleaseEvent(event);
}

void Widget::paintEvent(QPaintEvent* event)
{
    QPainter painter(this);
    painter.fillRect(this->rect(), QBrush(QColor(150, 150, 150)));

    QPen nPen;
    nPen.setColor(QColor(255, 0, 0));
    painter.setPen(nPen);
    painter.setBrush(QColor(0, 200, 200, 80));

    // 绘制矩形
    for (auto iter = m_points.begin(); iter != m_points.end(); ++iter)
    {
        painter.drawPolygon(*iter);
    }

    // 绘制分割三角形
    nPen.setColor(QColor(0, 255, 255));
    nPen.setWidth(4);
    painter.setPen(nPen);
    painter.setBrush(QColor(0, 0, 255));
    for (auto iter = m_points2.begin(); iter != m_points2.end(); ++iter)
    {
        painter.drawPolygon(*iter);
    }

    return QWidget::paintEvent(event);
}

void Widget::cleanAll(void)
{
    m_points.clear();
    m_points2.clear();
    this->update();
}

void Widget::getCurrentPolygons(QVector<QPolygon>& points)
{
    points = m_points;
}

其中 g_PolygonTool->getTrianglePolygons() 会将一个多边形分割成多个三角形。
关于多边形分割个成多个三角形,可以参考如下链接:
判断点是否在三角形内 和 点是否在矩形内
多边形构建三角形

实现的代码如下(按照上面的链接的做法,有时候分割凹多边形会有问题):

头文件

#ifndef POLYGONTOOL_H
#define POLYGONTOOL_H

#include <QObject>
#include <QPolygon>
#include <QVector>
#include <QVector3D>

#define g_PolygonTool PolygonTool::getInstance()

class PolygonTool : public QObject
{
    Q_OBJECT

public:
    static PolygonTool* getInstance(void);

    // 获取多边形-三角形数组
    QVector<QPolygon> getTrianglePolygons(const QPolygon& polygon);

private:
    PolygonTool(QObject* parent = nullptr);
    ~PolygonTool();

    // 获取凹角的索引
    int getConcaveIndex(const QPolygon& polygon);
    // 判断一个点是否在一个三角形中
    bool isInTriangle(QPolygon triangle, QPoint pos);
    // 获取凸多边形的三角形数组
    QVector<QPolygon> getConvexPolygonVec(const QPolygon& polygon);
    // 获取凹多边形的三角形数组
    QVector<QPolygon> getConcavePolygonVec(const QPolygon& polygon, int index);
};

#endif

cpp文件

#include "PolygonTool.h"
#include <QDebug>

PolygonTool::PolygonTool(QObject* parent)
    :QObject(parent)
{

}

PolygonTool::~PolygonTool()
{

}

PolygonTool* PolygonTool::getInstance(void)
{
    static PolygonTool instance;
    return &instance;
}

QVector<QPolygon> PolygonTool::getTrianglePolygons(const QPolygon& polygon)
{
    QVector<QPolygon> result;

    int index = this->getConcaveIndex(polygon);
    if (index >= 0){
        qDebug() << "ao Polygon" << index;

        result = this->getConcavePolygonVec(polygon, index);
    }
    else {
        qDebug() << "tu Polygon";

        result = this->getConvexPolygonVec(polygon);
    }

    return result;
}

int PolygonTool::getConcaveIndex(const QPolygon& polygon)
{
    for (int i = 0; i<polygon.size(); ++i)
    {
        QPoint pos1 = polygon[i];
        QPoint pos2;
        QPoint pos3;
        if (i == polygon.size() - 1)
        {
            pos2 = polygon[0];
            pos3 = polygon[1];
        }
        else if (i == polygon.size() - 2){
            pos2 = polygon[i + 1];
            pos3 = polygon[0];
        }
        else {
            pos2 = polygon[i + 1];
            pos3 = polygon[i + 2];
        }

        QVector3D vec1(pos2.x() - pos1.x(), pos2.y() - pos1.y(), 1.0);
        QVector3D vec2(pos3.x() - pos2.x(), pos3.y() - pos2.y(), 1.0);
        QVector3D destVec = QVector3D::crossProduct(vec1, vec2);
        if (destVec.z() < 0)
        {
            int index = i + 1;
            if (index == polygon.size())
                index = 0;

            return index;
        }
    }

    return -1;
}

QVector<QPolygon> PolygonTool::getConvexPolygonVec(const QPolygon& polygon)
{
    QPolygon tempPolygon = polygon;
    QVector<QPolygon> result;

    while (tempPolygon.size() > 3)
    {
        QPolygon polygon;
        polygon << tempPolygon[0] << tempPolygon[1] << tempPolygon[2];
        result << polygon;

        tempPolygon.removeAt(1);
    }
    result << tempPolygon;

    return result;
}

bool PolygonTool::isInTriangle(QPolygon triangle, QPoint pos)
{
    auto func = [&](QPoint A, QPoint B, QPoint C, QPoint P){
        QVector3D vecAB(B.x() - A.x(), B.y() - A.y(), 1.0);
        QVector3D vecAC(C.x() - A.x(), C.y() - A.y(), 1.0);
        QVector3D vecAP(P.x() -A.x(), P.y() - A.y(), 1.0);

        QVector3D v1 = QVector3D::crossProduct(vecAB, vecAC);
        QVector3D v2 = QVector3D::crossProduct(vecAB, vecAP);

        return QVector3D::dotProduct(v1, v2) >= 0;
    };

    return func(triangle[0], triangle[1], triangle[2], pos) && \
           func(triangle[1], triangle[2], triangle[0], pos) && \
           func(triangle[2], triangle[0], triangle[1], pos);
}

QVector<QPolygon> PolygonTool::getConcavePolygonVec(const QPolygon& polygon, int index)
{
    QPolygon trianglePolygon;

    int pos2Index = (index - 1 + polygon.size()) % polygon.size();
    int pos3Index = (index - 2 + polygon.size()) % polygon.size();

    // 向上取三个点
    QPoint pos1 = polygon[index];
    QPoint pos2 = polygon[pos2Index];
    QPoint pos3 = polygon[pos3Index];

    trianglePolygon << pos1 << pos2 << pos3;

    // 判断三角形中是否包括多边形的点
    bool hasIncluded = false;
    for (int i = 0; i != polygon.size(); ++i)
    {
        if (i == index || i == pos2Index || i == pos3Index)
            continue;

        if (isInTriangle(trianglePolygon, polygon[i]))
        {
            hasIncluded = true;
            break;
        }
    }

    // 包括,向下取点
    if (hasIncluded)
    {
        pos2Index = (index + 1 + polygon.size()) % polygon.size();
        pos3Index = (index + 2 + polygon.size()) % polygon.size();
        trianglePolygon.clear();

        trianglePolygon << pos1 << polygon[pos2Index] << polygon[3];
    }

    // 分割多边形
    QPolygon tempPolygon = polygon;
    QVector<QPolygon> result;

    // 添加三角形并删除中间的点
    result << trianglePolygon;
    tempPolygon.remove(pos2Index);

    if (tempPolygon.size() == 3)
        result << tempPolygon;              // 剩余三个点
    else
    {
        int index = getConcaveIndex(tempPolygon);
        if (index < 0)
        {
            result += getConvexPolygonVec(tempPolygon);    // 添加凸多边形的结果
        }
        else {
            result += getConcavePolygonVec(tempPolygon, index);   // 添加凹多边形的结果
        }
    }

    return result;
}

接下来就是生成顶点数据了,我这里是将2D的每个多边形都扩展了他的Z坐标

正面:多边形分割为多个三角形
背面:背面的顶点与正面的顶点是相同的
侧面:正面的相邻两个点和对应的背面的两个点,构成一个新的多边形,将其再分割为三角形

完整代码如下:

头文件:

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>
#include <QVector3D>
#include "widget.h"

class MyWidget : public QWidget
{
public:
    MyWidget(QWidget* parent = nullptr);
    ~MyWidget();

private:
    Widget* m_pWidget = nullptr;
    QString pointToString(QVector3D pos);
    QString colorToString(QColor color);
    QString polygonToString(QPolygon points, float zValue, QColor color);

    // 获取法线向量
    QVector3D getNormalVec(QVector3D aPos, QVector3D bPos, QVector3D cPos);

    float m_frontInterval = 1.0f;
    float m_frontInterval2 = 1.3f;

private slots:
    void onClicedButton(void);
    void onClickedClean(void);

private:
};

#endif

cpp文件:

#include "MyWidget.h"
#include <QVBoxLayout>
#include <QPushButton>
#include <QFileDialog>
#include <QTextStream>
#include <QMessageBox>
#include "PolygonTool.h"

MyWidget::MyWidget(QWidget* parent)
    :QWidget(parent)
{
    m_pWidget = new Widget;
    m_pWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    QVBoxLayout* mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(m_pWidget);

    QWidget* pButtonWidget = new QWidget;
    mainLayout->addWidget(pButtonWidget);
    QHBoxLayout* pLayout = new QHBoxLayout(pButtonWidget);

    QPushButton* pButton = new QPushButton(tr("Create"));
    pLayout->addWidget(pButton);
    QObject::connect(pButton, &QPushButton::clicked, this, &MyWidget::onClicedButton);

    QPushButton* pCleanButon = new QPushButton(tr("Clean"));
    pLayout->addWidget(pCleanButon);
    QObject::connect(pCleanButon, &QPushButton::clicked, this, &MyWidget::onClickedClean);
}

MyWidget::~MyWidget()
{

}

void MyWidget::onClicedButton(void)
{
    QString fileName = QFileDialog::getSaveFileName(this, "Get File Name", "./");
    QFile nFile(fileName);
    if (nFile.exists())
        QFile::remove(fileName);

    nFile.open(QFile::ReadWrite);
    QTextStream stream(&nFile);

    QVector<QPolygon> polygons;
    m_pWidget->getCurrentPolygons(polygons);

    for (auto iter = polygons.begin(); iter != polygons.end(); ++iter)
    {
        // 正面
        stream << polygonToString(*iter, m_frontInterval, QColor(200, 100, 100));

        // 后面
        stream << polygonToString(*iter, m_frontInterval2, QColor(0, 150, 150));

        // 侧面
        for (int i=0; i<iter->size(); ++i)
        {
            QStringList strList;
            strList.clear();

            QVector3D pos1(iter->at(i).x(), iter->at(i).y(), m_frontInterval);
            QVector3D pos2(iter->at(i).x(), iter->at(i).y(), m_frontInterval2);
            QVector3D pos3;
            QVector3D pos4;
            if (iter->size() - 1 == i)
            {
                pos3 = QVector3D(iter->at(0).x(), iter->at(0).y(), m_frontInterval2);
                pos4 = QVector3D(iter->at(0).x(), iter->at(0).y(), m_frontInterval);
            }
            else
            {
                pos3 = QVector3D(iter->at(i+1).x(), iter->at(i+1).y(), m_frontInterval2);
                pos4 = QVector3D(iter->at(i+1).x(), iter->at(i+1).y(), m_frontInterval);
            }

            // 第一个三角形
            strList << pointToString(pos1);
            strList << pointToString(pos2);
            strList << pointToString(pos3);
            strList << pointToString(getNormalVec(pos1, pos2, pos3));
            strList << colorToString(QColor(180, 180, 180));
            QString destString = strList.join(";");
            destString += "\n\r";
            stream << destString;

            // 第二个三角形
            strList.clear();
            strList << pointToString(pos1);
            strList << pointToString(pos3);
            strList << pointToString(pos4);
            strList << pointToString(getNormalVec(pos1, pos3, pos4));
            strList << colorToString(QColor(180, 180, 180));
            destString = strList.join(";");
            destString += "\n\r";
            stream << destString;

        }
    }

    nFile.close();
    QMessageBox::warning(this, "Successed", "Write Successed");
}

void MyWidget::onClickedClean(void)
{
    m_pWidget->cleanAll();
}

QString MyWidget::pointToString(QVector3D pos)
{
    QString str = "%1,%2,%3";
    QPointF npos;
    npos.setX(pos.x() * 1.0 / m_pWidget->width() - 0.5);
    npos.setY(pos.y() * 1.0 / m_pWidget->height() - 0.5);

    str = str.arg(npos.x()).arg(-npos.y()).arg(pos.z());

    return str;
}

QString MyWidget::colorToString(QColor color)
{
    // 颜色
    QString colorString = "%1,%2,%3";
    colorString = colorString.arg(color.red()).arg(color.green()).arg(color.blue());

    return colorString;
}

QString MyWidget::polygonToString(QPolygon points, float zValue, QColor color)
{
    QVector<QPolygon> vector = g_PolygonTool->getTrianglePolygons(points);
    QString destString;

    for (auto iter = vector.begin(); iter != vector.end(); ++iter)
    {
        QStringList strList;

        // 三角形 - 点1
        QVector3D aPos(iter->at(0).x(), iter->at(0).y(), zValue);
        strList << pointToString(aPos);

        // 三角形 - 点2
        QVector3D bPos = QVector3D(iter->at(1).x(), iter->at(1).y(), zValue);
        strList << pointToString(bPos);

        // 三角形 - 点3
        QVector3D cPos = QVector3D(iter->at(2).x(), iter->at(2).y(), zValue);
        strList << pointToString(cPos);

        // 法线
        QVector3D normalPos = getNormalVec(aPos, bPos, cPos);
        strList << pointToString(normalPos);

        // 颜色
        strList << colorToString(color);

        destString += strList.join(";");
        destString += "\n\r";
    }

    return destString;
}

// 获取法线向量
QVector3D MyWidget::getNormalVec(QVector3D aPos, QVector3D bPos, QVector3D cPos)
{
    QVector3D ab = bPos - aPos;
    QVector3D ac = cPos - aPos;

    return QVector3D::crossProduct(ab, ac);
}

函数 onClicedButton 会生成顶点数据文件。文件格式如下:

这里每一行为一个三角形的数据:
前三个表示三角形的顶点数据,(x, y z) 坐标
接下来的数据为法线,(通过向量叉乘就可以得到法线)
最后为颜色数据,使用0~255的RGB颜色数据

目前的代码中存在的问题:

  1. 有些凹多边形分割有问题
  2. 三角形顶点的顺序可能不正确
  3. 因为顶点顺序不对,导致的法线计算的方向存在反向的问题

2. OpenGL名字渲染工具

OpenGL渲染就比较简单了,将顶点数据传到一个VBO中,然后显示出来。

  • 通过鼠标左键操作,修改模型矩阵的值实现旋转的效果。
  • 通过鼠标滚轮,修改眼睛的位置,实现将物体拉进或者拉远。

完整代码如下:

头文件:

#ifndef OPENGLRENDERWIDGET_H
#define OPENGLRENDERWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions_2_0>
#include <QOpenGLFunctions_3_3_Core>
#include <QMatrix4x4>

class OpenglRenderWidget : public QOpenGLWidget, public QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    struct VertexAttributeData
    {
        // Postion
        float pos[3];
        float normal[3];

        float color[3];
    };

public:
    OpenglRenderWidget(QWidget* parent = nullptr);
    ~OpenglRenderWidget();

    void setPoints(const QVector<QVector<QVector3D>>& points);

protected:
    void initializeGL() override;
    void resizeGL(int w, int h) override;
    void paintGL() override;

    void wheelEvent(QWheelEvent * event) override;

    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;

private:
    bool initShaderProgram(void);

    GLuint m_shaderProgramId;
    QOpenGLShaderProgram* m_pShaderProgram = nullptr;
    QOpenGLShader* m_pVertexShader = nullptr;
    QOpenGLShader* m_pFragmentShader = nullptr;

    GLuint m_nVBOId;

    // Attribute Location
    GLint m_nPosAttrLocationId;
    GLint m_nNormalLocationId;
    GLint m_nColorAttrLocationId;

    GLint m_mLocalMat;
    GLint m_vLocalMat;
    GLint m_pLocalMat;
    GLint m_nmLocalMat;     // 法线矩阵,模型矩阵的逆矩阵的转置

    // Model Matrix
    QMatrix4x4 m_modelMatrix;
    QMatrix4x4 m_projectionMatrix;
    QMatrix4x4 m_viewMatrix;
    QMatrix4x4 m_nmMatrix;

    QVector3D m_eysPostion;
    qreal m_rotate = 0;

    QVector<QVector<QVector3D>> m_polygonF;
    QVector<VertexAttributeData> m_vertexData;

    bool m_isPressed = false;
    QPoint m_pressedPos;

private:
    // 光相关
    QVector3D m_lightPoint;     // 光源的位置(世界坐标)
    QVector3D m_lightColor;     // 光源颜色

    GLint m_lightPointLocal;
    GLint m_lightColorLocal;
};

#endif

cpp文件:

#include "OpenGLPolygonRender.h"
#include <QDebug>
#include <QMatrix4x4>
#include <QTimer>
#include <QWheelEvent>
#include <QTime>
#include <ctime>

OpenglRenderWidget::OpenglRenderWidget(QWidget* parent)
    :QOpenGLWidget(parent)
    , m_eysPostion(0, 0, 1)
    , m_lightPoint(20, 20, -20)
    , m_lightColor(1.0f, 1.0f, 1.0f)
{
    QTimer* pTimer = new QTimer(this);
    QObject::connect(pTimer, &QTimer::timeout, [&]{
        this->update();
    });
    pTimer->setInterval(30);
    pTimer->start();
}

OpenglRenderWidget::~OpenglRenderWidget()
{

}

void OpenglRenderWidget::initializeGL()
{
    this->initializeOpenGLFunctions();

    // 初始化GPU程序
    bool result = initShaderProgram();
    if (!result)
        return;

    m_shaderProgramId = m_pShaderProgram->programId();
    // 获取位置和颜色的locationID
    m_nPosAttrLocationId = glGetAttribLocation(m_shaderProgramId, "pos");
    m_nNormalLocationId = glGetAttribLocation(m_shaderProgramId, "normal");
    m_nColorAttrLocationId = glGetAttribLocation(m_shaderProgramId, "color");

    m_mLocalMat = glGetUniformLocation(m_shaderProgramId, "M");
    m_vLocalMat = glGetUniformLocation(m_shaderProgramId, "V");
    m_pLocalMat = glGetUniformLocation(m_shaderProgramId, "P");
    m_nmLocalMat = glGetUniformLocation(m_shaderProgramId, "NM");

    // 光相关
    m_lightPointLocal = glGetUniformLocation(m_shaderProgramId, "LightPos");
    m_lightColorLocal = glGetUniformLocation(m_shaderProgramId, "LightColor");
}

void OpenglRenderWidget::resizeGL(int w, int h)
{
    this->glViewport(0, 0, w, h);

    return QOpenGLWidget::resizeGL(w, h);
}

void OpenglRenderWidget::paintGL()
{
//    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glEnable(GL_DEPTH_TEST);
    glClearColor(51.0f / 255.0f, 76.0f / 255.0f, 76.0f / 255.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 使用shader
    m_pShaderProgram->bind();


//    m_rotate += 1;
    m_modelMatrix.setToIdentity();
    m_projectionMatrix.setToIdentity();
    m_viewMatrix.setToIdentity();

    m_viewMatrix.lookAt(m_eysPostion, QVector3D(0, 0, -50), QVector3D(0, 1.0, 0));
    glUniformMatrix4fv(m_vLocalMat, 1, GL_FALSE, m_viewMatrix.data());

    m_projectionMatrix.perspective(45.0f, this->width() * 1.0 / this->height(), 1.0f, 100.0f);
    glUniformMatrix4fv(m_pLocalMat, 1, GL_FALSE, m_projectionMatrix.data());

    glBindBuffer(GL_ARRAY_BUFFER, m_nVBOId);

    // 设置模型矩阵
    m_modelMatrix.translate(0, 0, -30);
    m_modelMatrix.rotate(m_rotate, QVector3D(0, 1, 0));
    glUniformMatrix4fv(m_mLocalMat, 1, GL_FALSE, m_modelMatrix.data());

    // 设置法线矩阵
    m_nmMatrix = m_modelMatrix.inverted().transposed();
    glUniformMatrix4fv(m_nmLocalMat, 1, GL_FALSE, m_nmMatrix.data());

    // 设置点光源的位置和颜色信息
    float lightPosData[3] = {m_lightPoint.x(), m_lightPoint.y(), m_lightPoint.z()};
    glUniform3fv(m_lightPointLocal, 1, lightPosData);
    float lightColorData[3] = {m_lightColor.x(), m_lightColor.y(), m_lightColor.z()};
    glUniform3fv(m_lightColorLocal, 1, lightColorData);

    // 绘制三角形
    int nIndex = 0;
    for (int i=0; i<m_polygonF.size(); ++i)
    {
        int count = m_polygonF[i].size() - 1;
        glDrawArrays(GL_TRIANGLES, nIndex, count);
        nIndex += count;
    }

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    m_pShaderProgram->release();
}

void OpenglRenderWidget::wheelEvent(QWheelEvent * event)
{
    qreal value = event->delta() / 100.0;
    qreal zValue = m_eysPostion.z() - value;
    m_eysPostion.setZ(zValue);
    qDebug() << m_eysPostion;

    this->update();
}

void OpenglRenderWidget::mousePressEvent(QMouseEvent* event)
{
    m_isPressed = true;
    m_pressedPos = event->pos();
}

void OpenglRenderWidget::mouseMoveEvent(QMouseEvent* event)
{
    if (m_isPressed)
    {
        int interval = event->pos().x() - m_pressedPos.x();
        m_pressedPos = event->pos();
        m_rotate += interval * 1.0 / 50;
        this->update();
    }
}

void OpenglRenderWidget::mouseReleaseEvent(QMouseEvent* event)
{
    m_isPressed = false;
}

bool OpenglRenderWidget::initShaderProgram(void)
{
    m_pShaderProgram = new QOpenGLShaderProgram(this);

    // 加载顶点着色器
    QString vertexShaderStr(":/vertexshader.vert");
    m_pVertexShader = new QOpenGLShader(QOpenGLShader::Vertex, this);
    bool result = m_pVertexShader->compileSourceFile(vertexShaderStr);
    if (!result)
    {
        qDebug() << m_pVertexShader->log();
        return false;
    }

    // 加载片段着色器
    QString fragmentShaderStr(":/fragmentshader.frag");
    m_pFragmentShader = new QOpenGLShader(QOpenGLShader::Fragment, this);
    result = m_pFragmentShader->compileSourceFile(fragmentShaderStr);
    if (!result)
    {
        qDebug() << m_pFragmentShader->log();
        return false;
    }

    // 创建ShaderProgram
    m_pShaderProgram = new QOpenGLShaderProgram(this);
    m_pShaderProgram->addShader(m_pVertexShader);
    m_pShaderProgram->addShader(m_pFragmentShader);
    return m_pShaderProgram->link();
}

void OpenglRenderWidget::setPoints(const QVector<QVector<QVector3D>>& points)
{
    m_polygonF = points;
    m_vertexData.clear();

    for (auto iter = points.begin(); iter != points.end(); ++iter)
    {
        // 获取颜色值
        QVector<QVector3D> pointVec = *iter;
        QColor color((int)pointVec[4].x(), (int)pointVec[4].y(), (int)pointVec[4].z());
        pointVec.pop_back();

        // 获取法线
        QVector3D normalVec = pointVec[3];

        foreach (QVector3D pos, pointVec)
        {
            VertexAttributeData nData;
            nData.pos[0] = pos.x();
            nData.pos[1] = pos.y();
            nData.pos[2] = pos.z();

            nData.normal[0] = normalVec.x();
            nData.normal[1] = normalVec.y();
            nData.normal[2] = normalVec.z();

            nData.color[0] = color.redF();
            nData.color[1] = color.greenF();
            nData.color[2] = color.blueF();

            m_vertexData.append(nData);
        }
    }

    this->makeCurrent();

    int totalSize = m_vertexData.size() * sizeof(VertexAttributeData);

    // 创建VBO
    glGenBuffers(1, &m_nVBOId);

    // 初始化VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_nVBOId);
    glBufferData(GL_ARRAY_BUFFER, totalSize, m_vertexData.data(), GL_STATIC_DRAW);

    // 设置顶点信息属性指针
    glVertexAttribPointer(m_nPosAttrLocationId, 3, GL_FLOAT, GL_FALSE, sizeof(VertexAttributeData), (void*)0);
    glEnableVertexAttribArray(m_nPosAttrLocationId);
    // 设置法线信息属性指针
    glVertexAttribPointer(m_nNormalLocationId, 3, GL_FLOAT, GL_FALSE, sizeof(VertexAttributeData), (void*)(sizeof (float) * 3));
    glEnableVertexAttribArray(m_nNormalLocationId);
    // 设置原色信息属性指针
    glVertexAttribPointer(m_nColorAttrLocationId, 3, GL_FLOAT, GL_FALSE, sizeof(VertexAttributeData), (void*)(sizeof (float) * 6));
    glEnableVertexAttribArray(m_nColorAttrLocationId);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    this->update();
}

VertexShader

#version 330

attribute highp vec3 color;
attribute highp vec3 pos;
attribute highp vec3 normal;

varying vec3 M_Color;
varying vec3 M_Normal;
varying vec3 M_Postion;

uniform mat4 M;
uniform mat4 V;
uniform mat4 P;
uniform mat4 NM;

void main(void)
{
    gl_Position = P * V * M * vec4(pos, 1.0);
    M_Color = color;

    M_Postion = mat3(M) * pos;

    M_Normal = mat3(NM) * normal;
}

FragmentShader

varying vec3 M_Color;
varying vec3 M_Normal;
varying vec3 M_Postion;

uniform vec3 LightPos;
uniform vec3 LightColor;

void main(void)
{
    // 环境光
    vec3 ambient = 0.1 * LightColor;

    // 漫反射
    vec3 norvec = normalize(M_Normal);      // 法线,单位向量
    vec3 lightDir = LightPos - M_Postion;   // 从光源到点的向量
    lightDir = normalize(lightDir);
    vec3 diffuse = max(abs(dot(norvec, lightDir)), 0.0) * LightColor;

    vec3 result = (ambient + diffuse) * M_Color;
//    vec3 result = M_Color;
    gl_FragColor = vec4(result, 1.0);
}

因为我这里的法线方向可能不准确,所以再计算漫反射的时候,我取了绝对值。


3. CPU名字渲染工具

使用CPU渲染,就要考虑两件事

  1. 投影
  2. 面消隐算法
3.1 投影

关于投影,我这边与OpenGL的做法是一样的,使用的投影矩阵(视锥体),一个世界坐标系转换成屏幕坐标需要两步

  1. 将世界坐标转换成标准设备坐标
  2. 标准设备坐标转换为屏幕坐标

关键代码如下:

// 处理世界坐标系坐标转化为屏幕坐标
QVector3D SoftRenderContext::disposeWorldToScreen(const QVector3D& worldPos)
{
    // 标准化设置坐标空间
    QVector3D ndcPos = m_projectionMatrix * m_viewMatrix * m_modeMatrix * worldPos;
//    qDebug() << "ndc Pos :" << worldPos << "; " << ndcPos;

    // 标准设备空间转成屏幕坐标
    QVector3D viewPos = toViewPort(ndcPos);
//    qDebug() << "view Pos :" << viewPos;

    return viewPos;
}
  1. 将模型坐标的点,经过乘以模型矩阵,得到世界坐标
  2. 再将世界坐标 * 视图矩阵 * 投影矩阵 等到标准设备坐标系中的点(取值在[0,1])
  3. 再将设备坐标转换成屏幕坐标,代码如下
QVector3D SoftRenderContext::toViewPort(const QVector3D& pos)
{
    QMatrix4x4 mat;

    mat.scale(m_nWidth * 1.0 / 2, m_nHeight * 1.0 / 2, 1.0f);
    mat.translate(1.0f, 1.0f, 0.0f);

//    qDebug() << mat;
    //mat.translate(0.0f, 1.0f, 0.0f);

    QVector3D tempPos = pos;
    tempPos.setY(-1.0 * tempPos.y());
    return mat * tempPos;
}

3.2 面消隐算法

常见的面消隐算法有很多,有 Z-Buffer算法 、 画家算法 、BSP树算法

这里我们用的是 Z-Buffer算法 这种算法最简单,但是效率不高。
遍历窗口的每一个像素,判断是否与多边形相交,如果相交则取Z值最小的颜色。

这种算法的缺点就是渲染速度太慢(比乌龟还慢)

具体细节可参考这篇文章
https://blog.csdn.net/Jurbo/article/details/75007260
https://www.cnblogs.com/wkfvawl/p/11911441.html

关键代码如下:

void SoftRenderContext::render(QPainter* painter)
{
    m_isRendering = true;
    QRectF rect(0, 0, m_nWidth, m_nHeight);
    painter->fillRect(rect, QBrush(QColor(120, 120, 120)));

    painter->setPen(m_color);

    // 先转化 成屏幕坐标
    converToViewPos();
    if (m_destTriangleData.size() <= 0){
        m_isRendering = false;
        emit renderFinised();
        return;
    }

    qDebug() << "Start Render...";

    int number = 0;
    for (int x = 0; x<m_nWidth; ++x)
    {
        for (int y = 0; y < m_nHeight; ++y)
        {
            int value = number * 1.0 / (m_nWidth * m_nHeight) * 100;
            number++;
            updateRenderProgress(value);

            IntersectRayTriangle::Point p1(x, y, 20000);
            IntersectRayTriangle::Point p2(x, y, -20000);

            // 设置射线,平行于Z轴
            IntersectRayTriangle::Ray ray(p1, p2);

            float zIndex = 8000000;
            bool needShow = false;      // 是否显示
            QColor color;               // 颜色

            // 遍历所有的三角形, Z-Buffer (改进版本)
            for (int i=0; i<m_srcTriangleData.size(); ++i)
            {
                IntersectRayTriangle::Point result;

                // 获取处理后的三角形
                IntersectRayTriangle::Triangle triangle = m_destTriangleData[i];

                // 判断是否穿透该三角形
                int isIntersec = IntersectRayTriangle::testIntersectRayTriangle(ray, triangle, &result);
                if (isIntersec == 1){
                    needShow = true;
                    if (result.z < zIndex)
                    {
                        QColor c;
                        c.setRedF(m_srcTriangleData[i].p1.color[0]);
                        c.setGreenF(m_srcTriangleData[i].p1.color[1]);
                        c.setBlueF(m_srcTriangleData[i].p1.color[2]);

                        color = c;
                        zIndex = result.z;
                    }
                }
            }

            if (needShow){
                painter->setPen(color);
                painter->drawPoint(QPointF(x, y));
            }
        }
    }

    m_isRendering = false;
    emit renderFinised();
}

完整代码下载:
链接:https://pan.baidu.com/s/1wzlXdAoIu58IGLsqjPfh4w
提取码:zd78

不会飞的纸飞机
扫一扫二维码,了解我的更多动态。

下一篇文章:圣诞节到了!!你的桌面下雪了吗?? - Qt趣味开发之让你的桌面下雪