《OGL dev》Etay Meiri Tutorial 29 - 3D Picking 笔记

3D拾取指获取屏幕上点击的像素对应的图元的技术。

3D拾取的原理是在正常渲染pass之前,插入一个将图元信息渲染到picking texture的各个通道中的pass。之后,点击屏幕时,从picking texture中读取到点击的像素对应的图元信息。

渲染到picking texture时,必须启用深度测试,当像素重叠时丢弃离摄影机较远的像素。以保证鼠标点击,读取picking texture时,获取的是离屏幕(摄影机)最近的图元。

场景中对象的索引,绘制一个对象内部调用draw call的索引,draw call内部图元的索引,三者唯一确定一个图元。

GS和FS的内置变量gl_PrimitiveID存储draw call内部图元的索引。如果存在GS,在GS中可以直接使用gl_PrimitiveID,在FS只能接收从GS输出的全局变量以获取gl_PrimitiveID的值。如果不存在GS,在FS中可以直接使用gl_PrimitiveID。gl_PrimitiveID中图元索引,从0开始编号。

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, WindowWidth, WindowHeight, 0, GL_RGB, GL_FLOAT, NULL);
  • GL_RGB32F是纹理内部格式,表示每个texel有三个浮点通道,对应的数据源格式为GL_RGB和GL_FLOAT。虽然数据源为NULL,但纹理要被写入,数据源格式是必须正确填写的。
PickingTexture::PixelInfo PickingTexture::ReadPixel(unsigned int x, unsigned int y)
{
    glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
    glReadBuffer(GL_COLOR_ATTACHMENT0);

    PixelInfo Pixel;
    glReadPixels(x, y, 1, 1, GL_RGB, GL_FLOAT, &Pixel);

    glReadBuffer(GL_NONE);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);

    return Pixel;
}
  • glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);,将framebuffer m_fbo绑定到读取。glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);,将framebuffer重置为默认framebuffer。
  • glReadBuffer(GL_COLOR_ATTACHMENT0);,表示从索引为0的color buffer读取,因为framebuffer支持包含多个color buffer,一次draw call,FS可以同时渲染到多个color buffer。虽然可以一次渲染到多个color buffer,但一次只能从一个color buffer读取。glReadBuffer(GL_NONE);,禁用从framebuffer读取。
  • glReadPixels(x, y, 1, 1, GL_RGB, GL_FLOAT, &Pixel);,从buffer中读取一个矩形区域的像素信息,返回到最后一个参数Pixel的地址中。Pixel的类型是由倒数第二和第三个参数确定的,这里使用GL_RGB和GL_FLOAT获取原始数据,如果使用其他参数,函数会在输出时转换到参数确定的类型。第一和第二个参数确定矩阵区域的左下角,之后两个参数是长宽,这里使用1和1表示只获取一个像素。
static void MouseCB(int Button, int State, int x, int y)
{
    s_pCallbacks->MouseCB(Button, State, x, y);
}


static void InitCallbacks()
{
    ...
    glutMouseFunc(MouseCB);
}
  • glutMouseFunc()获取鼠标点击事件。
  • MouseCB()的第一参数Button为GLUT_LEFT_BUTTON、 GLUT_MIDDLE_BUTTON和GLUT_RIGHT_BUTTON其中之一,表示哪个按键被按下。第二个参数State为GLUT_DOWN或GLUT_UP,表示按下或抬起。

虽然OpenGL默认是禁用混合的,但在某些没有明确禁用混合的平台上,需要调用glDisable(GL_BLEND);显式禁用混合。

glutReshapeFunc()获取窗口大小变化。当窗口大小变化时需要重新设定picking texture的宽高,才能保证3D拾取正确。