OPENGL 矩阵坐标系变换

主要内容总结自文章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)



所以在旋转中,下一帧图形的位置为:
(1)旋转XY轴

(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°。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值