如何在平移/旋转后重新计算轴对齐的边界框
Posted
技术标签:
【中文标题】如何在平移/旋转后重新计算轴对齐的边界框【英文标题】:How to recalculate axis-aligned bounding box after translate/rotate 【发布时间】:2011-05-19 03:46:50 【问题描述】:当我第一次加载我的对象时,我用最大和最小 (x,y,z) 点计算初始 AABB。但这是在物体空间中,物体在世界各地移动,更重要的是,它会旋转。
如何在每次平移/旋转对象时重新计算新的 AABB?这基本上发生在每一帧。每帧重新计算新的 AABB 是否会是一项非常密集的操作?如果是这样,还有什么替代方案?
我知道 AABB 会降低我的碰撞检测准确度,但实现碰撞检测代码比 OBB 更容易,我想一步一步来。
在从以下答案中获得一些见解后,这是我当前的代码:
typedef struct sAxisAlignedBoundingBox
Vector3D bounds[8];
Vector3D max, min;
AxisAlignedBoundingBox;
void drawAxisAlignedBoundingBox(AxisAlignedBoundingBox box)
glPushAttrib(GL_LIGHTING_BIT | GL_POLYGON_BIT);
glEnable(GL_COLOR_MATERIAL);
glDisable(GL_LIGHTING);
glColor3f(1.0f, 1.0f, 0.0f);
glBegin(GL_LINE_LOOP);
glVertex3f(box.bounds[0].x, box.bounds[0].y, box.bounds[0].z);
glVertex3f(box.bounds[1].x, box.bounds[1].y, box.bounds[1].z);
glVertex3f(box.bounds[2].x, box.bounds[2].y, box.bounds[2].z);
glVertex3f(box.bounds[3].x, box.bounds[3].y, box.bounds[3].z);
glEnd();
glBegin(GL_LINE_LOOP);
glVertex3f(box.bounds[4].x, box.bounds[4].y, box.bounds[4].z);
glVertex3f(box.bounds[5].x, box.bounds[5].y, box.bounds[5].z);
glVertex3f(box.bounds[6].x, box.bounds[6].y, box.bounds[6].z);
glVertex3f(box.bounds[7].x, box.bounds[7].y, box.bounds[7].z);
glEnd();
glBegin(GL_LINE_LOOP);
glVertex3f(box.bounds[0].x, box.bounds[0].y, box.bounds[0].z);
glVertex3f(box.bounds[5].x, box.bounds[5].y, box.bounds[5].z);
glVertex3f(box.bounds[6].x, box.bounds[6].y, box.bounds[6].z);
glVertex3f(box.bounds[1].x, box.bounds[1].y, box.bounds[1].z);
glEnd();
glBegin(GL_LINE_LOOP);
glVertex3f(box.bounds[4].x, box.bounds[4].y, box.bounds[4].z);
glVertex3f(box.bounds[7].x, box.bounds[7].y, box.bounds[7].z);
glVertex3f(box.bounds[2].x, box.bounds[2].y, box.bounds[2].z);
glVertex3f(box.bounds[3].x, box.bounds[3].y, box.bounds[3].z);
glEnd();
glPopAttrib();
void calculateAxisAlignedBoundingBox(GLMmodel *model, float matrix[16])
AxisAlignedBoundingBox box;
float dimensions[3];
// This will give me the absolute dimensions of the object
glmDimensions(model, dimensions);
// This calculates the max and min points in object space
box.max.x = dimensions[0] / 2.0f, box.min.x = -1.0f * box.max.x;
box.max.y = dimensions[1] / 2.0f, box.min.y = -1.0f * box.max.y;
box.max.z = dimensions[2] / 2.0f, box.min.z = -1.0f * box.max.z;
// These calculations are probably the culprit but I don't know what I'm doing wrong
box.max.x = matrix[0] * box.max.x + matrix[4] * box.max.y + matrix[8] * box.max.z + matrix[12];
box.max.y = matrix[1] * box.max.x + matrix[5] * box.max.y + matrix[9] * box.max.z + matrix[13];
box.max.z = matrix[2] * box.max.x + matrix[6] * box.max.y + matrix[10] * box.max.z + matrix[14];
box.min.x = matrix[0] * box.min.x + matrix[4] * box.min.y + matrix[8] * box.min.z + matrix[12];
box.min.y = matrix[1] * box.min.x + matrix[5] * box.min.y + matrix[9] * box.min.z + matrix[13];
box.min.z = matrix[2] * box.min.x + matrix[6] * box.min.y + matrix[10] * box.min.z + matrix[14];
/* NOTE: If I remove the above calculations and do something like this:
box.max = box.max + objPlayer.position;
box.min = box.min + objPlayer.position;
The bounding box will move correctly when I move the player, the same does not
happen with the calculations above. It makes sense and it's very simple to move
the box like this. The only problem is when I rotate the player, the box should
be adapted and increased/decreased in size to properly fit the object as a AABB.
*/
box.bounds[0] = Vector3D(box.max.x, box.max.y, box.min.z);
box.bounds[1] = Vector3D(box.min.x, box.max.y, box.min.z);
box.bounds[2] = Vector3D(box.min.x, box.min.y, box.min.z);
box.bounds[3] = Vector3D(box.max.x, box.min.y, box.min.z);
box.bounds[4] = Vector3D(box.max.x, box.min.y, box.max.z);
box.bounds[5] = Vector3D(box.max.x, box.max.y, box.max.z);
box.bounds[6] = Vector3D(box.min.x, box.max.y, box.max.z);
box.bounds[7] = Vector3D(box.min.x, box.min.y, box.max.z);
// This draw call is for testing porpuses only
drawAxisAlignedBoundingBox(box);
void drawObjectPlayer(void)
static float mvMatrix[16];
if(SceneCamera.GetActiveCameraMode() == CAMERA_MODE_THIRD_PERSON)
objPlayer.position = SceneCamera.GetPlayerPosition();
objPlayer.rotation = SceneCamera.GetRotationAngles();
objPlayer.position.y += -PLAYER_EYE_HEIGHT + 0.875f;
/* Only one of the two code blocks below should be active at the same time
Neither of them is working as expected. The bounding box doesn't is all
messed up with either code. */
// Attempt #1
glPushMatrix();
glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
glCallList(gameDisplayLists.player);
glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix);
glPopMatrix();
// Attempt #2
glPushMatrix();
glLoadIdentity();
glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix);
glPopMatrix();
calculateAxisAlignedBoundingBox(objPlayer.model, mvMatrix);
但它不能正常工作......我做错了什么?
【问题讨论】:
【参考方案1】:只需重新计算转换后的 AABB 的 AABB。这意味着转换 8 个顶点(8 个顶点 - 矩阵乘法)和 8 个顶点-顶点比较。
因此,在初始化时,您在模型空间中计算 AABB:对于模型的每个顶点的每个 x、y、z,您检查 xmin、xmax、ymin、ymax 等。
对于每一帧,您都会生成一个新的变换矩阵。在 OpenGL 中,这是通过 glLoadIdentity 后跟 glTransform/Rotate/Scale(如果使用旧 API)来完成的。正如 lmmilewski 所说,这就是模型矩阵。
您再次计算此转换矩阵(在 OpenGL 之外,例如使用 glm)。您还可以使用glGet 获取 OpenGL 的结果矩阵。
您将 AABB 的八个顶点中的每一个乘以该矩阵。使用 glm 进行矩阵向量乘法。您将获得经过改造的 AABB(在世界空间中)。它很可能是旋转的(不再是轴对齐的)。
现在你的算法可能只适用于轴对齐的东西,因此你的问题。所以现在你通过对变换后的边界框的边界框进行近似变换模型的新边界框:
对于新 AABB 的每个顶点的每个 x、y、z,您检查 xmin、xmax、ymin、ymax 等。这为您提供了一个世界空间 AABB,您可以将其用于您的裁剪算法。
这不是最佳的(AABB 方面)。你会得到很多空白空间,但在性能方面,重新计算整个网格的 AABB 要好得多。
至于变换矩阵,在drawObjectPlayer中:
gLLoadIdentity();
glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix);
// Now you've got your OWN Model Matrix (don't trust the GL_MODELVIEW_MATRIX flag : this is a workaround, and I know what I'm doing ^^ )
gLLoadIdentity(); // Reset the matrix so that you won't make the transformations twice
gluLookAt( whatever you wrote here earlier )
glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
// Now OpenGL is happy, he's got his MODELVIEW matrix correct ( gluLookAt is the VIEW part; Translate/Rotate is the MODEL part
glCallList(gameDisplayLists.player); // Transformed correcty
我无法进一步解释...正如 cmets 中所说,您必须这样做两次。顺便说一句,您不会在 OpenGL 3 中遇到这些问题和丑陋的解决方法,因为您将对自己的矩阵负全部责任。 OpenGL 2 中的等效项:
glm::mat4 ViewMatrix = glm::LookAt(...);
glm::mat4 ModelMatrix = glm::rotate() * glm::translate(...);
// Use ModelMatrix for whatever you want
glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
glLoadMatrix4fv( &ModelViewMatrix[0][0] ); // In OpenGL 3 you would use an uniform instead
干净多了,对吧?
【讨论】:
对不起,我不知道该怎么做。这正是我的问题。我不能只使用最大/最小 (x,y,z) 而不是 8 个顶点吗?如果必须的话,我可以用最大/最小值计算这 8 个顶点,但问题是如何在平移和旋转后将它们转换为世界空间。 现在不那么混乱了,谢谢。但是,我想避免为此使用 GLM 库,我更愿意自己进行矩阵计算。但这就是我失败的地方...... 你所做的一切,包括矩阵乘法,看起来都不错,除了这个:glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix),它返回 ViewMatrix * ModelMatrix。可悲的是,这是老式的 OpenGL,仅获取模型矩阵是没有意思的。解决方法:loadIdentity、translate、rotate、getfloat(现在你有 ModelMatrix)、loadIdentity、gluLookAt、translate、rotate(现在 opengl 对它的 modelviewmatrix 很满意) 哦,还有是一个错误(现在不是关键)。根据您旋转模型的方式,box.max 确实可以成为某个轴上的最小值,因此在 calculateAxisAlignedBoundingBox 结束时,您需要 box = computeAABB ( box.bounds ) (或多或少) 但是我已经这样做来获取模型矩阵,唯一的事情是我反过来做:loadIdentity、gluLookAt、translate、rotate、loadIdentity、translate、rotate、glGetFloat。它在我发布的代码中,在尝试 #2 中。这还不够吗?【参考方案2】:是的,您可以变换八个角顶点并对结果进行最小/最大处理,但有一种更快的方法,正如 Jim Arvo 在他的 Graphics Gems (1990) 一章中所描述的那样。
在性能方面,Arvo 的方法大致相当于两次转换而不是八次,基本上如下(这将框 A
转换为框 B
)
// Split the transform into a translation vector (T) and a 3x3 rotation (M).
B = zero-volume AABB at T
for each element (i,j) of M:
a = M[i][j] * A.min[j]
b = M[i][j] * A.max[j]
B.min[i] += a < b ? a : b
B.max[i] += a < b ? b : a
return B
Arvo 方法的一种变体使用中心/范围表示,而不是混合/最大值,这由 Christer Ericson 在实时碰撞检测 (photo) 中描述。
Graphics Gems 文章的完整 C 代码可以在 here 找到。
【讨论】:
这应该是公认的答案。这是实际代码中使用的内容。感谢您的参考。【参考方案3】:为此,您必须遍历每个顶点,计算其在世界中的位置(乘以模型视图)并找到每个对象内的最小/最大顶点坐标(就像您第一次计算它时一样)。
您可以稍微缩放您的 AABB,这样您就不必重新计算它 - 将其放大 sqrt(2) 倍就足够了 - 您的旋转对象总是适合 AABB。
还有一个问题是你旋转的方向(?)。如果总是合二为一,那么您只能在那个方向上扩大 AABB。
您可以选择使用边界球来代替 AABB。那你不用关心旋转,缩放也不成问题。
【讨论】:
问题是我该怎么做?我已经在对象空间中拥有了最大/最小 (x,y,z)。我也有对象在世界空间中的确切 (x,y,z) 位置。我的问题是如何使用此信息计算 AABB。 您需要世界空间中的最大/最小,而不是对象空间。也许我没有正确理解你,但正如我所说 - 你需要遍历所有顶点。对于每个顶点,计算它在世界空间中的 (x,y,z)。从这些世界空间坐标中选择每个轴的最小值和最大值以获得 AABB。 要将对象空间坐标转换为世界空间,将它们乘以模型矩阵。 放大 sqrt(2) 并使用边界球体只有在枢轴点是球体或 bbox 的中心时才有帮助。 我看到您仍在使用旧 API。您自己不维护模型矩阵-我一直这样做。在您的代码中,您不会乘以 MODEL 矩阵,而是乘以 MODELVIEW(模型和视图矩阵相乘)。您可以计算模型矩阵或推送opengl模型视图矩阵,加载标识,执行所有转换/旋转/缩放并获取模型视图矩阵,然后弹出矩阵 - 这样视图矩阵在模型视图中是标识的,因此您只能获得模型矩阵。我会选择自己实现模型矩阵 - 在您的程序中放置一个可访问的模型矩阵很有用。【参考方案4】:引用之前在 AABB @ Stack Overflow 上的回复:
“很遗憾,如果你的角色旋转了,你需要重新计算你的 AABB ......
斯库默德尔
受访者的建议和我的建议是,一旦你有 AABB 工作,就实施定向边界框,并且还要注意,你可以对网格的各个部分进行 aabb 来伪造碰撞检测,其准确性高于每个对象一个巨大的框。
【讨论】:
【参考方案5】:为什么不使用您的 GPU?今天我通过渲染几个框架来解决这个问题。
-
暂时将您的相机放在对象上方,指向对象
在物体上。
仅渲染您的对象,不使用灯光或
任何事物。
也使用正交投影。
然后读取帧缓冲区。黑色像素的行和列表示模型不存在。击中一个白色像素 - 您击中了模型 AABB 边框之一。
我知道这不是适用于所有情况的解决方案,但根据一些先验知识,这是非常有效的。 对于屏幕外渲染,请参阅here。
【讨论】:
这在技术上可以工作,但我怀疑它是否会因为绘图而有效,尤其是读取帧缓冲区部分以上是关于如何在平移/旋转后重新计算轴对齐的边界框的主要内容,如果未能解决你的问题,请参考以下文章