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
         #include
         #include
    4、实际变换中,先把箱子缩放0.5倍,再逆时针旋转90度,看代码效果。
       * glm::radians()将角度转为弧度。
       * 修改顶点着色器,添加uniform mat4类型的矩阵变量,再将该矩阵乘以位置向量。
        

         #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 = aTexCoord;
         }


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

二十、实例代码

//MLOpenGLWidget.h

#pragma once

#include 
#include 
#include 


class MLOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{
	Q_OBJECT

public:
	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 
#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,并赋予ID
	glGenVertexArrays(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)、创建和生成纹理1
	glGenTextures(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);
	//加载并生成纹理1
	int 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)、创建和生成纹理2
	glGenTextures(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);
	//加载并生成纹理2
	unsigned 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(trans, glm::radians(90.0), glm::vec3(0.0, 0.0, 1.0));
	//旋转矩阵+缩放因子0.5 得旋转矩阵*缩放矩阵
	//trans = glm::scale(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 


QT_BEGIN_NAMESPACE
namespace Ui { class MainWindowClass; };
QT_END_NAMESPACE


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    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

OpenGL: Transform知识_第1张图片

你可能感兴趣的:(OpenGL+Qt,图形渲染,计算机视觉)