详解MVP矩阵之ProjectionMatrix

发表于2017-09-07
评论0 9.8k浏览

简介

透视投影是3D固定流水线的重要组成部分,是将相机空间中的点从视锥体(frustum)变换到规则观察体(Canonical View Volume)中,待裁剪完毕后进行透视除法的行为。在算法中它是通过透视矩阵乘法和透视除法两步完成的。

 

代码实现

 

首先是Perspective的Camera,用Unity的Camera来作为参照。

 

 

(0,0,0)点放一个quad



加一个脚本来测试一下

  1. Debug.Log("ProjectionMatrix\n"   GetComponent().projectionMatrix);  
  2. Debug.Log("ViewMatrix\n"   GetComponent().worldToCameraMatrix);  
  3. Debug.Log("ModeMatrix\n"   mesh.transform.localToWorldMatrix);  

     

打印出相机的矩阵,结果如下

 

打印quad各个顶点的位置。

 


经过计算

P * V *M * Vector4(pos, 1) ,得到结果

 


C 代码 copy

  1. camera = new Camera(Vector3(0, 1, -10), 60,16.0f / 9, 0.3f, 1000);  
  2.     Matrix4x4 model = Matrix4x4::identity;  
  3.    
  4.     qDebug() <<"ProjectionMatri: " << camera->GetProjectionMatrix();  
  5.     qDebug() << "ViewMatrix: " << camera->GetViewMatrix();  
  6.     qDebug() << "ModelMatrix: " << model;  
  7.    
  8.     Vector3 vertice0(-0.5f, -0.5F, 0);  
  9.     Vector3 vertice1(0.5f, 0.5F, 0);  
  10.     Vector3 vertice2(0.5f, -0.5F, 0);  
  11.     Vector3 vertice3(-0.5f, 0.5F, 0);  
  12.    
  13.     Matrix4x4 mvp = camera->GetProjectionMatrix() * camera->GetViewMatrix() * model;  
  14.     //Vector3 tmp = mvp * vertice0;  
  15.     qDebug() << "vertice0" << mvp * Vector4(vertice0.x, vertice0.y, vertice0.z, 1);  
  16.     qDebug() << "vertice1" << mvp * Vector4(vertice1.x, vertice1.y, vertice1.z, 1);  
  17.     qDebug() << "vertice2" << mvp * Vector4(vertice2.x, vertice2.y, vertice2.z, 1);  
  18.     qDebug() << "vertice3" << mvp * Vector4(vertice3.x, vertice3.y, vertice3.z, 1);  
  19.    


结果


 

其中

Perspective函数如下

  1. Matrix4x4 Transform::Perspective(float fovy, float aspect, float zNear, float zFar)  
  2. {  
  3.      
  4.     float tanHalfFovy = tan(Mathf::Deg2Rad * fovy / 2);  
  5.    
  6.     Matrix4x4 result(0.0f);  
  7.     result[0 * 4   0] = 1.0f / (aspect * tanHalfFovy);  
  8.     result[1 * 4   1] = 1.0f / (tanHalfFovy);  
  9.     result[2 * 4   3] = -1.0f;  
  10.    
  11.     result[2 * 4   2] = -(zFar   zNear) / (zFar - zNear);  
  12.     result[3 * 4   2] = -(2.0f * zFar * zNear) / (zFar - zNear);  
  13.     return result;  
  14. }  
  15.    


同样功能的还有Frustum函数 copy

  1. Matrix4x4 Transform::Frustum(float l, float r, float b, float t, float zNear, float zFar)  
  2. {  
  3.     Matrix4x4 result(0.0f);  
  4.    
  5.     result[0] = 2 * zNear / (r - l);  
  6.     result[8] = (r   l) / (r - l);  
  7.     result[5] = 2 * zNear / (t - b);  
  8.     result[9] = (t   b) / (t - b);  
  9.     result[10] = -(zFar   zNear) / (zFar - zNear);  
  10.     result[14] = -(2 * zFar * zNear) / (zFar - zNear);  
  11.     result[11] = -1;  
  12.     result[15] = 0;  
  13.     return result;  
  14. }  


 

