【粗译】Opengl Tutorial 29: 3D Picking

http://ogldev.atspace.co.uk/www/tutorial29/tutorial29.html

 

Tutorial 29: 3D Picking

背景

    将鼠标单击显示3D场景的窗口与原语(假设是一个三角形)匹配的能力,该原语幸运地被投影到鼠标单击的相同像素处,称为3D pick。这对于各种交互用例非常有用,这些用例要求应用程序将用户单击的鼠标(本质上是2D)映射到场景中对象的本地/世界空间。例如,您可以使用它来选择一个对象或它的一部分作为将来操作的目标(例如删除等)。在本教程演示中,我们呈现了几个对象,并展示了如何用红色标记“触摸”三角形,使其突出。

        为了实现3D拾取,我们将利用阴影映射教程(#23)中引入的OpenGL特性——Framebuffer对象(FBO)。之前我们使用FBO进行深度缓冲,只是因为我们对从两个不同的角度比较像素的深度感兴趣。对于3D选择,我们将同时使用深度缓冲区和颜色缓冲区来存储呈现三角形的索引。

3D选择背后的技巧非常简单。我们将为每个三角形附加一个运行索引,并让FS输出像素所属三角形的索引。最终的结果是,我们得到了一个“颜色”缓冲区,它实际上并不包含颜色。相反,对于被某个原语覆盖的每个像素,我们得到这个原语的索引。当鼠标在窗口上单击时,我们将读取该索引(根据鼠标的位置)并呈现选中的三角形红色。通过在这个过程中组合一个深度缓冲区,我们可以保证当多个原语重叠在同一个像素上时,我们可以得到最顶层原语的索引(最接近摄像机)。

简而言之,这就是3D拾取。在进入代码之前,我们需要做出一些设计决策。例如,我们如何处理多个对象?我们如何处理每个对象的多个draw调用?我们是希望原语索引从一个对象增加到另一个对象,以便场景中的每个原语都有一个惟一的索引,还是希望它为每个对象重置索引?

本教程中的代码采用一种通用方法,可以根据需要进行简化。我们将为每个像素渲染一个三级索引:

1.像素所属对象的索引。场景中的每个对象都将得到一个惟一的索引。
2.对象中draw调用的索引。此索引将在新对象的开始处重置。
3.draw调用中的基本索引。此索引将在每次绘制调用开始时重置。

当我们读取一个像素的索引时,我们实际上会得到上面的三个。然后我们需要回到特定的原语。

我们需要渲染场景两次。第一次是所谓的“选择纹理”,它将包含原始索引,第二次是实际的颜色缓冲区。因此,主渲染循环将有一个选择阶段和一个渲染阶段。

注意:用于演示的spider模型来自Assimp源包。它包含多个VBs,允许我们测试这个案例。

Source walkthru
(picking_texture.h:23)

class PickingTexture
{
public:
    PickingTexture();

    ~PickingTexture();

    bool Init(unsigned int WindowWidth, unsigned int WindowHeight);

    void EnableWriting();

    void DisableWriting();

    struct PixelInfo {
        float ObjectID;
        float DrawID;
        float PrimID;

        PixelInfo()     {
            ObjectID = 0.0f;
            DrawID = 0.0f;
            PrimID = 0.0f;
        }
    };

    PixelInfo ReadPixel(unsigned int x, unsigned int y);

private:
    GLuint m_fbo;
    GLuint m_pickingTexture;
    GLuint m_depthTexture;
};

PickingTexture类表示将原始索引呈现到其中的FBO。它封装了framebuffer对象句柄、索引信息的纹理对象和深度缓冲区的纹理对象。它初始化时具有与主窗口相同的窗口宽度和高度,并提供三个关键函数。必须在选择阶段的开始调用EnableWriting()。然后我们渲染所有相关的对象。最后,我们调用DisableWriting()返回默认的framebuffer。要读取像素的索引,我们使用屏幕空间坐标调用ReadPixel()。这个函数返回一个结构,其中包含在背景部分中描述的三个索引(或id)。如果鼠标单击没有触及任何对象,那么PixelInfo结构的PrimID字段将包含0xFFFFFFFF.

(picking_texture.cpp:48)

bool PickingTexture::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
    // Create the FBO
    glGenFramebuffers(1, &m_fbo); 
    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);

    // Create the texture object for the primitive information buffer
    glGenTextures(1, &m_pickingTexture);
    glBindTexture(GL_TEXTURE_2D, m_pickingTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, WindowWidth, WindowHeight,
                0, GL_RGB, GL_FLOAT, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 
                m_pickingTexture, 0); 

    // 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); 

    // Disable reading to avoid problems with older GPUs
    glReadBuffer(GL_NONE);

    glDrawBuffer(GL_COLOR_ATTACHMENT0);

    // Verify that the FBO is correct
    GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

    if (Status != GL_FRAMEBUFFER_COMPLETE) {
        printf("FB error, status: 0x%x\n", Status);
        return false;
    }

    // Restore the default framebuffer
    glBindTexture(GL_TEXTURE_2D, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    return GLCheckError();
}

