位置: 编程技术 - 正文

OpenGL选择-拾取机制(转载自zwqxin)(opengl获取鼠标位置)

编辑:rootadmin

推荐整理分享OpenGL选择-拾取机制(转载自zwqxin)(opengl获取鼠标位置),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:opengl 获取物体坐标,opengl获取鼠标位置,opengl怎么用,opengl怎么用,opengl sphere,opengl选中物体,opengl拾取例子,opengl选项,内容如对您有帮助,希望把文章链接给更多的朋友!

原文地址: Allan "之类的。OpenGL拾取的名字机制也就是ID机制。当然,它里面没有什么排斥不唯一性的,但你让两个物件拥有相同Name的时候,你起码得知道自己在干嘛(是为了实现某些奇怪的效果吧,略谈~)。

2.名字栈机制

上头....不,应用程序怎么储存这些资料呢?啊,先说说应用程序怎么与那些被射中而打受伤报告的物件沟通吧,它怎么拿到报告呢?答案是名字栈。

在OpenGL内部,有一种叫做HIT Record[击中记录]的数据,保存在一个特别的自设定缓存区——Select Buffer[选择缓存]里。我们可以通过以下这个函数来设定SelectBuffer,给予一个已经分配好空间的INT数组作为参数(数组指针和具体大小):

glSelectBuffer (GLsizei size, GLuint *buffer);

至于我们到底应该给它多大的空间,下面讲述HIT Record数据结构的时候你就明白了。记住,HIT Record数据的填充是应用程序(上头)的任务,在拾取过程中我们无必要对它动手脚。我们更应该关心的是之前所说的沟通问题。首先看看几个分配Name(名字)的函数:

//初始化名字栈void glInitNames (); //向名字栈压入名字(ID)void glPushName(GLuint name); //从名字栈弹出名字(ID)void glPopName(); //直接把名字(ID)放在栈头位置上void glLoadName(GLunit name); //相当于://glPopName();//glPushName(name);

一般来说,栈这种数据结构,是一种前入前出的形式。因此一般也只允许访问栈首元素。现在不妨就假设场景中有三个物件好了,回头看看假设的OpenGL选择拾取逻辑:用户没有点击屏幕的时候一切如常,一旦用户进行了点选则触发拾取逻辑——在这里,我们给每个物件分配一个临时的名字(如前述,相当于ID):

const int OBJECT1 = 1;const int OBJECT2 = 2;const int OBJECT3 = 3; //渲染待选择物件的函数void RenderObjects(GLeum renderMode){ if( renderMode == GL_SELECT ) //选择模式 { glPushName(OBJECT1); RenderObject1(); glPopName(); glPushName(OBJECT2); RenderObject2(); glPopName(); glPushName(OBJECT3); RenderObject3(); glPopName(); RenderOtherRelated(); } else // ( renderMode == GL_RENDER ) 即正常渲染模式 { RenderObject1(); RenderObject2(); RenderObject3(); RenderOtherRelated(); }}

我们用一个renderMode枚举&#;来分开两种逻辑,即选择模式和正常渲染模式,事实上这是必须的,后面会详述。在选择模式下,分配名字。诶?先PUSH后POP,那不是在栈头压一个然后又弹出,然后再压再弹,等于什么都没有?嘿,这种无意义的事情谁也不会干啦,关键是中间夹着的渲染过程,后面等全部代码亮出后你就能理解了。

