本篇博客展示4个例子,都是关于旋转变换、平移变换和QMatrix的操作。
在QT的帮助中,提到QMatrix是行主序。
而opengl的矩阵是列主序的。乍一看,将QMatrix传入shader之前,qmatrix需要做转置处理。但是在实际的尝试中,我发现矩阵不应转置。
我们从前面的博客
Qt结合GLSL贴出纹理(二、采用QOpenGLShaderProgram和QOpenGLTexture)
出发,在那篇博客里,我们贴出来一个纹理,并没有做其他任何操作。现在我们在其基础上添加旋转操作。
例子1.
先看最简单的情形:让物体旋转45度
h文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
class MainWindow : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
GLuint m_uiVertLoc;
GLuint m_uiTexNum;
QOpenGLTexture * m_pTextures;
QOpenGLShaderProgram * m_pProgram;
GLfloat * m_pVertices;
protected:
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
};
#endif // MAINWINDOW_H
cpp文件:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QOpenGLWidget(parent)
{
}
MainWindow::~MainWindow()
{
m_pTextures->release();
delete m_pTextures;
delete m_pProgram;
delete [] m_pVertices;
}
void MainWindow::initializeGL()
{
initializeOpenGLFunctions();
m_uiTexNum = 0;
m_pVertices = new GLfloat[18];
//给顶点赋值
GLfloat arrVertices[18] = {0.0, 1.0, 0.0,
0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 1.0, 0.0,
0.0, 1.0, 0.0};
m_pVertices = new GLfloat[18];
memcpy(m_pVertices, arrVertices, 18 * sizeof(GLfloat));
QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
const char *vsrc =
"#version 330\n"
"in vec3 pos;\n"
"out vec2 texCoord;\n"
"uniform mat4 mat4MVP;\n"
"void main()\n"
"{\n"
" gl_Position = mat4MVP * vec4(pos, 1.0);\n"
" texCoord = pos.xy;\n"
"}\n";
vshader->compileSourceCode(vsrc);
QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
const char *fsrc =
"#version 330\n"
"out vec4 color;\n"
"in vec2 texCoord;\n"
"uniform sampler2D Tex\n;"
"void main()\n"
"{\n"
" color = texture(Tex, texCoord);\n"
//" color = vec4(1.0, 0.0, 0.0, 0.0);\n"
"}\n";
fshader->compileSourceCode(fsrc);
m_pProgram = new QOpenGLShaderProgram;
m_pProgram->addShader(vshader);
m_pProgram->addShader(fshader);
m_pProgram->link();
m_pProgram->bind();
m_uiVertLoc = m_pProgram->attributeLocation("pos");
m_pProgram->enableAttributeArray(m_uiVertLoc);
m_pProgram->setAttributeArray(m_uiVertLoc, m_pVertices, 3, 0);
m_pTextures = new QOpenGLTexture(QImage(QString("earth.bmp")).mirrored());
m_pTextures->setMinificationFilter(QOpenGLTexture::Nearest);
m_pTextures->setMagnificationFilter(QOpenGLTexture::Linear);
m_pTextures->setWrapMode(QOpenGLTexture::Repeat);
m_pProgram->setUniformValue("Tex", m_uiTexNum);
glEnable(GL_DEPTH_TEST);
glClearColor(0,0,0,1);
}
void MainWindow::paintGL()
{
//QMatrix4x4在声明时被默认为单位矩阵
QMatrix4x4 m1, m2, m3, m;
//m1.viewport(0,0,m_iWidth, m_iHeight,-15,15);//useless
//m1.ortho(-1.0f, +1.0f, -1.0f, 1.0f, 0.0f, 25.0f);//right//generate projection matrix
//m2.lookAt(QVector3D(20,0,10), QVector3D(0,0,0), QVector3D(0,1,0));//generate view matrix, right
//qDebug()<<m2;
//m3.translate(0,-0.707,0.0);//right, generate model matrices
m3.rotate(45, 0.0f, 0.0f, 1.0f);//right, generate model matrices
qDebug()<<m3;
m = m1 * m2 * m3;
m_pProgram->setUniformValue("mat4MVP", m);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_pTextures->bind(m_uiTexNum);
glDrawArrays(GL_TRIANGLES, 0, 6);
m_pTextures->release();
}
void MainWindow::resizeGL(int w, int h)
{
glViewport(0,0,w,h);
}
效果:
可见,图片的确绕Z轴旋转了45度。同时注意qDebug()打印出的矩阵。
按照https://en.wikibooks.org/wiki/GLSL_Programming/Vertex_Transformations的说法:
将alpha = 45 ,x = 0, y = 0, z = 1代入公式,不难发现,qDebug打印的结果与公式相符。
这说明我们在例子里对m3.rotate()函数的运用是正确的。
例子2. 旋转与平移结合
这个程序与例子1只变化了一处:增加了m3.translate(0,-0.5,0)
这里有两处要注意:
1)尽管先调用rotate函数,再调用了translate函数,但是纹理表现出来的效果是先平移,再旋转。因为纹理左下角不再位于窗口的中轴线x=0上了。
2)假如先进行旋转,再平移,则qDebug打印的结果应该是
0.707 -0.707 0 0
0.707 0.707 0 -0.5
0 0 1 0
0 0 0 1
与实际结果不符。
例子3. 将translate和rotate函数交换
此时显示效果与qDebug输出结果都正确了。
分析:QMatrix声明时,m3被默认赋值为单位矩阵。调用translate函数后,translate矩阵右乘单位矩阵m3.随后调用rotate函数,旋转矩阵再一次右乘m3得到最终的结果。前一篇博客
GLSL矩阵变换详解(一、总述)
中,我们提到,一串矩阵相乘,最先起作用的其实是最右面的矩阵。因此,尽管translate先调用,但是反而后起作用。
例子4.
为了验证例子3的分析,我们用两个独立的矩阵各自代表旋转和平移操作,然后把两者相乘:
可见,结果与例子3一样。这也就验证了例子三的猜测:translate函数与rotate函数的调用次序与实际的操作次序相反。