OpenGL拣选

OpenGL拣选

1.      名字堆栈

OpenGL提供一个在3D场景拣选模型的方法,这个章节将向你讲解这种方法的使用,下面就拣选的步骤:

(1)      得到鼠标单击的窗口坐标;

(2)      进入拣选模式;

(3)      重新定义投影矩阵,使只有鼠标附近的模型被渲染;

(4)      绘制场景,使用所有的图元进行拣选操作;

(5)      退出拣选模式,识别出哪一个模型被单击。

为了识别出拣选的模型,首先你必须给场景中的模型取名,OpenGL API允许你给每一个图元取名字,或者设置模型名字。在拣选模式下,没有模型被写入帧缓冲区,取而代之的是每一个模型的名字(包括深度值)被收集起来,没有名字的模型,只有深度值被收集。

使用OpenGL,当在拣选模式下,将有一个hits收集渲染的图元。Hits记录存储在选择缓冲区,由于opengl为每一个hit提供深度值,可以很快找到离我们近的那个图元。

初始化名字堆栈的方法:

voidglInitNames(void);这个是创建一个空的名字栈,你必须在入栈前调用这个函数。

voidglPushName(GLuint name);入栈一个模型名字。

void glPopName();出栈一个模型名字。

void glLoadName(GLunit name);将栈顶的元素替换掉,它等价于
glPopName();

glPushName(name);

2. 拣选模式

到目前为止,名字堆栈已经处理好了,这一节将带你为了拣选如何进入选择模式。

第一个步骤是告诉opengl hits记录要存储的位置,这个是调用下面的函数来实现的。

void glSelectBuffer(GLsizei size, GLuint *buffer);

Parameters:

buffer : An array of unsigned integers. This array is where OpenGL willstore thehit records.

size : The size of the array.

你必须确定在你调用这个函数之前已经进入选择模式,下一步是调用下面的函数进入选择模式:

void glRenderMode(GLenum mode);

Parameters:

mode - use GL_SELECT to enter the rendering mode and GL_RENDER to returnto normal rendering. This later value is the default

现在来到比较棘手的步骤,为了只绘制鼠标单击那小部分的场景必须重新定义投影矩阵,为了设置投影矩阵,必须将矩阵模式改到投影矩阵模式下。然后,应用需要保存当前矩阵保存当前正常的渲染模式,下一步就是单位化矩阵。

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();

好的,到目前为止,你已经清除了投影矩阵,因此现在必须重新设置一个投影矩阵以仅仅包含当前鼠标小范围的场景。为了实现这个,必须先调用下面这个函数:

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

Parameters:

x,y : These two values represent the cursor location. They define thecentre of the picking area. This value is specified in window coordinates.However note that window coordinates in OpenGL have the origin at the bottomleft corner of the viewport, whereas for the Operating system is the upper leftcorner.

width, height : The size of the picking region, too small and you may behard pressed to pick small objects, too large and you may end up with too manyhits.

viewport : The current viewport

glGetIntegerv(GL_VIEWPORT,viewport);
gluPickMatrix(cursorX,viewport[3]-cursorY,
            5,5,viewport);
gluPerspective(45,ratio,0.1,1000);
glMatrixMode(GL_MODELVIEW);
glInitNames();

3. 处理记录

为了处理拣选记录,第一步必须返回正常的渲染模式下,这个步骤调用glRenderMode传递GL_RENDER参数就可以了。这个函数返回的是我们在选择模式下的创建的拣选记录,这一步之后,我们可以处理拣选数据了,注意我们必须返回到正常渲染模式之下,不然是没有被写入缓冲区的。

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);
}

最后一步就是关于缓冲区的结构了,缓冲区里面存储的是我们按顺序拣选的数据。

Hits记录包含以下:

(1) 第一部分是我们所涉及的名字个数;

(2) 第二和第三部分是代表我们点击的最大和最小深度值;

(3) 接下来的系列代表着名字记录。

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");
   
}
代码如下:
render.h文件:
typedef struct cameras {
	float pos[3];
	float lookAt[3];
	float lookUp[3];
} camera;



// callback to initialize the scenes for each subwindow
void initScene(int argc, char **argv);

void init(camera *cam);

// callback to draw the window
void draw();


// callback when picking occurs
void picked(GLuint name,int sw);


// keyboard callback
void processKeyboard(unsigned char key, int x, int y);


// callback for quiting
void quit();

render.cpp文件如下:
 
#include <math.h>
#include <glut.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <stdio.h>
#include <stdlib.h>

#include "render.h"

static GLint snowman_display_list;


void quit() {}


void processKeyboard(unsigned char key, int x, int y) 
{
	printf("key: %d\n",key);
}


void picked(GLuint name, int sw) 
{
	printf("my name = %d in %d\n", name, sw);
}

//初始化相机参数
void init(camera *cam) 
{
	cam->pos[0] = 1.5;
	cam->pos[1] = 3.75;
	cam->pos[2] = 3;

	cam->lookAt[0] = 1.5;
	cam->lookAt[1] = 1.75;
	cam->lookAt[2] = 0;

	cam->lookUp[0] = 0;
	cam->lookUp[1] = 1;
	cam->lookUp[2] = 0;
}

