现在的位置: 首页 > 综合 > 正文

计算用于阴影剔除的包围体(shadow culling volume)

2012年02月16日 ⁄ 综合 ⁄ 共 2724字 ⁄ 字号 评论关闭
我们在做剔除的时候一般要用到视锥,用视锥的6个平面作为裁剪平面。但是对于渲染shadow map的时候
是不能直接使用视锥做剔除的,因为视锥外的物体是有可能将阴影投射到视锥里的。这时我们需要一个专门
用于阴影剔除的包围体(culling volume)
那么如何构造这个包围体呢。我们将视锥向光源方向投影,可以得到一个凸多边形的轮廓。
轮廓上的每条边都对应于视锥上的一条边,我们将视锥从这些边处切成两半,将上半部沿光源方向无限拉长,
构成一个新的,被拉长的包围体。再将上半部分这个“盖子”去掉,最终得到的一个非封闭的,
长筒状的东西就是我们需要的包围体了。任何可能将阴影投进视锥的物体都不会被包围体裁掉。
这个过程有点类似shadow volume的计算,只是shadow volume没有去掉“盖子”的过程。

代码

  1. // 判断两个平面的交线是否是轮廓线   
  2. int TestSilhouette(const Plane& p0, const Plane& p1, const Vector3& dir)  
  3. {  
  4.     float t0 = p0.m_Normal.Dot(dir);  
  5.     float t1 = p1.m_Normal.Dot(dir);  
  6.     if (t0 * t1 > 0.0f)  
  7.     {  
  8.         return 0;  
  9.     }  
  10.   
  11.     return t0 > 0.0f ? 1 : 2;  
  12. }  
  13.   
  14.   
  15.   
  16. // 算法:根据光源方向计算视锥体的轮廓线,由轮廓线和光源方向构造包围平面   
  17. // 包围平面过轮廓线且与光源方向平行   
  18. void CreateLightCullingVolume(Camera* pCamera, const Vector3& lightDir, /*out*/vector<Plane>& cullingPlanes)  
  19. {  
  20.     // 12条棱的索引,排列顺序为:近平面顺时针4条棱,   
  21.     // 远平面顺时针4条棱,远近平面之间顺时针4条棱   
  22.     static unsigned short index[] =   
  23.     {  
  24.         1, 0, 0, 2, 2, 3, 3, 1,  
  25.         5, 4, 4, 6, 6, 7, 7, 5,  
  26.         1, 5, 0, 4, 2, 6, 3, 7,  
  27.     };  
  28.   
  29.     // 每条棱都是由两个平面相交构成的,以下是与棱对应的12对平面,   
  30.     // 每条棱在每对平面上的第一个平面上是顺序的,在第二个平面上是逆序的   
  31.     // 例如第一条棱(0,1)(近平面上面的那条棱)在第一个平面FP_Near上是顺序的,(右手系拇指朝向法线),   
  32.     // 在第二个平面FP_Top上是逆序的   
  33.     static EFrustumPlane planePair[][2] =   
  34.     {  
  35.         {FP_Near, FP_Top}, {FP_Near, FP_Right}, {FP_Near, FP_Bottom}, {FP_Near, FP_Left},   
  36.         {FP_Top, FP_Far}, {FP_Right, FP_Far}, {FP_Bottom, FP_Far}, {FP_Left, FP_Far},   
  37.         {FP_Top, FP_Left}, {FP_Right, FP_Top}, {FP_Bottom, FP_Right}, {FP_Left, FP_Bottom},  
  38.     };  
  39.   
  40.     const Vector3* pCorner = pCamera->GetFrustumCorners();  
  41.     // 依次测试12条棱是否为轮廓线   
  42.     for (int i = 0; i < 12; i++)  
  43.     {     
  44.         int testSilhouette = TestSilhouette(pCamera->GetPlane(planePair[i][0]), pCamera->GetPlane(planePair[i][1]), lightDir);  
  45.         // 是轮廓线   
  46.         if (testSilhouette > 0)  
  47.         {  
  48.             // 如果是第一个平面,边是顺着的,如果是第二个平面,边是逆着的   
  49.             Vector3 edgeDir;  
  50.             if (testSilhouette == 1)  
  51.             {  
  52.                 edgeDir = pCorner[index[i * 2 + 1]] - pCorner[index[i * 2 + 0]];  
  53.             }  
  54.             else  
  55.             {  
  56.                 edgeDir = pCorner[index[i * 2 + 0]] - pCorner[index[i * 2 + 1]];  
  57.             }  
  58.             // 由轮廓线构造剔除平面,法线朝里   
  59.             Vector3 normal = lightDir.Cross(edgeDir);  
  60.             normal.Normalize();  
  61.   
  62.             // 将法线保存起来用于剔除   
  63.             m_LightPlanes.push_back(Plane(normal, pCorner[index[i * 2 + 0]]));  
  64.         }  
  65.     }  
  66.   
  67.     // 视锥平面中朝向光源的也都是剔除平面   
  68.     for (int i = 0; i < 6; i++)  
  69.     {  
  70.         const Plane& plane = pCamera->GetPlane((EFrustumPlane)i);  
  71.         if (plane.m_Normal.Dot(lightDir) > 0.0f)  
  72.         {  
  73.             vector<Plane>.push_back(plane);  
  74.         }  
  75.     }  
  76. }  

抱歉!评论已关闭.