3D picking Sample in XNA

识趣3D物体上的部分,下面给出官网的例程和解释。

 

 

Picking Sample

This sample shows how to determine whether a player's cursor is over an object, and how to find out where on the screen an object is.

Sample Overview

There are several situations where it can be useful to know the 3D equivalent of a point on the screen, and vice versa: for example, checking to see whether a player has clicked on an object, or drawing text above a player's head. This can be accomplished by using the Project and Unproject methods on the Viewport class. This sample demonstrates the use of these methods.

In the sample, there is a table with several objects on it. If the user moves the cursor over an object, the object's name is drawn.

Minimum Shader Profile

Vertex Shader Model 1.1
Pixel Shader Model 1.1

Sample Controls

This sample uses the following keyboard and gamepad controls.

Action Keyboard Control Gamepad Control
Move the cursor. N/A Left thumbstick
Move the camera.

UP ARROW, DOWN ARROW, LEFT ARROW, and RIGHT ARROW

or W, A, S, and D

Right thumbstick
Zoom in and out. Z and X Left and right triggers
Reset the camera. R Click the right thumbstick
Exit teh game. ESC or ALT+F4 BACK

On windows, you may also use the mouse to move cursor.

How the Sample Works

This sample shows how to determine whether a user's cursor is over an object, and how to find out where on the screen an object is. To do this, the sample uses Viewport.Project and Unproject to go between screen space and world space. What are world and screen space?

World space is a simply the coordinate system that your 3D game objects live in. When you move a 3D object from (0,0,0) to (0,0,1), you are changing its position in world space.

Screen space is a coordinate space that closely relates to your game's window, and corresponds to pixels. For example, in this sample, the screen space goes from (0,0) at the upper left of the game window to (853,480) at the lower right of the window. Most 2D games use screen space coordinates.

The Viewport.Project function takes a world space point and tell you where that point will be drawn on the screen. Viewport.Unproject has the opposite effect. It takes a screen space point, and returns a point in world space.

So, now that you understand what world space and screen space are, and how the Project and Unproject functions can convert between them, how can you use that information to tell whether the user's cursor is over an object? If the cursor is in 2D screen space, how can that "intersect" a 3D world space object?

To perform that intersection check, the sample uses Viewport.Unproject to convert two screen space points into world space. The first point is the cursor's position, with a z-value of zero: as close as possible to the camera. The second point is the same thing but with a z-value of one: as far away from the camera as possible. Using those two points, you can create a ray, and then check to see whether world space objects intersect that ray.

 

3D picking Sample in XNA_第1张图片

The ray is checked against the models' bounding spheres, so the result will not be pixel perfect. However, in many cases, this approach will be "good enough."

Going Beyond

It is possible that the ray could intersect multiple models at once. Try to change the code so that only the closest intersection is reported as an intersection. To do this, you'll have to change the RayIntersectsModel function to keep track of the closest collision. You can determine the closest collision by using the result of BoundingSphere.Intersects(Ray) function.

 

 

 

给出官网的连接网址

2.0版本的 3D picking Sample

 

我的电脑在升级后不再支持2.0版本了,但是重新加载一下所以的类文件直接建一个3.1版本的吧 呵呵 一样能运行的。

 

 

下面给出自己搜集的一个资料也挺好的。转载地址:http://www.csharpwin.net/ddwstp/net/csharp/2824dr808.shtml

 

这篇文章简单讨论一下拾取,所谓拾取就是判断一个三维场景中哪个对象被点击。要实现拾取可以有许多方法,每一个都有自己优点和缺点。

颜色键值

第一种方法是使用颜色键值。基本上,场景中的每个三维对象都会被分配一个唯一的颜色。当渲染场景时,会执行一个pass输出每个对象的颜色。这将创建一个场景包含了一系列对象本身的形状。接着对应鼠标(或其他东西,如目标矩形)位置的像素被放置到2D空间。像素所包含的颜色将组成场景中的物体,这个颜色告知我们哪个对象被点击。

这个方法的优点是拾取是“像素完美(pixel perfect)”的,也就是说,它可以以像素的精度告知对象的哪个位置被点击,这比其他方法精确得多,如果对象已经绘制,那么使用这个方法代价也是很小的。另外,如果在shader中要修改几何数据,如凹凸映射,我们也能实现拾取。

但是,这种方法也有一些缺点。首先,如果一个物体变得小于一个像素,我们将无法将它与靠近它的物体区别开来,因为我们只能达到一个像素的精度。其次,如果我们试图判断对象是否包含在一个区域中(如拖动一个盒子穿过屏幕),我们必须使用更多的采样点,这在某些计算机上可能是非常耗资源的。最后,我们只能获取最上层的对象,因为我们只能在一个像素上放置一种颜色。这意味着,如果一个对象是在另一个背后,我们就无法实现拾取,因为我们只保存了前面一个对象的颜色。这使得多个对象的选择很难,因为我们只能获得前面的对象而不能获得其后的对象。

我写了一个例子展示了这种技术,它可在Ziggyware.com上的以下网址http://www.ziggyware.com/readarticle.php?article_id=203下载。

 

多边形拾取

第二种方法是多边形拾取。思路是创建一个ray(射线),它从鼠标在三维空间中的位置出发,并延伸至无穷远(理论上而已,现实中我们无法做到这点,因为无法处理无穷大的数。但我们可以取到足够大直到不影响结果)。然后,我们检查ray和场景中的多边形。如果多边形之一与ray相交,我们就知道对象是在鼠标光标之下,那么我们将其添加到一个集合并移至下一个。一旦我们检查了所有对象,我们查看集合并选择最接近那个对象。

使用Viewport.Unproject()方法能容易地获得光标在3维空间中的位置,这个方法需要为鼠标的屏幕坐标使用一个Vector3变量,其中X和Y是鼠标在二维空间的X和Y位置, Z是离开“屏幕”的距离。因此,要获得我们需要的两个点,我们将创建一个Vector3代表屏幕上的光标,另外一个代表离屏幕无穷远的光标。对应分别为(X Pos, Y Pos, 0)和(X Pos, Y Pos, 1)。然而,由于我们不能使用无穷大的数,后者的Z坐标应该接近于0.99。

多边形拾取比颜色键值拾取更精确(译者注:原文是Polygon picking is more accute than color keys because it is not limted by the number of pixels in the image,但这样的话与前面所说的“像素拾取比其他方法精确得多”相矛盾),因为它不受图像中像素数量的限制。同样,只要在shader中不修改几何数据,它是非常完美的。它还可以实现叠在一起的多个对象的选取。

多边形拾取的缺点是它代价不菲。检查每个对象中的每个多边形与射线的相交很费时间。

在Ziggyware也有一个示例http://www.ziggyware.com/readarticle.php?article_id=103演示了如何实现多边形拾取。

 

包围盒(Bounding Volumes)

它是目前最有效的拾取方法,包围盒用几何体的近似形状代替几何体本身。最常用的形状是盒和球。检查射线与盒或球相交要远远快于检查数以千计的多边形。通常在一个模型中使用一组包围盒,这样可以使拾取更精确但同时仍然保留拾取的效率。

唯一的缺点是这种方法并不能精确到像素。但是,通常你无需让拾取精确到像素,往往检查盒,球或胶囊就足够了。

XNA提供了一些包围盒,并且已经包含了一些方法判断是否相交。这些包围盒有BoundingBox,BoundingSphere,BoundingFrustum,平面和Ray(射线)。所有对象的Intersects()方法能告诉你是否相交。

本文的最后一部分包含了实现包围盒拾取的普遍方法和适用我们的游戏引擎的特定方法。

混合实现

如果在特定场合你实在需要达到像素精度,可以考虑采取混合的办法。混合方法能让你在有些时候达到像素精度,而其余时候用近似形状。例如,如果您编写一个与一群敌人战斗的游戏,玩家可能不会注意即使他们射出的子弹偏离敌人头部几个像素远仍然可以击中敌人。接着,例如,因为玩家使用狙击视角拉近镜头,那么这种情况下就应切换到使用颜色键值拾取。

这样做更加可行,因为你只取样在视野中的像素点,你只需在玩家扣动板机时这样做。。您不必担心像素拾取的局限性,因为您正在观察的对象通常比一个像素大得多,也不必担心对象后面是什么。(除非您希望能够打穿某些材质,在这种情况下,您必须使用多边形拾取)。您也不用担心绘制颜色键的开销,因为你只需要绘制视野中的对象,因为视野通常很小所以这些对象不会很多,其余的对象可能是某些视野纹理。

优化

