在较新的GLM 版本中,创建一个矩阵时*切记一定*要给出初值(对角线初值),否则计算结果会严重出错:
glm::mat4 trans;//NO
trans = glm::translate(trans, glm::vec3(1, 1, 0));//结果严重出错
trans = glm::translate(glm::mat4(1), glm::vec3(1, 1, 0));//结果正确无误
(一般初值都为1)
另外,结果矩阵是列主序的,而不是行主序的
什么是行主序or列主序?对于一个矩阵m来说:
m[i][j];//行主序
M[j][i];//列主序
虽然实现都是二维数组,但两者对元素的逻辑排列顺序不同,行主序先从左往右,再从上往下阅读,列主序先从上往下,再从左往右阅读,这是因为OpenGL内部就是使用列主序的矩阵的,而DirectX则使用行主序
关于两者的关系:
M(行)=M(列)转置(Transpose)
另外,在使用GLM的过程中,尽量使用弧度制而非角度制,避免遇到计算错误
源码地址:https://learnopengl.com/code_viewer.php?type=header&code=camera
(注意上面写的内容,对里面内容做少量修改)
前置准备工作
这是整个摄像机类的成员组成,其中私有成员函数updateCameraVector用于根据之上的公有成员来修改该摄像机的欧拉角参数,接下来逐个解释各个成员的实现细节
1.Position、Front、Up、Right、WorldUp分别表示摄像机位置、摄像机前轴、摄像机上轴、摄像机右轴、世界上轴(这5个向量组成了一个完整的摄像机坐标系)
2.Yaw、Pitch分别表示偏航角和俯仰角,用于鼠标控制摄像机,主要影响前轴,从而影响上轴和右轴
3.MovementSpeed、MouseSensitivity、Zoom为杂项,字面意思
该类拥有两个构造函数:前者接受Position、Up(均为向量)、Yaw和Pitch(都具有默认参数),其余值默认给定,其中Front默认置为(0,0,-1),其余同理。
后者接受posX,posY....upX..upZ(标量)、Yaw和Pitch,其余同上,两者作用一致,一个接受向量,一个接受标量而已。
注意在其最后调用的updateCameraVector,其根据给定的Yaw和Pitch值计算出一个摄像机的初始朝向(毕竟在构造函数里我们没有指定右轴)
GetViewMatrix根据得到的摄像机位置、前轴和上轴提供给GLM供其构造lookAt矩阵
ProcessKeyboard函数由外界的按键回调函数调用,根据按下键值更新摄像机位置值。
ProcessMouseMovement由外界鼠标动作回调函数调用,这里的xoffset和yoffset已经由调用方计算好,作为参数传递入内,另外防止俯仰角超出90度发生错误,最后调用updateCameraVector更新摄像机的3个轴。
ProcessMouseScroll函数处理鼠标滚轮操作,只需要滚轮垂直方向的偏移yoffset。
updateCameraVector,唯一的私有成员函数,front根据Yaw和Pitch的值更新其3个分量,至于为什么是这么写的:(在硬盘报废前才写过,再写一遍也无妨了)
之前说过,摄像机的3个轴(前轴、上轴、右轴)共同构成了一个右手坐标系(前轴对应y轴,上轴对应z轴,右轴对应x轴),在已知偏航和俯仰角的情况下,怎么计算出前轴的各个分量(毕竟前轴关乎摄像机的指向)?这是一个典型的球坐标转笛卡尔坐标的问题:
上轴(Y)蓝色,前轴(Z)绿色,右轴(X)红色,俯仰角定义为A,偏航角定义为B(是的,就是这个角,定义的就是这样,很诡异)
得到:
y=sinA
x=cosAcosB
z=cosAsinB
问题解决
之后根据得到的Front更新计算右轴和上轴即可
PS.在源码里为什么要写成lastY-yoffset?,因为在NDC和屏幕坐标里,y轴正方向是相反的,NDC里向上,屏幕里向下
*最后再提一下位置值这个东西
教程第一章有两个地方出现了位置值,第一次是顶点属性指针,第二次是纹理单元
所以这个“位置值”到底应该怎么理解
直观的来看,VAO是一个容器,其中内含着顶点属性指针,注意看这些指针最后的那个数字,那个就是“位置值”,所以从这个角度来看位置值是用于区分同类型对象的
再看一下顶点着色器:
layout (location = 0) in vec3 position;
location代表的就是位置值的意思,position是这个vec3变量的名字,这句的意思就是:”位置值为0的顶点属性指针指向的数据,应当作为position的内容,进入顶点着色器中“
那么在配置顶点属性指针时有:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
第一个参数指定了该顶点属性指针的位置值,在这里,手动的将位置值设置为0 ,而该顶点属性指针配置的就是位置坐标。
最后再激活该顶点属性指针即可,参数为要激活的顶点属性指针的位置值:
glEnableVertexAttribArray(0);
纹理单元中的位置值含义同上。