一块剔除虫
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,归一化平面方程是浪费循环,你只关心符号,而不关心值的大小。
所以我在我的游戏引擎中实现了 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,归一化平面方程是浪费循环,你只关心符号,而不关心值的大小。