这是对learnopengl的简单笔记。原教程网址:learnopengl。原教程同时涉及图形学的基本理论与opengl API,本文更多关注API,而简化甚至省略了背后的图形学原理性内容。
常见的变换有放缩、平移、旋转。齐次坐标下它们的矩阵都很好推,略。此外还有仿射变换、投影变换等。
旋转变化中,很容易想到3维空间的欧拉角表示。但无论如何怎样决定xyz旋转的顺序,都可能导致万向节死锁,比如x-y-z顺序,可能x转完后,y转的时候把z移动到了x的位置,导致丢失了一个自由度,这意味着有部分旋转无法用单一固定的顺序表示。解决方法是四元数。
用GLM库
只用把GLM中的glm文件夹放入项目的include文件夹(或者加入到项目的include路径)就能使用,它全是头文件。常用以下三个:
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
初始化一个矩阵,新版本初始化矩阵默认是0矩阵,老版本模式是单位矩阵。要添加命名空间。初始化一个单位矩阵如下:
//老版本直接初始化.
glm::mat4 trans; //4*4 matrix
//新版本初始化单位矩阵
glm::mat4 trans = glm::mat4(1.0f); //maybe glm::mat4 trans(1.0f) is Okay too.
用GLM融合多个变换矩阵(就是矩阵相乘)。
GLM生成某种变换的矩阵,本质上就是合成到一个已有的变换矩阵(如果没有,就单位矩阵)中。
glm::mat4 trans; //must be a unit matrix
//平移translate.
trans = translate(trans,glm::vec3(1.0,1.0,0.0)); //第一个参数是要融合的矩阵,第二个是平移方向.
//放缩scale
trans = scale(trans,glm::vec3(0.5,0.5,0.1)); //第二个参数是将每个轴变为原来的多少倍.
//旋转rotate
trans = rotate(trans, glm::radiants(90.0),glm::vec3(0.0,0.0,0.1));
//第二个参数是弧度制的旋转角度,用radiants转化角度为弧度制.
//第三个参数是绕轴旋转的那个轴,这里是z轴。
代码中GLM合成变换矩阵的顺序是从左往右的,也就是说如果用合成矩阵代码的顺序为A,B,C,等效与A*B*C。所以实际的顺序其实是C,B,A。代码顺序是从左往右的!
GLM和OpenGL采用列主序的内存布局(fotran),一般的C等语言都是行主序的。所以将矩阵传给着色器的矩阵,会有一个是否转置矩阵的选项,这就是问要不要把行主序转化为列主序!
着色器中的变换矩阵
顶点的变换应该通过着色器在GPU中完成,而不是每次都在渲染循环内完成。通过着色器定义mat4的uniform完成。注意着色器语言当然有内建的矩阵数据类型。
比如,在着色器中将顶点用旋转矩阵旋转90度:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 transform; //glUniformMatrix4fv()设置这个uniform变换矩阵
void main()
{
gl_Position = transform * vec4(aPos, 1.0f);
TexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y); //旋转90度的讨巧.
}
而在主循环中:
glm::mat4 trans;
trans = rotate(trans,glm::radiants(90),glm::vec3(0.0, 0.0, 0.1));
unsigned int transformLoc = getUniformLocation(ourShader.ID,"transform");
glUniformMatrix4fv(transformLoc,1,GL_FALSE,(glm::value_ptr(trans)));
最后一个函数,第一个是uniform位置编号,第二个是要传送几个矩阵,第三个是是否要转置,第四个是要传送的数据。注意用 glm::value_ptr()转化矩阵为OpenGL可以识别接受的格式。