OpenGL-对象轮廓

时间:2018-12-22 17:39:04

标签: c++ opengl graphics highlight outlining

我正在尝试实现选择轮廓功能。这就是我现在要做的。

enter image description here

如您所见,当鼠标悬停并在所选对象周围绘制轮廓时,将正确选择对象。

我现在想做的就是以此方式勾勒出对象的可见边缘

enter image description here

左边是我现在要拥有的,右边是我想要实现的。

这是我现在使用的过程。

void paintGL()
{
    /* ... */

    int w = geometry().width();
    int h = geometry().height();

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glStencilMask(0xFF);

    setClearColor(Qt::GlobalColor::darkGray);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    glStencilMask(0x00);
    DrawGeometry();

    if (HoveredSphere != RgbFromColorToString(Qt::GlobalColor::black))
    {
        glBindFramebuffer(GL_FRAMEBUFFER, addFBO(FBOIndex::OUTLINE));
        {
            glStencilFunc(GL_ALWAYS, 1, 0xFF);
            glStencilMask(0xFF);

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

            DrawOutline(HoveredSphere, 1.0f - 0.025f);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());

        glBindFramebuffer(GL_READ_FRAMEBUFFER, addFBO(FBOIndex::OUTLINE));
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, defaultFramebufferObject());
        {
            // copy stencil buffer
            GLbitfield mask = GL_STENCIL_BUFFER_BIT;
            glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, mask, GL_NEAREST);

            glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
            glStencilMask(0x00);

            glDepthFunc(GL_LEQUAL);
            DrawOutline(HoveredSphere, 1.0f);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
    }

    update();
}

DrawGeometry绘制所有对象,DrawOutline绘制所选对象,该对象按作为第二个参数传递的因子缩放。

谢谢您的建议。

2 个答案:

答案 0 :(得分:6)

通过@MichaelMahn的技巧,我找到了解决方案。

首先,我在纹理中绘制所选对象的可见部分的轮廓。

IMG

然后,我使用此纹理通过检查相邻像素来计算轮廓,以确定我是否站在轮廓的边缘。

enter image description here

轮廓片段着色器

#version 450

uniform sampler2D silhouette;

in FragData
{
    smooth vec2 coords;
} frag;

out vec4 PixelColor;

void main()
{
    // if the pixel is black (we are on the silhouette)
    if (texture(silhouette, frag.coords).xyz == vec3(0.0f))
    {
        vec2 size = 1.0f / textureSize(silhouette, 0);

        for (int i = -1; i <= +1; i++)
        {
            for (int j = -1; j <= +1; j++)
            {
                if (i == 0 && j == 0)
                {
                    continue;
                }

                vec2 offset = vec2(i, j) * size;

                // and if one of the neighboring pixels is white (we are on the border)
                if (texture(silhouette, frag.coords + offset).xyz == vec3(1.0f))
                {
                    PixelColor = vec4(vec3(1.0f), 1.0f);
                    return;
                }
            }
        }
    }

    discard;
}

paintgl

void paintGL()
{
    int w = geometry().width();
    int h = geometry().height();

    setClearColor(Qt::GlobalColor::darkGray);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    DrawGeometry();

    // if we hover a sphere
    if (HoveredSphere != RgbFromColorToString(Qt::GlobalColor::black))
    {
        glBindFramebuffer(GL_READ_FRAMEBUFFER, defaultFramebufferObject());
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, addFBO(FBOIndex::SILHOUETTE));
        {
            // copy depth buffer
            GLbitfield mask = GL_DEPTH_BUFFER_BIT;
            glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, mask, GL_NEAREST);

            // set clear color
            setClearColor(Qt::GlobalColor::white);
            // enable depth test
            glEnable(GL_DEPTH_TEST);
            glDepthFunc(GL_LEQUAL);
            // clear color buffer
            glClear(GL_COLOR_BUFFER_BIT);

            // draw silhouette
            DrawSilhouette(HoveredSphere);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());

        // clear depth buffer
        glClear(GL_DEPTH_BUFFER_BIT);

        // draw outline
        DrawOutline();
    }
}

问题 ::现在,我要参数化轮廓的宽度,该轮廓的厚度当前固定为1个像素。

非常感谢您的任何建议!

答案 1 :(得分:2)

由于@Andrea的建议,我找到了以下解决方案。

轮廓片段着色器

#version 450

uniform sampler2D silhouette;

in FragData
{
    smooth vec2 coords;
} frag;

out vec4 PixelColor;

void main()
{
    // outline thickness
    int w = 3;

    // if the pixel is black (we are on the silhouette)
    if (texture(silhouette, frag.coords).xyz == vec3(0.0f))
    {
        vec2 size = 1.0f / textureSize(silhouette, 0);

        for (int i = -w; i <= +w; i++)
        {
            for (int j = -w; j <= +w; j++)
            {
                if (i == 0 && j == 0)
                {
                    continue;
                }

                vec2 offset = vec2(i, j) * size;

                // and if one of the pixel-neighbor is white (we are on the border)
                if (texture(silhouette, frag.coords + offset).xyz == vec3(1.0f))
                {
                    PixelColor = vec4(vec3(1.0f), 1.0f);
                    return;
                }
            }
        }
    }

    discard;
}

现在,当所选对象位于窗口边缘时,我仍然有一个小问题。

enter image description here

如您所见,轮廓被锐利切割。

我尝试在参数glTexParameterGL_TEXTURE_WRAP_S上与GL_TEXTURE_WRAP_T“玩”。

在上图中,您可以看到我得到的效果

glTexParameters(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameters(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

在下图中,您可以看到我得到的效果

glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, &(QVector4D (1.0f, 1.0f, 1.0f, 1.0f)[0]));
glTexParameters(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameters(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

enter image description here

我希望轮廓显示在窗口的边缘,但仅在必要时显示。

非常感谢!