当前位置: 首页 > news >正文

OpenGL: Transform知识

一、变换

二、向量

三、向量与标量运算

四、向量取反

五、向量加减

六、长度

七、向量相乘

八、矩阵

九、矩阵的加减

十、矩阵的数乘

十一、矩阵相乘
    1、矩阵相乘限制:
       * 只有当左侧矩阵的列数与右侧矩阵的行数相等,两个矩阵才能相乘。
       * 矩阵相乘不遵守交换律。A * B != B * A。
    2、矩阵相乘:
       * 取左侧矩阵的行,取右侧矩阵的列,决定了结果矩阵的输出值位置(行列)。
       *

         | 1  2 |   | 5  6 |   | 1*5+2*7  1*6+2*8 |   | 19  22 ||      | * |      | = |                  | = |        || 3  4 |   | 7  8 |   | 3*5+4*7  3*6+4*8 |   | 43  50 |


       * 结果矩阵维度(m, n), m是左侧矩阵的行数, n是右侧矩阵的列数。

十二、矩阵乘以向量
    1、M*N矩阵和N*1向量相乘,可以变换这个向量。

十三、单位矩阵
    1、单位矩阵是一个对角线全是1,其它全是0的N*N矩阵。
    2、单位矩阵乘以向量,使向量不变。

十四、缩放(Scale)
    1、不均匀(Non-uniform)缩放,每个轴的缩放因子(Scaling Factor)都不一样,会改变向量的方向和大小。
    2、均匀缩放(Uniform Scale),每个轴的缩放因为(Scaling Factor)都一样,不改变向量的方向,但改变向量的大小。
    3、我们把缩放因子表示为(S1, S2, S3),则为向量(x,y,z)定义一个缩放矩阵:
  

       | S1   0    0    0  |      | x |     | S1 * x ||                   |      |   |     |        || 0    S2   0    0  |      | y |     | S2 * y ||                   |   *  |   |  =  |        || 0    0    S3   0  |      | z |     | S3 * z ||                   |      |   |     |        || 0    0    0    1  |      | 1 |     |    1   |

十五、位移(Translation)
    1、位移是在原始向量的基础上加上另一个向量,从而获得一个在不同位置的新向量的过程。
    2、我们把位移向量表示为(Tx, Ty, Tz),则为向量(x,y,z)定义一个位移矩阵:
    

       | 1    0    0    Tx |      | x |     | x + Tx ||                   |      |   |     |        || 0    1    0    Ty |      | y |     | y + Ty ||                   |   *  |   |  =  |        || 0    0    1    Tz |      | z |     | z + Tz ||                   |      |   |     |        || 0    0    0    1  |      | 1 |     |    1   |

十六、旋转(Rotation)
    1、旋转用角(Angle)来表示,角可以是角度制或弧度制。
    2、弧度转角度: 角度 = 弧度 * 180 / PI。
       角度转弧度: 弧度 = 角度 * PI / 180。
    3、在3D空间中,旋转需要定义一个角和一个旋转轴(Rotation Axis),物体会沿着给定的旋转轴旋转特定角度。
    4、沿X轴旋转theta角:
    

       | 1    0       0     0 |      | x |     |          x          ||                      |      |   |     |                     || 0   cosT   -sinT   0 |      | y |     | cosT * y - sinT * z ||                      |   *  |   |  =  |                     || 0   sinT    cosT   0 |      | z |     | sinT * y + cosT * z ||                      |      |   |     |                     || 0    0       0     1 |      | 1 |     |          1          |


    5、沿Y轴旋转theta角:
    

       |  cosT   0   sinT   0 |      | x |     |  cosT * x + sinT * z ||                      |      |   |     |                      ||   0     1    0     0 |      | y |     |           y          ||                      |   *  |   |  =  |                      || -sinT   0   cosT   0 |      | z |     | -sinT * x + cosT * z ||                      |      |   |     |                      ||   0     0    0     1 |      | 1 |     |           1          |


    6、沿Z轴旋转theta角:
    

       | cosT   -sinT   0   0 |      | x |     | cosT * x - sinT * y ||                      |      |   |     |                     || sinT    cosT   0   0 |      | y |     | sinT * x + cosT * y ||                      |   *  |   |  =  |                     ||  0       0     1   0 |      | z |     |          z          ||                      |      |   |     |                     ||  0       0     0   1 |      | 1 |     |          1          |

