写在开头:文章是基于变换 - LearnOpenGL CN 教程的学习记录,强烈建议在网站上先弄清楚原理再看此文章。以Qt-GL窗口代替GLFW的写法,Qt库中一些类代替教程中的类,一起入坑。
效果图:
教程里从2d向量到3d向量开始慢慢的补一些数学概念,很多人应该都有此基础,即使全忘记了,也不要紧教程教的向量知识简单明了暂时够用。
接下来就是矩阵的基本知识,相加相减相乘。相加相减简单,相乘却需要注意几点:
- 只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘。
- 矩阵相乘不遵守交换律(Commutative),也就是说A⋅B≠B⋅AA⋅B≠B⋅A。
- 结果矩阵的维度是(n, m),n等于左侧矩阵的行数,m等于右侧矩阵的列数。
例如以下:
接下来就是学习位移、缩放、旋转的矩阵了。虽然看起来内容比较多,但实际上每一个数学库里都会有封装,看懂原理即可不要上手自己算。这里需要注意一点就是移位一定要放到最后面算。 单教程里的矩阵知识是不够的,最好还得需要别的书籍加以辅助,入门数学工具书《3D数学基础:图形与游戏开发》前9章应该够用。
提醒:矩阵在编程中分为行主序和列主序。行主序(Direct3D),列主序(OpenGL)。GLM库里也是列主序,还有qt中的 QMatrix4x4 也是列主序。下面就来看看:
如果刚接触到矩阵,可能这样看还是会一脸懵逼。按照我们平常手算矩阵,我们习惯是标准的线性代数矩阵来算,也就是和行矩阵是一样的书写方式。转到列主序不知道怎么表达,所以下图为列主序编程时初始化并对一个向量进行转换的写法。(或者m2 = glm::mt2{1,2
, 3,4}然后在进行一个矩阵的转置,也是可以的)
再说说这两种主序的一些该注意的区别:
1 由矩阵存储顺序引发的矩阵左乘/前乘或者右乘/后乘问题
从根本上来说,矩阵的定义是确定的,但是在不同的引擎的实现过程中对矩阵的存储方式选择了行主序矩阵或者右主序矩阵的存储方式,而由此引发了矩阵左乘或者是右乘的问题。
在线性代数中,矩阵乘法是“行*列”。
那么对于行主序矩阵,通常采用前乘。 如:
对于列主序矩阵,通常采用后乘。 如:
2 由矩阵存储顺序引发的变换顺序问题
而这种矩阵乘法的顺序通常影响了对目标物体的变换顺序。
在OpenGL中,因为其实际上是列主序矩阵,采用列向量,所以在OpenGL中,做变换时通常先处理右边的矩阵,然后再处理左边的矩阵。
如:你想表达先缩放后位移的矩阵 M = M位移 * M旋转; 代码如下:
glm::mat4 transform = glm::mat4(1.0f);
transform = glm::translate(transform, glm::vec3(0.5f, -0.5f, 0.0f));
transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));
在Direct3D中,因为其实际上是行主序矩阵,采用行向量,所以在Direct3D中,做变换时通常先处理左边的矩阵,然后在处理右边的矩阵。
如:你想表达先位移后缩放的矩阵 M = M位移 * M旋转;
接下来就是代码部分了
QMatrix4x4 代替 glm里的mat4
#ifndef TRANSFORMWIDGET_H
#define TRANSFORMWIDGET_H
#include <QWidget>
#include <QOpenGLWidget>
#include <QOpenGLExtraFunctions>
#include <QDebug>
#include <QOpenGLTexture>
#include "Shader.h"
#include <QElapsedTimer>
namespace Ui {
class TransformWidget;
}
class HelloTransform;
class TransformWidget : public QWidget
{
Q_OBJECT
public:
explicit TransformWidget(QWidget *parent = nullptr);
~TransformWidget();
private:
Ui::TransformWidget *ui;
HelloTransform *m_contentWidget;
};
class HelloTransform : public QOpenGLWidget,protected QOpenGLExtraFunctions
{
public:
HelloTransform();
~HelloTransform();
protected:
virtual void initializeGL();
virtual void resizeGL(int w, int h);
virtual void paintGL();
private:
Shader *m_shader;
QOpenGLTexture *m_combine_texture1;
QOpenGLTexture *m_combine_texture2;
QElapsedTimer m_time;
};
#endif // TRANSFORMWIDGET_H
cpp
#include "transformwidget.h"
#include "ui_transformwidget.h"
#include <QMatrix4x4>
#include <QVector3D>
TransformWidget::TransformWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::TransformWidget)
{
ui->setupUi(this);
m_contentWidget = new HelloTransform();
ui->verticalLayout->addWidget(m_contentWidget);
}
TransformWidget::~TransformWidget()
{
delete ui;
}
HelloTransform::HelloTransform()
{
}
HelloTransform::~HelloTransform()
{
}
static GLuint VBO, VAO, EBO = 0;
void HelloTransform::initializeGL()
{
this->initializeOpenGLFunctions();
m_shader = new Shader(":/shader/res/shaders/getting_started/5.1.transform.vs"
,":/shader/res/shaders/getting_started/5.1.transform.fs");
//垂直镜像mirrored
m_combine_texture1 = new QOpenGLTexture(QImage(":/texture/res/textures/container.jpg").mirrored());
if(!m_combine_texture1->isCreated()){
qDebug() << "Failed to load texture";
}
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
m_combine_texture1->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
m_combine_texture1->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
m_combine_texture1->setMinificationFilter(QOpenGLTexture::Linear);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
m_combine_texture1->setMagnificationFilter(QOpenGLTexture::Linear);
m_combine_texture2 = new QOpenGLTexture(QImage(":/texture/res/textures/awesomeface.png").mirrored());
if(!m_combine_texture2->isCreated()){
qDebug() << "Failed to load texture";
}
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
m_combine_texture2->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
m_combine_texture2->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
m_combine_texture2->setMinificationFilter(QOpenGLTexture::Linear);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
m_combine_texture2->setMagnificationFilter(QOpenGLTexture::Linear);
//设置纹理单元编号
m_shader->use();
m_shader->m_shaderProgram.setUniformValue(m_shader->m_shaderProgram.uniformLocation("texture1"), 0);
m_shader->m_shaderProgram.setUniformValue(m_shader->m_shaderProgram.uniformLocation("texture2"), 1);
float vertices[] = {
// positions // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// texture coord attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
m_time.start();
}
void HelloTransform::resizeGL(int w, int h)
{
this->glViewport(0,0,w,h);
}
void HelloTransform::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//激活纹理单元0
glActiveTexture(GL_TEXTURE0);
m_combine_texture1->bind();
glActiveTexture(GL_TEXTURE1);
m_combine_texture2->bind();
// create transformations 先旋转后位移
QMatrix4x4 transform;//等效glm::mat4(1.0f);
transform.translate(QVector3D(0.5f, -0.5f, 0.0f));
transform.rotate((float)m_time.elapsed()/10,QVector3D(0.0f, 0.0f, 1.0f));
// render container
m_shader->use();
m_shader->m_shaderProgram.setUniformValue("transform",transform);
// unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
// glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// second transformation
// ---------------------
transform = QMatrix4x4();
transform.translate(QVector3D(-0.5f, 0.5f, 0.0f));
float scaleAmount = static_cast<float>(sin((float)m_time.elapsed()/1000.0f));
transform.scale(QVector3D(scaleAmount, scaleAmount, scaleAmount));
m_shader->use();
m_shader->m_shaderProgram.setUniformValue("transform",transform);
// now with the uniform matrix being replaced with new transformations, draw it again.
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
update();
}