您的位置:首页 > 其它

在3D世界中创建不同的相机模式——检查对象是否可见

2010-10-18 21:32 465 查看

2.5 检查对象是否可见

问题 你想检查对象是否可见来决定是否要绘制这个物体。

解决方案 XNA拥有BoundingFrustum类支持这个功能,你可以通过传递View和Projection矩阵创建一个BoundingFrustum类,然后就可以很容易地在这个类中检查对象是否包含在视锥体中。

工作原理 视锥体定义了相机可以看到的范围,类似于削去顶部的金字塔,如图2-4所示,它的边是由视域角,近裁平面和远裁平面决定的,更多的信息可见教程2-1。

你应该让XNA只绘制包含在视锥体中的物体,否则,你会浪费显卡的处理能力。所以你需要一个方法用来检测对象是否在视锥体中。

XNA Framework中的BoundingFrustum类包含了实现这个功能完整方法,你只需通过指定相机的观察矩阵和投影矩阵创建BoundingFrustum类即可。





图2-4 相机的视锥体

BoundingFrustum对象允许你使用它的Contain方法,你可以传递一个Vector3和一个BoundingSphere或BoundingBox检查是否包含在相机的视锥中。这个方法会返回一个Containment Type对象,这个对象包含三个值:

Contains:完整包含在视锥体中的测试对象。
Intersects:部分包含在视锥体中的测试对象,这时这个对象与视锥体相交。
Disjoint:不在视锥体中的测试对象。
检查3D空间中的点是否在相机可视范围内 要检查一个点是否可见,只需将这个点传递到Contains方法:

Vector3 pointToTest = new Vector3(0, 0, 0);
BoundingFrustum cameraFrustum = new BoundingFrustum(fpsCam.ViewMatrix * fpsCam.ProjectionMatrix);
ContainmentType containmentType = cameraFrustum.Contains(pointToTest);
if (containmentType != ContainmentType.Disjoint)
{
Window.Title = "Point inside frustrum";
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
}
else
{
Window.Title = "Point outside frustrum";
graphics.GraphicsDevice.Clear(Color.Red);
}


这个例子中你检查了点(0,0,0)是否在视锥体中,将检查结果写在了窗口标题栏中并相应地改变背景颜色。

检查一个物体是否可见

如果你想检查一个物体是否可见,一个方法是事先检查模型的每个顶点,这样做非常耗时。更快的方法(尽管不太确切)是定义一个包含模型的包围体,然后检查这个包围体是否在视锥体中。能包含模型的最简单的包围体是一个球。

XNA已在BoundingSphere类中支持创建一个包围体,使用教程4-5中介绍的LoadModelWithBoundingSphere方法载入模型,并在Tag属性中保存BoundingSphere:

myModel = XNAUtils.LoadModelWithBoundingSphere(ref modelTransforms, "content/tiny", content);


技巧:因为模型的Tag属性可以存储任何类型的数据结构,你可以用让它存储任何与模型相关的数据,例如BoundingSphere,纹理等。你甚至还可以创建一个包含所有数据的结构并将这个结构存储在Tag属性中。

现在有了包围体你就可以测试它是否在视锥体中了:

BoundingFrustum cameraSight = new BoundingFrustum(fpsCam.ViewMatrix* fpsCam.ProjectionMatrix);
ContainmentType containmentType = cameraSight.Contains( (BoundingSphere)myModel.Tag);
if (containmentType != ContainmentType.Disjoint)
{
Window.Title = "Point inside frustrum";
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
Window.Title = "Point outside frustrum";
graphics.GraphicsDevice.Clear(Color.Red);
}


检查一个设置了世界矩阵的对象在大多数情况中,你的模型并不在(0,0,0)位置而且还可能旋转,缩放或通过设置世界矩阵放置在其他位置,更多信息可见教程4-2。显然包围体也应该旋转/缩放/移动已匹配模型。这可以通过模型的世界矩阵变换包围体实现:

Matrix worldMatrix = Matrix.CreateScale(0.01f, 0.01f, 0.01f) *Matrix.CreateTranslation(5, 0, 0);
BoundingFrustum cameraSight = new BoundingFrustum(fpsCam.ViewMatrix * fpsCam.ProjectionMatrix);
BoundingSphere origSphere = (BoundingSphere)myModel.Tag;
BoundingSphere transSphere = origSphere.Transform(worldMatrix);
ContainmentType containmentType = cameraSight.Contains(transSphere);
if (containmentType != ContainmentType.Disjoint)
{
Window.Title = "Model inside frustrum";
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
myModel.CopyAbsoluteBoneTransformsTo(modelTransforms);
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = modelTransforms[mesh.ParentBone.Index] * worldMatrix;
effect.View = fpsCam.ViewMatrix;
effect.Projection = fpsCam.ProjectionMatrix;
}
mesh.Draw();
}
Window.Title = "Model outside frustrum";
graphics.GraphicsDevice.Clear(Color.Red);
}


注意可见教程第三章学习如何绘制一个模型。这个代码中的主要变化是你调用了origSphere. Transform方法,这个方法获取经过世界变换的BoundingSphere。本例中transSphere的大小是origSphere百分之一,中心沿x轴移动了5个单位。只有当模型在视锥体时才会绘制这个模型。

代码

下面的代码会在一个指定位置绘制一个模型,当它在视锥体之外就不会被绘制:

protected override void LoadContent()
{
device = graphics.GraphicsDevice;
basicEffect = new BasicEffect(device, null);
cCross = new CoordCross(device);
myModel = XNAUtils.LoadModelWithBoundingSphere(ref modelTransforms,"content/tiny", content);
}
protected override void Draw(GameTime gameTime)
{
Matrix worldMatrix = Matrix.CreateScale(0.01f,0.01f,0.01f)*Matrix.CreateTranslation(5, 0, 0);
BoundingFrustum cameraSight = new BoundingFrustum(fpsCam.ViewMatrix * fpsCam.ProjectionMatrix);
BoundingSphere origSphere = (BoundingSphere)myModel.Tag;
BoundingSphere transSphere = origSphere.Transform(worldMatrix);
ContainmentType containmentType = cameraSight.Contains(transSphere);
if (containmentType != ContainmentType.Disjoint)
{
Window.Title = "Model inside frustrum";
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
//draw model
myModel.CopyAbsoluteBoneTransformsTo(modelTransforms);
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = modelTransforms[mesh.ParentBone.Index] * worldMatrix;
effect.View = fpsCam.ViewMatrix;
effect.Projection = fpsCam.ProjectionMatrix;
}
mesh.Draw();
}
}
else
{
Window.Title = "Model outside frustrum";
graphics.GraphicsDevice.Clear(Color.Red);
}

//draw coordcross
cCross.Draw(fpsCam.ViewMatrix, fpsCam.ProjectionMatrix);
base.Draw(gameTime);
}


扩展阅读

这个教程提出了一个检查点或物体是否在视锥体中的方法。但是如果场景中有大量的物体,那么要对每个对象进行检查会使程序变得很慢。在教程2-9会介绍一个更好的方法处理大场景。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