十七、可以多个矩阵复合,注意万向节死锁(Gimbal Lock)问题,避免这个问题是使用四元数(Quaternion),更安全更有效率。

十八、矩阵的组合
    1、使用矩阵进行变换的强大之处在于,根据矩阵之间的乘法,可以把多个变换组合到一个矩阵中。
    2、假设有一个顶点(x,y,z),希望将其缩放2倍,然后位移(1,2,3)个单位,则结果的变换矩阵为:
      

       | 1   0   0   1 |     | 2   0   0   0 |      | 2   0   0   1 ||               |     |               |      |               || 0   1   0   2 |     | 0   2   0   0 |      | 0   2   0   2 ||               |  *  |               |  =   |               || 0   0   1   3 |     | 0   0   2   0 |      | 0   0   2   3 ||               |     |               |      |               || 0   0   0   1 |     | 0   0   0   1 |      | 0   0   0   1 |


    3、因为矩阵乘法不遵守交换率,意味着它们的顺序很重要。
       当矩阵相乘时,最右边矩阵是第一个与向量相乘的,所以最右边矩阵代表着第一个变换,我们应该从右向左读矩阵乘法。
    4、注意!!!!!!!!!!!!!!!!!
       组合矩阵时,建议先进行缩放操作,然后旋转,最后才是位移,否则它们会消极地互相影响。(比如,先位移再缩放,位移的向量也会被缩放。)


十九、实践
    1、OpenGL没有自带任何的矩阵和向量知识,我们必须定义自己的数学类和函数。
    2、有个易于使用,且专门为OpenGL量身定做的数学库,GLM。
    3、GLM:
       * GLM, OpenGL Mathematics的缩写。
       * GLM是一个只有头文件的库。
       * GLM从0.9.9版本起,矩阵类型默认初始化为一个零矩阵,而不是单位矩阵。
         用glm::mat4 mat = glm::mat4(1.0f)初始化为单位矩阵。
       * 我们需要的GLM大多数功能可以在下面三个头文件中找到:
         #include <glm/glm.hpp>
         #include <glm/gtc/matrix_transform.hpp>
         #include <glm/gtc/type_ptr.hpp>
    4、实际变换中,先把箱子缩放0.5倍,再逆时针旋转90度,看代码效果。
       * glm::radians()将角度转为弧度。
       * 修改顶点着色器,添加uniform mat4类型的矩阵变量,再将该矩阵乘以位置向量。
        

         #version 330 corelayout(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 = aTexCoord;}


       * 将变换矩阵传递给着色器。
         unsigned int transformLoc = glGetUniformLocation(shaderProgramID, "transform");
         glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
       * 注意,代码看着顺序是先旋转再缩放,但是实际应用却是先变换缩放,再变换旋转!!!!!!
         因为OpenGL的向量是列向量,旋转矩阵*缩放矩阵*向量,向量先和缩放矩阵乘,再和旋转矩阵乘!!!

二十、实例代码

