【问题标题】:Frustum Culling Bug截锥体剔除错误
【发布时间】:2021-04-14 17:07:19
【问题描述】:

所以我在我的游戏引擎中实现了 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);

在所有情况下。

【问题讨论】:

    标签: c++ opengl matrix glm-math frustum


    【解决方案1】:

    您错误地使用了 GLM。根据paper of Gribb and Hartmann,您可以将平面方程提取为矩阵的不同的和或差,但在glm中,mat4 foo; foo[n]将产生第n个列(类似于 GLSL 的设计方式)。

    这里

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

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

    【讨论】:

    • 所以我也用 glm::row(foo, n) 试过了,结果更糟。现在每个网格都被剔除(这也是去除标准化)。
    • 您是否删除了错误的规范化?
    • 是的。规范化被移除。
    【解决方案2】:

    在遵循@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 UNIT VECTORS],按盒子各自的一半宽度、高度、深度缩放它们,并找到它们每个点积的总和[仅取每个点积 NO SIGNED DISTANCE 的正数] 平面法线将是您的“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是平面法线单位向量

    第三步:计算盒子的中点

     M=(Min+Max)/2;
    

    第四步:计算中点到平面的距离

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

    第 5 步:进行检查

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

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

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

      d<=-r //radius of the sphere
    

    将其放入名为 outside(Plane,Bounds) 的函数中,如果边界完全在平面之外,则对于 6 个平面中的每一个都返回 true

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

    【讨论】:

    • 抱歉,我无法按照您的投影进行操作。你是说你通过做 dot(plane_normal, axis_basis) * box_dimension_in_axis 来进行盒子的投影,然后将它与盒子的中点到平面的距离进行比较?
    • 是的,您基本上已经了解了一切。我已经编辑了我的答案以显示程序
    • 这修复了它!我不知道是否接受这个回复作为答案,因为其他回复也是正确的。我可以请有更多经验的人对此提出建议吗?我应该做出一个总结这两点的整体回应吗?让它“无人回答”?
    • @Alex Rowden 上一个答案只是解决了您的平面规范化代码中的问题,如果您参考 pdf 的第 8 页,您可以自己解决这些问题,但这个答案与剔除平面无关,但是取而代之的是截锥体/网格相交,解决了您的对象部分消失的问题。但话又说回来,这是你的问题,你是社区,所以我会让你决定:)
    • 这是一个合乎逻辑的结论,尽管我不想低估@derhass 的贡献,因为他们还指出我在应该使用行时使用了列。这个网站上有很多关于哪个是正确的触发器。尽管如此,我还是接受了这个答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-26
    • 1970-01-01
    • 1970-01-01
    • 2012-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多