简单的Viewing Frustum Culling 投影矩阵推导视锥体 ,清晰

Viewing Frustum Culling是图形绘制流水线中,将不可见物体(即不在视锥体内的物体)提前剔除的操作。

在实践中,精确判断物体的可见性开销较大,因而通常用物体包围球或包围盒与视锥体(平截头体,View frustum)做相交测试,以此粗略判断物体是否可见。

进一步地,我们可以采用如下方式来大致判断一个球体与视锥体是否相交:

球与视锥体相交的必要(非充分)条件是:其中心P与视锥体的6个面的符号距离(Signed distance)d均小于球的半径R。注意对于一个平面aX + bY+ cZ + d = 0 ([a, b, c]为平面法线方向N,d为平面与原点的符号距离), 一个点P(x, y, z)与该平面的符号距离为ax + by + cz + d。因此要粗略判断物体与视锥体是否相交,只需要将其包围球与视锥体的6个面分别计算符号距离即可,如果其与任一面的符号距离大于R,则可安全剔除。

那么如何算得视锥体每个面的面方程呢?一个比较通用的方法是求出视锥体的8个顶点,然后分别利用叉积和点积求出6个面的法线和原点距。 但如果我们已经知道投影矩阵,也可以用如下更简单的方法算得面方程:(以Direct3D为例,OpenGL与之相似,但需要注意的是其规范化设备坐标系的Z取值范围为[-1,1]而非[0,1])

对于一个场景,设物体空间中有一球体,中心坐标为P,半径为R,由某一视锥体定义的物体空间到规范化设备空间的变换矩阵为M= World *View *Projection。现需要求该视锥体各个面在物体空间中的面方程。回顾规范化设备坐标系的定义,我们可以很容易得知,M的效果可以看做是将视锥体的6个面分别变换到X=1, X=-1, Y=1, Y=-1, Z=0, Z=1这6个面上。例如,视锥体的近剪裁面经过M,会变换到Z=0平面上,远剪裁面变换到Z=1平面上,其他4个面也是类似变换。那么,对于位于视锥体近剪裁面上的任一一点P(x, y, z, 1),P' =P*M应该满足P'.z / P'.w = 0, 即P'.z = 0,展开P*M可得:x*M._13 + y*M._23 + z*M._33 + M._43=0。这正是近剪裁面在物体空间中的平面方程。

同理,对于远剪裁面有P'.z / P'.w = 1,展开P*M有x*(M._13-M._14) + y**(M._23-M._24)+ z*(M._33-M._34)+(M._43-M._44)=0,为远剪裁面在物体空间中的平面方程。

同理可得很容易求得其他4个面的面方程。

如下示例了使用DirectXMath的求面方程以及相交测试的完整代码:

  1. // mWVP is the Word-View-Projection Matrix
  2.  
  3. XMFLOAT4 plane[6];
  4. // x=1
  5. plane[0].x = mWVP._11 - mWVP._14;
  6. plane[0].y = mWVP._21 - mWVP._24;
  7. plane[0].z = mWVP._31 - mWVP._34;
  8. plane[0].w = mWVP._41 - mWVP._44;
  9. // x=-1
  10. plane[1].x = -mWVP._14 - mWVP._11;
  11. plane[1].y = -mWVP._24 - mWVP._21;
  12. plane[1].z = -mWVP._34 - mWVP._31;
  13. plane[1].w = -mWVP._44 - mWVP._41;
  14. // y=1
  15. plane[2].x = mWVP._12 - mWVP._14;
  16. plane[2].y = mWVP._22 - mWVP._24;
  17. plane[2].z = mWVP._32 - mWVP._34;
  18. plane[2].w = mWVP._42 - mWVP._44;
  19. // y=-1
  20. plane[3].x = -mWVP._14 - mWVP._12;
  21. plane[3].y = -mWVP._24 - mWVP._22;
  22. plane[3].z = -mWVP._34 - mWVP._32;
  23. plane[3].w = -mWVP._44 - mWVP._42;
  24. // z=1
  25. plane[4].x = mWVP._13 - mWVP._14;
  26. plane[4].y = mWVP._23 - mWVP._24;
  27. plane[4].z = mWVP._33 - mWVP._34;
  28. plane[4].w = mWVP._43 - mWVP._44;
  29. // z=0
  30. plane[5].x = -mWVP._13;
  31. plane[5].y = -mWVP._23;
  32. plane[5].z = -mWVP._33;
  33. plane[5].w = -mWVP._43;
  34. XMVECTOR xmPlane[6];
  35. // load and normalize
  36. for( UINT i=0; i<6; i++ )
  37. {
  38. xmPlane[i] = XMLoadFloat4( &plane[i] );
  39. xmPlane[i] = XMPlaneNormalize( xmPlane[i] );
  40. }
  41. // cull
  42. for( UINT s=0; s<objects.size(); s++ ) // traverse all objects
  43. {
  44. bool bInFrustum = true;
  45. XMVECTOR xmCenter = XMLoadFloat3( &objects[s].bSphere.center ); // bounding sphere
  46. float radius = objects[s].bSphere.radius;
  47. for( UINT i=0; i<6; i++ )
  48. {
  49. XMVECTOR xmD = XMPlaneDotCoord( xmPlane[i], xmCenter );
  50. float d;
  51. XMStoreFloat( &d, xmD );
  52. if( d > radius )
  53. {
  54. bInFrustum = false;
  55. break;
  56. }
  57. }
  58. objects[s].isInFrustum = bInFrustum;
  59. }

补充1:需要注意的是,透视投影的视锥体并非立方体,因此存在满足前述判断条件且与视锥体不相交的球体,但这种情况并不多见,故不做进一步判断)

补充2:另外请注意上述代码中面方程的符号。(例如plane[4]=[ -mWVP._13, -mWVP._23, -mWVP._33, -mWVP._43]定义了法线方向指向视锥体外的平面,而如果写作[ mWVP._13, mWVP._23, mWVP._33, mWVP._43],则定义的是法线方向指向视锥体内的平面,此时需要将判断条件更改为“若d<-R,则剔除”。)

posted on 2019-05-17 17:17  guanxi0808  阅读(1439)  评论(0编辑  收藏  举报

导航