You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

视锥体剔除(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 04:03:11