一、鼠标拾取原理
在设计软件中,常有鼠标拾取对象的功能。其实现有多种方法,本文介绍使用OpenGL离屏渲染实现鼠标拾取的方法。该方法分成两步:
第一步:将屏幕像素对应的物体ID写入与帧缓冲区同样大小的另一缓冲区中,可称离屏缓冲区;
第二步:在鼠标单击时,读取离屏缓冲区对应物体的ID,进行进一步处理即可。
二、渲染到纹理
首先要创建一个帧缓冲对象,并绑定它:
GLuint m_fbo = 0;
glGenFramebuffers(1, &m_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
接下来我们需要创建一个纹理图像,将它作为一个颜色附件附加到帧缓冲上。纹理的维度设置为窗口的宽度和高度,并且不初始化它的数据。物体的id就写入这个纹理中:
// Create the texture object for the primitive information buffer
GLuint m_pickingTexture = 0;
glGenTextures(1, &m_pickingTexture);
glBindTexture(GL_TEXTURE_2D, m_pickingTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32UI, WindowWidth, WindowHeight, 0, GL_RGB_INTEGER, GL_UNSIGNED_INT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pickingTexture, 0);
绘制物体id的时候还需要进行深度测试,不能后面的物体覆盖前面物体的id,所以还需要添加一个深度附件到缓冲中。
// Create the texture object for the depth buffer
glGenTextures(1, &m_depthTexture);
glBindTexture(GL_TEXTURE_2D, m_depthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthTexture, 0);
最后使用的时候用glBindFramebuffer绑定离屏帧缓冲,渲染管线会渲染到该缓冲中,渲染完需切换到默认帧缓冲。
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
.... 离屏渲染中间过程
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
三、写入物体ID到纹理与读取
在画每一个物体的时候,可以物体的id传入片元所色器的uniform变量中,片元着色器写入颜色即可。
#version 330
uniform uint gObjectIndex;
uniform uint gDrawIndex;
out uvec3 FragColor;
void main()
{
FragColor = uvec3(gObjectIndex, gDrawIndex, gl_PrimitiveID);
}
读取像素使用OpenGL提供的API:
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
glReadBuffer(GL_COLOR_ATTACHMENT0);
PixelInfo Pixel;
glReadPixels(x, y, 1, 1, GL_RGB_INTEGER, GL_UNSIGNED_INT, &Pixel);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
具体代码参见:
ogldev/tutorial31_youtube at master · emeiri/ogldev · GitHub