Direct3D中实现图元的鼠标拾取
- 格式:doc
- 大小:53.00 KB
- 文档页数:10
详解软件中的拾取(选择)功能注:本文针对软件研发,全文约2500字模型视图里的拾取(选择)操作,是工业软件(设计仿真)里上层应用的一个基本功能,属于下图中“交互系统”的一部分。
比如在三维模型视图里,我们可以选中一个对象赋材料;选中面或边,赋荷载和边界条件;选中两个对象执行布尔运算;编辑一条NURBS曲线时,选择一个控制点进行拖动等等。
拿到一款软件,首先体验拾取交互功能,每种软件的交互操作不太一样,好的拾取交互能简化操作,提高工作效率。
开源CAD软件FreeCAD里提供了对多种软件交互模式的切换。
如下图:1.基础功能从研发角度看,功能完全的拾取通常包括如下内容:1. 拾取一个三维对象(二维对象可以视为三维的一种特殊情况)2. 拾取三维几何对象上的面,线和点3. 点选(单选),即鼠标点一下选中对象4. 点选(多选),一般按住Ctrl+单选,可以选中多个对象5. 框选,按住鼠标拖动画出一个方框,方框内的选中。
框选又可以分为部分框选选中,和全部框选才算选中两种6. 反选,即物体选中后,再次执行选中操作即设置为不选中7. 拾取通常不会单独发生,会有其它事件触发,比如模型创建,对象高亮显示8. 当模型有前后遮挡,需要选中被挡模型9. 有过滤器,可以使用过滤器选中特定属性的对象10. 对外提供统一的拾取接口11. 处理大模型时,要解决性能瓶颈问题,参考工业软件研发中处理超大模型(4)(点击链接查看)2.拾取原理:根据软件技术选型,拾取原理有以下几种:1.在创建模型的时候,将模型和屏幕像素建立映射关系,当进行拾取时,计算鼠标所在像素位置,再查找映射关系。
这种操作偏底层,一般是渲染引擎做的事情,比如OPENGL里的模板测试。
2.如果仅使用渲染数据,在视图上,从鼠标位置垂直视图发射一条射线,计算射线和视图对象的位置关系,相交则代表拾取到。
3.操作系统处理鼠标事件时,鼠标的坐标位置一般是屏幕坐标(局部坐标),也就是像素值,当使用了三维几何内核时,可以将位置坐标转为世界坐标,也就是真实坐标,从鼠标所在世界坐标位置发出垂直视图的射线,计算和真实模型位置的相对位置关系,如果相交则代表拾取到。
OpenGL红宝书中提出了一种方法,使用后缓存来处理选择,个人觉得其完全可以替代标准选择机制。
使用方法与标准选择机制比较类似,不同的是其使用颜色来标记物体,每一组对象使用不同的颜色值,当发生选择时将场景以标记颜色在后缓存渲染一遍,然后读取帧缓存中所点选的点的颜色值(获取到颜色后不应交换前后缓存,而应直接清空后缓存以免该单色场景被显示出来),由该颜色值即可以得出被选中的是哪个对象。
和标准选择机制相比,这种方法同样需要重复渲染一次,但是由于其使用的是颜色标记,可以利用顶点缓存来显著地提高渲染速度,这是标准选择机制所无法做到的。
另外,因为读取的是像素数据而不是图元数据,拾取到的像素已经经过了Alpha测试和深度测试,所以也不会出现选取到没有光栅化的完全透明的物体或被遮挡物体的现象。
而在现在的标准24位颜色深度设备情况下,该方法能够标识256*256*256=16777216个不同的对象,这作为一般应用已经足够了。
使用OpenGL实现三维坐标的鼠标拣选Implementation of RIP(Ray-Intersection-Penetration)3D Coordinates Mouse Selection Using OpenGL顾露(武汉理工大学计算机系中科院智能设计与智能制造研究所湖北武汉 430070)摘要(Abstract):本文提出并实现一种用于三维坐标拣选的RIP(Ray-Intersection-Penetration)方法。
介绍了如何在已经渲染至窗口的三维场景中,使用鼠标或者相关设备拣选特定三维对象的方法。
此方法对于正交投影或透视投影均有效,相对于OpenGL自带的选择与反馈机制,本方法无论是拣选精度还是算法实现效率均高出许多,是一种比较通用的解决方案。
关键词(Keywords)正交投影(Ortho-Projection)、透视投影(Perspective-Projection)世界坐标系、屏幕坐标系、三维拣选、OpenGL一、简介(Introduction)OpenGL是一种比较“纯粹”的3D图形API,一般仅用于三维图形的渲染,对于特定领域的开发者(如游戏开发者)而言,如果选择使用OpenGL进行开发,类似碰撞检测的机制就都需要自行编写了。
Unity3d⽤⿏标拾取模型的顶点Unity3d ⽤⿏标拾取模型的顶点第⼀节近来想做⼀个东西,想实现3D MAX的部份功能,第⼀步⽤⿏标拾取模型的顶点,那么⾸先要做的是获取⿏标发出的射线与模型的碰撞点!先截个图,⽤到了Physics.Raycast⽅法,就是射线检测碰撞的⽅法!RaycastHit hit;Ray ray = Camera.mainCamera.ScreenPointToRay(Input.mousePosition);if (Physics.Raycast(ray, out hit, 100)){Debug.Log();if ( == "Sphere") return;spheretransform.position = hit.point;}那么要注意⼏个问题:1.场景中的摄像机必须是主摄像机主摄像机是什么意思呢?就是摄像机的tag是MainCamera2. Ray ray = Camera.mainCamera.ScreenPointToRay(Input.mousePosition);Camera.ScreenPointToRay()⽅法其实就是“通过2D屏幕坐标在3D空间中拾取”,我们的3D场景经过摄像机的投影,变成了2D图像,显⽰在了屏幕上,那么通过逆运算,就可以换算出⿏标点的位置的3D位置,从⽽可以进⼀步选取3D场景中的物体!当然这个摄像机是离不开的,是那个摄像机渲染的当前场景,就要⽤哪个摄像机进⾏ScreenPointToRay()来进⾏运算!先说⼀下射线,射线是什么东西呢?Ray,Ray有两个字段,origin和direction,其中origin就是摄像的起始点,direction是射线的⽅向!Physics.Raycast(Ray ray,out RaycastHit,float distance)第⼀个参数就是⼀条射线,就是判断射线与其它物体的碰撞!碰撞之后会返回RaycastHit信息,distance是射线发出的距离(我理解的对不对,望兄弟们指教!)通过Physics.Raycast()可以out出⼀个参数(形参),RaycastHit,这个可以获取到碰撞点RaycastHit.point;那么为了明显的看到它,我⽤了⼀个红⾊材质的球体,球体要⼩⼀些,不断设置球体的位置为碰撞点的位置,⼤家就很容易看到结果对不对!在调试的过程中,我发现红⾊的球不断的向屏幕移动过来,不解到底是什么原因,后来分析是因为,射线不断的红球发⽣了碰撞,碰撞点在球的表⾯,所以球就不断向屏幕外移动,所以要加⼀句if ( == "Sphere") return;4.我附上“通过2D屏幕坐标在3D空间中拾取”的理论⽂章⽤XNA语⾔描述 最近在做到和3D模型的拾取有关的东西,找到了这篇不错的⽂章,加上⾃⼰的修改,基本解决了⿏标点击3D模型获取3D模型的坐标点的问题。
directx射线法拾取3d物体数学原理directx射线法拾取3d物体数学原理(1)--原创通过2D屏幕坐标在3D空间中拾取需要的场合Directx提供了固定流水线和可编程流水线,他们都是为了实现一个目标,就是把3d模型如何投射到2d屏幕坐标上,这个过程,相关文档都有了详细的介绍,今天我们来看一下他的逆向过程,比如,你如何按下鼠标选中一个离你最近的3D 物体,如何让你的手枪打了一枪击中了远处的一个敌人的脑袋,cs中我们都玩过,那么如何判断的呢,我们就要从2D屏幕到3d屏幕的转化说开去。
为什么使用射线法一般常规思考,我们从3d的渲染流水线逆向就应该获取到3d中的对应位置,可是我们的屏幕是2D的,仅有x,y 如何表示3d中的z呢,这是个问题,不过,不要怕,我们在流水线中也考虑了这个投影的问题,当3d的物体投射到屏幕上时,我们指定了两个参数,一个是Near,一个是,far,一般在设置投射矩阵时,使用如下函数:D3DXMATRIX * D3DXMatrixPerspectiveFovLH(D3DXMATRIX *pOut,FLOAT fovy,FLOAT Aspect,FLOAT zn,FLOAT zf);这个函数最终形成的矩阵是这样子的xScale 0 0 0 0 yScale 0 0 0 0 zf/(zf-zn) 1 0 0 -zn*zf/(zf-zn) 0 其中: yScale = cot(fovY/2)xScale = yScale / Aspect通过这个函数把3d的已经渲染好的物体,投射在屏幕上。
我们现在已知屏幕上 (x,y),就是说我们能通过x,y找到z为zn,zf两个值时,屏幕上x,y对应的3d空间中的点坐标。
这就是形成一条空间射线的两个点的理论基础。
接下来又有了新问题,我们如何将屏幕中的(x,y)转化为空间中的坐标呢, 如何把屏幕的坐标转化为空间中的坐标屏幕的坐标系是这样的:左上角是0,0 右下角是w,h;而我们3d投影后形成2D坐标系是,中间(0,0),这里我们把投影后的2D坐标系标准化,转化成单位矩形(-1,-1)到(1,1),就是以中心往上下左右各给出1个单位量的大小。
3DOne简易操作教程(3):鼠标键盘篇在上文《3DOne简易操作教程(2):图形交互篇》中,小编给大家介绍了3DOne 丰富的图形交互功能,用户能够实现快速修改图形大小,对图形进行快速移动和翻转,令操作更便利。
同时,在用3DOne进行设计的过程中,还能用鼠标键盘实现快速操作,现在本文将为你介绍:1、灵活的鼠标拾取(1)直接选择选择边后:即时弹出屏显菜单/Minibar,菜单提供圆角、倒角、拔模、对齐移动命令。
边上即时附着半径DDD手柄,手柄提供默认值,用户可以即时拖拉修改。
选择面后:即时弹出屏显菜单/Minibar,菜单提供拉伸、面偏移、面移动、对齐移动材质命令,面上附着对应命令的DDD手柄。
选择体后:即时弹出屏显菜单/Minibar,菜单提供移动、缩放等功能。
(2)遮挡选择对已有对象点击,既不释放鼠标,也不移动鼠标,则界面会提供此位置所有可以选择的对象类别,方便用户选择被遮挡对象。
这就是Pick from list功能,只是命令的启动仿123D的做法,其启动方式也可以是,鼠标在某对象上提供1s后,鼠标自动切换到Pick from list功能,在此时点击,系统也会自动提供该列表。
(3)Shift PickShift Pick边,意思是选择相切的边。
Shift Pick面,则是选择能形成一个可识别的特征面。
(4)Alt Pick用于选择该鼠标位置上第2个合法对象。
(5)默认可选对象类型默认鼠标可选对象类型包括All, Sketch, Curve, Edge, Face, Shape。
2、快捷的键盘操作(1)Ctrl + C, Ctrl + V支持对体的复制和粘贴。
(2)Delete支持零件环境对体对象、草图整体的直接删除,其他对象如边、面不支持删除。
支持草图环境内草图几何标注的删除。
(3)Ctrl + 方向键实现视图旋转。
(4)Ctrl + Home = Align Plane。
3d拾取pick的原理
3D拾取(Pick)是计算机图形学中的一个重要概念,它指的是
在3D场景中确定用户点击的位置所对应的物体或物体表面的过程。
这个过程涉及到很多复杂的计算和算法,下面我将从多个角度来解
释3D拾取的原理。
首先,3D拾取的原理涉及到射线与物体的相交检测。
当用户在
屏幕上点击鼠标时,屏幕坐标会被转换成世界坐标系中的一条射线。
这条射线会与3D场景中的物体进行相交检测,以确定用户点击的位
置所对应的物体。
其次,3D拾取还涉及到空间分割和碰撞检测。
为了提高效率,
3D场景通常会进行空间分割,比如使用包围盒或者四叉树等数据结
构来组织场景中的物体。
当进行拾取操作时,可以先对可能相交的
物体进行筛选,然后再进行详细的碰撞检测,以确定最终的拾取结果。
另外,还有基于像素的拾取方法。
在一些情况下,可以直接利
用像素的颜色信息来确定用户点击的位置所对应的物体。
这种方法
通常用于一些特殊的应用场景,比如在一些游戏中。
此外,还有基于物体属性的拾取方法。
在一些需要对物体进行特定操作的情况下,可以根据物体的属性来确定拾取结果。
比如在一个包含多个可交互物体的场景中,可以根据物体的标识符或者其他属性来进行拾取。
总的来说,3D拾取的原理涉及到射线与物体的相交检测、空间分割和碰撞检测、基于像素的拾取方法以及基于物体属性的拾取方法等多个方面。
这些原理和方法通常会结合使用,以实现准确高效的3D拾取操作。
基于DirectX的三维地形绘制及三维拾取方法的研究摘要:随着图形处理技术及显卡处理能力等的日益成熟,三维游戏已经成为游戏市场的主导。
主要介绍基于microsoft的一套功能丰富的底层api--directx的最基本的三维地形绘制过程以及三维世界中拾取的研究,为游戏学习者提供一种易于理解的方式来进行3d技术中难点的学习。
关键词:游戏三维地形三维拾取中图分类号:tp391.9 文献标识码:a 文章编号:1007-3973(2013)006-101-021 引言随着游戏玩家经验的积累和科技体验、审美能力的提高,传统的二维世界游戏已经无法满足人们享受娱乐的需求,游戏为了适应市场的需要,必须变革。
从directx1.0~directx10的发展史可以窥见游戏世界的进化历程:从最初directx1.0仅能够做的直接读取硬件信息,开发的游戏能实现对二维图像进行加速,directx2.0中d3d部分的雏形到directx7.0支持t&l技术,使得cpu从繁重的位置、灯光等计算中解放出来,使得没有高速的cpu也同样能流畅的跑起3d游戏直至directx10统一了渲染架构,使得gpu更加高效,3d世界进一步拟真化。
directx作为当今游戏底层api的引导者这一格局,也明示了3d游戏才是市场发展的趋势这一事实。
因此,游戏学习者必须对3d游戏有所涉及。
了解三维游戏场景中最基本的地形绘制,有利于学习者对三维游戏有直观的认识。
拾取算法的研究,有助于游戏学习者对3d游戏的深入了解。
2 三维地形绘制游戏场景通常会架设在起伏的山坡,绿地,沙滩,雪地,树林等自然环境中,这样的场景可增加游戏的逼真度和吸引力。
友好精美的游戏场景无疑会使得游戏在竞争激烈的市场中获得第一眼优势。
2.1 地形高度图的创建、访问和修改通过地理学习可知,一般用灰度图来表示地理位置上的海拔,颜色越深的就表示海拔越高,灰度图是高度图的一种表示类型。
因此,进行三维地形绘制,我们使用一种最简单的方法,通过使用图形处理应用程序photoshop来进行高度图(指定灰度图类型)的绘制,通过颜色的设置来表示你想要的地形起伏。
第 1 页 共 6 页(1)选择“文件”→“重置”命令,重新设置系统。
(2)单击“创建”按钮 ,进入创建命令面板,单击“图形”按钮 ,进入图形创建命令面板,单击“线”按钮,在前视图中创建3条曲线,依次命名为Line01,Line02和Line03,并调整成如图1所示的形状。
图1创建曲线并调整形状(3)单击“矩形”按钮,在前视图中创建一个矩形,并命名为Rectangle01,参数设置如图2所示。
图2设置“矩形”参数(4)单击“创建”按钮 ,进入创建命令面板,单击“几何体”按钮 ,在几何体创建命令面板中选择“标准基本体”下拉列表中的选项。
(5)在前视图中选中曲线Line01,单击“放样”按钮,单击“创建方法”卷展栏中的“获取图形”按钮,在前视图中拾取矩形Rectangle01,如图.3所示。
图3拾取矩形Rectangle01第 2 页 共 6 页(6)单击“修改”按钮 ,进入修改命令面板,单击“变形”卷展栏中的“拟合”按钮,弹出如图.4所示的“拟合变形(X )”对话框。
图4“拟合变形(X )”对话框(7)单击“均衡”按钮 ,再单击“获取图形”按钮 ,在前视图中拾取曲线Line02,然后单击“显示Y 轴”按钮 ,在前视图中拾取曲线Line03,拟合变形效果如图.5所示。
图5拟合变效果(9)单击“显示Y 轴”按钮 ,在前视图中将曲线调整至如图.7所示的形状,调整后拟合物体在透视图中的形状如图.8所示。
图7拟合变效果图8调整后的拟合物体(10)单击“创建”按钮 ,进入创建命令面板,单击图形创建命令面板中的“线”按钮,在顶视图中创建一条曲线,并命名为Line04,如图6.3.9所示,单击“修改”按钮 ,进入修改命令面板,单击“样条线”按钮 ,在“轮廓”按钮后的微调框中输入2,并按回车键,在“修改器列表”下拉列表中选择“挤出”命令,设置它的数量值为80,并将其移动至如图6.3.10所示的位置。
第 3 页共6 页第 4 页 共 6 页图6.3.13 挤出并移动曲线Line05(14)重复第(11)和第(12)步的操作,得到的效果如图 6.3.14所示。
鼠标拾取3D物体算法研究及实现论文导读:采用克莱姆法则研究了射线与三角形相交。
用一般线性代数的方法来研究射线与三角形相交的条件及数学结论,并通过鼠标在屏幕上的点击位置,利用变换矩阵换算成拾取射线,计算该拾取射线与任意3D物体中的任意三角形是否相交。
关键词:克莱姆法则,变换矩阵,射线,重心坐标0.引言用鼠标拾取3D场景中的指定物体,是D3D编程中的一种较为常见,但比较复杂的算法,本文旨在从一般数学的方法来得到该算法的核心组成部分,并用实际代码实现该算法。
为了得到拾取的数学结论,本文中将给出演算过程。
科技论文。
1.理论部分1.1 重心坐标设三角形所在平面一点P的重心坐标为(a,b,c)则a+b+c = 1;三角形所在平面的所有点都可以用重心坐标表示,如果在三角形外面,则:a,b,c三个数当中必有一个为负数,这一点P的3D坐标为(Px, Py, Pz) 1.2 前提条件三角形的三个点为(顺时针)v0, v1, v2 则v0*a + v1*b + v2*c = (Px, Py,Pz); (设v0,v1,v2是点,且同样用重心坐标方式表示。
)因为:a+b+c = 1;所以: v0*a + v1*b + v2*c= v0 *(1-b-c) + v1*b + v2*c ;= b * (v1 - v0)+ c * (v2 - v0) + v0;-----------------1假设射线方程式F(x,y,z)= Orgin + rayVec * t;(其中Orgin为射线起点,rayVec为射线方向向量)----------------21.3 矩阵演算从1.2得知射线和P点的交点方程(由1和2):Orgin + rayVec * t = b * (v1 - v0) + c * (v2 - v0) + v0;列成矩阵形式:设v1 - v0 =edge1; v2 - v0 = edge2;则:Orgin + rayVec * t = b * edge1+ c * edge2 + v0变一下型:b * edge1 - rayVec *t + c * edge2 = Orgin - v0;我们设fDist =-t; 去掉负号;得到:b * edge1 - rayVec * fDist + c * dege2 = Orgin - v0;将上式按向量分量展开成方程组形式为:b * edge1.x -rayVec.x * fDist +c * edge2.x = (Orgin - v0).x;b * edge1.y - rayVec.y * fDist+c * edge2.y = (Orgin - v0).y;b * edge1.z - rayVec.z * fDist+c * edge2.z = (Orgin - v0).z;则可视为用使用克莱姆法则求b , fDist, c三个量Dot(edge1, Cross(rayVec, edge2)),即rayVec与edge2的叉乘,再与edge1点乘。
深入探索3D拾取技术在游戏中,玩家需要通过点击2D屏幕来选择3D物体,这个过程就是拾取(picking)。
拾取是3D游戏必不可少的基本操作,它实现了玩家和游戏世界内对象的交互。
虽然拾取技术很基本,但它却迷惑了很多3D初学者。
很多朋友都问过我关于拾取的细节问题,这让我觉得很有必要具体探讨一下该技术。
其实,拾取之所以让很多开发者感到复杂,主要原因在于它跨域了流水线的多个阶段,并且是逆流水线上行。
另外,它是一个2D信息扩展到3D的过程,必须对信息做相应的扩展和额外的计算才能够得到正确的结果。
下面我门具体分析一下这个技术。
水流线主要阶段分析我们来直观地看一下从相机空间到viewport的变换相机空间中的一个顶点v,经过透视变换后进入了CVV中。
这个变换矩阵实际上完成了两个工作:1)将顶点从3D空间投影到2D的投影平面(Projection Plane)上。
2)将投影平面上的2D投影点通过线性插值变换到齐次裁剪空间CVV中。
这些变换都通过透视矩阵一次完成。
我之所以把这一步分解为两步,因为这对于分析拾取很重要。
顶点进入齐次裁剪空间并经过CVV裁剪,最终进行透视除法从4D齐次形式变回成3D形式。
然后经过一个线性插值(被封装在视口(viewport)变换中),变换到viewport中,多个点以三角形的形式经过光栅化后被玩家看到。
最后一步的点变换可以描述为:3)将CVV中的点通过线性插值变换到viewport中。
分析了这个变换过程之后,我们知道了从相机空间开始实际处理点位置信息的操作,就是上面的三个步骤。
这样,我们可以先把顶点从viewport中先变换回投影平面上,也就是我们可以先完成(2)和(3)的逆处理。
这里我们不用考虑裁剪和透视除法这些操作,因为反推的时候,处于视口中的点,已经是经过裁剪后留下的有效点了,必定处于CVV内,也必定处于projection plane内!而且从viewport逆变换到projection plane,点一直保持2D形式。
通过渲染到浮点纹理实现三维对象拾取摘要(Abstract)本文介绍了一种在GPU上实现的,通过将坐标信息和对象指针绘制到一张RenderTargert浮点纹理的三维对象拾取方法。
该方法可以在约半帧的渲染时间内拾取包括对象指针坐标等信息,可以到达与屏幕象素大小同等的准确度。
关键字(Keywords):图元拾取、GPU、Shader RTT(渲染到纹理)、浮点纹理图1:Mouse Pick所RRT到的浮点纹理,图中RGB颜色值显示出来的是对象的世界坐标值,如左面的obj1顶部比较绿,反映出来的是对象的y坐标比较大,对象的rgb值分别对应xyz 坐标,alpha值对应对象指针1. 引入拾取技术〔pick〕,在3D渲染时鼠标选取地形上某点或者某件物品都要用到。
图2演示了一个拾取任务,鼠标在s点上单击一下,程序可以识别出鼠标拾取了茶壶。
以前的渲染管线是固定的,进入管线后我们的操作会受到限制。
随着现代GPU的出现和开展,在GPU的可编程才能给我们带来了高的灵敏性。
Shader 是我们自己定义的程序,用来替代固定渲染管线中的局部流程。
如今应用比较多的是Vertex Shader〔顶点着色器〕和Pixel Shader〔象素着色器〕,利用它们可以在渲染的过程中参加更多的技术以实现各种特效,而在本文里,我们利用shader进展坐标和颜色的转换编码,实现了把世界坐标和对象指针绘制到一张128位RGBA浮点RenderTarget纹理,然后通过LockRect纹理检索指定的象素值,实现了对象的Picking。
图2 NVIDIA SDK提供的一个使用Geometry Instancing绘制大量对象的例子,面对太空中这么多的对象,如何能快速鼠标拾取目的呢?2. 相关方法〔Related Work〕拾取技术一般都被看作时渲染的一个逆运算。
一般的渲染管线,是将一个物体从本身的局部空间(Local space),转换到所以物体同一的世界空间(World space),然后根据视锥转换到观察空间(View space),经过反面剔除(Backface Culling)、光照(Lighting)、裁剪(Clipping)等处理后,投影(Projection)到二维的平面上,最后在根据显示的环境进行视口变换(ViewPort Transform)和光栅化(Rasterization)最后显示出来。
前段时间弄d3d,看完红书和基于Visual+C#的DirectX开发实例,就自己练习了下这个程序sdk上面都有拾取,但是没有选中移动,最近的项目要用到,就简单仿了下3dmax的移动方式选中物体,再选取需要移动的平面,效果如下:拾取代码中会有个小问题,就是会出现重复多选的问题,本例中是通过比较Intersect函数返回参数IntersectInformation中交点距离的最短值找出mesh的;大家可以用list的遍历找最小值,方法很多然后是关于平面的移动,当选取完成之后。
会出现三个完全透明的平面,物体就是根据隐形平面的u v值进行移动的;重要代码如下:(我基本打成class类,后期工程的时候会加到dll中,这个只是演示,便于学习)这个是:class类[c-sharp]view plaincopy1using System;2using System.Collections.Generic;3using System.Linq;4using System.Text;5using System.Drawing;6using Microsoft.DirectX;7using Microsoft.DirectX.Direct3D;89namespace PickObject10{11public class Hawk_Class12 {13public Device device;//Device14public float Pan_Width, Pan_High; //pannel Size1516 //初始化类17public Hawk_Class(Device device,float w,float h)18 {19this.device = device;20 Pan_Width = w;21 Pan_High = h;22 }2324 //寻找交点25public void Check_Point_Face(float Mouse_x, float Mouse_y, Vector3 Cam_Postion,26 Mesh Chech_Mesh, Matrix Chech_Mesh_Postion, out bool Check_Bool, out IntersectInformation Point_Inf)27 {28 //计算摄像机投影窗口上对应的点击位置坐标29 Vector3 SVector = new Vector3();30 SVector.X = 2 * Mouse_x / this.Pan_Width - 1;31 SVector.Y = -2 * Mouse_y / this.Pan_High + 1;32 SVector.Z = 1.0f / (float)Math.Tan(Math.PI / 8);33 //视图矩阵34 Matrix viewMatrix = this.device.Transform.View;35 viewMatrix.Invert();//计算视图矩阵的逆矩阵36 //射线位置37 Vector3 rayPos = Vector3.TransformCoordinate(SVector, viewMatrix);38 //射线方向39 Vector3 rayDir = Vector3.Subtract(rayPos, Cam_Postion);40 //碰撞检测41 //将模型的世界矩阵进行逆变换42 Matrix inverseWorld = Matrix.Invert(Chech_Mesh_Postion);43 Vector3 localRayPos = Vector3.TransformCoordinate(rayPos, inverseWorld);44 Vector3 localRayDir = Vector3.TransformNormal(rayDir, inverseWorld);45 IntersectInformation PI;46bool result = Chech_Mesh.Intersect(localRayPos, localRayDir, out PI);47 Check_Bool = result;48 Point_Inf = PI;49 }5051 //创建一个mesh类52public void Create_Mesh(Vector3[] pointVectors,out Mesh M_H)53 {54 Mesh meshObject;55 //定义顶点56 CustomVertex.PositionColored[] arrayVertices = newCustomVertex.PositionColored[4];57 //定义索引58 Int16[] arrayIndices = new Int16[6];59 //定义属性60 AttributeRange attributeRange = new AttributeRange();61 //实例化网格对象62 meshObject = new Mesh(arrayIndices.Length / 3, arrayVertices.Length, MeshFlags.SystemMemory, CustomVertex.PositionColored.Format, device);63 //设置顶点坐标值64 arrayVertices[0].Position = pointVectors[0];65 arrayVertices[0].Color = Color.FromArgb(108, 255, 255,255).ToArgb();66 arrayVertices[1].Position = pointVectors[1];67 arrayVertices[1].Color = Color.FromArgb(108, 255, 255,255).ToArgb();68 arrayVertices[2].Position = pointVectors[2];69 arrayVertices[2].Color = Color.FromArgb(108, 255, 255,255).ToArgb();70 arrayVertices[3].Position = pointVectors[3];71 arrayVertices[3].Color = Color.FromArgb(108, 255, 255,255).ToArgb();72 //设置索引,顶部三角形73 arrayIndices[0] = 0; arrayIndices[1] = 1; arrayIndices[2] = 3;74 arrayIndices[3] = 3; arrayIndices[4] = 1; arrayIndices[5] = 2;75 //设置属性76 attributeRange.AttributeId = 0;77 attributeRange.FaceStart = 0;78 attributeRange.FaceCount = arrayIndices.Length / 3;79 attributeRange.VertexStart = 0;80 attributeRange.VertexCount = arrayVertices.Length;81 //设置网格对象的索引缓冲和顶点缓冲数据82 meshObject.VertexBuffer.SetData(arrayVertices, 0, LockFlags.None);83 meshObject.IndexBuffer.SetData(arrayIndices, 0, LockFlags.None);84 meshObject.SetAttributeTable(new AttributeRange[]{ attributeRange });85 M_H = meshObject;86 }87public void Create_Mesh_Face(Vector3[] pointVectors, out Mesh M_H)88 {89 Mesh meshObject;90 //定义顶点91 CustomVertex.PositionColored[] arrayVertices = newCustomVertex.PositionColored[3];92 //定义索引93 Int16[] arrayIndices = new Int16[3];94 //定义属性95 AttributeRange attributeRange = new AttributeRange();96 //实例化网格对象97 meshObject = new Mesh(arrayIndices.Length / 3, arrayVertices.Length, MeshFlags.SystemMemory, CustomVertex.PositionColored.Format, device);98 //设置顶点坐标值99 arrayVertices[0].Position = pointVectors[0];100 arrayVertices[0].Color = Color.FromArgb(0, 255, 255, 255).ToArgb(); 101 arrayVertices[1].Position = pointVectors[1];102 arrayVertices[1].Color = Color.FromArgb(0, 255, 255, 255).ToArgb(); 103 arrayVertices[2].Position = pointVectors[2];104 arrayVertices[2].Color = Color.FromArgb(0, 255, 255, 255).ToArgb(); 105 //设置索引,顶部三角形106 arrayIndices[0] = 0; arrayIndices[1] = 1; arrayIndices[2] = 2; 107 //设置属性108 attributeRange.AttributeId = 0;109 attributeRange.FaceStart = 0;110 attributeRange.FaceCount = arrayIndices.Length / 3;111 attributeRange.VertexStart = 0;112 attributeRange.VertexCount = arrayVertices.Length;113 //设置网格对象的索引缓冲和顶点缓冲数据114 meshObject.VertexBuffer.SetData(arrayVertices, 0, LockFlags.None); 115 meshObject.IndexBuffer.SetData(arrayIndices, 0, LockFlags.None); 116 meshObject.SetAttributeTable(new AttributeRange[]{ attributeRange });117 M_H = meshObject;118 }119120 //所有关于坐标的初始化121public void SetLine_and_Face(Mesh[] meshObjXYZ, Mesh[] checkface, out Material meshMaterialsXYZ,122 VertexBuffer_xyzLineVertexBuffer)123 {124 //三个标示的大平面125 Vector3[] points = new Vector3[4];126 points[0] = new Vector3(0f, 0f, 0f);127 points[1] = new Vector3(0f, 5f, 0f);128 points[2] = new Vector3(5f, 5f, 0f);129 points[3] = new Vector3(5f, 0f, 0f);130 Create_Mesh(points, out meshObjXYZ[0]);131132 Vector3[] points1 = new Vector3[4];133 points1[0] = new Vector3(0f, 0f, 0f);134 points1[1] = new Vector3(0f, 5f, 0f);135 points1[2] = new Vector3(0f, 5f, 5f);136 points1[3] = new Vector3(0f, 0f, 5f);137 Create_Mesh(points1, out meshObjXYZ[1]);138139 Vector3[] points2 = new Vector3[4];140 points2[0] = new Vector3(0f, 0f, 0f);141 points2[1] = new Vector3(0f, 0f, 5f);142 points2[2] = new Vector3(5f, 0f, 5f);143 points2[3] = new Vector3(5f, 0f, 0f);144 Create_Mesh(points2, out meshObjXYZ[2]);145146 //三个标示的大平面其材质147 Material temp = new Material();148 temp.Diffuse = Color.FromArgb(108, 255, 255, 255); 149 temp.Ambient = Color.White;150 temp.Specular = Color.White;//浅灰色151 temp.SpecularSharpness = 1.0F;152 meshMaterialsXYZ = temp;153154 //用于移动的三个大的透明平面155 Vector3[] points3 = new Vector3[3];156 points3[0] = new Vector3(-500f, -500f, 0f);157 points3[1] = new Vector3(-500f, 2000f, 0f);158 points3[2] = new Vector3(2000f, -500f, 0f);159 Create_Mesh_Face(points3, out checkface[0]);160161 Vector3[] points4 = new Vector3[3];162 points4[0] = new Vector3(0f, -500f, -500f);163 points4[1] = new Vector3(0f, 2000f, -500f);164 points4[2] = new Vector3(0f, -500f, 2000f);165 Create_Mesh_Face(points4, out checkface[1]);166167 Vector3[] points5 = new Vector3[3];168 points5[0] = new Vector3(-500f, 0f, -500f);169 points5[1] = new Vector3(-500f, 0f, 2000f);170 points5[2] = new Vector3(2000f, 0f, -500f);171 Create_Mesh_Face(points5, out checkface[2]);172173 //三根XYZ坐标的数据生成174using (GraphicsStream data = _xyzLineVertexBuffer.Lock(0, 0, LockFlags.None))175 {176 data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 0.0f, Color.Red.ToArgb()));177 data.Write(new CustomVertex.PositionColored(10.0f, 0.0f, 0.0f, Color.Red.ToArgb()));178 data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 0.0f, Color.Green.ToArgb()));179 data.Write(new CustomVertex.PositionColored(0.0f, 10.0f, 0.0f, Color.Green.ToArgb()));180 data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 0.0f, Color.White.ToArgb()));181 data.Write(new CustomVertex.PositionColored(0.0f, 0.0f, 10.0f, Color.White.ToArgb()));182 _xyzLineVertexBuffer.Unlock();183 }184 }185186 //给予一个mesh数组以及对应mesh位置的数组,确认鼠标选中的最近的mesh187 //(最后一个参数:<1>:选择哪个model,<2>:选择哪个平面)188public int Select_Mesh_Fun(float Mouse_x, float Mouse_y,Vector3Cam_Postion,189 Mesh[] Check_Mesh, Matrix[] Chech_Mesh_Postion,int flag)190 {191int Lenth_Data = Check_Mesh.Length;192int selected;193 IntersectInformation[] Pi = new IntersectInformation[3]; 194for (int i = 0; i < Lenth_Data; i++)195 {196bool result;197if(flag==99)198 Check_Point_Face(Mouse_x, Mouse_y, Cam_Postion, Check_Mesh[i], Chech_Mesh_Postion[i], out result, out Pi[i]);199else200 Check_Point_Face(Mouse_x, Mouse_y, Cam_Postion, Check_Mesh[i], Chech_Mesh_Postion[flag], out result, out Pi[i]);201if (!result)202 Pi[i].Dist = 9999.0f;203 }204205 #region //找出最短的mesh206float tempmin = Math.Min(Pi[0].Dist, Pi[1].Dist);207if (tempmin == Pi[0].Dist);208else209 selected = 1;210211 tempmin = Math.Min(tempmin, Pi[2].Dist);212if (tempmin == Pi[2].Dist)213 selected = 2;214215if (tempmin == 9999.0f)216 selected = 99;217218 #endregion219return selected;220 }221 }222223}这个是对应loop吐出画面的函数:[c-sharp]view plaincopy224device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkSlateBlue, 1.0f,0); //清除windows界面为深蓝色225 device.BeginScene();226227 device.RenderState.CullMode = Cull.None;//显示里面的机构228229 device.Lights[0].Type = LightType.Directional;230 device.Lights[0].Diffuse = System.Drawing.Color.Red;231 device.Lights[0].Direction = new Vector3(-1.0f, 1.0f, 1.0f);232 device.Lights[0].Enabled = true; //打开灯光233 device.Lights[1].Type = LightType.Directional;234 device.Lights[1].Diffuse = System.Drawing.Color.Green;235 device.Lights[1].Direction = new Vector3(1f, -1.0f, 1.0f);236 device.Lights[1].Enabled = true; //打开灯光237 device.RenderState.Ambient = Color.SlateGray;238239 device.SetRenderState(RenderStates.AlphaBlendEnable, true);//开启透明度240 device.RenderState.SourceBlend = Blend.SourceAlpha;241 device.RenderState.DestinationBlend = Blend.InvSourceAlpha;242243 //以实体形式绘制茶壶模型244 device.RenderState.FillMode = FillMode.Solid;245246for (int i = 0; i < meshObj.Length; i++)247 {248 device.Transform.World = meshPosition[i];249if (i == selected)250 {251 device.RenderState.Lighting = false;252 // XYZ 라인그리기253this.device.SetStreamSource(0, this._xyzLineVertexBuffer, 0);254this.device.VertexFormat =CustomVertex.PositionColored.Format;255this.device.DrawPrimitives(PrimitiveType.LineList, 0, 3); 256257 device.RenderState.Lighting = true;258for (int j = 0; j < 3; j++)//选中模型的选中平面显示259 {260if (j == selectedFace)261 device.Material = meshMaterialsXYZ;262else263 device.Material = new Material();264265 meshObjXYZ[j].DrawSubset(0);266 }267 meshMaterials[i].Diffuse = Color.FromArgb(48, 180, 168, 8);//选中的model透明268 device.Material = meshMaterials[i];269 meshObj[i].DrawSubset(0);270continue;271 }272 device.RenderState.Lighting = true;//灯光设置273274 //设置当前材质275 meshMaterials[i].Diffuse = Color.FromArgb(255, 180, 168, 8);276 device.Material = meshMaterials[i];277 meshObj[i].DrawSubset(0);278 }279280if (selectedFace != 99)//如果选中屏幕281 {282 device.Transform.World = checkfacePosition;283 checkface[1].DrawSubset(0);284 checkface[0].DrawSubset(0);285 checkface[2].DrawSubset(0);286 }287288 device.EndScene();还有一个具体会碰到的问题,就是刷新pannel.Invalidate();时候会出现闪屏的状况查了些资料。
Direct3D中实现图元的鼠标拾取查看文章 Direct3D中实现图元的鼠标拾取2007-05-05 15:463D交互图形应用程序中,常常要用鼠标去选择图形,其实现的机制基于鼠标拾取算法。
本文主要讲述如何在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._m11projPt.y*projPt.w = viewPt.y*ProjMatrix._m22projPt.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,公式2DIRview.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;其中ViewMatrix为观察矩阵,在D3D中可以用函数GetTransform( D3DTS_VIEW, &ViewMatrix )得到。
所以DIRworld = DIRview * inverse_ViewMatrix,其中inverse_ViewMatrix 为 ViewMatrix的逆矩阵。
观察点在观察坐标系中坐标为OriginView(0,0,0,1),所以其在世界坐标系中的坐标同样可以利用ViewMatrix矩阵,反变换至世界坐标系中,事实上我们可以很简单的判断出,其在世界坐标系中的表示为: OriginWorld =(inverse_ViewMatrix._41,inverse_ViewMatrix._42,inverse_ViewMatrix._43,1);到这里为止,判断射线与三角面是否相交的条件就完全具备了。
2.2 使用射线矢量对场景中的所有三角形图元求交,获得三角形索引值和重心坐标。
这一步骤地实现由两种途径:第一种方法非常简单,利用D3D提供的扩展函数D3DXIntersect可以轻松搞定一切。
见2.1第二种方法就是我们根据空间解析几何的知识,自己来完成射线三角形的求交算法。
一般来讲,应用上用第一种方法就足够了,但是我们如果要深入的话,必须理解相交检测的数学算法,这样才能自由的扩展,面对不同的需求,内容见2.2 下面分别讲解两种实现途径:2.2.1 D3D扩展函数实现求交这种方法很简单也很好用,对于应用来说应尽力是用这种方式来实现,毕竟效率比自己写得要高得多。
实际上其实没什么好讲的,大概讲一下函数D3DXIntersect吧 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 返回射线发出点到相交点的长度注意:以上红色字体部分均指最近的一个返回结果(即*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占多大权值,这种坐标定义方式就叫重心坐标。
2..2.2射线三角面相交的数学算法使用d3d扩展函数,毕竟有时不能满足具体需求,掌握了该方法,我们才能够获得最大的控制自由度,任意修改算法。
已知条件: 射线源点orginPoint,三角形三个顶点 v1,v2,v3,射线方向 Dir (均以三维坐标向量形式表示)算法目的: 判断射线与三角形是否相交,如果相交求出交点的重心坐标(U,V)和射线原点到交点的距离T。