1. 流程
GLM是OpenGL Mathematics的缩写,它是一个只有头文件的库,有了它,我们完成各种矩阵和向量操作就变得方便了许多,下载完 文件 后将它加入include目录即可
现在看看怎么来使用这个库:
首先是添加头文件:
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
然后我们创建一个(1,0,0)的初始坐标vec(齐次坐标):
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
怎么对这个坐标产生平移、旋转和缩放等操作呢?
我们可以先创建一个转换矩阵trans,以单位矩阵初始化:
glm::mat4 trans = glm::mat4(1.0f);
然后对它做一个位移(1, 1, 0)的转换:
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
然后绕z轴逆时针旋转90°:
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
最后0.5倍缩放:
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
转换完的矩阵与原坐标相乘即得到转换后的坐标:
vec = trans * vec;
如果要把它应用到顶点着色器中呢?
可以设定一个uniform变量将变换矩阵传入:
#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.0f);
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}
然后在程序中设置这个矩阵:
unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv函数的第一个参数是uniform的位置值。第二个参数告诉OpenGL我们将要发送多少个矩阵,这里是1。第三个参数询问我们是否希望对我们的矩阵进行转置(Transpose),也就是说交换我们矩阵的行和列。OpenGL开发者通常使用一种内部矩阵布局,叫做列主序(Column-major Ordering)布局。GLM的默认布局就是列主序,所以并不需要转置矩阵,我们填GL_FALSE。最后一个参数是真正的矩阵数据,但是GLM并不是把它们的矩阵储存为OpenGL所希望接受的那种,因此我们要先用GLM的自带的函数value_ptr来变换这些数据。
我们还可以加上实时旋转效果:
unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
//使用着色器程序
ourShader.use();
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
trans = glm::rotate(trans, timeValue/10000, glm::vec3(0.0f, 0.0f, 1.0f));
效果:
2. 练习
2.1 使用应用在箱子上的最后一个变换,尝试将其改变为先旋转,后位移。看看发生了什么,试着想想为什么会发生这样的事情
效果:
原因:矩阵的乘法不满足交换律
2.2 尝试再次调用glDrawElements画出第二个箱子,只使用变换将其摆放在不同的位置。让这个箱子被摆放在窗口的左上角,并且会不断的缩放(而不是旋转)。(sin函数在这里会很有用,不过注意使用sin函数时应用负值会导致物体被翻转)
重新计算变换矩阵再调用glDrawElements即可(貌似只能在原矩阵变量上修改):
while (!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
//调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 渲染指令
float timeValue = glfwGetTime();
float visi = (sin(timeValue) / 2.0f) + 0.5f;
int vertexColorLocation = glGetUniformLocation(ourShader.ID, "visibility");
unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
//使用着色器程序
ourShader.use();
glm::mat4 trans = glm::mat4(1.0f);
trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
trans = glm::rotate(trans, timeValue, glm::vec3(0.0f, 0.0f, 1.0f));
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniform2f(vertexColorLocation, visi, 1.0f);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture[0]);//绑定纹理,自动把纹理赋值给片段着色器的采样器
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture[1]);//绑定纹理,自动把纹理赋值给片段着色器的采样器
glBindVertexArray(VAO); // 一般每次使用时再绑定,这里只有一个VAO,所以实际上可以不绑
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
trans = glm::mat4(1.0f);
trans = glm::translate(trans, glm::vec3(-0.5f, 0.5f, 0.0f));
trans = glm::scale(trans1, glm::vec3(visi, visi, visi));
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//检查并调用事件,交换缓冲
glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上
glfwPollEvents();//检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)
}
效果: