200 likes | 330 Views
网络游戏脚本编程. 专业教程 北京汇众益智科技有限公司. 鼠标拾取. 什么是拾取,拾取能做什么?. 拾取操作指当我们在屏幕上用鼠标点击某个图元,应用程序能返回该图元的一个标志或某些相关信息。 有这些信息就得到该图元的控制权,我们可以删除,可以编辑,可以任意对待该图元. 拾取操作的原理. 拾取算法的思想很简单:得到鼠标点击处的屏幕坐标,通过投影矩阵和观察矩阵把该坐标转换为通过视点和鼠标点击点的一条射入场景的光线,该光线如果与场景模型的三角形相交(本文只处理三角形图元),则获取该相交三角形的信息。.
E N D
网络游戏脚本编程 专业教程 北京汇众益智科技有限公司
什么是拾取,拾取能做什么? 拾取操作指当我们在屏幕上用鼠标点击某个图元,应用程序能返回该图元的一个标志或某些相关信息。 有这些信息就得到该图元的控制权,我们可以删除,可以编辑,可以任意对待该图元 拾取操作的原理 拾取算法的思想很简单:得到鼠标点击处的屏幕坐标,通过投影矩阵和观察矩阵把该坐标转换为通过视点和鼠标点击点的一条射入场景的光线,该光线如果与场景模型的三角形相交(本文只处理三角形图元),则获取该相交三角形的信息。 从数学角度来看,我们只要得到射线的方向矢量和射线的出射点,我们就具备了判断射线与空间一个三角面是否相交的条件
坐标系变换 d3d坐标系转换的流程,如下图: 所以我们要通过一系列的反变换,得到我们关心的值在世界坐标中的表示。
确定鼠标选取点的屏幕坐标 Windows API提供了函数来完成屏幕坐标的获取,使用GetCursorPos获得鼠标指针位置,然后再利用ScreenToClient转换到客户区坐标系(以窗口视区左上角为坐标原点),设该坐标为(POINT screenPt)。 得到该点在投影空间内的表示
透视投影内部原理 透视投影变换,就是view 空间到project 空间的带透视性质的坐标变换 变换之后必须保证view空间中所有处于可视范围内的点统统落在project空间的可视区域内 view空间中可视范围 view空间中的可见范围就是常说的视平截体(view frustum)。如图,
从view空间的x正半轴看过去是下图这个样子: 上下边界为y = ± z * tan(fov/2)
project空间的可视范围。这个空间应当是处于我们所见到的屏幕上。实际上将屏幕表面视作project空间的xoy平面,再加一条垂直屏幕向里的z轴,这样就构成了我们想要的坐标系。现在我们可以用视口(view port)的大小来描述这个可视范围了。比如说全屏幕640*480的分辨率,原点在屏幕中心,那我们得到的可视区域为一个长方体,它如下图所示:
将project空间的可视范围定义为x∈[-1,1], y∈[-1,1], z∈[0,1]的一个立方体
从project空间的x正半轴看看我们的视区范围: 这个区域的上下边界为y’=±1
根据比例关系,screenPt与投影空间上的点projPt之间的关系为根据比例关系,screenPt与投影空间上的点projPt之间的关系为 假设图形程序窗口的宽为screenWidth,高为screenHeight, projPt.x = (screenPt.x-screenWidth/2)/screenWidth*2; projPt.y = (screenPt.y-screenHeight/2)/screenHeight*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,则根据观察空间到投影空间的变换关系则我们可以通过 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; ProjMatrix = = 所以, (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;
化简一下三个分量同除近剪切面z坐标Zn,该方向矢量可写作化简一下三个分量同除近剪切面z坐标Zn,该方向矢量可写作 DIRview = (projPt.x/projMatrix._m11,projPt.y/projMatrix._m22,1) 代入公式 projPt.x = (screenPt.x-screenWidth/2)/screenWidth*2; projPt.y = (screenPt.y-screenHeight/2)/screenHeight*2; projPt.z =0; DIRview.x = (2*screenPt.x/screenWidth-1)/projMatrix._m11; DIRview.y = (2*screenPt.y/screenHeight-1)/projMatrix._m22; DIRview.z = 1;
转换Dir到世界坐标空间,并得到观察点在世界坐标系中的坐标转换Dir到世界坐标空间,并得到观察点在世界坐标系中的坐标 由于最终的运算要在世界坐标空间中进行,所以我们还需要把矢量DIRview从观察空间转换为世界坐标空间中的矢量DIRworld。 因为 DIRview = DIRworld*ViewMatrix; 其中ViewMatrix为观察矩阵,在D3D中可以用函数GetTransform( D3DTS_VIEW, &ViewMatrix )得到。 所以DIRworld = DIRview * inverse_ViewMatrix,其中inverse_ViewMatrix为 ViewMatrix的逆矩阵。
使用射线矢量对场景中的所有三角形图元求交,获得三角形索引值和重心坐标。使用射线矢量对场景中的所有三角形图元求交,获得三角形索引值和重心坐标。 这一步骤地实现由两种途径: 第一种方法非常简单,利用D3D提供的扩展函数D3DXIntersect第二种方法就是我们根据空间解析几何的知识,自己来完成射线三角形的求交算法。
D3D扩展函数实现求交 D3D SDK该函数声明如下 HRESULT D3DXIntersect( LPD3DXBASEMESH pMesh, CONST D3DXVECTOR3 *pRayPos, CONST D3DXVECTOR3 *pRayDir, BOOL *pHit, DWORD *pFaceIndex, FLOAT *pU, FLOAT *pV, FLOAT *pDist, LPD3DXBUFFER *ppAllHits, DWORD *pCountOfHits ); l pMesh指向一个ID3DXBaseMesh的对象,最简单的方式是从.x文件获得,描述了要进行相交检测的三角面元集合的信息,具体规范参阅direct9 SDK l pRayPos 指向射线发出点 l pRayDir 指向前面我们辛辛苦苦求出的射线方向的向量 l pHit 当检测到相交图元时,指向一个true,不与任何图元相交则为假 l pU 用于返回重心坐标U分量 l pV返回重心坐标V分量 l pDist 返回射线发出点到相交点的长度 l ppAllHits用于如果存在多个相交三角面返回相交的所有结果 l pCountOfHits 返回共有多少个三角形与该射线相交
重心坐标的概念 其中pU和pV用到了重心坐标的概念,一个三角形有三个顶点,在迪卡尔坐标系中假设表示为V1(x1,y1,z1),V2(x2,y2,z2),V3(x3,y3,z3),则三角形内任意一点的坐标可以表示为 pV = V1 + U(V2-V1) + V(V3-V1),所以已知三个顶点坐标的情况下,任意一点可用坐标(U,V)来表示,其中 参数U控制V2在结果中占多大的权值,参数V控制V3占多大权值,最终1-U-V控制V1占多大权值,这种坐标定义方式就叫重心坐标。