3D物体拾取及XNA实现

拾取原理

    拾取主要用来表示能过鼠标在屏幕上单击来选中某个3D模型,然后就可以获取这个模型信息,同时也可以对这个模型进行编辑。

    拾取算法的主要思想是:得到鼠标点击处的屏幕坐标,通过投影矩阵和观察矩阵把该坐标转换为通过视点和鼠标点击点的一条射入场景的光线,该光线如果与场景模型的三角形相交,则获取该相交三角形的信息。

拾取的具体过程如下:

1.使用获取鼠标当前状态。

2.把屏幕坐标转换为屏幕空间坐标

屏幕中心是(0, 0),屏幕右下角是 (1*(屏幕宽度/屏幕高度), 1)。屏幕空间的x坐标这样计算: ((鼠标x坐标) / (屏幕宽度/2))1.0f) *(屏幕宽度/屏幕高度)

屏幕空间的y坐标这样计算: (1.0f − ((鼠标y坐标) / (屏幕高度/ 2 ) )

3.计算摄像机视图中的宽度和高度的截距比。如下计算:

view ratio = tangent(camera field of view / 2 )

通常,你只需要一次,然后保存这个结果供以后使用。在摄像机视野改变的时候,你需要重新计算。

4.把屏幕空间坐标转换成摄像机空间坐标系近景裁剪平面中的一个点。

Near point = ((屏幕空间x坐标)*(近景裁剪平面的Z值)*(view ratio ),
(屏幕空间y坐标)
*(近景裁剪平面的Z值)*(view ratio ),
(-近景裁剪平面的Z值) )

5.把屏幕空间坐标转换成摄像机空间坐标系远景裁剪平面中的一个点。

Far point = ((屏幕空间x坐标)* (远景裁剪平面的Z值)*(view ratio ),
(屏幕空间y坐标)
*(远景裁剪平面的Z值 )*(view ratio),
(-远景裁剪平面的Z值) )

6.使用Invert 获得摄像机视图矩阵的一个Invert的拷贝。使用摄像机视图矩阵(Matrix)来把世界坐标转化成摄像机坐标,使用这个反转的矩阵可以把摄像机坐标转化成世界坐标。

Matrix invView = Matrix.Invert(view);

7.使用反转的视图矩阵(view Matrix ) 和Transform方法把远景点和近景点转换成世界空间坐标。

Vector3 worldSpaceNear = Vector3.Transform(cameraSpaceNear, invView);

Vector3 worldSpaceFar = Vector3.Transform(cameraSpaceFar, invView);

8.创建一个射线(Ray)类的对象,起点是近景点,指向远景点。

Ray pickRay = new Ray(worldSpaceNear, worldSpaceFar - worldSpaceNear);

9.对世界空间中的所有物体循环调用 Intersects 方法来检测 Ray 是否与其相交。如果相交,则检测是不是目前为止距玩家最近的物体,如果是,记录下这个物体以及距离值,替换掉之前找到的最近的物体的记录。当完成对所有物体的检测后,最后一次记录的那个物体就是玩家用鼠标点击的距离玩家最近的物体。

XNA实现

     效果图如下:

 

主要方法如下:

 

  1   public class Game1:Game
  2    {
  3        GraphicsDeviceManager graphi;
  4        model[] models;
  5        Texture2D texture;
  6
  7        Matrix view;
  8        Matrix projection;
  9
 10        int selectIndex = -1;
 11
 12        public Game1()
 13        {
 14            graphi = new GraphicsDeviceManager(this);
 15            Content.RootDirectory = "Content";
 16            IsMouseVisible = true;
 17        }

 18
 19        protected override void Initialize()
 20        {
 21            models = new model[4];
 22            models[0= new model();
 23            models[0].position = Vector3.Zero;
 24
 25
 26            models[1= new model();
 27            models[1].position =new Vector3(80,0,0);
 28
 29            models[2= new model();
 30            models[2].position = new Vector3(-8000);
 31
 32            models[3= new model();
 33            models[3].position = new Vector3(80800);
 34
 35            //观察矩阵
 36            view = Matrix.CreateLookAt(new Vector3(00300), Vector3.Forward, Vector3.Up);
 37            //投影矩阵
 38            projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 110000);
 39            base.Initialize();
 40        }

 41
 42
 43        protected override void LoadContent()
 44        {
 45            //载入模型文件
 46            models[0].mod = Content.Load<Model>("bsphere");
 47            models[1].mod = Content.Load<Model>("cub");
 48            models[2].mod = Content.Load<Model>("pyramid");
 49            models[3].mod = Content.Load<Model>("teaport");
 50
 51            //载入选中物体的贴图纹理
 52            texture = Content.Load<Texture2D>("sp");
 53            base.LoadContent();
 54        }

 55
 56        /// <summary>
 57        /// 更新
 58        /// </summary>
 59        /// <param name="gameTime"></param>

 60        protected override void Update(GameTime gameTime)
 61        {
 62            CheckMousClick();
 63            base.Update(gameTime);
 64        }

 65
 66        /// <summary>
 67        /// 绘制
 68        /// </summary>
 69        /// <param name="gameTime"></param>

 70        protected override void Draw(GameTime gameTime)
 71        {
 72            GraphicsDevice.Clear(Color.CornflowerBlue);
 73
 74            for (int i = 0; i < models.Length;i++ )
 75            {
 76                foreach (ModelMesh mesh in models[i].mod.Meshes)
 77                {
 78                    foreach(BasicEffect effect in mesh.Effects)
 79                    {
 80                        effect.TextureEnabled = true;
 81                        //根据是否选中设置物体的贴图
 82                        if (i != selectIndex)
 83                        {
 84                            //如果没有选中,不贴图
 85                            effect.Texture = null;
 86                        }

 87                        else
 88                            effect.Texture = texture;
 89
 90                        effect.World = Matrix.CreateTranslation(models[i].position);
 91                        effect.View = view;
 92                        effect.Projection = projection;
 93
 94                        effect.EnableDefaultLighting();
 95                        effect.LightingEnabled = true;
 96                    }

 97
 98                    mesh.Draw();
 99                }

100                
101            }

102
103            base.Draw(gameTime);
104        }

105
106        /// <summary>
107        /// 取得射线
108        /// </summary>
109        /// <returns></returns>

110        private Ray GetRay()
111        {
112            MouseState ms = Mouse.GetState();
113            Vector3 neerSource = new Vector3(ms.X, ms.Y, 0);
114            Vector3 farSource = new Vector3(ms.X, ms.Y, 1);
115
116            Vector3 neerPosi = GraphicsDevice.Viewport.Unproject(neerSource, projection, view, Matrix.Identity);
117            Vector3 farPosi = GraphicsDevice.Viewport.Unproject(farSource, projection, view, Matrix.Identity);
118            Vector3 direction=farPosi-neerPosi;
119            direction.Normalize();
120            return new Ray(neerPosi, direction);
121        }

122
123        /// <summary>
124        /// 鼠标单击
125        /// </summary>

126        private void CheckMousClick()
127        {
128            if (Mouse.GetState().LeftButton==ButtonState.Pressed)
129            {
130                Ray ray = GetRay();
131                for (int i=0;i<models.Length;i++)
132                {
133                    BoundingSphere bs=models[i].mod.Meshes[0].BoundingSphere;
134                    bs.Center = models[i].position;
135                    Nullable<float> result = ray.Intersects(bs);
136                    if (result.HasValue)
137                    {
138                        selectIndex = i;
139                    }

140                }

141            }

142        }

143    }

144
145    /// <summary>
146    /// 自定义模型结构
147    /// </summary>

148    public struct  model
149    {
150        public Model mod;
151        public Texture2D text;
152        public Vector3 position;
153    }

 

其中用到了XNA中的Viewport.Unproject方法和Ray.Intersects方法,它们的内部结构分别如下:

 

 1public Vector3 Unproject(Vector3 source, Matrix projection, Matrix view, Matrix world)
 2{
 3    Vector3 position = new Vector3();
 4    Matrix matrix = Matrix.Invert(Matrix.Multiply(Matrix.Multiply(world, view), projection));
 5    position.X = (((source.X - this.X) / ((floatthis.Width)) * 2f) - 1f;
 6    position.Y = -((((source.Y - this.Y) / ((floatthis.Height)) * 2f) - 1f);
 7    position.Z = (source.Z - this.MinDepth) / (this.MaxDepth - this.MinDepth);
 8    position = Vector3.Transform(position, matrix);
 9    float a = (((source.X * matrix.M14) + (source.Y * matrix.M24)) + (source.Z * matrix.M34)) + matrix.M44;
10    if (!WithinEpsilon(a, 1f))
11    {
12        position = (Vector3) (position / a);
13    }

14    return position;
15}

16
17 
18
19 
20

 

 

 1public void Intersects(ref Ray ray, out float? result)
 2{
 3    result = 0;
 4    float num = 0f;
 5    float maxValue = float.MaxValue;
 6    if (Math.Abs(ray.Direction.X) < 1E-06f)
 7    {
 8        if ((ray.Position.X < this.Min.X) || (ray.Position.X > this.Max.X))
 9        {
10            return;
11        }

12    }

13    else
14    {
15        float num11 = 1f / ray.Direction.X;
16        float num8 = (this.Min.X - ray.Position.X) * num11;
17        float num7 = (this.Max.X - ray.Position.X) * num11;
18        if (num8 > num7)
19        {
20            float num14 = num8;
21            num8 = num7;
22            num7 = num14;
23        }

24        num = MathHelper.Max(num8, num);
25        maxValue = MathHelper.Min(num7, maxValue);
26        if (num > maxValue)
27        {
28            return;
29        }

30    }

31    if (Math.Abs(ray.Direction.Y) < 1E-06f)
32    {
33        if ((ray.Position.Y < this.Min.Y) || (ray.Position.Y > this.Max.Y))
34        {
35            return;
36        }

37    }

38    else
39    {
40        float num10 = 1f / ray.Direction.Y;
41        float num6 = (this.Min.Y - ray.Position.Y) * num10;
42        float num5 = (this.Max.Y - ray.Position.Y) * num10;
43        if (num6 > num5)
44        {
45            float num13 = num6;
46            num6 = num5;
47            num5 = num13;
48        }

49        num = MathHelper.Max(num6, num);
50        maxValue = MathHelper.Min(num5, maxValue);
51        if (num > maxValue)
52        {
53            return;
54        }

55    }

56    if (Math.Abs(ray.Direction.Z) < 1E-06f)
57    {
58        if ((ray.Position.Z < this.Min.Z) || (ray.Position.Z > this.Max.Z))
59        {
60            return;
61        }

62    }

63    else
64    {
65        float num9 = 1f / ray.Direction.Z;
66        float num4 = (this.Min.Z - ray.Position.Z) * num9;
67        float num3 = (this.Max.Z - ray.Position.Z) * num9;
68        if (num4 > num3)
69        {
70            float num12 = num4;
71            num4 = num3;
72            num3 = num12;
73        }

74        num = MathHelper.Max(num4, num);
75        maxValue = MathHelper.Min(num3, maxValue);
76        if (num > maxValue)
77        {
78            return;
79        }

80    }

81    result = new float?(num);
82}

83
84 
85

 

 

 

 

posted @ 2008-09-17 01:58  齐.net  阅读(4755)  评论(5编辑  收藏  举报