Qt趣味开发之打造一个3D名字渲染小工具
作者:互联网
本篇文章讲介绍三个软件:
- 名字数据生成工具
- OpenGL名字渲染工具
- CPU名字渲染工具
这三个小东西一共花了我三天时间,接下来分别看一下这是三个软件的显示效果:
- 名字数据生成工具 ,点击Create可以生成名字数据(本人不姓李哈^v^)
- OpenGL名字渲染工具 , 使用Opengl渲染成3D图像,点击Load加载之前生成的数据
- 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颜色数据
目前的代码中存在的问题:
- 有些凹多边形分割有问题
- 三角形顶点的顺序可能不正确
- 因为顶点顺序不对,导致的法线计算的方向存在反向的问题
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)投影
关于投影,我这边与OpenGL的做法是一样的,使用的投影矩阵(视锥体),一个世界坐标系转换成屏幕坐标需要两步
- 将世界坐标转换成标准设备坐标
- 标准设备坐标转换为屏幕坐标
关键代码如下:
// 处理世界坐标系坐标转化为屏幕坐标
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;
}
- 将模型坐标的点,经过乘以模型矩阵,得到世界坐标
- 再将世界坐标 * 视图矩阵 * 投影矩阵 等到标准设备坐标系中的点(取值在[0,1])
- 再将设备坐标转换成屏幕坐标,代码如下
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;
}
(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
标签:include,polygon,void,pos,iter,QVector3D,趣味,3D,Qt 来源: https://blog.csdn.net/douzhq/article/details/110262105