必须掌握的前置技能:https://blog.csdn.net/Jaihk662/article/details/103811465(向量与矩阵)
其实也不难,也就是一些线性代数的基本知识,暂时只需要理解到这种程度就好,后面可以再说
一、GLM库
和SOIL一样,GLM也是一个openGL的库,里面存着各种数学公式和算法
OpenGL环境配置(超全整合版)GLM库也可以从这篇文章中的链接中下载到,有个叫做glm-0.9.9.8的文件夹就是了,打开后将其中整个glm文件夹丢到VS的includes文件夹中(C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\include)就OK了,不需要额外进行其它操作
一样,我们测试一下:
#include<iostream>
#include<opengl/glew.h>
#define GLEW_STATIC
#include<GLFW/glfw3.h>
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
#include<glm/gtc/type_ptr.hpp>
#include"Shader.h"
#include<opengl/freeglut.h>
#include<SOIL.h>
int main()
{
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
glm::mat4 trans = glm::translate(glm::mat4(1.0f), glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
printf("向量平移后结果:(%.0f, %.0f, %.0f)\n", vec.x, vec.y, vec.z);
trans = glm::scale(glm::mat4(1.0f), glm::vec3(3, 3, 2));
vec = trans * vec;
printf("在此之后,向量缩放后结果:(%.0f, %.0f, %.0f)\n", vec.x, vec.y, vec.z);
trans = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
vec = trans * vec;
printf("在此之后,向量围绕z轴旋转90度后结果:(%.0f, %.0f, %.0f)\n", vec.x, vec.y, vec.z);
vec = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f);
trans = glm::mat4(1.0f);
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(3, 3, 2));
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
printf("(%.0f, %.0f, %.0f)\n", vec.x, vec.y, vec.z);
}
可以从上面看出,我们暂时只用到了以下3个库
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
#include<glm/gtc/type_ptr.hpp>
结合文章开头的那篇文章(线代基础公式XX),我们看一下其中的一些函数/用法
基础变量/方法:
- glm::vec4:定义一个长度为4的向量
- glm::mat4:定义一个4x4的矩阵
- glm::mat4(1.0f):获得一个单位矩阵E
- glm::radians(90.0f):将角度转化为弧度
几个基础矩阵运算:
- glm::translate(, ):获得向量对应的位移矩阵,用矩阵乘以位移矩阵并得到最后的结果
- glm::scale(, ):获得向量对应的缩放矩阵,用矩阵乘以位移矩阵并得到最后的结果
- glm::rotate(, ):获得向量对应的旋转矩阵,用矩阵乘以位移矩阵并得到最后的结果
可以看出上面的代码中看出3点:这里也作为提醒
- 所有矩阵都是左乘,我们也可以先同时将位移矩阵、缩放矩阵和旋转矩阵先相乘得到一个新的矩阵,并拿这个新的矩阵乘与我们的向量以同时实现平移、旋转和缩放
- 矩阵乘法不遵循交换律
- 我们必须需要齐次坐标
二、会旋转的长方形
我们来实际运用一下,我们以这一章(SOIL库)的代码为基础扩展
效果如下:
非常简单,我们只需要将时间作为旋转和缩放的参数就好了,如下:
GLint transformLoc = glGetUniformLocation(shaderYellow.Program, "transform");
glm::mat4 trans = glm::translate(glm::mat4(1.0f), glm::vec3(0.5f, -0.5f, 0.0f));
trans = glm::rotate(trans, (GLfloat)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
GLfloat val = sin(glfwGetTime());
trans = glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, 0.5f, 0.0f));
trans = glm::scale(trans, glm::vec3(val, val, val));
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
可以看出,我们绘制了两次,一次的位移向量为右下,并进行旋转,一次的位移向量为左上,并进行缩放(当然我们肯定是先进行的旋转,再进行的位移),其中trans即最终的变换矩阵,我们在计算完毕后用有Matrix4fv后缀的glUniform函数将其发送给着色器
- glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans)):第1个参数是uniform的位置值,第2个参数表示我们需要传送的矩阵个数,这里肯定是1,第三个参数表示是否对矩阵进行置换(Transpose),也就是说交换矩阵的行和列,最后一个参数才是真正的矩阵数据,但是GLM并不是把它们的矩阵储存为OpenGL所希望接受的那种,因此我们要先用GLM的自带的函数value_ptr来变换这些数据
其中对于第3个参数,OpenGL开发者通常使用一种内部矩阵布局,叫做列主序(Column-major Ordering)布局,GLM的默认布局就是列主序,所以我们并不需要置换矩阵,填GL_FALSE,
所谓的列主序布局,即是将长度为n的向量表示成的形式,并且与矩阵进行乘法运算时只能使用左乘(left or pre-multiplication)
其实顶点着色器我们当然也要修改,多一个uniform的变量,应该很容易理解:
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec4 color;
layout (location = 2) in vec2 texture;
out vec4 colorIn;
out vec2 texIn;
uniform mat4 transform;
void main()
{
gl_Position = transform * vec4(position, 1.0);
colorIn = color;
texIn = vec2(texture.x, 1.0f - texture.y);
}
好了到这就大功告成了,完整代码如下,其中片段着色器和Shader.h的代码可以在SOIL库这一章找到,没有修改
#include<iostream>
#include<opengl/glew.h>
#define GLEW_STATIC
#include<GLFW/glfw3.h>
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
#include<glm/gtc/type_ptr.hpp>
#include"Shader.h"
#include<opengl/freeglut.h>
#include<SOIL.h>
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
const GLuint WIDTH = 800, HEIGHT = 600;
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
glewExperimental = GL_TRUE;
glewInit();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
Shader shaderYellow("VShader.txt", "FShaderY.txt");
GLfloat vertices[] =
{
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f
};
GLuint indices[] =
{
0, 1, 2, //用前3个顶点绘制第一个三角形
1, 2, 3 //用后3个顶点绘制第二个三角形
};
GLuint VBO, EBO, VAO, texture;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glGenTextures(1, &texture);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBindTexture(GL_TEXTURE_2D, texture);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
int picWidth, picHeight;
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);
unsigned char* image = SOIL_load_image("timg.jpg", &picWidth, &picHeight, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, picWidth, picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GLint transformLoc = glGetUniformLocation(shaderYellow.Program, "transform");
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, texture);
shaderYellow.Use();
GLint transformLoc = glGetUniformLocation(shaderYellow.Program, "transform");
glm::mat4 trans = glm::translate(glm::mat4(1.0f), glm::vec3(0.5f, -0.5f, 0.0f));
trans = glm::rotate(trans, (GLfloat)glfwGetTime(), glm::vec3(0.0f, 0.0f, 1.0f));
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
GLfloat val = sin(glfwGetTime());
trans = glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, 0.5f, 0.0f));
trans = glm::scale(trans, glm::vec3(val, val, val));
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glfwSwapBuffers(window);
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}