OpenGL选择与拾取GL_SELECT 附源码

Stella981
• 阅读 454

【一个提示】该方法虽然可行但是已经淘汰很多年,建议自行尝试,后面也许会写论文最好的方式是:

1. 使用Kd-tree组织场景中的物体,以便于快速查找。

2. 使用屏幕坐标->空间三维坐标的逆矩阵变换,实现选取。

在介绍开始,首先给出工程和可执行程序的下载链接:

         OpenGL选择和拾取obj模型_代码及可执行文件

         或者http://download.csdn.net/detail/mahabharata_/9709959

    程序的执行结果如下:

OpenGL选择与拾取GL_SELECT 附源码

首先,我们在OpenGL的绘制方式有两种:GL_RENDER和GL_SELECT。顾名思义,GL_RENDER是渲染模式,也就是默认的绘制方式,通俗的讲,就是绘制操作都会被绘制在屏幕上;GL_SELECT则是选择模式,这种方式不会被绘制在屏幕上,之后的矩阵变化为选取矩阵的变化。

这里对GL_RENDER不做过多说明,因为这是默认方式,就是操纵显示在屏幕上时的矩阵变换。

对于GL_SELECT:在用OpenGL进行图形编程的时候,通常要用鼠标进行交互操作,比如用鼠标点选择画面中的物体,我们称之为拾取(Picking),这里我们介绍一下:在OpenGL中如何拾取,如何利用OpenGL提供的一系列函数来完成拾取,再简单介绍下OpenGL的名字栈(Name stack),拾取矩阵(Picking Matrix)等等。

 1. 首先获得一系列参数信息:

           const int BUFSIZE = 1024;  //缓冲区selectBuf
           GLuint selectBuf[BUFSIZE];
           glSelectBuffer (BUFSIZE, selectBuf);

           GLint viewport[4];        //获取视口viewport的信息
           glGetIntegerv (GL_VIEWPORT, viewport);

2. 进入GL_SELECT模式,并初始化名字栈

           glRenderMode(GL_SELECT);
           glInitNames();
           glPushName(-1);

3. 保存一下矩阵信息:

           glPushMatrix();

4. 在GL_SELECT模式下,指定拾取窗口,并进行投影变换(GL_PROJECTION)

投影变换事实上在GL_RENDER模式下已经进行过一次,但是需要在GL_SELECT模式下,再进行一次。

           glMatrixMode (GL_PROJECTION);      // 投影变换
           glLoadIdentity ();
           gluPickMatrix((GLdouble)x, (GLdouble) (viewport[3]-y), 5.0, 5.0, viewport); // 指定选取窗口(x,y为鼠标点击位置)
           gluPerspective(45.0, (GLfloat)width()/(GLfloat)height(), 0.1, 10000.0);     // 指定视景体

5. 在GL_SELECT模式下,进行模型视图变换GL_MODELVIEW

           glMatrixMode(GL_MODELVIEW);    // 2: GL_SELECT下模型视图变换
           glLoadIdentity();
           gluLookAt(0.0,0.0,10.0, 0.0,0.0,0.0, 0.0,1.0,0.0);   //指定摄像机位置
           /*
              为了保证选取结果的正确性,在GL_RENDER下进行的所有缩放、平移、旋转等操作,这里也要进行一次。
             */

6. 在GL_SELECT重新绘制物体,这里使用我上传的程序中的例子。

调用绘制函数,总共有3个人物,绘制函数为renderNPC(GLenum mode);

          for(int i=0; i< 3 ;i++)
          {
               _characters[i]->renderNPC(GL_SELECT);  // 使用GL_SELECT方式渲染一次(并不绘制在屏幕上)
          }

关于renderNPC(GLenum mode)的具体实现,在后面给出。

7. 出栈,让之前进行的矩阵操作释放掉。

         glPopMatrix();

8. 对选取的名字栈进行处理(选取结果已经储存在名字栈中了)

          // 切换回GL_RENDER模式
          GLint hits = glRenderMode (GL_RENDER); // 返回的hit:表示名字栈中结果的个数。
          glMatrixMode (GL_PROJECTION);    /// 此处需要重新进行一次GL_RENDER模式下的投影变换。
          glLoadIdentity ();
          gluPerspective(45.0, (GLfloat)width()/(GLfloat)height(), 0.1, 10000.0);
 
          if(hits)  ///如果选中物体,设置为"选中"状态
          {
              _characters[selectBuf[3]]->_isSelected=!( _characters[selectBuf[3]]->_isSelected );
          }
  
          updateGL();  // 在屏幕上重绘,因为有的物体已经被选中。

       关于在上面出现的renderNPC(GLenum mode)函数,下面给出其内部细节(为了简单表述,以画方块为例)。

          void renderNPC(GLenum model)
          {
              glPushMatrix();           
              /*
                 这里进行属于本物体的旋转、平移、缩放变换
              */
             if(model == GL_SELECT)
             {
                 glLoadName(_name);   //这里的_name为预先设计的该物体的名字,为一个GLuint值,比如1、2、3、.....
             }
             
             if(_isSelected == true)  ///如果被选中
             {   
              /*
                    对于选取的物体,进行诸如修改颜色啊啥啥啥的操作。
              */
             }
             glutSolidCube(3.0);     //绘制出物体,这里以一个“方块”举例。
             glPopMatrix();
          }

此外,我们还需要了解的最后一点,名字栈的机制,名字栈最终保存在selectBuffer[]中。当某物件被“拾取”(被光束射中)的时候,对应的名字和相关信息便会被提交给HIT Record,存储在selectBuffer里面。相关信息包括该物件离光束发射处(相机/眼睛)最近的点的深度值和最远的点的深度值等等,以下反映了当一个物件被拾取后,名字栈机制向HIT Record发送的信息(然后HIT Record把此信息存入selectBuffer):  

击中的物件的名字的数目

这个物件中最近的点的深度值

这个物件中最远的点的深度值

击中的物件的名字之一

击中的物件的名字之二 
(若有多个名字,则如此类推...)

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
2年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
2年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这