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实现
效果图如下:


主要方法如下:
public class Game1:Game2
{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(-80, 0, 0);31

32
models[3] = new model();33
models[3].position = new Vector3(80, 80, 0);34

35
//观察矩阵36
view = Matrix.CreateLookAt(new Vector3(0, 0, 300), Vector3.Forward, Vector3.Up);37
//投影矩阵38
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1, 10000);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
else88
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 model149
{150
public Model mod;151
public Texture2D text;152
public Vector3 position;153
}
其中用到了XNA中的Viewport.Unproject方法和Ray.Intersects方法,它们的内部结构分别如下:
public 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) / ((float) this.Width)) * 2f) - 1f;6
position.Y = -((((source.Y - this.Y) / ((float) this.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

public 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
else14
{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
else39
{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
else64
{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



浙公网安备 33010602011771号