Opengl的流水线如下图



乘完了MVP矩阵,得到的是Clip Coordinates。

坐标除以w之后(称为透视除法),得到了NDC坐标

 

最后通过线性变换,得到最终的屏幕空间坐标。

 

Opengl里面设置view port通常是这样 copy

  1. glViewport(x, y, w, h);  
  2. glDepthRange(n, f);  


对应关系如下

 


还是继续上面的例子进行计算

 

同除以w之后,得到NDC坐标

  1. Vector3 ndcPos0 = Vector3(clipPos0.x / clipPos0.w, clipPos0.y / clipPos0.w, clipPos0.z / clipPos0.w);  
  2. Vector3 ndcPos1 = Vector3(clipPos1.x / clipPos1.w, clipPos1.y / clipPos1.w, clipPos1.z / clipPos1.w);  
  3. Vector3 ndcPos2 = Vector3(clipPos2.x / clipPos2.w, clipPos2.y / clipPos2.w, clipPos2.z / clipPos2.w);  
  4. ector3 ndcPos3 = Vector3(clipPos3.x / clipPos3.w, clipPos3.y / clipPos3.w, clipPos3.z / clipPos3.w);  

 

在一个640* 360 的窗口中,

  1. int x = 0, y = 0, w = 640, h = 360, n = 0.3, f =1000;  
  2. Vector3 screenPos0(w * 0.5f * ndcPos0.x   x   w * 0.5f, h* 0.5f * ndcPos0.y   y   h *0.5f, 0.5f *(f - n) * ndcPos0.z   0.5f * (f   n));  
  3. Vector3 screenPos1(w * 0.5f * ndcPos1.x   x   w * 0.5f, h* 0.5f * ndcPos1.y   y   h *0.5f, 0.5f *(f - n) * ndcPos1.z   0.5f * (f   n));  
  4. Vector3 screenPos2(w * 0.5f * ndcPos2.x   x   w * 0.5f, h* 0.5f * ndcPos2.y   y   h *0.5f, 0.5f *(f - n) * ndcPos2.z   0.5f * (f   n));  
  5. Vector3 screenPos3(w * 0.5f * ndcPos3.x   x   w * 0.5f, h* 0.5f * ndcPos3.y   y   h *0.5f, 0.5f *(f - n) * ndcPos3.z   0.5f * (f   n));  

 

和最终显示的结果一致。

 

