实现思路
我们可以为每一个模型赋予其一个唯一的颜色ID,并将其传入shader中,然后在GL_COLOR_ATTACHMENT1等这些额外的颜色缓冲中绘制。当鼠标点击时,可以通过glReadPixels()获取像素值并遍历所有的模型ID,就能得知是否选中模型或选中了哪一个模型。
实现过程
1.创建帧缓冲
(部分代码借鉴阿西拜大佬的教程)
为了方便,笔者就全用贴图绑定帧缓冲了。创建两个GL_RGB贴图作为颜色缓冲0和1,,一个深度模板贴图并绑定
//创建帧缓冲
glGenFramebuffers(1,&FBO);
glBindFramebuffer(GL_FRAMEBUFFER,FBO);
//创建第零颜色缓冲贴图
glGenTextures(1,&frame_color_texture);
glBindTexture(GL_TEXTURE_2D,frame_color_texture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,width(),height(),0,GL_RGB,GL_UNSIGNED_BYTE,NULL);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,frame_color_texture,0); //将纹理绑定为颜色缓冲
//创建深度与模板缓冲贴图
glGenTextures(1,&frame_depth_stencil_texture);
glBindTexture(GL_TEXTURE_2D,frame_depth_stencil_texture);
glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH24_STENCIL8,width(),height(),0,GL_DEPTH_STENCIL,GL_UNSIGNED_INT_24_8,NULL);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_TEXTURE_2D,frame_depth_stencil_texture,0);
//创建第一颜色缓冲贴图
glGenTextures(1,&frame_id_texture);
glBindTexture(GL_TEXTURE_2D,frame_id_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width(), height(), 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT1,GL_TEXTURE_2D,frame_id_texture,0);
注意:颜色附着1不能直接只设一个单通道灰度图进去,shader传出来的还是一个四元数
开启Buffer绘制
GLenum drawBuffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
glDrawBuffers(2, drawBuffers);
2.设置模型与shader
这里笔者接下来的代码,都是基于ID的范围是0-255所写的,需要拓展的话,三个通道都可以用。
具体设置ID就不写了,确保不重复就行。直接上shader
片元着色器:
#version 330 core
layout(location =0) out vec4 FragColor;
layout(location =1) out vec4 Index;
uniform int ModelIndex;
void main(){
...
float _index=float(ModelIndex);
Index=vec4(_index/255.0,0.0,0.0,1.0);
}
location规定往那个颜色缓冲中传值。因为ID范围是0-255,而RED通道范围是0-1,所以除以255缩放一下。
3.像素获取
在qt鼠标点击事件mousePressEvent(QMouseEvent *event)中,切换刚刚创建好的帧缓冲,并使用glReadPixels()获取特定的像素值
makeCurrent();
//切换帧缓冲,为读取索引值做好准备
glBindFramebuffer(GL_FRAMEBUFFER,FBO);
//从颜色附件1中读取像素值(索引值)
GLubyte pixel[4];
glReadBuffer(GL_COLOR_ATTACHMENT1);
glReadPixels(
(int)event->pos().x(),
this->height()-(int)event->pos().y()
,1,1
,GL_RGB,GL_UNSIGNED_BYTE
,pixel);
int flag=0; //标识是否有模型被选中
//遍历模型,检测哪个被选中
foreach(auto modelinfo,m_Models){
if(modelinfo.model_index==pixel[0]){
flag=1;
modelinfo.isSelected=true;
qDebug()<<"The model be selected"<<" index: "<<pixel[0];
}else{
modelinfo.isSelected=false;
}
}
if(flag==0){
qDebug()<<"No model be selected"<<" index: "<<pixel[0];
}
//操作执行完毕,切换回默认帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject() );
doneCurrent();
makeCurrent()和doneCurrent(): 因为是在initializeGL(),resizeGL()和paintGL()之外修改状态机,所以要添加上下文。
glReadBuffer(): 让glReadPiexls()去读颜色附件1而不是0的值
GLubyte: 因为附件1的格式是GL_UNSIGNED_BYTE,所以用一个GLuyte的数组去承接值。
this->height()-(int)event->pos().y(): 贴图的原点在左下角,但窗口的原点在左上角,所以要转换一下。
pixel[0]: shader会自动将0-1的浮点数转换回GL_UNSIGNED_BYTE(相当于又乘了个255),所以pixel[0]的值正好是模型ID。
4.实现效果
openGLU
可以看到,即使模型重叠到一起,程序依然可以区分不同的模型。