OpenGL入门——矩阵变换与坐标系统
一、OpenGL的数学库GLM
向量和矩阵的运算就不作说明了,直接介绍OpenGL中如何使用矩阵变换。
GLM(官网:OpenGL Mathematics (g-truc.net))是OpenGL Mathematics的缩写,它是一个只有头文件的库,也就是说只需包含对应的头文件就行了,不用链接和编译。把头文件的根目录复制到项目的includes文件夹,就可以使用这个库了。
GLM大多功能都包含在以下四个头文件中
#include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp>
向量位移示例
//向量平移:向量(1, 0, 0)位移(1, 1, 0)个单位 void vector_translate() { glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);//定义一个向量 glm::mat4 trans = glm::mat4(1.0f);//定义4x4单位矩阵 trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));//创建位移矩阵:translate函数将给定矩阵乘以位移向量得到位移变换矩阵 vec = trans * vec;//变换矩阵与向量相乘得到位移后的向量 std::cout << vec.x << "," << vec.y << "," << vec.z << std::endl;//输出:2,1,0 }
上一节OpenGL入门——多个纹理 - 一只小瓶子 - 博客园 (cnblogs.com)进行旋转缩放平移示例
在顶点着色器中将顶点坐标进行旋转缩放
//vertex shader source #version 330 core layout(location = 0) in vec3 position; //位置X,Y,Z layout(location = 1) in vec3 color; //颜色R,G,B layout(location = 2) in vec2 texture; //纹理S,T out vec3 vertexColor; //顶点颜色 out vec2 textureCoord; //顶点对应纹理坐标 uniform mat4 transform; //变换矩阵 void main() { gl_Position = transform * vec4(position, 1.0); //顶点坐标左乘变换矩阵 vertexColor = color; //从顶点数据那里得到的输入颜色 textureCoord = texture; //从顶点数据那里得到的对应纹理坐标 }
把变换矩阵传递给着色器
//定义变换矩阵 glm::mat4 trans = glm::mat4(1.0f);//定义4x4单位矩阵 trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));//平移到右下角 trans = glm::scale(trans, glm::vec3(0.5, 0.5, 1.0));//x,y轴缩小到一半 trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));//绕着z轴旋转90度(参数为弧度值) unsigned int transformLoc = glGetUniformLocation(shader.getShaderID(), "transform");//获得着色器中变换矩阵位置 glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));//赋值变换矩阵:因为是左乘变换矩阵,所以是先旋转再缩放再平移
注意变换顺序,因为着色器中顶点是左乘变换矩阵的,所以变换顺序是相反的(先旋转再缩放后平移)
示例效果
二、坐标系统
物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统,将物体的坐标变换到几个过渡坐标系的优点在于,在这些特定的坐标系统中,一些操作或运算更加方便和容易。
5个重要的坐标系统:
- 局部空间(Local Space,或者称为物体空间(Object Space))
- 世界空间(World Space)
- 观察空间(View Space,或者称为视觉空间(Eye Space))
- 裁剪空间(Clip Space)
- 屏幕空间(Screen Space)
这就是一个顶点在最终被转化为片段之前需要经历的所有不同状态
- 局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。
- 下一步是将局部坐标变换为世界空间坐标,世界空间坐标是处于一个更大的空间范围的。这些坐标相对于世界的全局原点,它们会和其它物体一起相对于世界的原点进行摆放。
- 接下来我们将世界坐标变换为观察空间坐标,使得每个坐标都是从摄像机或者说观察者的角度进行观察的。
- 坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上。
- 最后,我们将裁剪坐标变换为屏幕坐标,我们将使用一个叫做视口变换(Viewport Transform)的过程。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。这一阶段在顶点着色器运行的最后被自动执行。
第4步将观察坐标转换为裁剪坐标的方式称为投影,投影的形式有两种:正射投影(类似立方体的平截头箱)和透视投影(近大远小)。
从局部坐标到裁剪坐标的公式:
三、OpenGL 3D
进行3D绘图时,首先在顶点着色器中进行坐标转换
//vertex shader source #version 330 core layout(location = 0) in vec3 position; //位置X,Y,Z layout(location = 1) in vec3 color; //颜色R,G,B layout(location = 2) in vec2 texture; //纹理S,T out vec3 vertexColor; //顶点颜色 out vec2 textureCoord; //顶点对应纹理坐标 uniform mat4 model; //模型矩阵矩阵:局部空间->世界空间 uniform mat4 view; //观察矩阵:世界空间->观察空间 uniform mat4 projection;//透视投射矩阵:观察空间->裁剪空间 void main() { gl_Position = projection * view * model * vec4(position, 1.0); //顶点坐标变换 vertexColor = color; //从顶点数据那里得到的输入颜色 textureCoord = texture; //从顶点数据那里得到的对应纹理坐标 }
然后定义变换矩阵传入着色器中
///定义变换矩阵 //模型矩阵矩阵:局部空间->世界空间 glm::mat4 model = glm::mat4(1.0f); model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));//绕X轴旋转-55度 unsigned int transModelLoc = glGetUniformLocation(shader.getShaderID(), "model");//获得变换矩阵位置 glUniformMatrix4fv(transModelLoc, 1, GL_FALSE, glm::value_ptr(model));//赋值变换矩阵 //观察矩阵:世界空间->观察空间 glm::mat4 view = glm::mat4(1.0f); view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));//向Z轴负方向移动3,相当于把物体往后移动 unsigned int transViewLoc = glGetUniformLocation(shader.getShaderID(), "view");//获得变换矩阵位置 glUniformMatrix4fv(transViewLoc, 1, GL_FALSE, glm::value_ptr(view));//赋值变换矩阵 //透视投射矩阵:观察空间->裁剪空间 glm::mat4 projection = glm::mat4(1.0f); projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);//第2个参数是:窗口宽度 / 窗口高度 unsigned int transProjectionLoc = glGetUniformLocation(shader.getShaderID(), "projection");//获得变换矩阵位置 glUniformMatrix4fv(transProjectionLoc, 1, GL_FALSE, glm::value_ptr(projection));//赋值变换矩阵
效果图