OpenGL学习笔记:拾取与选择
在开发OpenGL程序时,一个重要的问题就是互动,假设一个场景里面有很多元素,当用鼠标点击不同元素时,期待作出不同的反应,那么在OpenGL里面,是怎么知道我当前鼠标的位置是哪一个物体呢?
OpenGL有一套机制,叫做Picking, 里面涉及到几个核心概念:
1. selection mode. 选择模式
2. name stack. 名字栈
3. hit record。 命中记录
4. viewing volume。 视角范围
在OpenGL的picking中,选择物体不是选择一个单独的物体,而是选择一片范围内的所有物体。这种设计思路是有点奇怪,但是OpenGL就是这麽设计的。假如鼠标当前的位置是(200,200),普通的应用就是选择在点(200,200)处的物体ID, 但OpenGL不然,它是选择以(200,200)为中心的,比如长宽都为20的这个范围内可见的物体,也就是说,前面一种选择,是以点为选择依据,OpenGL的选择,是以中心为(200,200),长宽各位10的面为依据,好像举着一个画框,只要在这个画框内的物体,都会当作返回结果。
我不是很理解为什么OpenGL要这麽设计,但肯定有它的道理。以上的几个核心概念,就是配合这种设计思路的,不难看出:
1. Viewing volume。就是指用来选择的画框
2. hit record。就是所返回的物体数据
3. name stack。是用来分配并保存物体ID的堆栈
4. selection mode. OpenGL有三种模式,Render mode。就是普通的绘图模式,Select mode。在picking时的选择模式,Feedback mode。不画图,不选择,而是把所有最终的渲染完之后的绘画指令返回给用户。在绘图仪上作画等方面特别有用
当需要相应鼠标选择事件的时候,要首先进入selection mode, 然后才能执行相应的操作步骤
以下是详细的操作步骤:
1. 获取鼠标位置
2. 进入选择模式selection mode
3. 设置画框大小view volume,当然,是根据鼠标位置来的
4. 像往常一样绘制场景
5. 退出选择模式,得到选择结果
进入选择模式:
1. glSelectBuffer,设置选择缓冲区,hit record会被OpenGL保存在里面
2. glRenderMode(GL_SELECT),正式进入选择模式
设置画框大小:
1. gluPickMatrix。设置画框近平面大小
2. gluPerspective。影响画框的容量
实例代码:
glSelectBuffer(BUFSIZE,selectBuf);
glRenderMode(GL_SELECT); glMatrixMode(GL_PROJECTION); //保存投影矩阵
glPushMatrix();
glLoadIdentity(); glGetIntegerv(GL_VIEWPORT,viewport);
gluPickMatrix(cursorX,viewport[3]-cursorY,5,5,viewport);
gluPerspective(45,ratio,0.1,1000);注意,上面的代码中在执行gluPickMatrix值钱先保存了投影矩阵,然后再重新创造一个新的供自己使用。原因是gluPickMatrix会操作投影举证,该函数和后续的gluPerspective共同作用,最终会生成一个全新的投影矩阵,该矩阵中的所有物体,都会被视为选中。为了不影响旧有的投影矩阵,因此需要保存先
绘制场景:
绘制场景的时候有一点比较重要,那就是对每个需要绘制的单独元素,起一个名字(ID),这样OpenGL才能在推出选择模式的时候告知调用者,哪个物体被选择了。当然如果非不取,那OpenGL也会返回被选择的物体,只是不带名字。因此绘制场景的步骤一般如下:
1. glInitNames, 初始化name stack。 OpenGL的中对元素名字的操作需要通过一个栈,叫做名字栈,为啥这麽墨迹,不直接指定名字?具体原因是OpenGL总要有个地方去读取名字,而且,一个名字下包括哪些需要绘画的内容?OpenGL怎么知道当前画的这个人头是属于上个人的还是上上个人的?只能通过名字栈的变化才能知道。而且一个元素可以有多个内容,怎么搞?于是乎名字栈就应运而生,OpenGL认为这个东东可以解决这一系列问题
2. glPushName。 创建一个名字。
3. 开始画啊画啊画
4. glPopName。一个元素绘制结束
5. 从步骤2循环到4的一个个元素绘制
实例代码:
glInitNames(); glPushName(BODY);
drawBody();
glPopName(); glPushName(HEAD);
drawHead();
drawEyes();
glPopName(); drawGround(); //不带名字的元素还有另外一个有用的函数,glLoadName。它的作用是用一个新的名字替代栈顶当前的名字。说白了,就等同于:
glPopName()
glPushName(newName)
OpenGL里面,一个物体还可以有多个名字,这个貌似有点荒唐,确实也比较荒唐,但OpenGL也有它的道理,比如一个元素,它的名字叫“head",那么,如果我想知道这个头是谁的头,怎么搞?OpenGL告诉你压力不大,再给他取一个名字叫”Aka",那么你就知道这是Aka的头。也就是说,我绘制了一个元素,然后给它两个名字,一个叫head, 一个叫aka,那么当你选择了该物体的时候,你就知道了,这个物体是aka的头。
示例代码如下:
glPushName(Aka)
glPushName(Head)
draw()
glPopName(Head)
glPopName(Aka)
于是,draw出来的物体就有两个名字了.
处理选择结果:
处理选择接过之前,要先推出selection模式,然后OpenGl会返回一个数目,被选中物体的数目,跟据该数目,就知道在选择缓冲区里面保存了多少个物体,每个物体都有固定的数据结构,一个个的读取该数据结构,就知道哪些物体被选取了。
示例代码:
void stopPicking() { int hits; // restoring the original projection matrix
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glFlush(); // returning to normal rendering mode
hits = glRenderMode(GL_RENDER);//得到选中物体的数目 // if there are hits process them
if (hits != 0)
processHits(hits,selectBuf);
}重要的是选择缓冲区中hit record的数据结构,该结构是一个GLuinit的类型,也就说里面所有的数据都是uinit,等等,名字不是也在里面吗,这可是string啊,no, 名字其实不是string,准确的说应该叫ID,普通的1,2,3而已
每一个元素的结构提如下:
1. 该物体的名字的数目。一个物体可以有多个名字,你懂的
2. 该物体被选中区域的最小Z值。我们往往根据这个值得知哪一个才是鼠标点所在的物体
3. 该物体被选中区域的最大Z值。
4. 名字
5. 名字。。
由于物体可以有多个名字,甚至可以木有名字,因此元素的结构不是固定大小的,以下为例:
Hit Record Contents Description 0 表示这个元素没名字 4.2822e+009 最小Z值 4.28436e+009 最大Z值 1 这个元素有1个名字 4.2732e+009 最小Z值 4.27334e+009 最大Z值 6 名字叫6 2 这个元素有2个名字 4.27138e+009 最小Z值 4.27155e+009 最大Z值 2 第一个名字叫2 5 第二个名字叫5
所以呢,只需要对着选择缓冲区,一个个的读下去就对了,示例代码:
void processHits2 (GLint hits, GLuint buffer[])
{
unsigned int i, j;
GLuint names, *ptr, minZ,*ptrNames, numberOfNames; printf ("hits = %d\n", hits);
ptr = (GLuint *) buffer;
minZ = 0xffffffff;
for (i = 0; i < hits; i++) {
names = *ptr;
ptr++;
if (*ptr < minZ) {
numberOfNames = names;
minZ = *ptr;
ptrNames = ptr+2;
} ptr += names+2;
}
printf ("The closest hit names are ");
ptr = ptrNames;
for (j = 0; j < numberOfNames; j++,ptr++) {
printf ("%d ", *ptr);
}
printf ("\n"); }注意:以上所有的概念和函数,都只在selection mode下面有效。如果转到了Render mode,系统怎么处理这些函数呢?很简单,华丽的无视掉。
OpenGL学习笔记:拾取与选择的更多相关文章
- OpenGL学习笔记3——缓冲区对象
在GL中特别提出了缓冲区对象这一概念,是针对提高绘图效率的一个手段.由于GL的架构是基于客户——服务器模型建立的,因此默认所有的绘图数据均是存储在本地客户端,通过GL内核渲染处理以后再将数据发往GPU ...
- OpenGL学习笔记2017/8/29
OpenGL学习日志: 感谢doing5552 的OpenGL入门学习:http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html 相 ...
- OpenGL学习笔记(1) 画一个三角形
最近找实习有一丢丢蛋疼,沉迷鬼泣5,四周目通关,又不想写代码,写篇笔记复习一下,要好好学图形学啊 用OpenGL画一个三角形 项目的简介 记录一下跟着learnOpenGL学习的过程 笔记里的代码放在 ...
- OpenGL学习笔记0——安装库
最近需要做一个基于Zigbee室内无线定位的系统,受到TI公司ZigBee Sensor Monitor软件的启发,打算用OpenGL来做一个3D显示空间内物体位置的程序.学习阶段选择VS2010+O ...
- OpenGL学习笔记:Console工程下如何不显示控制台黑窗口只显示Windows窗口
刚学习OpenGL,绘制图形的时候,如果不进行设置,运行的时候会先出现黑窗口再出现Windows窗口. 其实要去除控制台窗口非常简单,只需要修改工程设置,把子系统改成Windows,程序的入口点改成m ...
- opengl学习笔记
准备: 1.准备资源:从GLEW1.13.0下载GLEW,并且解压出glew-1.13.0目录.从FreeGLUT官网下载3.0.0版本.直接从这里下的编译后的FreeGLUT,选for MSVC,下 ...
- OpenGL学习笔记(四)纹理
目录 要完成的纹理效果 纹理环绕方式 纹理过滤 多级渐远纹理 加载与创建纹理 stb_image库的使用方法 生成纹理对象 应用纹理 纹理单元 参考资料:OpenGL中文翻译 要完成的纹理效果 纹理是 ...
- OpenGL学习笔记5——嵌入Qt框架
学习OpenGL也有段时间了,前几篇将GL最基本的画图过程解析了一下,后面进阶的就随项目需要再学.因为之前一直是用glut这个实用工具包来开发很方便,但是会附带一个控制台的窗口,实在觉得有些low,因 ...
- OpenGL 学习笔记 01 环境配置
以下教程仅适用于Mac下的Xcode编程环境!其他的我也不会搞. 推荐教程:opengl-tutorial 本项目Github网址 OpenGL太可怕了...必需得把学的记下来,不然绝壁 ...
随机推荐
- 使用jenkins构建持续集成平台
jenkins + Maven + svn/git + tomcat 的持续集成平台 项目管理流程: 需求分析----原型设计----开发代码----提交测试-----内部测试-----确认上线( ...
- A线段树
线段树专题 顾琪坤 1.简介: 打acm的时候,经常会碰到一类问题,比方给你n个数的序列,然后动态的更改某些数的值,然后又动态地询问某个区间的值的和或者其它乱七八糟的东西,对于单个更改或者询问,也许很 ...
- Powershell连接Office 365各组件的方法
参考: http://www.exchangecn.com/office365/20150108_540.html 1. 适用于 IT 专业人员 RTW 的 Microsoft Online Serv ...
- 【转】移动Web单页应用开发实践——页面结构化
1. 前言 在开发面向现代智能手机的移动Web应用的时候,无法避免一个事实,就是需要开发单页应用(Single Page WebApp).对于不同的系统需求,单页应用的粒度会不同,可能是整个系统都使用 ...
- CentOs 6.6 安装配置 SVN
① 挂载光盘 mount /dev/cdrom /mnt/cdrom ② yum 安装 svn yum -y install subversion ③ 创建svn 版本库根目录 mkdir -p /w ...
- TCP移动端跟服务器数据交互
同一台笔记本下的客户端和服务端 TCPClient 客户端: // RootViewController.h#import <UIKit/UIKit.h>#import "As ...
- JS面相对象
一.理解对象: //第一种:基于Object对象 var person = new Object(); person.name = 'My Name'; person.age = ; person.g ...
- 我的第一个chrome扩展(3)——继续读样例
1.操作用户正在浏览的界面 http://www.ituring.com.cn/article/60212 问题:1.google未定义ID,用name为何无法找到? 2.如何让整个按钮一起动?原函数 ...
- I方法 thinkphp
function I($name,$default=null,$filter=null,$datas=null) { static $_PUT = null; $default_filter='htm ...
- 理解tornado
计算密集型:多进程 IO密集型:多线程 能产生IO阻塞的情况很多,比如网络通讯.磁盘读写.当发生阻塞时,CPU是闲置的,此时如果就一个线程就没法处理其他事情了. 所以对于含有IO阻塞的环境,多线程 ...