(本文是LearnOpenGL的学习笔记,教程中文翻译地址https://learnopengl-cn.github.io/)
0.前言
上一篇笔记记录了纹理(Texture)使用:https://blog.csdn.net/gongjianbo1992/article/details/104119033,本文将学习变换(Transform)的使用。
尽管我们现在已经知道了如何创建一个物体、着色、加入纹理,给它们一些细节的表现,但因为它们都还是静态的物体,仍是不够有趣。我们可以尝试着在每一帧改变物体的顶点并且重配置缓冲区从而使它们移动,但这太繁琐了,而且会消耗很多的处理时间。我们现在有一个更好的解决方案,使用(多个)矩阵(Matrix)对象可以更好的变换(Transform)一个物体。
教程中还列出了一些常用的数学知识:https://learnopengl-cn.github.io/01%20Getting%20started/07%20Transformations/
1.如何实现
OpenGL没有自带任何的矩阵和向量知识,所以我们必须定义自己的数学类和函数。LearnOpenGL教程使用的GLM数学库,这里我们使用Qt自带的工具类(如QMatrix4x4等)。要做的,就是把算好的矩阵传递给OpenGL,我们首先查询uniform变量的地址,然后把矩阵数据发送给着色器。
本节的运算主要使用到了矩阵和矩阵相乘(多次变换操作组合),矩阵和向量相乘(计算好的矩阵乘上之前的顶点向量)。根据矩阵之间的乘法,我们可以把多个变换组合到一个矩阵中,这样我们把最终计算好的矩阵去和顶点向量相乘就行了。
单位矩阵:
缩放:
平移:
延x轴旋转:
延y轴旋转:
延z轴旋转:
矩阵组合:
代码也很简单,就是在之前纹理的基础上计算了一个变换矩阵,传入着色器中和之前的顶点输出做运算。下面的代码表示x轴右移0.5,y轴下移0.5,延z轴旋转rotate度(Qt做了封装操作简单了不少)。
(对于教程代码移植到QtWidgets,之后我尽量使用Qt封装的类)
2.实现代码
(项目git链接:https://github.com/gongjianbo/OpenGLwithQtWidgets.git)
我的GLTransform类实现效果(平移和旋转):
GLTransform类代码:
#ifndef GLTRANSFORM_H
#define GLTRANSFORM_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QTimer>
//变换
class GLTransform : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
explicit GLTransform(QWidget *parent = nullptr);
~GLTransform();
protected:
//设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
void initializeGL() override;
//渲染OpenGL场景,每当需要更新小部件时使用
void paintGL() override;
//设置OpenGL视口、投影等,每当尺寸大小改变时调用
void resizeGL(int width, int height) override;
private:
//着色器程序
QOpenGLShaderProgram _shaderProgram;
//顶点数组对象
QOpenGLVertexArrayObject _vao;
//顶点缓冲
QOpenGLBuffer _vbo;
//索引缓冲
QOpenGLBuffer _ebo;
//纹理(因为不能赋值,所以只能声明为指针)
QOpenGLTexture *_texture1 = nullptr;
QOpenGLTexture *_texture2 = nullptr;
//定时器,做动画效果
QTimer *_timer=nullptr;
//旋转角度
int _rotate=0;
};
#endif // GLTRANSFORM_H
#include "GLTransform.h"
GLTransform::GLTransform(QWidget *parent)
: QOpenGLWidget(parent)
{
_timer=new QTimer(this);
connect(_timer,&QTimer::timeout,this,[this](){
_rotate+=2;
update();
});
_timer->setInterval(50);
}
GLTransform::~GLTransform()
{
makeCurrent();
_vbo.destroy();
_ebo.destroy();
_vao.destroy();
delete _texture1;
delete _texture2;
doneCurrent();
}
void GLTransform::initializeGL()
{
//为当前上下文初始化OpenGL函数解析
initializeOpenGLFunctions();
//着色器代码
//in输入,out输出,uniform从cpu向gpu发送
//因为OpenGL纹理颠倒过来的,所以取反vec2(aTexCoord.x, 1-aTexCoord.y);
const char *vertex_str=R"(#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoord;
uniform mat4 transform;
void main()
{
gl_Position = transform*vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = vec2(aTexCoord.x, 1-aTexCoord.y);
})";
const char *fragment_str=R"(#version 330 core
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
gl_FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
})";
//将source编译为指定类型的着色器,并添加到此着色器程序
if(!_shaderProgram.addCacheableShaderFromSourceCode(
QOpenGLShader::Vertex,vertex_str)){
qDebug()<<"compiler vertex error"<<_shaderProgram.log();
}
if(!_shaderProgram.addCacheableShaderFromSourceCode(
QOpenGLShader::Fragment,fragment_str)){
qDebug()<<"compiler fragment error"<<_shaderProgram.log();
}
//使用addShader()将添加到该程序的着色器链接在一起。
if(!_shaderProgram.link()){
qDebug()<<"link shaderprogram error"<<_shaderProgram.log();
}
//VAO,VBO
float vertices[] = {
// positions // colors // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left
};
//EBO
unsigned int indices[] = {
0, 1, 3, // first Triangle
1, 2, 3 // second Triangle
};
_vao.create();
_vao.bind();
//QOpenGLVertexArrayObject::Binder vaoBind(&_vao);
_vbo=QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
_vbo.create();
_vbo.bind();
_vbo.allocate(vertices,sizeof(vertices));
_ebo=QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
_ebo.create();
_ebo.bind();
_ebo.allocate(indices,sizeof(indices));
// position attribute
int attr = -1;
attr = _shaderProgram.attributeLocation("aPos");
_shaderProgram.setAttributeBuffer(attr, GL_FLOAT, 0, 3, sizeof(GLfloat) * 8);
_shaderProgram.enableAttributeArray(attr);
// color attribute
attr = _shaderProgram.attributeLocation("aColor");
_shaderProgram.setAttributeBuffer(attr, GL_FLOAT, sizeof(GLfloat) * 3, 3, sizeof(GLfloat) * 8);
_shaderProgram.enableAttributeArray(attr);
// texture coord attribute
attr = _shaderProgram.attributeLocation("aTexCoord");
_shaderProgram.setAttributeBuffer(attr, GL_FLOAT, sizeof(GLfloat) * 6, 2, sizeof(GLfloat) * 8);
_shaderProgram.enableAttributeArray(attr);
// texture 1
//直接生成绑定一个2d纹理, 并生成多级纹理MipMaps
_texture1 = new QOpenGLTexture(QImage(":/box.jpg"), QOpenGLTexture::GenerateMipMaps);
if(!_texture1->isCreated()){
qDebug() << "Failed to load texture";
}
// set the texture wrapping parameters
// 等于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
_texture1->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
_texture1->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
// set texture filtering parameters
//等价于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
_texture1->setMinificationFilter(QOpenGLTexture::Linear);
_texture1->setMagnificationFilter(QOpenGLTexture::Linear);
// texture 2
//直接生成绑定一个2d纹理, 并生成多级纹理MipMaps
_texture2 = new QOpenGLTexture(QImage(":/face.png"), QOpenGLTexture::GenerateMipMaps);
if(!_texture2->isCreated()){
qDebug() << "Failed to load texture";
}
// set the texture wrapping parameters
// 等于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
_texture2->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
_texture2->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);//
// set texture filtering parameters
//等价于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
_texture2->setMinificationFilter(QOpenGLTexture::Linear);
_texture2->setMagnificationFilter(QOpenGLTexture::Linear);
_shaderProgram.bind();
_shaderProgram.setUniformValue("texture1", 0);
_shaderProgram.setUniformValue("texture2", 1);
_shaderProgram.release();
_timer->start();
}
void GLTransform::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//当我们需要绘制透明图片时,就需要关闭GL_DEPTH_TEST并且打开混合glEnable(GL_BLEND);
//glDisable(GL_DEPTH_TEST);
//glEnable(GL_BLEND);
//基于源像素Alpha通道值的半透明混合函数
//glBlendFunc(GL_SRC_ALPHA, GL_ONE);
//纹理单元的应用
glActiveTexture(GL_TEXTURE0);
_texture1->bind();
glActiveTexture(GL_TEXTURE1);
_texture2->bind();
//计算变换矩阵
QMatrix4x4 transform;
transform.translate(QVector3D(0.5f, -0.5f, 0.0f));
transform.rotate(_rotate, QVector3D(0.0f, 0.0f, 1.0f));
_shaderProgram.bind();
_shaderProgram.setUniformValue("transform",transform);
//绘制
_vao.bind();
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
_vao.release();
_texture1->release();
_texture2->release();
_shaderProgram.release();
}
void GLTransform::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
}
3.参考
LearnOpenGL:https://learnopengl-cn.github.io/01%20Getting%20started/07%20Transformations/