完整的代码 copy

  1. //Perspective matrix test  
  2. *  
  3. camera = new Camera(Vector3(0, 1, -10), 60, 16.0f / 9, 0.3f, 1000);  
  4. Matrix4x4 model = Matrix4x4::identity;  
  5.   
  6. qDebug() <<"ProjectionMatri: " << camera->GetProjectionMatrix();  
  7. qDebug() << "ViewMatrix: " << camera->GetViewMatrix();  
  8. qDebug() << "ModelMatrix: " << model;  
  9.   
  10. Vector3 vertice0(-0.5f, -0.5F, 0);  
  11. Vector3 vertice1(0.5f, 0.5F, 0);  
  12. Vector3 vertice2(0.5f, -0.5F, 0);  
  13. Vector3 vertice3(-0.5f, 0.5F, 0);  
  14.   
  15. Matrix4x4 mvp = camera->GetProjectionMatrix() * camera->GetViewMatrix() * model;  
  16. //Vector3 tmp = mvp * vertice0;  
  17.   
  18. Vector4 clipPos0 = mvp * Vector4(vertice0.x, vertice0.y, vertice0.z, 1);  
  19. Vector4 clipPos1 = mvp * Vector4(vertice1.x, vertice1.y, vertice1.z, 1);  
  20. Vector4 clipPos2 = mvp * Vector4(vertice2.x, vertice2.y, vertice2.z, 1);  
  21. Vector4 clipPos3 = mvp * Vector4(vertice3.x, vertice3.y, vertice3.z, 1);  
  22.   
  23. qDebug() << "clipPos0" << clipPos0;  
  24. qDebug() << "clipPos1" << clipPos1;  
  25. qDebug() << "clipPos2" << clipPos2;  
  26. qDebug() << "clipPos3" << clipPos3;  
  27.   
  28. Vector3 ndcPos0 = Vector3(clipPos0.x / clipPos0.w, clipPos0.y / clipPos0.w, clipPos0.z / clipPos0.w);  
  29. Vector3 ndcPos1 = Vector3(clipPos1.x / clipPos1.w, clipPos1.y / clipPos1.w, clipPos1.z / clipPos1.w);  
  30. Vector3 ndcPos2 = Vector3(clipPos2.x / clipPos2.w, clipPos2.y / clipPos2.w, clipPos2.z / clipPos2.w);  
  31. Vector3 ndcPos3 = Vector3(clipPos3.x / clipPos3.w, clipPos3.y / clipPos3.w, clipPos3.z / clipPos3.w);  
  32.   
  33. qDebug() << "ndcPos0" << ndcPos0;  
  34. qDebug() << "ndcPos1" << ndcPos1;  
  35. qDebug() << "ndcPos2" << ndcPos2;  
  36. qDebug() << "ndcPos3" << ndcPos3;  
  37.   
  38. int x = 0, y = 0, w = 640, h = 360, n = 0.3, f =1000;  
  39. Vector3 screenPos0(w * 0.5f * ndcPos0.x   x   w * 0.5f, h* 0.5f * ndcPos0.y   y   h *0.5f, 0.5f *(f - n) * ndcPos0.z   0.5f * (f   n));  
  40. Vector3 screenPos1(w * 0.5f * ndcPos1.x   x   w * 0.5f, h* 0.5f * ndcPos1.y   y   h *0.5f, 0.5f *(f - n) * ndcPos1.z   0.5f * (f   n));  
  41. Vector3 screenPos2(w * 0.5f * ndcPos2.x   x   w * 0.5f, h* 0.5f * ndcPos2.y   y   h *0.5f, 0.5f *(f - n) * ndcPos2.z   0.5f * (f   n));  
  42. Vector3 screenPos3(w * 0.5f * ndcPos3.x   x   w * 0.5f, h* 0.5f * ndcPos3.y   y   h *0.5f, 0.5f *(f - n) * ndcPos3.z   0.5f * (f   n));  
  43.   
  44. qDebug() << "screenPos0" << screenPos0;  
  45. qDebug() << "screenPos1" << screenPos1;  
  46. qDebug() << "screenPos2" << screenPos2;  
  47. qDebug() << "screenPos3" << screenPos3;*/  


正交投影

在正交相机中,除了projection矩阵计算和透视相机的计算不一样,其他流程都一样。这里给出正交矩阵的计算代码.

先看Unity,相机参数设置如下

注意一下size指的是

This is half of the vertical size of theviewing volume. Horizontal viewing size varies depending on viewport's aspectratio.

 

正交相机常常用于UI显示。

对于一个640*360的屏幕,size就等于h/2 = 180.


 

这时候得到的透视矩阵是


 

C 代码

  1. Matrix4x4 Transform::OrthoFrustum(float l, float r, float b, float t, float zNear, float zFar)  
  2. {  
  3.     Matrix4x4 result(0.0f);  
  4.     result[0] = 2 / (r - l);  
  5.     result[5] = 2 / (t - b);  
  6.     result[10] = -2 / (zFar - zNear);  
  7.     result[15] = 1;  
  8.    
  9.     //result[12] = -(r   l) / (r - l);  
  10.     //result[13] = -(t   b) / (t - b);  
  11.     result[14] = -(zFar   zNear) / (zFar - zNear);  
  12.    
  13.     return result;  
  14. }  
  15.     Matrix4x4 projection = Transform::OrthoFrustum(0.0f, static_cast(creationFlags.width), 0.0f, static_cast(creationFlags.height), 0.3, 1000);  
  16.     qDebug() << "Orthographic ProjectionMatri: " << projection;  


 

运行结果



后面计算的流程基本和透视投影的计算一致。


至此,MVP完全搞定!

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引