模型(Model)、视图(View)和投影(Projection)矩阵
1。模型矩阵(在Object Coordinates内)
这个三维模型,和我们心爱的红色三角形一样,是由一组顶点定义的。顶点的XYZ坐标是相对于物体中心定义的:也就是说,若某顶点位于(0, 0, 0),它就在物体的中心。
也许玩家需要用键鼠控制这个模型,所以我们希望能够移动它。这简单,只需学会:缩放*旋转*平移就行了。在每一帧中,用算出的这个矩阵,去乘(在GLSL中乘,不是C++中!)所有的顶点,物体就动了。唯一不动的就是世界坐标系(World Space)的中心
现在,物体所有顶点都位于世界坐标系。下图中黑色箭头的意思是:从模型坐标系(Model Space)(顶点都相对于模型的中心定义)变换到世界坐标系(顶点都相对于世界坐标系中心定义)。
下图概括了这一过程:
2。视图矩阵(World Coordinates)
起初,相机位于世界坐标系的原点。移动世界只需乘上一个矩阵。假如你想把相机向右(X轴正方向)移动3个单位,这和把整个世界(包括网格)向左(X轴负方向)移3个单位是等效的!
下图展示了:从世界坐标系(顶点都相对于世界坐标系中心定义)到观察坐标系(Camera Space,顶点都相对于相机定义)的变换。
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。投影矩阵(Eye Coordinates)
现在,我们处于观察坐标系中。这意味着,经历了这么多变换后,现在一个坐标为(0,0)的顶点,应该被画在屏幕的中心。但仅有x、y坐标还不足以确定物体是否应该画在屏幕上:它到相机的距离(z)也很重要!两个x、y坐标相同的顶点,z值较大的一个将会最终显示在屏幕上。
这就是所谓的透视投影(perspective projection):
好在用一个4×4矩阵就能表示这个投影¹ :
// Generates a really hard-to-read matrix, but a normal, standard 4×4 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.
);
最后一个变换:
从观察坐标系(顶点都相对于相机定义)到齐次坐标系(Homogeneous Space)(顶点都在一个小立方体中定义。立方体内的物体都会在屏幕上显示)的变换。
最后一幅图示:
再添几张图,以便大家更好地理解投影变换。投影前,蓝色物体都位于观察坐标系中,红色的东西是相机的视域四棱锥(frustum):这是相机实际能看见的区域。
用投影矩阵去乘前面的结果,得到如下效果:
此图中,视域四棱锥变成了一个正方体(每条棱的范围都是-1到1,图上不太明显),所有的蓝色物体都经过了相同的形变。因此,离相机近的物体就显得大一些,远的显得小一些。和真实生活中一样!
让我们从视域四棱锥的“后面”看看它们的模样:
这就是你得出的图像了!看上去太方方正正了,因此,还需要做一次数学变换使之适合实际的窗口大小:
这就是实际渲染的图像啦!
4。复合变换:模型视图投影矩阵(MVP)
… 再来一串亲爱的矩阵乘法:
// C++ : compute the matrix
glm::mat3 MVPmatrix = projection * view * model; // Remember : inverted !
// GLSL : apply it
transformed_vertex = MVP * in_vertex;
总结
第一步:创建模型视图投影(MVP)矩阵。任何要渲染的模型都要做这一步。
// Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit 100 units
glm::mat4 Projection = glm::perspective(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, remember ?
gl_Position = MVP * v;
}
opengl坐标系的变换
Object Coordinates
对象的本地坐标系——任何变换之前的最初位置.为了变换(transformation)这些对象,在opengl es1.0,1.1中可以调用glRotate(),glTranslatef(),glScalef()这些方法。
Eye Coordinates
在opengl es1.0,1.1中:
使用GL_MODELVIEW矩阵和Object 坐标相乘所得。在OpenGL中用GL_MODELVIEW将对象对象空间(Object Space)变换到视觉空间(eye space)。GL_MODELVIEW矩阵是模型矩阵(Model Matrix)和视觉矩阵(View Matrix)的组合 (Mview * Mmodel)。其中,Model 变换将Object Space转换到World Space,View 变换是将World space变换到eye space
法线向量(Normal vectors)——从对象坐标系(Object coordinates)变换到视觉坐标系(eye coordinates),它是用来计算光照(lighting calculation)的.注意法线向量(Normal vectors)的变换和顶点的不同。其中视觉矩阵(view matrix)是GL_MODELVIEW逆矩阵的转置矩阵和法线向量(Normal vector)相乘所得,即:
剪切面坐标系(Clip Coordinates)
视觉坐标系和GL_PROJECTION矩阵相乘,得到剪切面坐标系。GL_PROJECTION矩阵定义了可视的空间(截头锥体)以及顶点数据如何投影到屏幕上(透视(perspective)或者正交化(orthogonal)),它被称为剪切面坐标系的原因是(x,y,z)变换之后要和±w比较
标准化设备坐标系(NDC)
将剪切面坐标系除以w所得,它被称为透视除法(perspective division)
.它更像是窗口坐标系,只是还没有转换或者缩小到屏幕像素。其中它取值范围在3个轴向从-1到1标准化了。
窗口坐标系(Window Coordinates)/屏幕坐标系(Screen Coordinates)
将标准化设备坐标系(NDC)应用于视口转换。NDC将缩小和平移以便适应屏幕的透视。窗口坐标系最终传递给OpenGL的管道处理变成了fragment。glViewPort()函数
用来定义最终图片映射的投影区域。同样,glDepthRange()用来决定窗口坐标系的z坐标。窗口坐标系由下面两个方法给出的参数计算出来
glViewPort(x,y,w,h);
glDepthRange(n,f);
视口转换公式很简单,通过NDC和窗口坐标系的线性关系得到:
附注
1 : […]好在用一个4×4矩阵就能表示这个投影:实际上,这句话并不对。透视变换不是仿射(affine)的,因此,透视投影无法完全由一个矩阵表示。向量与投影矩阵相乘之后,它齐次坐标的每个分量都要除以自身的W(透视除法)。W分量恰好是-Z(投影矩阵会保证这一点)。这样,离原点更远的点,被除了较大的Z值;其X、Y坐标变小,点与点之间变紧,物体看起来就小了,这才产生了透视效果。
【参考】:http://www.opengl-tutorial.org/zh-hans/beginners-tutorials-zh/tutorial-3-matrices-zh/