XNA 3D游戏开发入门基本——鼠标选择3D模型(3D物体的拾取)

来源:互联网 发布:js截取url后面的 编辑:程序博客网 时间:2024/04/20 19:39

拾取原理

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

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

拾取的具体过程如下:

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

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

屏幕中心是(0, 0),屏幕右下角是 (1*(屏幕宽度/屏幕高度), 1)。

屏幕空间的x坐标这样计算: ((鼠标x坐标) / (屏幕宽度/2))-1.0f) *(屏幕宽度/屏幕高度)

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

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

如下计算:

?
1
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)来把世界坐标转化成摄像机坐标,使用这个反转的矩阵可以把摄像机坐标转化成世界坐标。

?
1
Matrix invView = Matrix.Invert(view);

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

?
1
2
Vector3 worldSpaceNear = Vector3.Transform(cameraSpaceNear, invView);
Vector3 worldSpaceFar = Vector3.Transform(cameraSpaceFar, invView);

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

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

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

实现

主要方法如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
public class Game1:Game
{
GraphicsDeviceManager graphi;
model[] models;
Texture2D texture;
Matrix view;
Matrix projection;
intselectIndex = -1;
publicGame1()
{
graphi =new GraphicsDeviceManager(this);
Content.RootDirectory ="Content";
IsMouseVisible =true;
}
protectedoverride voidInitialize()
{
models =new model[4];
models[0] =new model();
models[0].position = Vector3.Zero;
models[1] =new model();
models[1].position =newVector3(80,0,0);
models[2] =new model();
models[2].position =new Vector3(-80, 0, 0);
models[3] =new model();
models[3].position =new Vector3(80, 80, 0);
//观察矩阵
view = Matrix.CreateLookAt(newVector3(0, 0, 300), Vector3.Forward, Vector3.Up);
//投影矩阵
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1, 10000);
base.Initialize();
}
protectedoverride voidLoadContent()
{
//载入模型文件
models[0].mod = Content.Load<Model>("bsphere");
models[1].mod = Content.Load<Model>("cub");
models[2].mod = Content.Load<Model>("pyramid");
models[3].mod = Content.Load<Model>("teaport");
//载入选中物体的贴图纹理
texture = Content.Load<Texture2D>("sp");
base.LoadContent();
}
/**//// <summary>
/// 更新
/// </summary>
/// <param name="gameTime"></param>
protectedoverride voidUpdate(GameTime gameTime)
{
CheckMousClick();
base.Update(gameTime);
}
/**//// <summary>
/// 绘制
/// </summary>
/// <param name="gameTime"></param>
protectedoverride voidDraw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
for(int i = 0; i < models.Length;i++ )
{
foreach(ModelMesh mesh inmodels[i].mod.Meshes)
{
foreach(BasicEffect effectin mesh.Effects)
{
effect.TextureEnabled =true;
//根据是否选中设置物体的贴图
if(i != selectIndex)
{
//如果没有选中,不贴图
effect.Texture =null;
}
else
effect.Texture = texture;
effect.World = Matrix.CreateTranslation(models[i].position);
effect.View = view;
effect.Projection = projection;
effect.EnableDefaultLighting();
effect.LightingEnabled =true;
}
mesh.Draw();
}
}
base.Draw(gameTime);
}
/**//// <summary>
/// 取得射线
/// </summary>
/// <returns></returns>
privateRay GetRay()
{
MouseState ms = Mouse.GetState();
Vector3 neerSource =new Vector3(ms.X, ms.Y, 0);
Vector3 farSource =new Vector3(ms.X, ms.Y, 1);
Vector3 neerPosi = GraphicsDevice.Viewport.Unproject(neerSource, projection, view, Matrix.Identity);
Vector3 farPosi = GraphicsDevice.Viewport.Unproject(farSource, projection, view, Matrix.Identity);
Vector3 direction=farPosi-neerPosi;
direction.Normalize();
returnnew Ray(neerPosi, direction);
}
/**//// <summary>
/// 鼠标单击
/// </summary>
privatevoid CheckMousClick()
{
if(Mouse.GetState().LeftButton==ButtonState.Pressed)
{
Ray ray = GetRay();
for(int i=0;i<models.Length;i++)
{
BoundingSphere bs=models[i].mod.Meshes[0].BoundingSphere;
bs.Center = models[i].position;
Nullable<float> result = ray.Intersects(bs);
if(result.HasValue)
{
selectIndex = i;
}
}
}
}
}
/**//// <summary>
/// 自定义模型结构
/// </summary>
publicstruct model
{
publicModel mod;
publicTexture2D text;
publicVector3 position;
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Vector3 Unproject(Vector3 source, Matrix projection, Matrix view, Matrix world)
{
Vector3 position =new Vector3();
Matrix matrix = Matrix.Invert(Matrix.Multiply(Matrix.Multiply(world, view), projection));
position.X = (((source.X -this.X) / ((float)this.Width)) * 2f) - 1f;
position.Y = -((((source.Y -this.Y) / ((float)this.Height)) * 2f) - 1f);
position.Z = (source.Z -this.MinDepth) / (this.MaxDepth -this.MinDepth);
position = Vector3.Transform(position, matrix);
floata = (((source.X * matrix.M14) + (source.Y * matrix.M24)) + (source.Z * matrix.M34)) + matrix.M44;
if(!WithinEpsilon(a, 1f))
{
position = (Vector3) (position / a);
}
returnposition;
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public void Intersects(refRay ray, outfloat? result)
{
result = 0;
floatnum = 0f;
floatmaxValue = float.MaxValue;
if(Math.Abs(ray.Direction.X) < 1E-06f)
{
if((ray.Position.X < this.Min.X) || (ray.Position.X >this.Max.X))
{
return;
}
}
else
{
floatnum11 = 1f / ray.Direction.X;
floatnum8 = (this.Min.X - ray.Position.X) * num11;
floatnum7 = (this.Max.X - ray.Position.X) * num11;
if(num8 > num7)
{
floatnum14 = num8;
num8 = num7;
num7 = num14;
}
num = MathHelper.Max(num8, num);
maxValue = MathHelper.Min(num7, maxValue);
if(num > maxValue)
{
return;
}
}
if(Math.Abs(ray.Direction.Y) < 1E-06f)
{
if((ray.Position.Y < this.Min.Y) || (ray.Position.Y >this.Max.Y))
{
return;
}
}
else
{
floatnum10 = 1f / ray.Direction.Y;
floatnum6 = (this.Min.Y - ray.Position.Y) * num10;
floatnum5 = (this.Max.Y - ray.Position.Y) * num10;
if(num6 > num5)
{
floatnum13 = num6;
num6 = num5;
num5 = num13;
}
num = MathHelper.Max(num6, num);
maxValue = MathHelper.Min(num5, maxValue);
if(num > maxValue)
{
return;
}
}
if(Math.Abs(ray.Direction.Z) < 1E-06f)
{
if((ray.Position.Z < this.Min.Z) || (ray.Position.Z >this.Max.Z))
{
return;
}
}
else
{
floatnum9 = 1f / ray.Direction.Z;
floatnum4 = (this.Min.Z - ray.Position.Z) * num9;
floatnum3 = (this.Max.Z - ray.Position.Z) * num9;
if(num4 > num3)
{
floatnum12 = num4;
num4 = num3;
num3 = num12;
}
num = MathHelper.Max(num4, num);
maxValue = MathHelper.Min(num3, maxValue);
if(num > maxValue)
{
return;
}
}
result =new float?(num);
}

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 买家写错快递地址怎么办 淘宝退款选择服务类型出不来怎么办 网购东西发错了怎么办 拒签商家不退款怎么办 红米手机无响应怎么办 淘宝网登录密码忘记了怎么办 淘宝网密码忘记了怎么办 京东抢购不发货怎么办 微商不想做了怎么办 减肥过程中饿了怎么办 滴滴车龄超过6年怎么办 网络公选课没过怎么办 大学网络课挂了怎么办 淘宝虚拟订单买家恶意退款怎么办 淘宝卖家虚拟发货怎么办 淘宝买虚拟产品被骗了怎么办 哈尔滨暖气低于十八度怎么办 淘客店铺没人买怎么办 淘宝商家不给退货怎么办 淘宝卖家拒绝退款申请怎么办 运费险赔付少了怎么办 买了运费险退货怎么办 卖家运费险退货怎么办 京东生鲜有坏的怎么办 与上级意见不一致时你将怎么办 物金所倒闭投资怎么办 电商平台欺骗客户怎么办 pdf电脑打开是乱码怎么办 excel表格打开是乱码怎么办 win10安装软件出现乱码怎么办 华为手机速度越来越慢怎么办 oppo手机速度越来越慢怎么办 安卓手机速度越来越慢怎么办 青桔单车忘了锁怎么办 华为手机反应太慢了怎么办 魅族关机键失灵怎么办 oppa79手机开不开机怎么办 黑衣服洗完发白怎么办 白衣服被黑衣服染色了怎么办 评职称单位领导不推荐怎么办 支付宝被限制收款怎么办