选择和反馈模式基本知识
渲染物体时候除了直接在屏幕上显示,还可以启用选择或反馈模式绘制物体但是不显示,得到一些绘图信息。
选择模式只是表明那些物体和视景体相交,用挑选功能可以实现用户和绘制图元的交互。
反馈模式是返回绘图信息,用于绘图仪的设备中。
选择和反馈模式下,绘图信息都是直接返回给应用程序而不是像渲染模式一样将信息发送到帧缓存,所以在选择和反馈模式下屏幕冻结不会进行绘图,颜色,深度,模板,累积缓存区的数据不会发生变化。
反馈模式和选择模式下的图元信息,都是没有经过任何片段测试返回的,所以没有经过测试的信息也会从选择或反馈信息中得到。
且非选择和反馈模式下,与他们相关的函数不会起作用。
拾取的判断,是将点击射线来自屏幕点击点的(clickPoint.x, clickPoint.y, znear), (clickPoint.x, clickPoint.y, zfar)
,转换到世界或视图坐标系得到射线(不是原点到鼠标的射线);然后与视图内的可拾取物体的包围盒做相交检测判断。
选择模式步骤
// 1.使用glSelectBuffer函数指定用于返回点击记录的数组。 和视景体相交的图元会导致一次选择点击,如果出现一次点击也会把点击记录写入。每条点击记录包含四项,当点击发生时候名字堆栈中的名称数量,当前点击与视景体相交的图元的所有顶点的最小和最大窗口z值会乘以2^23 - 1,点击发生时名字堆栈的内容,从底部的元素开始。
glSelectBuffer (BUFSIZE, selectBuf);
// 2.用glRenderMode指定GL_SELECT,进入选择模式;默认是GL_RENDER_MODE,反馈模式是GL_FEEDBACK
(void) glRenderMode (GL_SELECT);
// 3.对名字进行初始化
glInitNames();
glPushName(0);//避免glLoadName出错
// 4.定义用于选择的视景体,这里和绘制物体的视景体一样;交替用于绘制图元的函数和操纵名字栈的函数,绘制物体会使用最近的名字,。
//glLoadName后进行了drawcall调用,因此尽管名字堆栈只有一个元素,但是drawcall使得名字和物体产生的联系,一个元素足够了。
选择模式下得到和视景体相交的图元点击记录
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
/* draw a triangle with vertices at (x1, y1), (x2, y2)
* and (x3, y3) at z units away from the origin.
*/
void drawTriangle (GLfloat x1, GLfloat y1, GLfloat x2,
GLfloat y2, GLfloat x3, GLfloat y3, GLfloat z)
{
glBegin (GL_TRIANGLES);
glVertex3f (x1, y1, z);
glVertex3f (x2, y2, z);
glVertex3f (x3, y3, z);
glEnd ();
}
/* draw a rectangular box with these outer x, y, and z values */
void drawViewVolume (GLfloat x1, GLfloat x2, GLfloat y1,
GLfloat y2, GLfloat z1, GLfloat z2)
{
glColor3f (1.0, 1.0, 1.0);
glBegin (GL_LINE_LOOP);
glVertex3f (x1, y1, -z1);
glVertex3f (x2, y1, -z1);
glVertex3f (x2, y2, -z1);
glVertex3f (x1, y2, -z1);
glEnd ();
glBegin (GL_LINE_LOOP);
glVertex3f (x1, y1, -z2);
glVertex3f (x2, y1, -z2);
glVertex3f (x2, y2, -z2);
glVertex3f (x1, y2, -z2);
glEnd ();
glBegin (GL_LINES); /* 4 lines */
glVertex3f (x1, y1, -z1);
glVertex3f (x1, y1, -z2);
glVertex3f (x1, y2, -z1);
glVertex3f (x1, y2, -z2);
glVertex3f (x2, y1, -z1);
glVertex3f (x2, y1, -z2);
glVertex3f (x2, y2, -z1);
glVertex3f (x2, y2, -z2);
glEnd ();
}
/* drawScene draws 4 triangles and a wire frame
* which represents the viewing volume.
*/
void drawScene (void)
{
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (40.0, 4.0/3.0, 1.0, 100.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
gluLookAt (7.5, 7.5, 12.5, 2.5, 2.5, -5.0, 0.0, 1.0, 0.0);
glColor3f (0.0, 1.0, 0.0); /* green triangle */
drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -5.0);
glColor3f (1.0, 0.0, 0.0); /* red triangle */
drawTriangle (2.0, 7.0, 3.0, 7.0, 2.5, 8.0, -5.0);
glColor3f (1.0, 1.0, 0.0); /* yellow triangles */
drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -1.0);
drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -9.0);
drawViewVolume (0.0, 5.0, 0.0, 5.0, 0.0, 10.0);
}
/* processHits prints out the contents of the selection array
*/
void processHits (GLint hits, GLuint buffer[])
{
unsigned int i, j;
GLuint names, *ptr;
printf ("hits = %d\n", hits);
ptr = (GLuint *) buffer;
for (i = 0; i < hits; i++) { /* for each hit */
names = *ptr;
printf (" number of names for hit = %d\n", names); ptr++;
printf(" z1 is %g;", (float) *ptr/0x7fffffff); ptr++;
printf(" z2 is %g\n", (float) *ptr/0x7fffffff); ptr++;
printf (" the name is ");
for (j = 0; j < names; j++) { /* for each name */
printf ("%d ", *ptr); ptr++;
}
printf ("\n");
}
}
/* selectObjects "draws" the triangles in selection mode,
* assigning names for the triangles. Note that the third
* and fourth triangles share one name, so that if either
* or both triangles intersects the viewing/clipping volume,
* only one hit will be registered.
*/
#define BUFSIZE 512
void selectObjects(void)
{
GLuint selectBuf[BUFSIZE];
GLint hits;
// 1.使用glSelectBuffer函数指定用于返回点击记录的数组。
glSelectBuffer (BUFSIZE, selectBuf);
// 2.用glRenderMode指定GL_SELECT,进入选择模式;默认是GL_RENDER_MODE,反馈模式是GL_FEEDBACK
(void) glRenderMode (GL_SELECT);
// 3.对名字进行初始化
glInitNames();
glPushName(0);//避免glLoadName出错
// 4.定义用于选择的视景体,这里和绘制物体的视景体一样;交替用于绘制图元的函数和操纵名字栈的函数,绘制物体会使用最近的名字。
glPushMatrix ();
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho (0.0, 5.0, 0.0, 5.0, 0.0, 10.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glLoadName(1);
drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -5.0);
//load后进行了drawcall调用,因此尽管名字堆栈只有一个元素,但是drawcall使得名字和物体产生的联系,一个元素足够了。
glLoadName(2);
drawTriangle (2.0, 7.0, 3.0, 7.0, 2.5, 8.0, -5.0);
glLoadName(3);
drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -1.0);
drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -9.0);
glPopMatrix ();
glFlush ();
hits = glRenderMode (GL_RENDER);
processHits (hits, selectBuf);
}
void init (void)
{
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
}
void display(void)
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawScene ();
selectObjects ();
glFlush();
}
void keyboard(unsigned char key, int x, int y)
{
switch (key) {
case 27:
exit(0);
break;
}
}
/* Main Loop */
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize (200, 200);
glutInitWindowPosition (100, 100);
glutCreateWindow (argv[0]);
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}
挑选步骤:
1.使用了特殊的挑选矩阵 gluPickMatrix x投影矩阵gluOrtho2D把绘图限制在一个视口的小区域内。
2.在光标处绘制的物体,就会导致一次点击;因此挑选一般是用于判断哪些物体是在靠近光标的位置绘制的。和视景体相交的图元会导致一次选择点击,通过drawSquares后在glSelectBuffer数组里面就可以捕获到。
3.根据selectBuf选择的点击明,刚好是行列名来改变board颜色,并在GL_RENDER下glutPostRedisplay绘制变化的图元。
OGL的挑选模式是,根据传入的鼠标位置,构建挑选矩阵,然后全部物体用选择模式提交一次渲染得到点击的名字列表,根据名字列表来索引到物体,修改物体的相应属性,再次绘制得到交互结果。
DX是直接场景管理,将可交互物体的对象名和摄像机空间中的包围盒建立一个集合,且将这些集合用8叉树空间的模式来进行管理(剔除不在视景体里面的物体),在摄像机空间,
点击射线来自屏幕点击点的(clickPoint.x, clickPoint.y, znear), (clickPoint.x, clickPoint.y, zfar),将射线转换到视图坐标系中的射线,当鼠标点击后如果是2D正交投影的直接转换到世界空间坐标系进行射线和平面交叉检查;如果是3D透视投影的,通过遍历集合中的物体和射线的相交检查。显然类似DX中的拾取技术是更加有效的。
挑选模式下的实例和解释:
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
int board[3][3]; /* amount of color for each square */
/* Clear color value for every square on the board */
void init(void)
{
int i, j;
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j ++)
board[i][j] = 0;
glClearColor (0.0, 0.0, 0.0, 0.0);
}
/* The nine squares are drawn. In selection mode, each
* square is given two names: one for the row and the
* other for the column on the grid. The color of each
* square is determined by its position on the grid, and
* the value in the board[][] array.
*/
void drawSquares(GLenum mode)
{
GLuint i, j;
for (i = 0; i < 3; i++) {
if (mode == GL_SELECT)
glLoadName(i);// 名字1
for (j = 0; j < 3; j++) {
if (mode == GL_SELECT)
glPushName (j);// 名字2,用两个名字来代表一个图元。
glColor3f ((GLfloat) i/3.0, (GLfloat) j/3.0,
(GLfloat) board[i][j]/3.0);
glRecti (i, j, i+1, j+1);
if (mode == GL_SELECT)
glPopName (); // 会弹出来,每次名字堆栈中只有两个名字。
}
}
}
/* processHits prints out the contents of the
* selection array.
*/
void processHits (GLint hits, GLuint buffer[])
{
unsigned int i, j;
GLuint ii, jj, names, *ptr;
printf ("hits = %d\n", hits);
ptr = (GLuint *) buffer;
for (i = 0; i < hits; i++) { /* for each hit */
names = *ptr;
printf (" number of names for this hit = %d\n", names); ptr++;
printf(" z1 is %g;", (float) *ptr/0x7fffffff); ptr++;
printf(" z2 is %g\n", (float) *ptr/0x7fffffff); ptr++;
printf (" names are ");
for (j = 0; j < names; j++) { /* for each name */
printf ("%d ", *ptr);
if (j == 0) /* set row and column */
ii = *ptr;
else if (j == 1)
jj = *ptr;
ptr++;
}
printf ("\n");
board[ii][jj] = (board[ii][jj] + 1) % 3;
}
}
/* pickSquares() sets up selection mode, name stack,
* and projection matrix for picking. Then the
* objects are drawn.
*/
#define BUFSIZE 512
void pickSquares(int button, int state, int x, int y)
{
GLuint selectBuf[BUFSIZE];
GLint hits;
GLint viewport[4];
if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
return;
glGetIntegerv (GL_VIEWPORT, viewport);
glSelectBuffer (BUFSIZE, selectBuf);
(void) glRenderMode (GL_SELECT);
glInitNames();
glPushName(0);
glMatrixMode (GL_PROJECTION);
glPushMatrix ();
glLoadIdentity ();
/* create 5x5 pixel picking region near cursor location */
// 1.使用了特殊的挑选矩阵x投影矩阵gluOrtho2D把绘图限制在一个视口的小区域内。
gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y),
5.0, 5.0, viewport);
gluOrtho2D (0.0, 3.0, 0.0, 3.0);
// 2.在光标处绘制的物体,就会导致一次点击;因此挑选一般是用于判断哪些物体是在靠近光标的位置绘制的。
// 和视景体相交的图元会导致一次选择点击,通过drawSquares后在glSelectBuffer数组里面就可以捕获到。
drawSquares (GL_SELECT);
glMatrixMode (GL_PROJECTION);
glPopMatrix ();
glFlush ();
hits = glRenderMode(GL_RENDER);
//3.根据selectBuf选择的点击明,刚好是行列名来改变board颜色,并在GL_RENDER下glutPostRedisplay绘制变化的图元。
processHits(hits, selectBuf);
glutPostRedisplay();
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
drawSquares (GL_RENDER);
glFlush();
}
void reshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D (0.0, 3.0, 0.0, 3.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void keyboard(unsigned char key, int x, int y)
{
switch (key) {
case 27:
exit(0);
break;
}
}
/* Main Loop */
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (100, 100);
glutInitWindowPosition (100, 100);
glutCreateWindow (argv[0]);
init ();
glutReshapeFunc (reshape);
glutDisplayFunc(display);
glutMouseFunc (pickSquares);
glutKeyboardFunc (keyboard);
glutMainLoop();
return 0;
}
如果多个物体在选中,可以在挑选的点击记录里面深度值最低的值
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
glDepthRange(0.0, 1.0); /* The default z mapping */
}
/* The three rectangles are drawn. In selection mode,
* each rectangle is given the same name. Note that
* each rectangle is drawn with a different z value.
*/
void drawRects(GLenum mode)
{
if (mode == GL_SELECT)
glLoadName(1);
glBegin(GL_QUADS);
glColor3f(1.0, 1.0, 0.0);
glVertex3i(2, 0, 0);
glVertex3i(2, 6, 0);
glVertex3i(6, 6, 0);
glVertex3i(6, 0, 0);
glEnd();
if (mode == GL_SELECT)
glLoadName(2);
glBegin(GL_QUADS);
glColor3f(0.0, 1.0, 1.0);
glVertex3i(3, 2, -1);
glVertex3i(3, 8, -1);
glVertex3i(8, 8, -1);
glVertex3i(8, 2, -1);
glEnd();
if (mode == GL_SELECT)
glLoadName(3);
glBegin(GL_QUADS);
glColor3f(1.0, 0.0, 1.0);
glVertex3i(0, 2, -2);
glVertex3i(0, 7, -2);
glVertex3i(5, 7, -2);
glVertex3i(5, 2, -2);
glEnd();
}
/* processHits() prints out the contents of the
* selection array.
*/
// 自定义的深度挑选记录
typedef struct tagNameRecord{
GLuint objNum;
GLuint *objName;
GLfloat zValue;
}NameRecord;
void processHits(GLint hits, GLuint buffer[])
{
unsigned int i, j;
GLuint names, *ptr;
NameRecord nearName;
memset(&nearName, 0, sizeof(NameRecord));
nearName.zValue = 100;
printf("hits = %d\n", hits);
ptr = (GLuint *) buffer;
for (i = 0; i < hits; i++) { /* for each hit */
names = *ptr;
printf(" number of names for hit = %d\n", names); ptr++;
float z1Value = (float)*ptr / 0x7fffffff;
printf(" z1 is %g;", (float) *ptr/0x7fffffff); ptr++;
float z2Value = (float)*ptr / 0x7fffffff;
printf(" z2 is %g\n", (float) *ptr/0x7fffffff); ptr++;
printf(" the name is ");
if (nearName.zValue > z1Value)
{
nearName.zValue = z1Value;
nearName.objName = ptr;
nearName.objNum = names;
}
for (j = 0; j < names; j++) { /* for each name */
printf("%d ", *ptr); ptr++;
}
printf("\n");
}
// 获取z值最近的物体的名称。
printf("proce ssHits nearName.zValue object name:\n");
for (j = 0; j < nearName.objNum; j++) { /* for each name */
printf("%d ", *nearName.objName); nearName.objName++;
}
printf("\n");
}
/* pickRects() sets up selection mode, name stack,
* and projection matrix for picking. Then the objects
* are drawn.
*/
#define BUFSIZE 512
void pickRects(int button, int state, int x, int y)
{
GLuint selectBuf[BUFSIZE];
GLint hits;
GLint viewport[4];
if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
return;
glGetIntegerv(GL_VIEWPORT, viewport);
glSelectBuffer(BUFSIZE, selectBuf);
(void) glRenderMode(GL_SELECT);
glInitNames();
glPushName(0);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
/* create 5x5 pixel picking region near cursor location */
gluPickMatrix((GLdouble) x, (GLdouble) (viewport[3] - y),
5.0, 5.0, viewport);
glOrtho(0.0, 8.0, 0.0, 8.0, -0.5, 2.5);
drawRects(GL_SELECT);
glPopMatrix();
glFlush();
hits = glRenderMode(GL_RENDER);
processHits(hits, selectBuf);
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawRects(GL_RENDER);
glFlush();
}
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, 8.0, 0.0, 8.0, -0.5, 2.5);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void keyboard(unsigned char key, int x, int y)
{
switch (key) {
case 27:
exit(0);
break;
}
}
/* Main Loop
*/
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize (200, 200);
glutInitWindowPosition (100, 100);
glutCreateWindow(argv[0]);
init();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutMouseFunc(pickRects);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}
反馈模式
反馈模式是在光栅化后,返回的顶点或颜色,纹理信息都是光栅化窗口坐标系中的坐标。反馈模式和选择模式下的图元信息,都是没有经过任何片段测试的,所以没有经过测试的信息也会从选择或反馈信息中得到。
步骤:
1.声明反馈模式的数组,数组的类型GL_3D顶点,GL_3D_COLOR包括了顶点和颜色,GL_3D_COLOR_TEXTURE顶点颜色和纹理.
2.设置为反馈渲染模式,GL_FEEDBACK。
3.绘制物体,glPassThrough()插入标记,区分一些图形裁剪剔除。
4.返回的顶点数量,返回的结构包括type1,valueseq1,...typen, valueseqn。
反馈模式代码:
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
/* Initialize lighting.
*/
void init(void)
{
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
}
/* Draw a few lines and two points, one of which will
* be clipped. If in feedback mode, a passthrough token
* is issued between the each primitive.
*/
void drawGeometry (GLenum mode)
{
glBegin(GL_LINE_LOOP);// GL_LINE_STRIP模式会产生2条线段,Loop是3条
glNormal3f (0.0, 0.0, 1.0);
glVertex3f (40.0, 30.0, 0.0);
glVertex3f (60.0, 60.0, 0.0);
glVertex3f (70.0, 40.0, 0.0);
glEnd ();
if (mode == GL_FEEDBACK)
glPassThrough (1.0);
glBegin (GL_POINTS);
glVertex3f (-100.0, -100.0, -100.0); /* will be clipped */
glEnd ();
if (mode == GL_FEEDBACK)
glPassThrough (2.0);
glBegin (GL_POINTS);
glNormal3f (0.0, 0.0, 1.0);
glVertex3f (50.0, 50.0, 0.0);
glEnd ();
}
/* Write contents of one vertex to stdout. */
void print3DcolorVertex (GLint size, GLint *count,
GLfloat *buffer)
{
int i;
printf (" ");
for (i = 0; i < 7; i++) {
printf ("%4.2f ", buffer[size-(*count)]);
*count = *count - 1;
}
printf ("\n");
}
/* Write contents of entire buffer. (Parse tokens!) */
void printBuffer(GLint size, GLfloat *buffer)
{
GLint count;
GLfloat token;
count = size;
while (count) {
token = buffer[size-count]; count--;
if (token == GL_PASS_THROUGH_TOKEN) {
printf ("GL_PASS_THROUGH_TOKEN\n");
printf (" %4.2f\n", buffer[size-count]);
count--;
}
else if (token == GL_POINT_TOKEN) {
printf ("GL_POINT_TOKEN\n");
print3DcolorVertex (size, &count, buffer);
}
else if (token == GL_LINE_TOKEN) {
printf ("GL_LINE_TOKEN\n");
print3DcolorVertex (size, &count, buffer);
print3DcolorVertex (size, &count, buffer);
}
else if (token == GL_LINE_RESET_TOKEN) {
printf ("GL_LINE_RESET_TOKEN\n");
print3DcolorVertex (size, &count, buffer);
print3DcolorVertex (size, &count, buffer);
}
}
}
void display(void)
{
GLfloat feedBuffer[1024];
GLint size;
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho (0.0, 100.0, 0.0, 100.0, 0.0, 1.0);
glClearColor (0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
drawGeometry (GL_RENDER);
// 1.声明反馈模式的数组,数组的类型GL_3D顶点,GL_3D_COLOR包括了顶点和颜色,GL_3D_COLOR_TEXTURE顶点颜色和纹理.
glFeedbackBuffer (1024, GL_3D_COLOR, feedBuffer);
// 2.设置为反馈渲染模式,GL_FEEDBACK
(void) glRenderMode (GL_FEEDBACK);
// 3.绘制物体,glPassThrough()插入标记,区分一些图形裁剪剔除
drawGeometry (GL_FEEDBACK);
// 4.返回的顶点数量,返回的结构包括type1,valueseq1,...typen, valueseqn。
size = glRenderMode (GL_RENDER);
printBuffer (size, feedBuffer);
}
void keyboard(unsigned char key, int x, int y)
{
switch (key) {
case 27:
exit(0);
break;
}
}
/* Main Loop */
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (100, 100);
glutInitWindowPosition (100, 100);
glutCreateWindow(argv[0]);
init();
glutDisplayFunc(display);
glutKeyboardFunc (keyboard);
glutMainLoop();
return 0;
}