//绘制雪人
void drawSnowMan() 
{
	glColor3f(1.0f, 1.0f, 1.0f);

	// Draw Body	
	glTranslatef(0.0f ,0.75f, 0.0f);
	glutSolidSphere(0.75f,20,20);


	// Draw Head
	glTranslatef(0.0f, 1.0f, 0.0f);
	glutSolidSphere(0.25f,20,20);

	// Draw Eyes
	glPushMatrix();
	glColor3f(0.0f,0.0f,0.0f);
	glTranslatef(0.05f, 0.10f, 0.18f);
	glutSolidSphere(0.05f,10,10);
	glTranslatef(-0.1f, 0.0f, 0.0f);
	glutSolidSphere(0.05f,10,10);
	glPopMatrix();

	// Draw Nose
	glColor3f(1.0f, 0.5f , 0.5f);
	glRotatef(0.0f,1.0f, 0.0f, 0.0f);
	glutSolidCone(0.08f,0.5f,10,2);
}

//创建ID

GLuint createDL() 
{
	GLuint snowManDL;

	// Create the id for the list
	snowManDL = glGenLists(1);

	glNewList(snowManDL,GL_COMPILE);
	drawSnowMan();
	glEndList();

	return(snowManDL);
}

//初始化场景
void initScene(int argc, char **argv) 
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);

	snowman_display_list = createDL();
}



void draw() 
{
	// Draw ground
	glColor3f(0.9f, 0.9f, 0.9f);
	glBegin(GL_QUADS);
	glVertex3f(-100.0f, 0.0f, -100.0f);
	glVertex3f(-100.0f, 0.0f,  100.0f);
	glVertex3f( 100.0f, 0.0f,  100.0f);
	glVertex3f( 100.0f, 0.0f, -100.0f);
	glEnd();

	// Draw 4 SnowMen

	for(int i = 0; i < 2; i++)
		for(int j = 0; j < 2; j++) 
		{
			glPushMatrix();
			glPushName(i*2+j);
			glTranslatef(i*3.0,0,-j * 3.0);
			glCallList(snowman_display_list);
			glPopName();
			glPopMatrix();
		}
}





main.cpp文件如下:
#include <math.h>
#include <glut.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "render.h"


static camera cam;

// Selection Buffer
#define SelBufferSize 512


static int mainWindow;
static int border = 6, h = 200, w = 350;


// Picking Stuff //
#define RENDER					1
#define SELECT					2
#define BUFSIZE 1024
GLuint selectBuf[BUFSIZE];
GLint hits;
int mode = RENDER;
int cursorX, cursorY;


//----------------------
// Resizing
//----------------------

void changeSize(int w1, int h1) 
{

	float ratio;

	h = h1;
	w = w1;
	// Prevent a divide by zero, when window is too short
	// (you cant make a window of zero width).

	if(h == 0)
		h = 1;

	ratio = 1.0f * w / h;
	// Reset the coordinate system before modifying
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Set the viewport to be the entire window
	glViewport(0, 0, w, h);

	// Set the clipping volume
	gluPerspective(45, ratio, 0.1, 1000);

	// setting the camera now
	glMatrixMode(GL_MODELVIEW);
}


//---------------
// Picking Stuff
//---------------


void startPicking() 
{

	GLint viewport[4];
	float ratio;

	glSelectBuffer(BUFSIZE, selectBuf);

	glGetIntegerv(GL_VIEWPORT, viewport);

	glRenderMode(GL_SELECT);

	glInitNames();

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();

	gluPickMatrix(cursorX, viewport[3] - cursorY, 5, 5, viewport);
	ratio = (viewport[2] + 0.0) / viewport[3];
	gluPerspective(45, ratio, 0.1, 1000);
	glMatrixMode(GL_MODELVIEW);
}

//单击处理
void processHits2 (GLint hits, GLuint buffer[], int sw)
{
	GLint i, j, numberOfNames;
	GLuint names, *ptr, minZ, *ptrNames;

	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;
	}
	if (numberOfNames > 0) 
	{
		printf ("You picked snowman  ");
		ptr = ptrNames;
		for (j = 0; j < numberOfNames; j++,ptr++) 
		{ 
			printf ("%d ", *ptr);
		}
	}
	else
		printf("You didn't click a snowman!");
	printf ("\n");

}

void stopPicking() 
{

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glFlush();
	hits = glRenderMode(GL_RENDER);
	if (hits != 0)
	{
		processHits2(hits,selectBuf,0);
	}
	mode = RENDER;
}



//-----------------
// Rendering
//-----------------

//绘制场景
void renderScene() 
{


	glutSetWindow(mainWindow);  

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if (mode == SELECT) 
	{
		startPicking();
	}
	glLoadIdentity();


	gluLookAt(cam.pos[0], cam.pos[1], cam.pos[2], cam.lookAt[0], cam.lookAt[1], cam.lookAt[2], cam.lookUp[0], cam.lookUp[1], cam.lookUp[2]);


	draw();

	if (mode == SELECT) 
		stopPicking();
	else
		glutSwapBuffers();
}



//-------------------
// Keyboard and Mouse
//-------------------

//处理键盘函数
void processNormalKeys(unsigned char key, int x, int y) 
{

	if (key == 27) 
	{
		quit();
		exit(0);
	}
	else
		processKeyboard(key, x, y);
}




void mouseStuff(int button, int state, int x, int y) 
{
	if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
		return;

	cursorX = x;
	cursorY = y;
	mode = SELECT;
}





//---------
// Main
//---------

int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH  | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(w, h);
	mainWindow = glutCreateWindow("SnowMen from Lighthouse 3D");

	glutKeyboardFunc(processNormalKeys);
	glutReshapeFunc(changeSize);
	glutDisplayFunc(renderScene);
	glutMouseFunc(mouseStuff);
	glutIdleFunc(renderScene);


	initScene(argc, argv);
	init(&cam);

	glutMainLoop();

	return(0);
}

参考于:
http://www.lighthouse3d.com/opengl/picking/index.php3?openglway

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值