上面的代码初始化了PickingTexture类。我们生成一个FBO并将它绑定到GL_FRAMEBUFFER目标。然后我们生成两个纹理对象(用于像素信息和深度)。注意,包含像素信息的纹理的内部格式是GL_RGB32F。这意味着每个texel是一个由3个浮点数组成的向量。即使我们没有使用数据初始化这个纹理(glTexImage2D的最后一个参数为NULL),我们仍然需要提供正确的格式和类型(第7和第8参数)。匹配GL_RGB32F的格式和类型分别是GL_RGB和GL_FLOAT。最后,我们将这个纹理附加到FBO的GL_COLOR_ATTACHMENT0目标上。这将使它成为片段着色器输出的目标。

深度缓冲的纹理对象是按照阴影贴图教程中的相同方式创建和附加的,所以我们不会在这里再次查看它。初始化之后,我们检查FBO的状态,并在返回之前恢复默认对象。

(picking_texture.cpp:82)

void PickingTexture::EnableWriting()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
}

在我们开始渲染到选择纹理之前,我们需要使它能够writing。这意味着将FBO绑定到GL_DRAW_FRAMEBUFFER。

(picking_texture.cpp:88)

void PickingTexture::DisableWriting()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

在我们完成对选择纹理的渲染之后,我们告诉OpenGL,从现在开始,我们想要通过绑定0到GL_DRAW_FRAMEBUFFER目标来渲染到默认的framebuffer中。

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;
}

这个函数在屏幕上获取一个坐标,并从选择纹理返回相应的texel。这个texel是浮点数的3向量,这正是PixelInfo结构所包含的。要从FBO读取数据,我们必须首先将其绑定到GL_READ_FRAMEBUFFER目标。然后,我们需要指定使用glReadBuffer()函数从哪个颜色缓冲区读取数据。原因是FBO可以包含多个颜色缓冲区(FS可以同时呈现为多个颜色缓冲区),但是我们一次只能读取一个缓冲区。函数glReadPixels执行实际读取。它接受一个矩形,该矩形使用其左下角(第一对参数)和宽度/高度(第二对参数)指定,并将结果读入最后一个参数给出的地址。在我们的例子中,矩形的大小是一个texel。我们还需要告诉这个函数格式和数据类型,因为对于某些内部格式(如带符号或无符号标准化定点),函数能够在输出时将内部数据转换为另一种类型。在我们的示例中,我们需要原始数据,因此使用GL_RGB作为格式,GL_FLOAT作为类型。完成之后,我们必须重置读取缓冲区和framebuffer。

(picking.vs)

#version 330

layout (location = 0) in vec3 Position;

uniform mat4 gWVP;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
}

This is the VS of the PickingTechnique class. This technique is responsible for rendering the pixel info into the PickingTexture object. As you can see, the VS is very simple since we only need to transform the vertex position.

(picking.fs)

#version 330

uniform uint gDrawIndex; 
uniform uint gObjectIndex; 

out vec3 FragColor;

void main()
{
    FragColor = vec3(float(gObjectIndex), float(gDrawIndex),float(gl_PrimitiveID + 1));
}

 

见:http://ogldev.atspace.co.uk/www/tutorial29/tutorial29.html

未完..........

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值