Direct3D中实现图元的鼠标拾取
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Direct3D中实现图元的鼠标拾取
查看文章 Direct3D中实现图元的鼠标拾取
2007-05-05 15:46
3D交互图形应用程序中,常常要用鼠标去选择图形,其实现的机制基于鼠标拾取算法。本文主要讲述如何在D3D中实现图元的鼠标拾取。为了讨论简单,本文假定读者理解D3D 坐标变换流程和基本的图形学知识,如果阅读有困难请参考相关资料。
1、什么是拾取,拾取能做什么,
首先,拾取操作指当我们在屏幕上用鼠标点击某个图元应用程序能返回该图元的一个标志和某些相关信息。有图形程序设计经验的人都知道,有这些信息就表示我们有了对该图元的控制权,我们可以删除,可以编辑,可以任意对待该图元,至于你到底想干什么,就是阁下自己的事了^_^。
2、拾取操作的步骤和实现
拾取算法的思想很简单:得到鼠标点击处的屏幕坐标,通过投影矩阵和观察矩阵把该坐标转换为通过视点和鼠标点击点的一条射入场景的光线,该光线如果与场景模型的三角形相交(本文只处理三角形图元),则获取该相交三角形的信息。本文讲述的方法除可以得到三角形的一个索引号以外还可以得到相交点的重心坐标。
从数学角度来看,我们只要得到射线的方向矢量和射线的出射点,我们就具备了判断射线与空间一个三角面是否相交的条件,本文主要讨论如何获得这些条件,并描述了射线三角面相交判断算法和D3D的通常实现方法。根据拾取操作的处理顺序,大概可以依次分为以下几个步骤
2.1( 变换并获得通过视点和屏幕上点击点的射线矢量(Dir) 详细介绍之前,为了大家方便理解,我们要先简单说一下d3d坐标转换的大概流程,如下图:
所以我们要通过一系列的反变换,得到我们关心的值在世界坐标中的表示。
2.1.1 确定鼠标选取点的屏幕坐标
这一步是非常简单的Windows给我们提供了API来完成屏幕坐标的获取,使用GetCursorPos获得鼠标指针位置,然后再利用ScreenToClient转换坐标到客户区
坐标系(以窗口视区左上角为坐标原点,单位为像素),设该坐标为(POINT screenPt)。
2.1.2 得到Dir在观察坐标空间内的表示
在观察坐标系中,Dir是一条从观察坐标原点出发的射线,所以我们只需要再
确定一个该射线经过的点,就可以得到它在观察坐标系中的表示。假设我们要求的
射线上的另外一点为该射线与透视投影平截头体近剪切面的交点,针对最普遍的透
视投影而言,透视投影平截头体经投影变换后,变成一个1/2立方体(请允许我这
么叫^_^,因为它的大小为一个正方体的一半,x,y方向边长为2,z方向为1)如图:
投影坐标系以近剪切面中心为坐标原点,该立方体从z轴负向看过去与图形程
序视区相对应,最终近剪切面(前剪切面)上一点与屏幕坐标之间的对应关系如下图
所示:
projPt.y = (screenPt.y-screenHeight/2)/screenHeight*2; (公式2) projPt.z =0;(实际该值可任意取,不影响最终结果。为了处理简单,我们取改值为0,表示该点取在近剪切面上)得到projPt后,我们需要做的是把该点坐标从投影空间转换到观察空间(view space),根据透视投影的定义,可假设点
(projPt.x,projPt.y,projPt.z)
对应的其次坐标为 (projPt.x*projPt.w,projPt.y*projPt.w,
projPt.z*projPt.w,projPt.w)
我们可以通过 GetTransform( D3DTS_PROJECTION, &ProjMatrix)函数获得投影矩阵ProjMatrix,则根据观察空间到投影空间的变换关系则
(projPt.x*projPt.w,projPt.y*projPt.w,projPt.z*projPt.w,projPt.w)= (viewPt.x,viewPt.y,viewPt.z, 1)*pProjMatrx;
根据定义和图形学原理
根据比例关系,screenPt与投影空间上的点projPt之间的关系为假设图形程序窗口的宽为screenWidth,高为screenHeight,
projPt.x = (screenPt.x-screenWidth/2)/screenWidth*2; (公式1) 所以, (projPt.x*projPt.w,projPt.y*projPt.w,projPt.z*projPt.w,projPt.w) = ( viewPt.x*ProjMatrix._m11,
viewPt.y*ProjMatrix._m22,
viewPt.z*Q-QZn,
viewPt.z)
所以
projPt.x*projPt.w = viewPt.x*ProjMatrix._m11
projPt.y*projPt.w = viewPt.y*ProjMatrix._m22
projPt.z*projPt.w = viewPt.z*Q-QZn (注意projPt.z = 0) projPt.w = viewPt.z;
解得
viewPt.x = projPt.x*Zn/ ProjMatrix._m11;
viewPt.y = projPt.y*Zn/ ProjMatrix._m22;
viewPt.z = Zn;
好了,到这里为止我们终于求出了射线与近剪切面交点在观察坐标系中的坐标,现在我们拥有了射线的出发点(0,0,0)和射线方向上另外一点
(viewPt.x,viewPt.y,viewPt.z),则该射线的方向矢量在观察空间中的表示可确定为(viewPt.x-0,viewPt.y-0,viewPt.z-0),化简一下三个分量同除近剪切面z坐标Zn,该方向矢量可写作
DIRview = (projPt.x/projMatrix._m11,projPt.y/projMatrix._m22,1) 代入公式1,公式2
DIRview.x = (2*screenPt.x/screenWidth-1)/projMatrix._m11; DIRview.y = (2*screenPt.y/screenHeight-1)/projMatrix._m22; DIRview.z = 1;
其中screenWidth和screenHeight可以通过图像显示的backBuffer的目标表面(D3DSURFACE_DESC)来获得,该表面在程序初始化时由用户创建。
2.1.3 转换Dir到世界坐标空间,并得到观察点在世界坐标系中的坐标由于最终的运算要在世界坐标空间中进行,所以我们还需要把矢量DIRview从观察空间转换为世界坐标空间中的矢量DIRworld。
因为
DIRview = DIRworld*ViewMatrix;