OpenGL 利用framebuffer实现快速精确的点选拾取

本文介绍利用framebuffer来实现opengl的点选拾取,编程语言C++,GLSL,编程环境VS2008,依赖库glew,OpenGL版本4.3。

不多说,直接介绍原理。此原理依赖opengl的几个技术特点包括 着色其多重输出,颜色值的宽动态表述,离屏渲染。

完整代码请看https://pan.baidu.com/s/1slaOpkx

email: hj3952321@163.com

离屏渲染就是将着色器的输出定向到自定义framebuffer中,而不是屏幕,framebuffer定义如下:

enum RBUFFERenum{DEPTH=0,POS,KEY_AND_ID,VID,MP0_WP2_0,MP1_WP2_1,MP2_WP2_2,WP0,WP1,RENDERBUFFERS_COUNT};

struct Buffers
{
    RBUFFERenum mBufferName;
    GLenum mAttachIndex;
    GLenum mInternalFormat;
};

 static Buffers gbuffers[RENDERBUFFERS_COUNT] = {    \
                                                        {DEPTH, GL_DEPTH_ATTACHMENT, GL_DEPTH_COMPONENT32F},
                                                        {POS, GL_COLOR_ATTACHMENT0, GL_RGBA32F},
                                                        {KEY_AND_ID, GL_COLOR_ATTACHMENT1, GL_RGBA32I},
                                                        {VID, GL_COLOR_ATTACHMENT2, GL_RGBA32I},
                                                        {MP0_WP2_0, GL_COLOR_ATTACHMENT3, GL_RGBA32F},
                                                        {MP1_WP2_1, GL_COLOR_ATTACHMENT4, GL_RGBA32F},
                                                        {MP2_WP2_2, GL_COLOR_ATTACHMENT5, GL_RGBA32F},
                                                        {WP0, GL_COLOR_ATTACHMENT6, GL_RGBA32F},
                                                        {WP1, GL_COLOR_ATTACHMENT7, GL_RGBA32F}
                                                        };

Framebuffer::Framebuffer(GLuint width, GLuint height)
{
    glGenFramebuffers(1,&mFramebuffer);
    glGenRenderbuffers(RENDERBUFFERS_COUNT, mRenderbuffers);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebuffer);
    int i = 0;
    for(; i < RENDERBUFFERS_COUNT; i++)
    {
        glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffers[i]);
        glRenderbufferStorage(GL_RENDERBUFFER, gbuffers[i].mInternalFormat, width, height);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, gbuffers[i].mAttachIndex, GL_RENDERBUFFER, mRenderbuffers[i]);
    }
    unsigned int status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
}
 对应片段着色器如下:

#version 430 core
layout (location = 0) out vec4 oPos;
layout (location = 1) out ivec4 key_and_id;
layout (location = 2) out ivec4 oVID;
 
layout (location = 3) out vec4 omPoso0;
layout (location = 4) out vec4 omPoso1;
layout (location = 5) out vec4 omPoso2;
 
layout (location = 6) out vec4 owPoso0;
 
layout (location = 7) out vec4 owPoso1;//MAX_COLOR_ATTACHMENTS = 8
 
uniform int globalKey1 = int(4);
uniform int localKey1 = int(16);
uniform int globalKey2 = int(64);
uniform int localKey2 = int(256);
 
in GS_FS_INTERFACE{
    in vec3 pos;
    in ivec3 VID;
    in vec3 mPoso[3];
    in vec3 wPoso[3];
};
void main(void)
{
    
    oPos = vec4(pos, 1);
    oVID = ivec4(VID, 2);    
    key_and_id = ivec4(globalKey1, globalKey2, localKey1, gl_PrimitiveID + 1);
 
    omPoso0 = vec4(mPoso[0],wPoso[2].x);
    omPoso1 = vec4(mPoso[1],wPoso[2].y);
    omPoso2 = vec4(mPoso[2],wPoso[2].z);
    
    owPoso0 = vec4(wPoso[0],7);
    owPoso1 = vec4(wPoso[1],8);
}

着色器多重输出的buffer个数不同驱动实现有所差别,我所使用的支持最多8个buffer,所以着色器的out location从0到7.此着色器拾取对象是三角形,能够得到像素点的世界坐标和所在的三角形图元的ID,及三角形图元三个角所对应的模型坐标和世界坐标,最关键的是key_and_id,根据这个输出用户自定义的三个key值,可以找到像素点对应的原始数据模型。其中点所在图元的三个点的坐标和ID需要几何着色器来辅助得到。最简单的点选只需要oPos和key_and_id,oPos是通过片段着色器光栅化插值得到的,这个必须有,而用户根据key_and_id就能够获得顶点原始信息,进而就能得到其它信息。这里有个细节需要注意,glDraw命令的每一次执行,图元ID(gl_PrimitiveID)是从0开始计数的,而gl_vertexID是与VBO对应的,也是说比如VBO中有8个三角形图元对应有24个顶点坐标,分两次绘制,第一次绘制前4个图元,得到的图元ID是0-3,顶点ID是0-11,第二次绘制后4个图元,得到的图元ID也是0-3,但顶点ID是12-23.输出的图元ID做了加1处理,当读到的ID是0时,说明此处没有图元绘制。

阅读更多
个人分类: OpenGL C++ GLSL
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