OpenGL3.3鼠标拾取物体

OpenGL3.3鼠标拾取物体

本文翻译自:http://www.lighthouse3d.com/tutorials/opengl-selection-tutorial/

在3D场景中拾取或选择特定项目可能对某些应用程序很有用。可以通过单击一个对象来执行选择,这需要一种确定鼠标放置在哪个对象上的方法。

实现此目的的简单解决方案是使用颜色编码,以特定颜色绘制每个可拾取对象。读取鼠标所在的像素以提供颜色,从而可以识别物体。

选择模式下的渲染使用非常简单的着色器,将恒定的颜色应用于像素。颜色是一个统一变量,在绘制每个对象之前应将其设置为唯一值。

顶点着色器可以如下所示:

#version 330
 
uniform mat4 m_pvm;
 
in vec4 position;
 
void main()
{
    gl_Position = m_pvm * position ;
}

片段着色器很简单:

#version 330
 
uniform int code;
 
out vec4 outputF;
 
void main()
{
    outputF = vec4(code/255.0, 0, 0, 0);
}

重要的是,我们收到的code是整数而不是浮点数。在RGB模式下,每个分量只有256个可能的值,因此当您使用浮点数设置颜色时,OpenGL将选择最接近的可能颜色,这可能与您提供的值不完全相同。

请注意,我们将除以255.0,而不是除以255。使用后一种方法将是整数除法,结果也将是整数(0或1)。

如果有超过255个对象可供选择,则 uniformcode可以是ivec4类型。

假设我们的场景有4个国际象棋棋子,如下图所示:

在这里插入图片描述

在此示例中,我们有四个可点击的对象,我们将为其分配从1到4的代码。选择渲染例程的背景可以设置为黑色,因此零表示没有任何选择。使用选择程序时,我们将获得如下图像(对比度大大提高):

在这里插入图片描述
该图像将永远不会呈现给用户,因为选择渲染不会交换缓冲区。

以下例程接收鼠标窗口坐标,并执行必要的步骤来确定选择了哪个对象。首先调用选择渲染程序,然后再进行选择。然后,它从后台缓冲区读取像素并检查返回的颜色。

要读取像素,我们将使用函数glReadPixels
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *data);

参数:

  • x,y:要读取的矩形块的第一个像素的坐标
  • width,height:块的大小。
  • format:像素数据的格式:GL_STENCIL_INDEX, GL_DEPTH_COMPONENT, GL_DEPTH_STENCIL, GL_RED, GL_GREEN, GL_BLUE, GL_RGB, GL_BGR, GL_RGBA, GL_BGRA
  • type:像素分量的数据类型:GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, GL_HALF_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE_3_3_2, GL_UNSIGNED_BYTE_2_3_3_REV, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_5_6_5_REV, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_4_4_4_4_REV, GL_UNSIGNED_SHORT_5_5_5_1, GL_UNSIGNED_SHORT_1_5_5_5_REV, GL_UNSIGNED_INT_8_8_8_8, GL_UNSIGNED_INT_8_8_8_8_REV, GL_UNSIGNED_INT_10_10_10_2, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_24_8, GL_UNSIGNED_INT_10F_11F_11F_REV, GL_UNSIGNED_INT_5_9_9_9_REV, or GL_FLOAT_32_UNSIGNED_INT_24_8_REV
  • data:返回的像素数据。
void processSelection(int xx, int yy) {
 
    unsigned char res[4];
    GLint viewport[4]; 
 
    renderSelection();
 
    glGetIntegerv(GL_VIEWPORT, viewport);
    glReadPixels(xx, viewport[3] - yy, 1,1,GL_RGBA, GL_UNSIGNED_BYTE, &res);
    switch(res[0]) {
        case 0: printf("Nothing Picked \n"); break;
        case 1: printf("Picked yellow\n"); break;
        case 2: printf("Picked red\n"); break;
        case 3: printf("Picked green\n"); break;
        case 4: printf("Picked blue\n"); break;
        default:printf("Res: %d\n", res[0]);
    }
}

为了获得正确的像素,我们必须将鼠标窗口坐标(左上角为原点)转换为帧缓冲区坐标(左下角的原点)。这要求我们知道视口的高度。函数glGetIntegerv可用于此目的,将GL_VIEWPORT作为第一个参数。返回变量viewport是一个包含4个项目的数组,该数组提供x和y视口窗口坐标(起点:左上角),然后是视口的宽度和高度。然后,可以读取像素,并正确解码检索到的颜色。

选择渲染程序必须遵循与常规程序有关几何变换的相同步骤,以使对象位于屏幕上的同一位置。通常,选择渲染程序是常规渲染程序的简化版本,其中仅绘制对象的子集,并且未设置任何图形效果,例如照明。

对象子集包括所有可点击的对象以及相关的遮挡物。这个想法是,如果某些棋子在用户看不见的空间内,则它们不应出现在颜色编码的图像中。这可以通过用与背景相同的颜色绘制遮挡物(例如房间的墙壁)来轻松实现。

void renderSelection(void) {
 
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
    //set matrices to identity
    ...
    // set camera as in the regular rendering function
    ....
 
    // use the selection shader
    glUseProgram(selectionProgramID);
 
    //perform the geometric transformations to place the first pawn
    ...
    // set the uniform with the appropriate color code
    glProgramUniform1i(selectionProgramID, codeVarLocation, 1);
    // draw first pawn
    ...
     
    // repeat the above steps for the remaining objects, using different codes
 
    //don't swap buffers
    //glutSwapBuffers();
 
    // restore clear color if needed
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
}

下载:

  • 带有完整源代码和着色器的VS2010项目(ZIP

该项目需要glewfreeglutglut


欢迎关注我的公众号 江达小记

  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值