反馈像选择一样也是一种渲染模式,不会往帧缓冲区中写数据,而是把信息填充到反馈缓冲区中。与选择模式返回名称栈不同,反馈的这些信息包括窗口坐标中经过变换的顶点数据,经过光照计算后的颜色数据,以及纹理数据和其他在光栅化图元中需要使用的数据。
通过调用glRenderMode(GL_FEEDBACK)进入反馈模式,调用glRenderMode(GL_RENDER)填充反馈缓冲区,并返回正常渲染模式。
反馈缓冲区
反馈缓冲区是浮点数数组,通过glFeedbackBuffer来指定。
void glFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer);
第一个参数指定反馈缓冲区的大小,第二个参数type描述了反馈缓冲区中每个反馈顶点的信息。其可用的值如下表:
type参数 | 坐标 | 颜色 | 纹理 | 总值 |
GL_2D | x,y | —— |
—— | 2 |
GL_3D | x,y,z | —— | —— | 3 |
GL_3D_COLOR | x,y,z | k | —— | 3+k |
GL_3D_COLOR_TEXTURE | x,y,z | k | 4 | 7+k |
GL_4D_COLOR_TEXTURE | x,y,z,w | k | 4 | 8+k |
ps:如果支持多重纹理,反反馈只返回纹理单位0的纹理坐标
函数的最后一个参数,是指向反馈数据的指针。
反馈的数据
在反馈模式下,每个将要光栅化的图元都会往反馈缓冲区中写值。写的值取决于你设置的type类型。没有光照的2D和3D图元对应的type是GL_2D和GL_3D,而有光照的3D图元使用GL_3D_COLOR, 有光照且有纹理坐标的,使用GL_3D_COLOR_TEXTURE或GL_4D_COLOR_TEXTURE.
每块反馈数据都是从一个表示图元类型的标记开始,紧接着是描述图元顶点以及相关信息的值。我们可以解析这些标记然后确定哪些类型的图元被渲染了。包含的标记如下表格
标记 | 图元 |
GL_POINT_TOKEN | 点 |
GL_LINE_TOKEN | 直线 |
GL_LINE_RESET_TOKEN | 点画模式被重置时 为线段 |
GL_POLYGON_TOKEN | 多边形 |
GL_BITMAP_TOKEN | 位图 |
GL_DRAW_PIXEL_TOKEN | 绘制的像素矩形 |
GL_COPY_PIXEL_TOKEN | 拷贝的像素矩形 |
GL_PASS_THROUGH_TOKEN | 用户自定义的标记 |
点,位图和像素的标记后面跟着的是单个顶点的数据和可能有的颜色和纹理数据(根据type类型的设置),直线标记后面跟着的是两个顶点的集合,多边形标记后面跟着的是一组顶点。用户自定义的标记(GL_PASS_THROUGH_TOKEN)后面跟着的是一个用户设置的浮点值。下面是GL_3D类型的反馈缓冲区示例:
用户自定义标记
反馈是在变换,光照,多边形剔除以及使用glPolygonMode设置多边形模式之后发生的。如果你有一些复杂的多边形是通过OpenGL内部的函数调用分解成多个三角形来绘制的(如之前讨论过的曲面细分),那么反馈的数据就可能包含了这些三角形的数据。在这种情况下反馈的数据不好分析。为了数据便于解析,我们可以在渲染该多边形之前插入一些过渡标记。通过void glPassThrough(GLfloat token);函数设置一个过渡标记。这个函数调用会在反馈缓冲区中插入一个GL_PASS_THROUGH_TOKEN的标记,并其后紧跟着函数设置的参数token值。
PS:如果不是在反馈模式下,这个函数调用不会产生任何效果。在glBegin/glEnd之间调用glPassThrough会产生GL_INVALID_OPERATION错误。
反馈模式的步骤与选择模式类似:
设置反馈缓冲区glFeedbackBuffer
进入反馈模式glRenderMode(GL_FEEDBACK)
绘制图元,其中可以通过glPassThrough设置用户自定义的标记
通过glRenderMode(GL_RENDER)填充了反馈缓冲区,并返回正常模式返回值是反馈数组的值的个数。
解析这些数据,并进行你想要的操作。
反馈的例子
下面是绘制一个圆环和一个球体,通过你的鼠标选择,在你选择的物体外绘制一个矩形边框。效果图
其中绘制物体的函数如下:
void DrawObjects(void)
{
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslatef(-0.75f, 0.0f, -2.5f);
//初始化名称栈
glInitNames();
glPushName(0);
//圆环
glColor3f(1.0f, 1.0f, 0.0f);
//圆环的名称
glLoadName(TORUS);
//设置一个自定义的过渡标记,与圆环的名称相同,以便后面的解析判断
glPassThrough((GLfloat)TORUS);
DrawTorus(40, 20);
//球体
glColor3f(0.5f, 0.0f, 0.0f);
glTranslatef(1.5f, 0.0f, 0.0f);
//球体的名称
glLoadName(SPHERE);
//球体的过渡标记
glPassThrough((GLfloat)SPHERE);
DrawSphere(0.5f);
glPopMatrix();
}
鼠标点击的回调函数,处理选择
#define BUFFER_LENGTH 64
void ProcessSelection(int xPos, int yPos)
{
// 选择缓冲区
static GLuint selectBuff[BUFFER_LENGTH];
GLint hits, viewport[4];
//设置选择缓冲区
glSelectBuffer(BUFFER_LENGTH, selectBuff);
glGetIntegerv(GL_VIEWPORT, viewport);
//切换到投影矩阵
glMatrixMode(GL_PROJECTION);
glPushMatrix();
//进入选择模式
glRenderMode(GL_SELECT);
glLoadIdentity();
//在鼠标位置下,创建一个挑选矩阵
gluPickMatrix(xPos, viewport[3] - yPos + viewport[1], 2,2, viewport);
gluPerspective(60.0f, fAspect, 1.0, 425.0);
//绘制物体
DrawObjects();
//收集点击记录
hits = glRenderMode(GL_RENDER);
//
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
// 如果选中了一个物体,处理选择的信息
if(hits == 1)
{
MakeSelection(selectBuff[3]);
if(selectedObject == selectBuff[3])
selectedObject = 0;
else
selectedObject = selectBuff[3];
}
glutPostRedisplay();
}
处理反馈信息的函数
#define FEED_BUFF_SIZE 32768
void MakeSelection(int nChoice)
{
//选择缓冲区
static GLfloat feedBackBuff[FEED_BUFF_SIZE];
int size,i,j,count;
//初始化边界的最大和最小值
boundingRect.right = boundingRect.bottom = -999999.0f;
boundingRect.left = boundingRect.top = 999999.0f;
//设置反馈缓冲区
glFeedbackBuffer(FEED_BUFF_SIZE,GL_2D, feedBackBuff);
//进入反馈模式
glRenderMode(GL_FEEDBACK);
//绘制物体
DrawObjects();
//离开反馈模式
size = glRenderMode(GL_RENDER);
//解析反馈的数据,获得被选择物体的最大的和最小的窗口坐标
i = 0;
while(i < size)
{
// 搜索用户自定义的标记
if(feedBackBuff[i] == GL_PASS_THROUGH_TOKEN)
//判断标记是否与我们选择的物体名称相同
if(feedBackBuff[i+1] == (GLfloat)nChoice)
{
i+= 2;
while(i < size && feedBackBuff[i] != GL_PASS_THROUGH_TOKEN)
{
//多边形的标记
if(feedBackBuff[i] == GL_POLYGON_TOKEN)
{
//获得所有多边形的反馈信息
count = (int)feedBackBuff[++i];//有多少个顶点
i++;
for(j = 0; j < count; j++) //遍历每一个顶点
{
//取得x的最大最小值
if(feedBackBuff[i] > boundingRect.right)
boundingRect.right = feedBackBuff[i];
if(feedBackBuff[i] < boundingRect.left)
boundingRect.left = feedBackBuff[i];
i++;
//取得y的最大最小值
if(feedBackBuff[i] > boundingRect.bottom)
boundingRect.bottom = feedBackBuff[i];
if(feedBackBuff[i] < boundingRect.top)
boundingRect.top = feedBackBuff[i];
i++;
}
}
else
i++;
}
break;
}
i++;
}
}
渲染场景的函数
void RenderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//绘制物体
DrawObjects();
//选中了某个物体,绘制矩形边框
if(selectedObject != 0)
{
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
//重新设置投影矩阵为正交投影,映射可视区域匹配窗体坐标
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(viewport[0], viewport[2], viewport[3], viewport[1], -1, 1);
glMatrixMode(GL_MODELVIEW);
glDisable(GL_LIGHTING);
glColor3f(1.0f, 0.0f, 0.0f);
//画矩形边框
glBegin(GL_LINE_LOOP);
glVertex2i(boundingRect.left, boundingRect.top);
glVertex2i(boundingRect.left, boundingRect.bottom);
glVertex2i(boundingRect.right, boundingRect.bottom);
glVertex2i(boundingRect.right, boundingRect.top);
glEnd();
glEnable(GL_LIGHTING);
}
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glutSwapBuffers();
}