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

24 篇文章 0 订阅
15 篇文章 0 订阅

拾取原理

拾取主要用来表示能过鼠标在屏幕上单击来选中某个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;
int selectIndex = -1;
public Game1()
{
graphi = new GraphicsDeviceManager( this );
Content.RootDirectory = "Content" ;
IsMouseVisible = true ;
}
protected override void Initialize()
{
models = new model[4];
models[0] = new model();
models[0].position = Vector3.Zero;
models[1] = new model();
models[1].position = new Vector3(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( new Vector3(0, 0, 300), Vector3.Forward, Vector3.Up);
//投影矩阵
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1, 10000);
base .Initialize();
}
protected override void LoadContent()
{
//载入模型文件
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>
protected override void Update(GameTime gameTime)
{
CheckMousClick();
base .Update(gameTime);
}
/**/ /// <summary>
/// 绘制
/// </summary>
/// <param name="gameTime"></param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
for ( int i = 0; i < models.Length;i++ )
{
foreach (ModelMesh mesh in models[i].mod.Meshes)
{
foreach (BasicEffect effect in 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>
private Ray 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();
return new Ray(neerPosi, direction);
}
/**/ /// <summary>
/// 鼠标单击
/// </summary>
private void 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>
public struct model
{
public Model mod;
public Texture2D text;
public Vector3 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);
float a = (((source.X * matrix.M14) + (source.Y * matrix.M24)) + (source.Z * matrix.M34)) + matrix.M44;
if (!WithinEpsilon(a, 1f))
{
position = (Vector3) (position / a);
}
return position;
}
?
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( ref Ray ray, out float ? result)
{
result = 0;
float num = 0f;
float maxValue = 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
{
float num11 = 1f / ray.Direction.X;
float num8 = ( this .Min.X - ray.Position.X) * num11;
float num7 = ( this .Max.X - ray.Position.X) * num11;
if (num8 > num7)
{
float num14 = 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
{
float num10 = 1f / ray.Direction.Y;
float num6 = ( this .Min.Y - ray.Position.Y) * num10;
float num5 = ( this .Max.Y - ray.Position.Y) * num10;
if (num6 > num5)
{
float num13 = 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
{
float num9 = 1f / ray.Direction.Z;
float num4 = ( this .Min.Z - ray.Position.Z) * num9;
float num3 = ( this .Max.Z - ray.Position.Z) * num9;
if (num4 > num3)
{
float num12 = num4;
num4 = num3;
num3 = num12;
}
num = MathHelper.Max(num4, num);
maxValue = MathHelper.Min(num3, maxValue);
if (num > maxValue)
{
return ;
}
}
result = new float ?(num);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值