即使使用包围盒近似,如果游戏中有太多对象,因为要执行相交检查,游戏仍会被大大拖慢。幸运的是,我们可以减少检查对象的数量。如果使用一些优化技术,我们可以无需检查场景中的大多数对象。最常见的和最有用的方法是四叉树,八叉树,BSP,遮挡筛选和视景体裁剪。实现包围盒近似实现包围盒近似是非常简单的。首先,我们找到两点构成拾取线:对应从鼠标在3D空间的屏幕位置指向无穷远处。然后我们创建一条射线使用第一个点和下一个点的方向。然后,我们使用它来检查每个模型的BoundingBoxes。该BoundingBoxes可以通过合并模型中的每个ModelMesh计算获得,或在素材管道中遍历每个顶点,检查它们是否低于最低点或高于最高点,如果是,那么将最低点或最高点移至的顶点位置。这两个点就作为BoundingBox的顶点。这一步最好在素材管道中实现,以避免重复进行这一步。

下面的代码说明如何处理包围盒的集合:

      
      
      
      
public BoundingBox Pick ( int X, int Y, out float Dist, List < BoundingBox > Check,
Matrix View, Matrix Projection)
{
Vector3 nearSource
= new Vector3(( float )X, ( float )Y, 0 );
Vector3 farSource
= new Vector3(( float )X, ( float )Y, .99f);

Vector3 nearPoint
= Engine.GraphicsDevice.Viewport.Unproject(nearSource,
Projection, View, Matrix.CreateTranslation(
0 , 0 , 0 ));
Vector3 farPoint
= Engine.GraphicsDevice.Viewport.Unproject(farSource,
Projection, View, Matrix.CreateTranslation(
0 , 0 , 0 ));

Vector3 direction
= farPoint - nearPoint;
direction.Normalize();

Ray ray
= new Ray(nearPoint, direction);

BoundingBox closest
= null ;
float ? closestDist = float .MaxValue;

foreach (BoundingBox box in check)
{
float ? dist;
ray.Intersects(
ref box, out dist);

if (dist != null )
if (dist.Value < closestDist)
{
closestDist
= dist;
closest
= box;
}
}

Dist
= (closestDist != null ? closestDist.Value : 0 );
return closest;
}
以下代码显示在游戏引擎中的实现:
      
      
      
      
public Component Pick ( int X, int Y, out float Dist)
{
Vector3 nearSource
= new Vector3(( float )X, ( float )Y, 0 );
Vector3 farSource
= new Vector3(( float )X, ( float )Y, .99f);

Vector3 nearPoint
= Engine.GraphicsDevice.Viewport.Unproject(nearSource,
Camera.Projection,
Camera.View, Matrix.CreateTranslation(
0 , 0 , 0 ));
Vector3 farPoint
= Engine.GraphicsDevice.Viewport.Unproject(farSource,
Camera.Projection,
Camera.View, Matrix.CreateTranslation(
0 , 0 , 0 ));

Vector3 direction
= farPoint - nearPoint;
direction.Normalize();

Ray ray
= new Ray(nearPoint, direction);

Component closest
= null ;
float ? closestDist = float .MaxValue;

foreach (GameScreen screen in Engine.GameScreens)
foreach (Component component in screen.Components)
if (component is I3DComponent)
{
BoundingBox bbox
= ((I3DComponent)component).BoundingBox;

float ? dist;
ray.Intersects(
ref bbox, out dist);

if (dist != null && component != Camera && component != Grid)
if (dist.Value < closestDist)
{
closestDist
= dist;
closest
= component;
}
}

Dist
= (closestDist != null ? closestDist.Value : 0 );
return closest;
}
注意:组件返回的BoundingBoxes应在组件位置上加上一个偏移量,即返回的BoundingBox的顶角的位置是原始顶角位 置加上组件的位置。

 

 

 

说明:

判定在一个3D场景中鼠标是否经过了一个物体,以及如何在屏幕中找到物体。

在某些情况下我们需要知道一些3D物体在屏幕中的位置,以及是否经过了某个物体。例如判断玩家是否点中了一个物体,并在上面显示文字。可以通过使用Viewport类中的Project和Unproject方法来实现。本例显示如何实现这一方法。

本例中绘制了一张桌子,上面放着一些物体,当光标经过时,相应的名字会绘制在物体上。

你可能感兴趣的:(3D picking Sample in XNA)