拾取原理
拾取主要用来表示能过鼠标在屏幕上单击来选中某个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);
}
|