主要内容总结自文章http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-3-matrices/
所谓的坐标系变换就是调用某个函数后得到的坐标。
关系如下图:(当时小谢给我画的~)
绿色框框内的表示需要自己写代码的~
- 齐次坐标(Homogeneous coordinates)
三维顶点(x,y,z),引入新分量w,得到(x,y,z,w)。
- 若w==1,则向量(x, y, z, 1)为空间中的点。
- 若w==0,则向量(x, y, z, 0)为方向。
二者区别:对于旋转,二者无区别。当旋转点和方向时,结果是一样的。
但对于平移(将点沿着某个方向移动),平移一个方向是毫无意义的。
齐次坐标使得可以用同一个公式对点和方向作运算。
三种model transform(Translate,Rotate,Scale):
1、平移矩阵(Translation matrices)
平移矩阵是最简单的变换矩阵。
平移矩阵是这样的:
其中,X、Y、Z是点的位移增量。
例如,若想把向量(10, 10, 10, 1)沿X轴方向平移10个单位,可得:
这样就得到了齐次向量(20,10,10,1)!末尾的1表示这是一个点,而不是方向。经过变换计算后,点仍然是点。
对一个代表Z轴负方向的向量作上述平移变换:
还是原来的(0,0,-1,0)方向,恰好印证了前面的结论:”平移一个方向是毫无意义的”。
代码表示平移变换:
glm::mat4 TranslateMat = glm::translate(glm::mat4(1.0), glm::vec3(10.0, 0.0, 0.0));
2、缩放矩阵(Scaling matrices)
例如把一个向量(点或方向皆可)沿各方向放大2倍:
w还是没变。大多数情况一般不会去缩放向量,但在某些特殊情况下它就派上用场了。(单位矩阵只是缩放矩阵的一个特例,其(X, Y, Z) = (1, 1, 1)。单位矩阵同时也是旋转矩阵的一个特例,其(X, Y, Z)=(0, 0, 0))。
用C++表示:
glm::mat4 ScaleMat = glm::scale(glm::mat4(1.0), glm::vec3(2.0));
3、旋转矩阵(Rotation matrices)
(2)旋转XZ轴
(3)旋转YZ轴
用C++表示:
glm::mat4 RotateMat = glm::rotate(glm::mat4(1.0), gAngle, glm::vec3(0.0, 1.0, 0.0));
4、累积变换
把这些矩阵相乘就能将旋转、平移和缩放向量组合起来,例如:
glm::mat4 WorldTransformMat = TranslateMat * RotateMat * ScaleMat;
注意:首先执行缩放,接着旋转,最后才是平移。这就是矩阵乘法的工作方式。
变换的顺序不同,得出的结果也不同。
实际上,上述顺序正是在变换游戏角色或者其他物体时所需的:先缩放;再调整方向;最后平移。例如,假设有个船的模型(为简化,略去旋转):
-
错误做法:
- 按(10, 0, 0)平移船体。船体中心目前距离原点10个单位。
-
将船体放大2倍。以原点为参照,每个坐标都变成原来的2倍,就出问题了。最后您得到的是一艘放大的船,但其中心位于2*10=20。这并非您预期的结果。
-
正确做法:
- 将船体放大2倍,得到一艘中心位于原点的大船。
- 平移船体。船大小不变,移动距离也正确。
-
模型(Model)、观察(View)和投影(Projection)矩阵
1、模型矩阵
这个三维模型和可爱的红色三角形一样,由一组顶点定义。顶点的XYZ坐标是相对于物体中心定义的:也就是说,若某顶点位于(0,0,0),则其位于物体的中心。
我们希望能够移动它,玩家也需要用键鼠控制这个模型。只需记住:缩放旋转平移就够了。在每一帧中,用算出的这个矩阵去乘(在GLSL中乘,不是在C++中)所有的顶点,物体就会移动。唯一不动的是世界空间(World Space)的中心。
现在,物体所有顶点都位于世界空间。下图中黑色箭头的意思是:从模型空间(Model Space)(顶点都相对于模型的中心定义)变换到世界空间(顶点都相对于世界空间中心定义)。
下图概括了这一过程:
2、观察矩阵
摄像机的原理也是相通的。如果想换个角度观察一只猴子,您可以移动摄像机也可以移动猴子
起初,摄像机位于世界坐标系的原点。移动世界只需乘一个矩阵。假如你想把摄像机向右(X轴正方向)移动3个单位,这和把整个世界(包括网格)向左(X轴负方向)移3个单位是等效的!
glm::mat4 TranslateMat = glm::translate(glm::mat4(1.0), glm::vec3(0.0,0.0,3.0));
下图展示了:从世界空间(顶点都相对于世界空间中心定义)到摄像机空间(Camera Space,顶点都相对于摄像机定义)的变换。
趁脑袋还没爆炸,来欣赏一下GLM强大的glm::LookAt函数吧:
glm::mat4 CameraMatrix = glm::LookAt(
cameraPosition, // the position of your camera, in world space
cameraTarget, // where you want to look at, in world space
upVector // probably glm::vec3(0,1,0), but (0,-1,0) would make you looking upside-down, which can be great too
);
下图解释了上述变换过程:
3、投影矩阵
现在,我们处于摄像机空间中。这意味着,经历了这么多变换后,现在一个坐标X==0且Y==0的顶点,应该被画在屏幕的中心。但仅有x、y坐标还不足以确定物体是否应该画在屏幕上:它到摄像机的距离(z)也很重要!两个x、y坐标相同的顶点,z值较大的一个将会最终显示在屏幕上。
这就是所谓的透视投影(perspective projection):
好在用一个4x4矩阵就能表示这个投影:
// Generates a really hard-to-read matrix, but a normal, standard 4x4 matrix nonetheless
glm::mat4 projectionMatrix = glm::perspective(
FoV, // The horizontal Field of View, in degrees : the amount of "zoom". Think "camera lens". Usually between 90° (extra wide) and 30° (quite zoomed in)
4.0f / 3.0f, // Aspect Ratio. Depends on the size of your window. Notice that 4/3 == 800/600 == 1280/960, sounds familiar ?
0.1f, // Near clipping plane. Keep as big as possible, or you'll get precision issues.
100.0f // Far clipping plane. Keep as little as possible.
);
4、最后一个变换:
从摄像机空间(顶点都相对于摄像机定义)到齐次坐空间(Homogeneous Space)(顶点都在一个小立方体中定义。立方体内的物体都会在屏幕上显示)的变换。
最后一幅图示:
再添几张图,以便大家更好地理解投影变换。投影前,蓝色物体都位于摄像机空间中,红色的东西是摄像机的平截头体(frustum):这是摄像机实际能看见的区域。
用投影矩阵去乘前面的结果,得到如下效果:
此图中,平截头体变成了一个正方体(每条棱的范围都是-1到1,图不太明显),所有的蓝色物体都经过了相同的变形。因此,离摄像机近的物体就显得大一些,远的显得小一些。这和现实生活一样!
让我们从平截头体的”后面”看看它们的模样:
这就是您得到的图像!看上去太方方正正了,因此,还需要做一次数学变换使之适合实际的窗口大小。
这就是实际渲染的图像啦!
总结
- 第一步:创建模型观察投影(MVP)矩阵。任何要渲染的模型都要做这一步。
// Projection matrix : 45° Field of View, 4: ratio, display range : 0.1 unit 100 units
glm::mat4 Projection = glm::perspective(glm::radians(45.0f), 4.0f / 3.0f, 0.1f, 100.0f);
// Camera matrix
glm::mat4 View = glm::lookAt(
glm::vec3(4,3,3), // Camera is at (4,3,3), in World Space
glm::vec3(0,0,0), // and looks at the origin
glm::vec3(0,1,0) // Head is up (set to 0,-1,0 to look upside-down)
);
// Model matrix : an identity matrix (model will be at the origin)
glm::mat4 Model = glm::mat4(1.0f); // Changes for each model !
// Our ModelViewProjection : multiplication of our 3 matrices
glm::mat4 MVP = Projection * View * Model; // Remember, matrix multiplication is the other way around
- 第二步:把MVP传给GLSL
// Get a handle for our "MVP" uniform.
// Only at initialisation time.
GLuint MatrixID = glGetUniformLocation(programID, "MVP");
// Send our transformation to the currently bound shader,
// in the "MVP" uniform
// For each model you render, since the MVP will be different (at least the M part)
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
- 第三步:在GLSL中用MVP变换顶点
in vec3 vertexPosition_modelspace;
uniform mat4 MVP;
void main(){
// Output position of the vertex, in clip space : MVP * position
vec4 v = vec4(vertexPosition_modelspace,1); // Transform an homogeneous 4D vector
gl_Position = MVP * v;
}
- 搞定!三角形和第二课的一样,仍然在原点(0,0,0),然而是从点(4,3,3)透视观察的;摄像机的朝上方向为(0,1,0),视野(field of view)45°。