这里你要明白这种用名字栈分配名字的做法——它沟通了该名字所代表物件与应用程序内部的HIT Record。当某物件被“拾取”(被光束射中)的时候,对应的名字和相关信息便会被提交给HIT Record,存储在SelectBuffer里面。相关信息包括该物件离光束发射处(相机/&#;睛)最近的点的深度&#;和最远的点的深度&#;等等,以下反映了当一个物件被拾取后,名字栈机制向HIT Record发送的信息(然后HIT Record把此信息存入SelectBuffer):

击中的物件的名字的数目这个物件中最近的点的深度&#;这个物件中最远的点的深度&#;击中的物件的名字之一击中的物件的名字之二 (若有多个名字,则如此类推...)

上述为一般式,因为一个物件可以配两个或多个名字(这是可能用于某些特殊应用的,在参考文章Picking - LightHouse里提到,有兴趣的可以去看看)。更一般的应用,如上述例子,则会产生如下信息:

击中的物件的名字的数目(即:1)这个物件中最近的点的深度&#;这个物件中最远的点的深度&#;这个物件的唯一的名字

例子代码中共会产生1条,2条,或3条这样的记录(第一项都是一样的,为1,因为它们的名字唯一),因为用户这一次点击啊,可能就击中了3个物件中的其中一个,也可能一次过击中两个或三个(如果物件靠得近,或者视点离物件很远导致物件在屏幕上显得距离近)。如果是一次多条记录的话,它们会按检测顺序(例子中即是渲染顺序)依次紧跟着存入SelectBuffer里。这样就完了吗?还没呢!还有可能出现第4条记录会发送到HIT Record上(或者单独发送或者一同):

0这个物件中最近的点的深度&#;这个物件中最远的点的深度&#;

没错,它就是RenderOtherRelated()所代表的物件。从上面的记录也可看出,显然我们没有给它分配名字,但它确实参与了拾取过程(更准确地说,因为它也在renderMode == GL_SELECT 这个选择项里)。为什么允许这样的情况出现呢?根据参考文章Picking - LightHouse所说,譬如我们遇上了这种情况:场景由多个房间组成(并且不进行场景划分),每个房间里有不少魔法石待玩家拾取。那么如果我们单单就让这些魔法石都在选择模式里分配名字,然后拾取,“射线光束”就很容易穿过墙壁,“不小心地”把隔壁房间的魔法石给捡了- -。如果墙壁也作为待拾取物,则射线在碰到墙壁的时候就停止了,但是墙壁没有分配名字,因此只有一条首项为0的记录被发送,这样很好,后期判断处理很简捷。但是缺点是……

缺点是它只有三项。同样,具有两个名字的物件的HIT Record记录有五项,然后拥有多个名字的物件则是更多项……问题在于我们在SelectBuffer里读取的时候怎么办呢?(譬如一次拾取过程中获得含几条记录的HIT Record,读取各名字来确定物件时。)我们最怕不规则的数组……

那么,用glSelectBuffer设定SelectBuffer的时候,你现在应该知道了,其大小应该根据你认为一次点选可能击中的物件数的最大&#;,结合那些在GL_SELECT下的三项,多项的非正常物件数来决定。

本篇最后说一下那两个深度&#;:

1. 它们是物件对应Z-BUFFER中的深度&#;,乘以2^ -1后取整而得到;

2. 它们的&#;不是线性连续的,因为Z-BUFFER中的深度&#;本来就不是线性连续的(倒是它们的倒数是线性连续的,并符合插&#;规则,具体的自己去找本3D图形学数学原理的书看去~)

3.它们是在投影变换之后得到的,经过的视截体裁减过程(what? ),因此针对的是物件在视锥体内的部分的离&#;最近最远点,而不一定就是实际世界空间中完整物件的最近最远点。

好了,接下来的事项我留在了下篇日志中:乱弹OpenGL 选择- 拾取机制Ⅱ

那么,开头的假设——OpenGL拾取机制基于射线检测——哈,是错的。的确它们有相同处,至少目前探讨的名字栈机制与这个假设是否成立没有半点关系。注意,在真正的OpenGL拾取机制中,从相机(&#;睛)发出的不是光束射线,而是“目光”~!

3. 真正的拾取机制

OpenGL选择-拾取机制(转载自zwqxin)(opengl获取鼠标位置)

还记得前篇提及的一般游戏引擎中所使用的“射线检测”机制吗?这确实是实现成本非常小的技术,而且无关OPENGL或D3D管道。但是依靠人工的进行点面检测将涉及两个难题:屏幕场景中很多物件怎么办?要把射线与所有这些物件都做一次叉叉检测么;物件由很大量三角形面组成怎么办?连渲染模型本身都够呛了,何况还得在点选时把射线方程与各个面纠缠一次直到检测到或全部检测完发现做了无用功为止……(所以现在都改射线-包围盒检测了~但前一问题尤在。)

OpenGL内置拾取机制利用的是固定渲染管道的东西——视截体裁减。留意过3D图形学的朋友应该记得流水线是怎么“砍掉”场景中不在视野内的部分的,这里我就不探讨其数学原理了——其实也就相交检测之类,不过交给更可靠的流水线去快速实现而已。而这里,OpenGL也是应用的流水线的这个能力,只不过用来“砍”场景的平头视锥题是个“萎缩版”的而已。

(看不到图片的见贴:[显示本站所有图片] )

这是去年9月做的一个小DEMO,场景中有三个模型(物件),能进行鼠标拾取操作从而对模型进行平面移动。如你所见,整个渲染窗口是一个投影平面,也是视锥的近截面。流水线把视锥之外的都喀嚓掉了,譬如船模的船头部分,实际上已经被切断,然后在切断处形成暂时的流水线顶点。那么,我们看鼠标吧。它正在拾取其中一个白色模型,在点击鼠标进行拾取的刹那,在渲染窗口屏幕上形成了一个看不见的小矩形(我在画图工具里用个黄色矩形形象化表示~),这个小矩形就是在拾取时(renderMode == GL_SELECT),OPENGL世界中唯一能显示出来的区域——没错,它就是拾取刹那产生的新平头视截体的近平面!这个新视截体在下图草草表现出来:

注意红色椭圆圈着的就是新视截体的近、远截面,与之间的四条连线构成一个小型平头锥体,是不是形同于意同于“目光”呢?其实也算一种射线吧哈哈。(注意,在屏幕上的小矩形实际上长宽不过个像素而已,形象化出来的框不代表真实比例的大小。)在拾取阶段,小锥体外的东西都被裁掉了。OPENGL世界里只剩下黄色框内的东西会被渲染……该部分属于白色模型——对,它被选上了。

怎么生成这个小视截体呢?你已经猜到了。投影矩阵!

既然原来gluPerspective设定的投影矩阵来把视图空间下的坐标转为投影空间下的坐标表示,同时形成一个视截体描述[乱弹OpenGL中的矩阵变换(上)] ,那么肯定也有另一个投影矩阵能做类&#;的事情——它在原来投影矩阵功效的基础上,再对三维空间进行裁切。输入这个矩阵的参数肯定包括鼠标的屏幕坐标,目标屏幕矩形(上图的黄色框)的长宽,可能还应该把当前的VIEWPORT给它来准确裁切(VIEWPORT一般在投影之后才给出,所以它没可能预知道)。OpenGL有一个神秘的矩阵设置函数能帮我们完成输入到输出的转换:

void gluPickMatrix(GLdouble x, GLdouble y, GLdouble witdth, GLdouble height, GLint viewport[4]);

参数分别就是OPENGL下鼠标的屏幕窗口坐标(OPENGL以左下角为原点,WINDOWS下以左上角为原点),目标屏幕矩形的长宽和VIEWPORT(包含起点的屏幕窗口坐标,长和宽,当前的VIEWPORT可以由glGetIntegerv(GL_VIEWPORT, viewportbuffer)获得)。目标屏幕矩形的长宽也叫容差,代表选取时可能的误差范围,可以料想,容差越大,误选取的可能性大些;容差越小,选取所需越灵敏,越难一击选中物件。而因为我们在WINDOWS下得到的鼠标位置都是WINDOWS标准的,因此y&#;要经过一个小转换到OPENGL标准(viewport[3] - mouse.y,前者为窗口屏幕的宽)。看代码:

void OnLButtonDown(UINT nFlags, CPoint point) { GLuint selectBuff[]; GLint hits, viewport[4]; glSelectBuffer(, selectBuff); glGetIntegerv(GL_VIEWPORT, viewport); glRenderMode(GL_SELECT); glInitNames(); //glPushName(-1); int n = ; glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluPickMatrix((GLdouble) point.x, (GLdouble) (viewport[3] - point.y), n, n, viewport); gluPerspective(PersFov,(GLfloat)VB_WIDTH/(GLfloat)VB_HEIGHT,NearSight,FarSight); glMatrixMode(GL_MODELVIEW); //glLoadIdentity(); NOT NEED camera.Look();//IMPORTANTTTTTTTT RenderObjects(GL_SELECT); glPopMatrix(); hits = glRenderMode (GL_RENDER); .........

这是鼠标左键单击时消息处理代码的前半部分。前面四行本可扔进构造函数和/或初始化函数,为了功能一致性就留在这里了。glRenderMode决定了之前前篇(乱弹OpenGL选择-拾取机制Ⅰ] )所说的两种渲染模式的选择(GL_SELECT和GL_RENDER)。注意,OpenGL拾取机制限定前篇所说的名字栈机制只有在glRenderMode帮助应用程序设定GL_SELECT后才会生效,所以关于Name的函数应该在GL_SELECT标志设定后才弄。

glMatrixMode(GL_PROJECTION)转入投影矩阵的设置。对了,首先在改变OPENGL内部的矩阵形式前,得把当前的非拾取用的矩阵变换设置存储下来[glPushMatrix()],等应用程序返回GL_RENDER正常模式的时候再弹回出来[glPopMatrix()]作为正常渲染使用。而在这PUSH和POP之间,就是OpenGL拾取-选择逻辑了。

重置单位阵后,接连的gluPickMatrix,gluPerspective,然后设定GL_MODELVIEW标志而对模型视图矩阵进行设定——相机的视图变换和RenderObjects内,每物件的渲染函数内可能有的模型变换。RenderObjects(GL_SELECT)类&#;于上篇中的那段代码。最终问题来了:在代码逻辑上,名字栈是怎么通知应用程序——该物件被选择了的呢?不妨设我们选中的是Object1。另外,不妨展开代码看清楚点:

//展开代码: glPushMatrix(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPickMatrix((GLdouble) point.x, (GLdouble) (viewport[3] - point.y), n, n, viewport); gluPerspective(PersFov,(GLfloat)VB_WIDTH/(GLfloat)VB_HEIGHT,NearSight,FarSight); glMatrixMode(GL_MODELVIEW); camera.Look(); //RenderObjects(GL_SELECT); glPushName(OBJECT1); //1 RenderObject1(); //2 glPopName(); //3 glPushName(OBJECT2); //4 RenderObject2(); //5 glPopName(); //6 glPushName(OBJECT3); //7 RenderObject3(); //8 glPopName(); //9 //没特别要求就不要了,或者别放在选择逻辑里 // RenderOtherRelated(); // glPopMatrix();

注意,无论是RenderObject1()还是RenderObject2(),这类函数的核心特性就是通过顶点画三角形。这些顶点是按顺序一批一批地进入渲染管道的,通过T&L,进行顶点处理……也就是说,当第2行(//2)产生的顶点进入流水线时,第5行产生的顶点还跟在后头呢,一般不会干涉。

所以,情况是Object1和它的名字OBJECT1先进入流水线处理,进行坐标变换—— 它们(顶点)先被左乘一个模型矩阵(可能是单位阵也可能是其他,看你RenderObject1里面有无移动旋转缩放类操作了),然后被左乘一个视图矩阵(由相机camera.Look(),实质即gluLookAt设定和操作),然后被左乘投影矩阵(由gluPerspective设定和操作),然后又是被一个投影矩阵gluPickMatrix(又可直接叫拾取矩阵)左乘,而形成(被裁切而剩下)之前所述的小黄框内部分的顶点(或者加上暂时流水线顶点)。因此应用程序找到对应的名字栈中的名字OBJECT1发送到HIT Record中,存储在SelectBuffer中。

好吧,Object2的顶点来了,但是在gluPickMatrix里被冷落了(根据鼠标坐标判断出裁减视锥里没它嘛),同时不会在它那里形成框框,对应的名字没被“记住”,应用程序于是不发送HIT Record了……其他的,类&#;吧。(提示:对该代码逻辑过程混乱的朋友可以在看完本文后顺便补补OpenGL矩阵知识哦,当然,也是听我乱弹的- -[乱弹OpenGL中的矩阵变换(下)] )

于是你明白了吧,即使名字栈被不断压入弹出,它起指涉作用的也就在处理其对应的Object的顶点的刹那而已,之后它就该为下一位到达流水线的Object负责了。

4. 拾取的后处理

好,最耐人寻味的已经讲完了。说一下处理完拾取逻辑后的事情吧:hits = glRenderMode (GL_RENDER)一句可以返回HIT数,即当次拾取所击中的物件数(可能是OPENGL内部对发送HIT Record的次数进行记数了吧)。

........................hits = glRenderMode (GL_RENDER); //E//glViewport(0,0,VB_WIDTH,VB_HEIGHT); //E//glMatrixMode(GL_PROJECTION); //E//glLoadIdentity(); //E//gluPerspective(PersFov,(GLfloat)VB_WIDTH/(GLfloat)VB_HEIGHT,NearSight,FarSight); //E//glMatrixMode(GL_MODELVIEW);//E//camera.Look(); int modelselect = 0; //离&#;睛最近的物件的名字(ID) if (hits >0){ int n=0; double minz=selectBuff[1]; for(int i=1;i<hits;i&#;&#;) { if (selectBuff[1&#;i*4]<minz) {n=i;minz=selectBuff[1&#;i*4];} } modelselect = selectBuff[3&#;n*4]; //E// RenderGLScene();//Invalidate(FALSE); }

加了//E//的语句其实可要可不要。因为有时候我们会希望每次选择逻辑结束后马上重绘整个场景,而不是等到下个渲染循环(注意,鼠标操作是一种消息中断,期间渲染主循环被暂停了哦)。这样就得额外实施一次主循环里干的事情了,如果场景大,一次渲染的过程长,那么为了保证过度,这样的强制重绘是很有必要的。这时//E//语句是要的

拾取的后处理,取决于你具体想通过拾取来干嘛了。毕竟OpenGL只负责给你做拾取检测,提供被拾取物件的个数,名字,和最近最远的Z-BUFFER增大&#;(见上篇中HIT Record结构)。譬如这里就返回离&#;睛最近的物件的名字(ID)吧~你看,固定每个记录的项数大小为4的HIT Record事后取数据起来就是方便就是好啊。

最后顺带一提,别以为选择模式下render的物体会有可能在屏幕上灵光一闪哦!事实上我是听说只要HIT Record一被发送(或者没得发),流水线就都会舍弃掉&#;前这些顶点不再往下处理的……啊,我只是听说,听说。

Qt新渲染底层Scene Graph研究(三) Qt新渲染底层SceneGraph研究(三)上一篇文章介绍了QtQuick和SceneGraph的一些理论上的内容。这也是我最新的研究成果。接下来我要介绍一下如何使用SceneGrap

vs配置OpenGL SuperBible5环境 刚拿到opengl编程宝典第五版,打算配置好书中源码的编程环境,折腾了一天,终于弄好了,现在记录下完整的过程:参考博客:

3D网络游戏开发学习书籍概览 这篇文章是接着前三篇文章学好C要阅读的书籍和学好Windows编程要看的书籍以及黑客修炼,反击黑客,安全问题要阅读的书籍的基础上而写的,如果想了

标签: opengl获取鼠标位置

本文链接地址:https://www.jiuchutong.com/biancheng/369654.html 转载请保留说明!

上一篇:OpenGL入门学习(opengl怎么学)

下一篇:Qt新渲染底层Scene Graph研究(三)(qt渲染引擎)

  • 个体工商户缴纳税目
  • 财务报表一季度销售大福下降
  • 退休职工怎么填写单位吗
  • 即征即退办理时限是多少
  • 制作原始人类用的工具
  • 调整上年度所得税计提分录
  • 减免的教育费附加和地方教育费附加怎么做分录
  • 拆迁房视同销售成本可以抵减吗?
  • 电梯维修增值税
  • 返聘人员工伤可以申请工伤鉴定吗
  • 小规模专票丢了怎么办
  • 安装工程开发票
  • 如何计算房地产容积率与土地面积
  • 办公室设计费取费标准一览表
  • 对外投资亏损可以记股权投资减少吗
  • 所得税费用属于损失吗
  • 本年利润年末账务处理
  • 公司旅游的费用怎么算
  • 中国电信服务
  • 从银行提取现金属于什么会计要素
  • win11专业版企业版家庭版哪个玩游戏好
  • mac只能读不能写
  • 材料采购差异的影响因素
  • win11如何设置开机自启动软件
  • 应用程序错误0x000000
  • linux进程操作命令
  • php导出数据到excel
  • desktop病毒
  • 索尼体积最小的微单
  • php的数据类型主要有哪几种
  • 事业单位在建工程会计账务处理
  • 房屋出租房产税如何计算
  • 2023年第十一批专项债
  • ajax自动带cookie
  • 哈希表散列函数
  • 什么是会计确认的基础
  • 带息票据和不带息票据怎么区分
  • 一般纳税人上月有普通发票收入没入账可以次月加上吗
  • 增值税发票上多了一个点能刮掉吗
  • 出让土地的土地出让金与抵押权
  • 差旅费需要缴纳增值税吗
  • sql的应用
  • 如何恢复sql server误删除的数据库
  • 增值税专票什么用处
  • 会计和外贸哪个工作强度大
  • 工程造价咨询服务费收费标准
  • 营业外收入在资产负债表
  • 技术人员工资计入生产成本吗
  • 业务招待费专用会计科目
  • 材料按计划成本计价下,外购材料结算
  • 企业征地款会计分录
  • 因企业增资而产生的费用
  • 合伙企业所得税率
  • 企业存货计价方法发生变更案例
  • 无发票 入账
  • 未取得合法支付凭据和与本单位无关的收入
  • sql语言包括哪三种类型
  • mysql中的存储过程
  • SQL Server 2008 到底需要使用哪些端口?
  • mysql 5.1.6
  • 键盘设施
  • centos 查看文件夹
  • Win7笔记本突然wifi功能没了
  • win7系统关闭自动休眠
  • 测试模式win8专业版 9200
  • bat脚本怎么运行
  • 面向对象实例化
  • Unity3D面试题整合
  • unity碰撞抖动怎么解决
  • python怎么爬
  • nodejs lua
  • ps如何把图片放大缩小
  • window.onerror()的用法与实例分析
  • jquery制作左导航特效
  • jquery怎么修改样式
  • android布局背景颜色的代码
  • 国税电子税务局官网
  • 广东国家税务局电子税务局官网入口
  • 郑州国税局投诉电话
  • 营业收入是含增值税的收入吗
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设