视锥体剔除(Frustum Culling)无法正确过滤包围盒问题排查
视锥体剔除误判相交问题排查与解决
问题现象
视锥体剔除功能整体正常,但存在部分包围盒被误判为与视锥体相交的情况。在Z=-3的XY平面上设置了10×10的球体网格,球体间距为4单位,测试中大量不在视锥体内的球体被标记为相交状态(绿色表示内部,蓝色表示相交,红色表示外部)。
测试场景代码
for ( int X = -20; X <= 20; X += 2 ) { for ( int Y = -20; Y <= 20; Y += 2 ) { (...) SphereEntity->SetPosition ( glm::vec3 ( X * 2, Y * 2, -3 ) ); } }
视锥体参数
- 位置:(0, 0, 0)
- FOV:π/2
- 近平面:0.1
- 远平面:10
- 宽高比:1
- 朝向:-Z轴方向
碰撞检测函数
CollisionResult CheckAABBFrustumCollision ( const BoundingBox &Box, const Frustum &ViewFrustum ) { glm::vec3 Corners[8]; CalculateBoundingBoxCorners ( Box, Corners ); const Geometry::Plane *Planes = ViewFrustum.GetPlanesReference (); for ( unsigned PlaneIndex = 0; PlaneIndex < 6; ++PlaneIndex ) { unsigned Inside = 0, Outside = 0, Intersect = 0; for ( unsigned CornerIndex = 0; CornerIndex < 8; ++CornerIndex ) { float Distance = GetDistanceFromPlaneToPoint ( Planes[PlaneIndex], Corners[CornerIndex] ); if ( Distance < 0.0f ) ++Outside; else if ( Distance > 0.0f ) ++Inside; else ++Intersect; if ( ( ( Outside != 0 ) && ( Inside != 0 ) ) ||// Early exit. If there are points inside and outside the frustum, then it's automatically an intersection ( Intersect != 0 ) ) return CollisionResult::Intersect; } if ( Outside == 8 ) // if all corners of the bounding box are outside this plane, then it's guaranteed the bounding box is outside the frustum return CollisionResult::Outside; } return CollisionResult::Inside; } float GetDistanceFromPlaneToPoint ( const Plane &OtherPlane, const glm::vec3 &OtherPoint ) { return glm::dot ( OtherPlane.GetNormal (), OtherPoint - OtherPlane.GetPoint () ); }
排查后确认Intersect变量从未被触发,误判原因是检测到包围盒部分顶点在视锥体内、部分在外部。
视锥体初始化函数
void Frustum::Calculate ( void ) { const glm::vec3 XVector = glm::normalize ( Transform.Orientation * glm::vec3 ( 1.0f, 0.0f, 0.0f ) ); const glm::vec3 YVector = glm::normalize ( Transform.Orientation * glm::vec3 ( 0.0f, 1.0f, 0.0f ) ); const glm::vec3 ZVector = glm::normalize ( Transform.Orientation * glm::vec3 ( 0.0f, 0.0f, 1.0f ) ); const float Tangent = (float) tan ( 0.5f * Perspective.FOV ); const float HalfNearHeight = Perspective.Near * Tangent; const float HalfFarHeight = Perspective.Far * Tangent; const float HalfNearWidth = HalfNearHeight * Perspective.AspectRatio; const float HalfFarWidth = HalfFarHeight * Perspective.AspectRatio; glm::vec3 NearClip = -ZVector * Perspective.Near; glm::vec3 FarClip = -ZVector * Perspective.Far; /* . 6---7 . /. /| . 2---3 | . | 4.|.5 . |. |/ . 0---1 */ Corners[0] = Transform.Position + NearClip - YVector * HalfNearHeight - XVector * HalfNearWidth;//NearBottomLeft Corners[1] = Transform.Position + NearClip - YVector * HalfNearHeight + XVector * HalfNearWidth;//NearBottomRight Corners[2] = Transform.Position + NearClip + YVector * HalfNearHeight - XVector * HalfNearWidth;//NearTopLeft Corners[3] = Transform.Position + NearClip + YVector * HalfNearHeight + XVector * HalfNearWidth;//NearTopRight Corners[4] = Transform.Position + FarClip - YVector * HalfFarHeight - XVector * HalfFarWidth;//FarBottomLeft Corners[5] = Transform.Position + FarClip - YVector * HalfFarHeight + XVector * HalfFarWidth;//FarBottomRight Corners[6] = Transform.Position + FarClip + YVector * HalfFarHeight - XVector * HalfFarWidth;//FarTopLeft Corners[7] = Transform.Position + FarClip + YVector * HalfFarHeight + XVector * HalfFarWidth;//FarTopRight // Doing all these extra calculations just for debug purposes. This way the point is exactly at the center of the frustum plane #if 1 glm::vec3 MidPoint[6]; glm::vec3 Normal[6]; MidPoint[0] = ( ( ( Corners[2] + Corners[3] ) / 2 ) + ( ( Corners[6] + Corners[7] ) / 2 ) ) / 2; Normal[0] = glm::normalize ( glm::cross ( Corners[2] - Corners[3], Corners[6] - Corners[3] ) ); MidPoint[1] = ( ( ( Corners[0] + Corners[1] ) / 2 ) + ( ( Corners[4] + Corners[5] ) / 2 ) ) / 2; Normal[1] = glm::normalize ( glm::cross ( Corners[1] - Corners[0], Corners[5] - Corners[0] ) ); MidPoint[2] = ( ( ( Corners[0] + Corners[2] ) / 2 ) + ( ( Corners[4] + Corners[6] ) / 2 ) ) / 2; Normal[2] = glm::normalize ( glm::cross ( Corners[4] - Corners[0], Corners[6] - Corners[0] ) ); MidPoint[3] = ( ( ( Corners[1] + Corners[3] ) / 2 ) + ( ( Corners[5] + Corners[7] ) / 2 ) ) / 2; Normal[3] = glm::normalize ( glm::cross ( Corners[1] - Corners[5], Corners[3] - Corners[5] ) ); MidPoint[4] = ( ( ( Corners[0] + Corners[1] ) / 2 ) + ( ( Corners[2] + Corners[3] ) / 2 ) ) / 2; Normal[4] = glm::normalize ( glm::cross ( Corners[2] - Corners[0], Corners[3] - Corners[0] ) ); MidPoint[5] = ( ( ( Corners[4] + Corners[5] ) / 2 ) + ( ( Corners[6] + Corners[7] ) / 2 ) ) / 2; Normal[5] = glm::normalize ( glm::cross ( Corners[6] - Corners[7], Corners[4] - Corners[7] ) ); Planes[0].SetFromNormalAndPoint ( Normal[0], MidPoint[0]);// Top Planes[1].SetFromNormalAndPoint ( Normal[1], MidPoint[1] );// Bottom Planes[2].SetFromNormalAndPoint ( Normal[2], MidPoint[2] );// Left Planes[3].SetFromNormalAndPoint ( Normal[3], MidPoint[3] );// Right Planes[4].SetFromNormalAndPoint ( Normal[4], MidPoint[4] );// Near Planes[5].SetFromNormalAndPoint ( Normal[5], MidPoint[5] );// Far #else Planes[0].SetFrom3Points (Corners[3], Corners[2], Corners[6]);// Top Planes[1].SetFrom3Points ( Corners[0], Corners[1], Corners[5] );// Bottom Planes[2].SetFrom3Points ( Corners[0], Corners[4], Corners[6] );// Left Planes[3].SetFrom3Points ( Corners[5], Corners[1], Corners[3] );// Right Planes[4].SetFrom3Points ( Corners[0], Corners[2], Corners[3] );// Near Planes[5].SetFrom3Points ( Corners[7], Corners[6], Corners[4] );// Far #endif }
已多次检查该函数未发现问题,首个被误判相交的球体包围盒中,顶点0、1被判定为外部,但顶点2被错误判定为内部,导致整体被标记为相交。
编辑说明
问题已解决!
内容的提问来源于stack exchange,提问作者Joao Pincho
相关产品推荐
相关产品推荐

