仰视或俯视时Frustum Culling(视锥体剔除)功能失效求助
视锥体剔除(Frustum Culling)仰视/俯视失效问题
相机非上下朝向时,视锥体剔除功能正常(已通过控制台绘制调用数验证),但仰视或俯视时功能失效(疑似缩放异常,但无法确定)。
相关代码实现
Frustum.h
struct BoundingBox { Vec center; Vec extents; }; struct Plane_t { Vec normal; float distance; }; struct Frustum_t { Plane_t topface; Plane_t bottomface; Plane_t rightface; Plane_t leftface; Plane_t farface; Plane_t nearface; }; Frustum_t MakeFrustumFromView(const Vec& pos, const Vec& dir, float fov, float near, float far); bool isBoundingBoxOnForwardPlane(const BoundingBox& boundingbox, const Plane_t& plane); bool IsBoundingBoxInFrustum(const BoundingBox& boundingbox, const Frustum_t& frustum, const Mat4& transform);
Frustum.cpp
inline Plane_t MakeFrustumPlane(const Vec& point, const Vec& norm) { return { norm, norm.Dot(point) }; } Frustum_t MakeFrustumFromView(const Vec& pos, const Vec& dir, float fov, float near, float far) { Vec normdir = dir.Normalize(); Vec upvec = { 0.0f, 1.0f, 0.0f }; Vec right = normdir.Cross(upvec).Normalize(); Vec up = right.Cross(normdir).Normalize(); Frustum_t frustum; const float fHalfVSide = far * tanf(fov * DEG2RAD * 0.5f); const float fHalfHSide = fHalfVSide * ((float)g_pGlobals->m_iScreenWidth / g_pGlobals->m_iScreenHeight); const Vec fDirFar = normdir * far; frustum.nearface = MakeFrustumPlane(pos + normdir * near, normdir); frustum.farface = MakeFrustumPlane(pos + fDirFar, -normdir); frustum.rightface = MakeFrustumPlane(pos, (fDirFar - right * fHalfHSide).Cross(up)); frustum.leftface = MakeFrustumPlane(pos, up.Cross(fDirFar + right * fHalfHSide)); frustum.topface = MakeFrustumPlane(pos, right.Cross(fDirFar - up * fHalfVSide)); frustum.bottomface = MakeFrustumPlane(pos, (fDirFar + up * fHalfVSide).Cross(right)); return frustum; } bool isBoundingBoxOnForwardPlane(const BoundingBox& boundingbox, const Plane_t& plane) { const float r = boundingbox.extents.x * fabsf(plane.normal.x) + boundingbox.extents.y * fabsf(plane.normal.y) + boundingbox.extents.z * fabsf(plane.normal.z); return -r <= (plane.normal.Dot(boundingbox.center) - plane.distance); } bool IsBoundingBoxInFrustum(const BoundingBox& boundingbox, const Frustum_t& frustum, const Mat4& transform) { Vec4D globalCenter = transform * Vec4D(boundingbox.center, 1.0f); Vec bbRight = Vec(transform.m0, transform.m1, transform.m2) * boundingbox.extents.x; Vec bbUp = Vec(transform.m4, transform.m5, transform.m6) * boundingbox.extents.y; Vec bbForward = -Vec(transform.m8, transform.m9, transform.m10) * boundingbox.extents.z; const float newIi = fabsf(Vec(1.0f, 0.0f, 0.0f).Dot(bbRight)) + fabsf(Vec(1.0f, 0.0f, 0.0f).Dot(bbUp)) + fabsf(Vec(1.0f, 0.0f, 0.0f).Dot(bbForward)); const float newIj = fabsf(Vec(0.0f, 1.0f, 0.0f).Dot(bbRight)) + fabsf(Vec(0.0f, 1.0f, 0.0f).Dot(bbUp)) + fabsf(Vec(0.0f, 1.0f, 0.0f).Dot(bbForward)); const float newIk = fabsf(Vec(0.0f, 0.0f, 1.0f).Dot(bbRight)) + fabsf(Vec(0.0f, 0.0f, 1.0f).Dot(bbUp)) + fabsf(Vec(0.0f, 0.0f, 1.0f).Dot(bbForward)); const BoundingBox globalBoundingBox = { globalCenter.ToVec(), {newIi, newIj, newIk} }; return (isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.leftface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.rightface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.topface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.bottomface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.nearface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.farface)); }
问题根源与修复方案
1. 平面方程定义错误(核心问题)
原代码中MakeFrustumPlane生成的平面距离参数不符合标准平面方程(ax + by + cz + d = 0),导致后续包围盒平面判断逻辑完全反向。
修复代码:
// 修正平面生成函数,确保符合标准平面方程 inline Plane_t MakeFrustumPlane(const Vec& point, const Vec& norm) { Vec normalizedNorm = norm.Normalize(); // 标准平面方程中d = -normal·point return { normalizedNorm, -normalizedNorm.Dot(point) }; } // 修正平面判断逻辑,确保包围盒在视锥体内部时返回true bool isBoundingBoxOnForwardPlane(const BoundingBox& boundingbox, const Plane_t& plane) { const float r = boundingbox.extents.x * fabsf(plane.normal.x) + boundingbox.extents.y * fabsf(plane.normal.y) + boundingbox.extents.z * fabsf(plane.normal.z); return (plane.normal.Dot(boundingbox.center) + plane.distance) >= -r; }
2. 相机垂直朝向时up向量计算异常
当相机朝向接近(0,1,0)或(0,-1,0)时,固定使用(0,1,0)作为初始up向量,会导致normdir.Cross(upvec)结果趋近于零向量,后续正交化得到的right/up向量完全错误,进而生成错误的视锥体平面。
修复代码:
Frustum_t MakeFrustumFromView(const Vec& pos, const Vec& dir, float fov, float near, float far) { Vec normdir = dir.Normalize(); Vec upvec = { 0.0f, 1.0f, 0.0f }; // 当相机朝向与初始up向量几乎平行时,切换初始up向量为侧方向,避免叉乘结果为零 if (fabsf(normdir.Dot(upvec)) > 0.99f) { upvec = { 1.0f, 0.0f, 0.0f }; } Vec right = normdir.Cross(upvec).Normalize(); Vec up = right.Cross(normdir).Normalize(); // 剩余逻辑保持不变... }
3. 包围盒变换逻辑错误
原代码计算变换后包围盒extents的方式错误,使用世界空间轴点乘变换后的局部轴,会导致旋转后的包围盒extents计算偏差,在相机垂直朝向时误差被放大。推荐直接生成局部包围盒的8个顶点,变换后重新计算世界空间AABB:
修复代码:
bool IsBoundingBoxInFrustum(const BoundingBox& boundingbox, const Frustum_t& frustum, const Mat4& transform) { // 生成局部包围盒的8个顶点 Vec localVertices[] = { boundingbox.center + Vec(boundingbox.extents.x, boundingbox.extents.y, boundingbox.extents.z), boundingbox.center + Vec(boundingbox.extents.x, boundingbox.extents.y, -boundingbox.extents.z), boundingbox.center + Vec(boundingbox.extents.x, -boundingbox.extents.y, boundingbox.extents.z), boundingbox.center + Vec(boundingbox.extents.x, -boundingbox.extents.y, -boundingbox.extents.z), boundingbox.center + Vec(-boundingbox.extents.x, boundingbox.extents.y, boundingbox.extents.z), boundingbox.center + Vec(-boundingbox.extents.x, boundingbox.extents.y, -boundingbox.extents.z), boundingbox.center + Vec(-boundingbox.extents.x, -boundingbox.extents.y, boundingbox.extents.z), boundingbox.center + Vec(-boundingbox.extents.x, -boundingbox.extents.y, -boundingbox.extents.z) }; // 变换到世界空间并计算AABB Vec vertexWS = (transform * Vec4D(localVertices[0], 1.0f)).ToVec(); Vec minWS = vertexWS; Vec maxWS = vertexWS; for (int i = 1; i < 8; ++i) { vertexWS = (transform * Vec4D(localVertices[i], 1.0f)).ToVec(); minWS.x = min(minWS.x, vertexWS.x); minWS.y = min(minWS.y, vertexWS.y); minWS.z = min(minWS.z, vertexWS.z); maxWS.x = max(maxWS.x, vertexWS.x); maxWS.y = max(maxWS.y, vertexWS.y); maxWS.z = max(maxWS.z, vertexWS.z); } BoundingBox globalBoundingBox; globalBoundingBox.center = (minWS + maxWS) * 0.5f; globalBoundingBox.extents = (maxWS - minWS) * 0.5f; return (isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.leftface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.rightface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.topface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.bottomface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.nearface) && isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.farface)); }
内容的提问来源于stack exchange,提问作者Alphenex
相关产品推荐
相关产品推荐

