一、基础知识
主要是一些向量和矩阵的计算方式。大学本科期间的线性代数里面的内容。
缩放矩阵
$$\begin{bmatrix}
S_{1} & 0 & 0 & 0 \\
0 & S_{2} & 0 & 0 \\
0 & 0 & S_{3} & 0 \\
0 & 0 & 0 & 1 \\
\end{bmatrix} \bullet \begin{pmatrix}
x \\
y \\
z \\
1\end{pmatrix} = \begin{pmatrix}
S_{1} \bullet x \\
S_{2} \bullet y \\
S_{3} \bullet z \\
1\end{pmatrix}$$
位移矩阵
旋转矩阵
沿X轴旋转;
沿Y轴旋转:
沿Z轴旋转:
复合变换
比如先将一个顶点(x, y, z)缩放2倍,然后再位移(1, 2, 3)个单位,最终的变换矩阵就是下面:
二、实践
在上一节的基础上,我们删掉颜色属性,于是顶点数组就变成了:
float vertices[] = {
// 位置 // 纹理坐标
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // 右上
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // 左上
};
同时删除顶点着色器和片段着色器上关于颜色的代码。
我们这一节的主题是变换,我们需要给没给顶点位置乘一个变换矩阵,将其变换到我们需要的位置,所以我们在顶点着色器中定义一个uniform变量transform,作为我们需要传入的变换矩阵:
#version 330 core
layout(location=0) in vec3 aPos;
layout(location=1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 transform;
void main()
{
gl_Position = transform * vec4(aPos, 1.0);
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}
片段着色器不需要改变,只需要删除有关顶点颜色属性的代码即可:
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}
Qt提供了QMatrix4x4
类,我们可以使用这个类很方便的进行各种三维空间的变换。
我们在上一节的基础上,首先绕Z轴旋转90度,然后每个轴都缩放0.5倍:
void GLWidget::initializeGL(){
...
m_program->bind(); //在修改uniform值之前,一定要绑定着色器程序到当前激活的opengl上下文
// 我们首先绕Z轴逆时针旋转90度,然后每个轴都缩放0.5倍
QMatrix4x4 transform; // 默认创建一个单位矩阵
transform.rotate(90, QVector3D(0, 0, 1)); // 绕Z轴逆时针旋转90度
transform.scale(QVector3D(0.5, 0.5, 0.5)); // 每个轴都缩放0.5倍
m_program->setUniformValue("transform", transform); // 将该矩阵传递至定点着色器
...
}
我们可以得到如下效果:
完整代码如下:
查看代码
//glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLExtraFunctions>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
public:
explicit GLWidget(QWidget *parent = nullptr);
~GLWidget();
protected:
void resizeGL(int w, int h) override;
void initializeGL() override;
void paintGL() override;
private:
QOpenGLVertexArrayObject *m_vao; //顶点数组对象
QOpenGLBuffer *m_vbo; //顶点缓冲对象
QOpenGLBuffer *m_ebo; //元素缓冲对象
QOpenGLShader *m_vshader; //顶点着色器
QOpenGLShader *m_fshader; //片段着色器
QOpenGLShaderProgram *m_program; //着色器程序对象
QOpenGLTexture *m_texture1 ; //纹理对象
QOpenGLTexture *m_texture2 ; //纹理对象
};
#endif // GLWIDGET_H
//glwidget.cpp
#include "glwidget.h"
#include <QDir>
#include <QFile>
#include <QDebug>
#include <QImage>
#include <QMatrix4x4>
float vertices[] = {
// 位置 // 纹理坐标
0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // 右上
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f // 左上
};
unsigned int indices[] = {
// 注意索引从0开始!
// 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
// 这样可以由下标代表顶点组合成矩形
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
makeCurrent();
}
GLWidget::~GLWidget()
{
if (m_program == nullptr) { return; }
makeCurrent();
delete m_program;
delete m_vshader;
delete m_fshader;
m_vbo->destroy();
m_vao->destroy();
doneCurrent();
}
//设置OpenGL视口、投影等。每当窗口部件被调整大小时调用(也在第一次显示时调用,因为所有新创建的窗口部件都自动获得调整大小事件)。
void GLWidget::resizeGL(int w, int h)
{
}
//设置OpenGL资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次。
void GLWidget::initializeGL()
{
initializeOpenGLFunctions();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);// 设置清屏颜色
glEnable(GL_DEPTH_TEST); // 启用深度测试
glEnable(GL_COLOR_BUFFER_BIT); // 启用颜色缓冲区
// 创建顶点着色器对象,并编译
m_vshader = new QOpenGLShader(QOpenGLShader::Vertex);
m_vshader->compileSourceFile(QString(":/shader/shader.vs"));
// 创建片段着色器对象,并编译
m_fshader = new QOpenGLShader(QOpenGLShader::Fragment);
m_fshader->compileSourceFile(QString(":/shader/shader.fs"));
// 创建着色器程序对象,添加顶点着色器和片段着色器,并链接它们
m_program = new QOpenGLShaderProgram;
m_program->addShader(m_vshader);
m_program->addShader(m_fshader);
m_program->link();
// 创建顶点数组对象,并绑定到当前上下文
m_vao = new QOpenGLVertexArrayObject;
m_vao->create();
QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);
// 创建顶点缓冲对象,
m_vbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_vbo->create();
m_vbo->bind();
// 分配显存大小,并搬运至显存
m_vbo->allocate(vertices, sizeof(vertices));
// 创建元素缓冲对象,
m_ebo = new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
m_ebo->create();
m_ebo->bind();
// 分配显存大小,并搬运至显存
m_ebo->allocate(indices, sizeof(indices));
// 链接顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
// 顶点属性默认是禁用的,启用顶点属性0(location=0)
m_program->enableAttributeArray(0);
// 纹理坐标属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float)));
m_program->enableAttributeArray(1);
// 加载纹理
m_texture1 = new QOpenGLTexture(QImage(":/image/container.jpg").mirrored());
// 设置纹理环绕方式
m_texture1->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
m_texture1->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
// 设置多级渐远纹理过滤方式
m_texture1->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);
// 加载纹理
m_texture2 = new QOpenGLTexture(QImage(":/image/awesomeface.png").mirrored());
// 设置纹理环绕方式
m_texture2->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
m_texture2->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);
// m_texture2
m_texture1->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, QOpenGLTexture::Linear);
// 我们需要手动设置哪个采样器对应哪个纹理单元
m_program->bind(); //在修改uniform值之前,一定要绑定着色器程序到当前激活的opengl上下文
m_program->setUniformValue("texture1", 0);
m_program->setUniformValue("texture2", 1);
// 我们首先绕Z轴逆时针旋转90度,然后每个轴都缩放0.5倍
QMatrix4x4 transform; // 默认创建一个单位矩阵
transform.rotate(90, QVector3D(0, 0, 1)); // 绕Z轴逆时针旋转90度
transform.scale(QVector3D(0.5, 0.5, 0.5)); // 每个轴都缩放0.5倍
m_program->setUniformValue("transform", transform); // 将该矩阵传递至定点着色器
m_vao->release();
m_vbo->release();
// VAO还在使用时,不能释放EBO
}
//渲染OpenGL场景。每当窗口部件需要更新时调用。
void GLWidget::paintGL()
{
// 清空颜色缓冲区和深度缓冲区
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 将纹理绑定到当前激活的纹理单元
m_texture1->bind(0);
m_texture2->bind(1);
// 绑定着色器程序至当前上下文,相当于调用glUseProgram()
m_program->bind();
// 绑定VAO,调用设置的一组状态
QOpenGLVertexArrayObject::Binder vaoBinder(m_vao);
// 绘制三角形
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
m_program->release();
// 交换缓存
auto *context = QOpenGLContext::currentContext();
context->swapBuffers(context->surface());
//使用swapBuffers后,必须调用makeCurrent后才能使用其他OpenGL函数。
makeCurrent();
}
本文来自博客园,原创作者:Clemens,转载请注明原文链接:https://www.cnblogs.com/errorman/p/17650201.html