//MLOpenGLWidget.h#pragma once#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLShaderProgram>class MLOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{Q_OBJECTpublic:MLOpenGLWidget(QWidget * parent = nullptr);~MLOpenGLWidget();enum Shape { None, Rect, Circle, Triangle };void DrawShape(Shape shape);void WireFrame(bool bWire);protected:void initializeGL() override;void resizeGL(int w, int h) override;void paintGL() override;private:unsigned int VAO;unsigned int VBO;unsigned int EBO;unsigned int textureID1;unsigned int textureID2;Shape m_shape = None;QOpenGLShaderProgram m_shaderProgram;float time = 10.0f;
};//MLOpenGLWidget.cpp#include "MLOpenGLWidget.h"
#include <QDebug>
#include "stb_image.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"MLOpenGLWidget::MLOpenGLWidget(QWidget * parent) : QOpenGLWidget(parent)
{
}MLOpenGLWidget::~MLOpenGLWidget()
{makeCurrent();glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);glDeleteVertexArrays(1, &VAO);glDeleteTextures(1, &textureID1);glDeleteTextures(1, &textureID2);doneCurrent();
}void MLOpenGLWidget::DrawShape(Shape shape)
{//触发重新绘制m_shape = shape;update();
}void MLOpenGLWidget::WireFrame(bool bWire)
{makeCurrent();if (bWire){glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);}else{glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);}doneCurrent();update();
}void MLOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();//创建VAO、VBO、EBO,并赋予IDglGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);//绑定VAO、VBO、EBO对象glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);  //注意, 因为VAO已经绑定,此句会被VAO的最后一个指针偷偷记录下来, 指向EBO。//顶点数据float vertices[] = {//位置                //颜色             //纹理0.5f,  0.5f,  0.0f,  1.0f, 0.0f, 0.0f,  1.0f, 1.0f,   // 右上角0.5f, -0.5f,  0.0f,  0.0f, 1.0f, 0.0f,  1.0f, 0.0f,   // 右下角-0.5f, -0.5f,  0.0f,  0.0f, 0.0f, 1.0f,  0.0f, 0.0f,   // 左下角-0.5f,  0.5f,  0.0f,  0.3f, 0.5f, 0.8f,  0.0f, 1.0f,   // 左上角};glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//索引数据unsigned int indices[] = {0, 1, 3, // 第一个三角形1, 2, 3  // 第二个三角形};glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//配置VAO位置属性指针glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0);glEnableVertexAttribArray(0);//配置VAO颜色属性指针glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(3 * sizeof(float)));glEnableVertexAttribArray(1);//配置VAO纹理坐标属性指针glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(6 * sizeof(float)));glEnableVertexAttribArray(2);//(1)、创建和生成纹理1glGenTextures(1, &textureID1);glBindTexture(GL_TEXTURE_2D, textureID1);//为当前绑定的纹理对象设置环绕、过滤方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//加载并生成纹理1int width, height, nrChannels;stbi_set_flip_vertically_on_load(true);unsigned char * data1 = stbi_load("E:/02.LearnSummary/16.OpenGL+Qt/41_Transform/41_Transform/textures/container.jpg", &width, &height, &nrChannels, 0);if (data1){//当前绑定的纹理会被附加上纹理图像数据。glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data1);//为当前绑定的纹理自动生成所有需要的多级渐远纹理。glGenerateMipmap(GL_TEXTURE_2D);}else{qDebug() << "Failed to load texture1\n";}stbi_image_free(data1);glBindTexture(GL_TEXTURE_2D, 0);//(2)、创建和生成纹理2glGenTextures(1, &textureID2);glBindTexture(GL_TEXTURE_2D, textureID2);//为当前绑定的纹理对象设置环绕、过滤方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//加载并生成纹理2unsigned char * data2 = stbi_load("E:/02.LearnSummary/16.OpenGL+Qt/41_Transform/41_Transform/textures/awesomeface.png", &width, &height, &nrChannels, 0);if (data2){glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data2);glGenerateMipmap(GL_TEXTURE_2D);}else{qDebug() << "Failed to load texture2\n";}stbi_image_free(data2);glBindTexture(GL_TEXTURE_2D, 0);//(1)着色器程序: 添加纹理//(2)着色器程序: 多个纹理采样器多个纹理m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/1_RotateScaleShader.vert");m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/1_RotateScaleShader.frag");m_shaderProgram.link();
}void MLOpenGLWidget::resizeGL(int w, int h)
{
}//绘制都是在此函数中,其它地方都是触发绘制
void MLOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);//将纹理对象绑定到纹理单元glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, textureID1);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, textureID2);m_shaderProgram.bind();//将纹理采样器绑定到纹理单元m_shaderProgram.setUniformValue("ourTexture1", 0);m_shaderProgram.setUniformValue("ourTexture2", 1);//一、//定义一个向量(1, 0, 0)/*glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);*///定义一个矩阵变量/*glm::mat4 trans = glm::mat4(1.0f);*///平移(1,1,0)单位,单位矩阵+平移向量,构造平移变换矩阵/*trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));*///平移向量/*vec = trans * vec;*///结果为: 2 1 0/*qDebug() << vec.x << vec.y << vec.z;*///二、先把箱子逆时针旋转90度,再缩放0.5倍。//(1)glm方式//创建单位变换矩阵//glm::mat4 trans = glm::mat4(1.0f);//单位矩阵+逆时针旋转90度+绕Z轴 得旋转矩阵//trans = glm::rotate<float>(trans, glm::radians(90.0), glm::vec3(0.0, 0.0, 1.0));//旋转矩阵+缩放因子0.5 得旋转矩阵*缩放矩阵//trans = glm::scale<float>(trans, glm::vec3(0.5, 0.5, 0.5));//(2)Qt方式//创建单位矩阵变量//QMatrix4x4 trans4;//单位矩阵+逆时针旋转90度+绕Z轴 得旋转矩阵//trans4.rotate(90.0, QVector3D(0.0, 0.0, 1.0));//旋转矩阵+缩放因子 得旋转矩阵*缩放矩阵//trans4.scale(0.5);//将变换矩阵传递给顶点着色器//m_shaderProgram.setUniformValue("transform", trans4);switch (m_shape){case MLOpenGLWidget::None:break;case MLOpenGLWidget::Rect:{//三、随着时间旋转//注意!!!代码看着先平移,再旋转。//但实际变换是先旋转变换,再平移变换,因为平移矩阵*旋转矩阵*向量,向量先和旋转矩阵乘,再和平移矩阵乘,OpenGL的向量是列向量。QMatrix4x4 trans4;trans4.translate(QVector3D(0.5f, -0.5f, 0.0f));trans4.rotate(time, QVector3D(0.0, 0.0, 1.0));m_shaderProgram.setUniformValue("transform", trans4);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);time += 10;break;}case MLOpenGLWidget::Circle:break;case MLOpenGLWidget::Triangle:break;default:break;}
}//MainWindow.h#pragma once#include <QtWidgets/QMainWindow>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindowClass; };
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget * parent = nullptr);~MainWindow();private:void ActDrawRectTriggered(bool checked);void ActClearTriggered(bool checked);void ActWireFrameTriggered(bool checked);private:Ui::MainWindowClass * ui;
};//MainWindow.cpp#include "MainWindow.h"
#include "ui_MainWindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindowClass())
{ui->setupUi(this);setCentralWidget(ui->openGLWidget);connect(ui->actDrawRect, &QAction::triggered, this, &MainWindow::ActDrawRectTriggered);connect(ui->actClear, &QAction::triggered, this, &MainWindow::ActClearTriggered);connect(ui->actWireFrame, &QAction::triggered, this, &MainWindow::ActWireFrameTriggered);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::ActDrawRectTriggered(bool checked)
{ui->openGLWidget->DrawShape(MaLanOpenGLWidget::Rect);
}void MainWindow::ActClearTriggered(bool checked)
{ui->openGLWidget->DrawShape(MaLanOpenGLWidget::None);
}void MainWindow::ActWireFrameTriggered(bool checked)
{ui->openGLWidget->WireFrame(checked);
}

MainWindow.ui

http://www.xdnf.cn/news/656515.html

相关文章:

  • 8.1.2 商品信息动态网站 - JSP+Servlet实现动态网站
  • 基于DDD的企业团餐订餐平台微服务架构设计与实现
  • 使用 Cannonballs 进行实用导体粗糙度建模
  • IP动态伪装开关
  • C#实现SSE通信方式的MCP Server
  • 十三: 神经网络的学习
  • 集星云推短视频矩阵系统的定制化与私有化部署方案
  • 将YOLO格式的数据集转换为mmdetection格式
  • 【密码学——基础理论与应用】李子臣编著 第十三章 数字签名 课后习题
  • 数据保护在Web3应用中的重要性及其实现
  • vue+ThreeJs 创建过渡圆圈效果
  • 行为型:状态模式
  • SmartSoftHelp 图片资源技术保护可执行添加水印方案---深度优化版:SmartSoftHelp DeepCore XSuite
  • 【FAQ】HarmonyOS SDK 闭源开放能力 —Account Kit(4)
  • 第二十章:数据治理之数据指标(二):数据指标和数据指标体系
  • 日语学习-日语知识点小记-构建基础-JLPT-N4阶段(29):ので
  • “轩辕杯“云盾砺剑CTF挑战赛 Web wp
  • 限流系列:sentinel
  • 哈希表基础知识
  • 选择SEO公司时需要注意哪些关键指标?
  • 多模态大语言模型arxiv论文略读(九十二)
  • 2025.05.26【Wordcloud】词云图绘制技巧
  • pkg-config的功能与作用说明
  • jeecg-boot vue点击左侧菜单跳转无菜单栏的全屏页面
  • PostgreSQL日志管理完整方案(AI)
  • 学习心得(14--16)
  • 使用 Vuex 实现用户注册与登录功能
  • HTML流星雨
  • 充电枪IEC62196/EN 62196测试内容
  • 【PC网上邻居--1】基于Samba协议的局域网文件共享系统设计与实现