[英]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);
在所有情况下。
您错误地使用了 GLM。 根据Gribb 和 Hartmann 的论文,您可以将平面方程提取为矩阵不同行的和或差,但在 glm, mat4 foo; foo[n]
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
必须相应地缩放。 它甚至在论文本身中进行了详细解释。 但是,由于您只需要知道一个点位于哪个半空间上,因此对平面方程进行归一化是浪费循环,因此您只关心符号,而不关心值的大小。
在遵循@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
第五步:做检查
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) 的 function 中,如果边界完全在平面之外,那么对于 6 个平面中的每一个,它都会返回 true
bool is_inside_frustum()
{
for(Plane plane:frustum_planes)
{
if(outside(plane,AABB))
{
return false
}
}
return true;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.