一块剔除虫

Frustum Culling Bug

所以我在我的游戏引擎中实现了 Frustum Culling,但我遇到了一个奇怪的错误。我正在渲染一座被分割成块的建筑,我只渲染平截头体中的块。我的相机从大约 (-.033, 11.65, 2.2) 开始,一切看起来都很好。我开始四处走动,没有闪烁。当我在视锥体剔除代码中设置断点时,我可以看到它确实剔除了一些网格。一切似乎都很棒。然后,当我到达建筑物的中心时,可见的大约 (3.9, 4.17, 2.23) 网格开始消失。另一边也是如此。我不明白为什么会存在这个错误。

我使用此处列出的提取方法实现了截锥体剔除 Extracting View Frustum Planes (Gribb & Hartmann method)。我不得不使用 glm::inverse() 而不是它建议的转置,我认为矩阵数学是为行主矩阵给出的,所以我翻转了它。总而言之,我的截锥平面计算看起来像

std::vector<Mesh*> render_meshes;
auto comboMatrix = proj * glm::inverse(view * model);
glm::vec4 p_planes[6];

p_planes[0] = comboMatrix[3] + comboMatrix[0]; //left
p_planes[1] = comboMatrix[3] - comboMatrix[0]; //right
p_planes[2] = comboMatrix[3] + comboMatrix[1]; //bottom
p_planes[3] = comboMatrix[3] - comboMatrix[1]; //top
p_planes[4] = comboMatrix[3] + comboMatrix[2]; //near
p_planes[5] = comboMatrix[3] - comboMatrix[2]; //far

for (int i = 0; i < 6; i++){
    p_planes[i] = glm::normalize(p_planes[i]);
}
for (auto mesh : meshes) {
    if (!frustum_cull(mesh, p_planes)) {
        render_meshes.emplace_back(mesh);
    }
}

然后我决定根据其边界框剔除每个网格(由 ASSIMP 使用 aiProcess_GenBoundingBoxes 标志计算)如下(返回 true 意味着剔除)

glm::vec3 vmin, vmax;
for (int i = 0; i < 6; i++) {
    // X axis
    if (p_planes[i].x > 0) {
        vmin.x = m->getBBoxMin().x;
        vmax.x = m->getBBoxMax().x;
    }
    else {
        vmin.x = m->getBBoxMax().x;
        vmax.x = m->getBBoxMin().x;
    }
    // Y axis
    if (p_planes[i].y > 0) {
        vmin.y = m->getBBoxMin().y;
        vmax.y = m->getBBoxMax().y;
    }
    else {
        vmin.y = m->getBBoxMax().y;
        vmax.y = m->getBBoxMin().y;
    }
    // Z axis
    if (p_planes[i].z > 0) {
        vmin.z = m->getBBoxMin().z;
        vmax.z = m->getBBoxMax().z;
    }
    else {
        vmin.z = m->getBBoxMax().z;
        vmax.z = m->getBBoxMin().z;
    }
    if (glm::dot(glm::vec3(p_planes[i]), vmin) + p_planes[i][3] > 0)
        return true;
    
}
return false;

有指导吗?

更新 1: 对表示平面的完整 vec4 进行归一化是不正确的,因为只有 vec3 表示平面的法线。此外,此实例不需要归一化,因为我们只关心距离的符号(而不是大小)。

同样重要的是要注意我应该使用矩阵的行而不是列。我通过替换

来实现这一点
p_planes[0] = comboMatrix[3] + comboMatrix[0];

p_planes[0] = glm::row(comboMatrix, 3) + glm::row(comboMatrix, 0);

在所有情况下。

在遵循@derhass 解决方案正确标准化平面以进行相交测试后,您将执行以下操作

在将您的框投影到我们称为 p 的那个平面上之后,对于边界框平面相交,在计算框的中点后说 m,在计算该中点与平面的距离后说 d 以检查交点我们做

  d<=p

但是对于截锥体剔除,我们只是不希望我们的盒子不与我们的截锥体平面相交,但我们希望它与我们的平面相距 -p 距离,只有这样我们才能确定我们的盒子没有任何部分与我们的飞机相交

    if(d<=-p)//then our box is fully not intersecting our plane so we don't draw it or cull it[d will be negative if the midpoint lies on the other side of our plane]

与三角形类似,我们检查三角形的所有 3 个点与平面的距离是否为负数。

为了将一个盒子投影到一个平面上,我们取盒子的 3 个轴 [x,y,z 单位向量],按盒子各自的一半宽度、高度、深度缩放它们,并找到它们的每一个的总和点积[只取每个点积的正数,无符号距离]与平面法线,这将是你的 'p'

不适用于 AABB 的上述方法,您也可以使用相同的方法针对 OOBB 进行剔除,因为只有轴会改变。

编辑: 如何将边界框投影到平面上?

我们以 AABB 为例 它有以下参数

Lower extent Min(x,y,z)
Upper extent Max(x,y,z)
Up Vector      U=(0,1,0)
Left Vector.   L=(1,0,0)
Front Vector.  F=(0,0,1)

第 1 步:计算半尺寸

half_width=(Max.x-Min.x)/2;
half_height=(Max.y-Min.y)/2;
half_depth=(Max.z-Min.z)/2;

第2步:将盒子的每个单独的轴投影到平面法线上,只取每个点积的正值乘以每个半维并求出总和。确保盒子轴和平面法线都是单位向量。

float p=(abs(dot(L,N))*half_width)+
        (abs(dot(U,N))*half_height)+
        (abs(dot(F,N))*half_depth);

 abs() returns absolute magnitude we want it to be positive 
 because we are dealing with distances

其中N是平面法线单位向量

第 3 步:计算框的中点

 M=(Min+Max)/2;

第 4 步:计算中点与平面的距离

d=dot(M,N)+plane.w

第 5 步:进行检查

d<=-p //return true i.e don't render or do culling

你可以看到如何使用他的 OOBB,其中 U、F、L 向量是 OOBB 的轴,中心(中点)和半尺寸是你手动传入的参数

对于球体,您也可以计算球体中心与平面的距离(称为 d),但要进行检查

  d<=-r //radius of the sphere

将其放入一个名为 outside(Plane,Bounds) 的函数中,如果边界完全位于平面之外,则 returns 为真,然后对于 6 个平面中的每一个

   bool is_inside_frustum()
  {
    for(Plane plane:frustum_planes)
   {
      if(outside(plane,AABB))
     { 
       return false
     }
    }
    return true;
  } 

您没有正确使用 GLM。根据 paper of Gribb and Hartmann,您可以将平面方程提取为矩阵的不同 的和或差,但在 glm 中,mat4 foo; foo[n] 将产生 n -th column(类似于 GLSL 的设计方式)。

这里是

for (int i = 0; i < 6; i++){
    p_planes[i] = glm::normalize(p_planes[i]);
}

也没有意义,因为 glm::normalize(vec4) 只会对 4D 向量进行归一化。这将导致平面沿其法线方向移动。只有 xyz 个组件必须达到单位长度,并且 w 必须相应缩放。它甚至在论文本身中有详细解释。但是,因为你只需要知道一个点在哪一半-space,归一化平面方程是浪费循环,你只关心符号,而不关心值的大小。