Opengl渲染纹理与部分透明度(半透明度)然后渲染到屏幕

M2t*_*2tM 8 c++ opengl blending render-to-texture glblendfunc

我找到了一些被问过的地方,但我还没有找到一个好的答案.

问题:我想要渲染到纹理,然后我想将渲染的纹理绘制到屏幕IDENTICALLY,如果我跳过渲染到纹理步骤并且只是直接渲染到屏幕,它将如何显示.我目前正在使用混合模式glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA).我也有glBlendFuncSeparate来玩.

我希望能够将部分透明的重叠项呈现给此纹理.我知道混合功能目前正在弄乱基于Alpha的RGB值.我已经看到一些模糊的建议使用"预乘alpha",但描述的含义很差.我在photoshop中制作png文件,我知道它们有一个半透明的位,你不能像TGA一样独立编辑alpha通道.如果需要,我可以切换到TGA,虽然PNG更方便.

现在,为了这个问题,我们假设我们没有使用图像,而是使用带有alpha的全彩色四边形.

一旦我将场景渲染到纹理,我需要将该纹理渲染到另一个场景,并且我需要将纹理再次呈现为部分透明度.事情就是崩溃的地方.在之前的混合步骤中,我明确地改变了基于Alpha的RGB值,如果Alpha为0或1,则再次执行它,但如果它在中间,则结果是那些部分半透明像素进一步变暗.

玩混合模式我运气很少.我能做的最好就是渲染纹理:

glBlendFuncSeparate(GL_ONE,GL_ONE_MINUS_SRC_ALPHA,GL_ONE,GL_ONE);

我发现使用glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)多次渲染将近似正确的颜色(除非事情重叠).但这并不完美(正如您在下图中看到的那样,绿色/红色/蓝色框重叠的部分会变暗或累积alpha.(编辑:如果我在渲染中进行多次绘制以筛选部分而且仅渲染一次纹理,alpha累积问题消失,它确实有效,但为什么?!我不想为屏幕呈现相同的纹理数百次,以使其正确累积)

以下是一些详细说明问题的图像(多个渲染过程使用基本混合(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA),并且它们在纹理渲染步骤中多次渲染.右边的3个框渲染为100%红色,绿色或蓝色(0-255)但蓝色的阿尔法值为50%,红色的阿尔法值为25%,绿色的阿尔法值为75%: 在此输入图像描述

那么,我想知道的细分:

  1. 我将混合模式设置为:X
  2. 我将场景渲染为纹理.(也许我必须使用一些混合模式或多次渲染?)
  3. 我将混合模式设置为:Y
  4. 我在现有场景上将纹理渲染到屏幕上.(也许我需要一个不同的着色器?也许我需要渲染纹理几次?)

期望的行为是,在该步骤结束时,最终的像素结果与我刚刚执行此操作相同:

  1. 我将混合模式设置为:(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
  2. 我将我的场景渲染到屏幕上.

而且,为了完整性,这里有一些我的原始天真尝试的代码(只是定期混合):

    //RENDER TO TEXTURE.
    void Clipped::refreshTexture(bool a_forceRefresh) {
        if(a_forceRefresh || dirtyTexture){
            auto pointAABB = basicAABB();
            auto textureSize = castSize<int>(pointAABB.size());
            clippedTexture = DynamicTextureDefinition::make("", textureSize, {0.0f, 0.0f, 0.0f, 0.0f});
            dirtyTexture = false;
            texture(clippedTexture->makeHandle(Point<int>(), textureSize));
            framebuffer = renderer->makeFramebuffer(castPoint<int>(pointAABB.minPoint), textureSize, clippedTexture->textureId());
            {
                renderer->setBlendFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                SCOPE_EXIT{renderer->defaultBlendFunction(); };

                renderer->modelviewMatrix().push();
                SCOPE_EXIT{renderer->modelviewMatrix().pop(); };
                renderer->modelviewMatrix().top().makeIdentity();

                framebuffer->start();
                SCOPE_EXIT{framebuffer->stop(); };

                const size_t renderPasses = 1; //Not sure?
                if(drawSorted){
                    for(size_t i = 0; i < renderPasses; ++i){
                        sortedRender();
                    }
                } else{
                    for(size_t i = 0; i < renderPasses; ++i){
                        unsortedRender();
                    }
                }
            }
            alertParent(VisualChange::make(shared_from_this()));
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是我用来设置场景的代码:

    bool Clipped::preDraw() {
        refreshTexture();

        pushMatrix();
        SCOPE_EXIT{popMatrix(); };

        renderer->setBlendFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        SCOPE_EXIT{renderer->defaultBlendFunction();};
        defaultDraw(GL_TRIANGLE_FAN);

        return false; //returning false blocks the default rendering steps for this node.
    }
Run Code Online (Sandbox Code Playgroud)

以及渲染场景的代码:

test = MV::Scene::Rectangle::make(&renderer, MV::BoxAABB({0.0f, 0.0f}, {100.0f, 110.0f}), false);
test->texture(MV::FileTextureDefinition::make("Assets/Images/dogfox.png")->makeHandle());

box = std::shared_ptr<MV::TextBox>(new MV::TextBox(&textLibrary, MV::size(110.0f, 106.0f)));
box->setText(UTF_CHAR_STR("ABCDE FGHIJKLM NOPQRS TUVWXYZ"));
box->scene()->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({0, 0, 1, .5})->position({80.0f, 10.0f})->setSortDepth(100);
box->scene()->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({1, 0, 0, .25})->position({80.0f, 40.0f})->setSortDepth(101);
box->scene()->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({0, 1, 0, .75})->position({80.0f, 70.0f})->setSortDepth(102);
test->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({.0, 0, 1, .5})->position({110.0f, 10.0f})->setSortDepth(100);
test->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({1, 0, 0, .25})->position({110.0f, 40.0f})->setSortDepth(101);
test->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({.0, 1, 0, .75})->position({110.0f, 70.0f})->setSortDepth(102);
Run Code Online (Sandbox Code Playgroud)

这是我的屏幕画:

renderer.clearScreen();
test->draw(); //this is drawn directly to the screen.
box->scene()->draw(); //everything in here is in a clipped node with a render texture.
renderer.updateScreen();
Run Code Online (Sandbox Code Playgroud)

*编辑:FRAMEBUFFER SETUP/TEARDOWN代码:

void glExtensionFramebufferObject::startUsingFramebuffer(std::shared_ptr<Framebuffer> a_framebuffer, bool a_push){
    savedClearColor = renderer->backgroundColor();
    renderer->backgroundColor({0.0, 0.0, 0.0, 0.0});

    require(initialized, ResourceException("StartUsingFramebuffer failed because the extension could not be loaded"));
    if(a_push){
        activeFramebuffers.push_back(a_framebuffer);
    }

    glBindFramebuffer(GL_FRAMEBUFFER, a_framebuffer->framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, a_framebuffer->texture, 0);
    glBindRenderbuffer(GL_RENDERBUFFER, a_framebuffer->renderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, roundUpPowerOfTwo(a_framebuffer->frameSize.width), roundUpPowerOfTwo(a_framebuffer->frameSize.height));

    glViewport(a_framebuffer->framePosition.x, a_framebuffer->framePosition.y, a_framebuffer->frameSize.width, a_framebuffer->frameSize.height);
    renderer->projectionMatrix().push().makeOrtho(0, static_cast<MatrixValue>(a_framebuffer->frameSize.width), 0, static_cast<MatrixValue>(a_framebuffer->frameSize.height), -128.0f, 128.0f);

    GLenum buffers[] = {GL_COLOR_ATTACHMENT0};
    //pglDrawBuffersEXT(1, buffers);


    renderer->clearScreen();
}

void glExtensionFramebufferObject::stopUsingFramebuffer(){
    require(initialized, ResourceException("StopUsingFramebuffer failed because the extension could not be loaded"));
    activeFramebuffers.pop_back();
    if(!activeFramebuffers.empty()){
        startUsingFramebuffer(activeFramebuffers.back(), false);
    } else {
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);

        glViewport(0, 0, renderer->window().width(), renderer->window().height());
        renderer->projectionMatrix().pop();
        renderer->backgroundColor(savedClearColor);
    }
}
Run Code Online (Sandbox Code Playgroud)

我清晰的屏幕代码:

void Draw2D::clearScreen(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
Run Code Online (Sandbox Code Playgroud)

Ret*_*adi 13

基于我运行的一些计算和模拟,我提出了两个相似的解决方案,似乎可以解决这个问题.一个使用预乘颜色和单个(单独)混合功能,另一个使用没有预乘颜色,但需要在此过程中多次更改混合功能.

选项1:单混合功能,预乘

这种方法在整个过程中使用单一混合函数.混合功能是:

glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA,
                    GL_ONE_MINUS_DST_ALPHA, GL_ONE);
Run Code Online (Sandbox Code Playgroud)

它需要预先乘的颜色,这意味着如果你的输入颜色通常是(r, g, b, a),你可以使用(r * a, g * a, b * a, a).您可以在片段着色器中执行预乘.

顺序是:

  1. 将混合功能设置为(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE).
  2. 将渲染目标设置为FBO.
  3. 使用预乘颜色渲染要渲染到FBO的图层.
  4. 将渲染目标设置为默认帧缓冲区.
  5. 使用预乘颜色在FBO内容下方渲染所需的图层.
  6. 渲染FBO附件,而不应用预乘,因为FBO中的颜色已经预乘.
  7. 使用预乘颜色在FBO内容之上渲染所需的图层.

选项2:切换混合功能,无预乘

这种方法不需要为任何步骤预先乘以颜色.缺点是在此过程中必须将混合功能切换几次.

  1. 将混合功能设置为(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE).
  2. 将渲染目标设置为FBO.
  3. 渲染要渲染到FBO的图层.
  4. 将渲染目标设置为默认帧缓冲区.
  5. (可选)将混合功能设置为(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).
  6. 在FBO内容下面渲染您想要的图层.
  7. 将混合功能设置为(GL_ONE, GL_ONE_MINUS_SRC_ALPHA).
  8. 渲染FBO附件.
  9. 将混合功能设置为(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).
  10. 在FBO内容之上渲染所需的图层.

解释和证明

我认为选项1更好,可能更有效,因为它不需要在渲染过程中切换混合功能.所以下面的详细解释是针对选项1的.选项2的数学运算几乎完全相同.唯一真正的区别在于,选项2使用GL_SOURCE_ALPHA混合函数的第一项来在必要时执行预乘,其中选项1期望预乘的颜色进入混合函数.

为了说明这是有效的,让我们来看一个渲染3层的例子.我将对ra组件进行所有计算.计算gb将等于的计算r.我们将按以下顺序渲染三个图层:

(r1, a1)  pre-multiplied: (r1 * a1, a1)
(r2, a2)  pre-multiplied: (r2 * a2, a2)
(r3, a3)  pre-multiplied: (r3 * a3, a3)
Run Code Online (Sandbox Code Playgroud)

对于参考计算,我们将这3个层与标准GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA混合函数混合.我们不需要在这里跟踪生成的alpha,因为DST_ALPHA在blend函数中没有使用它,我们还没有使用预乘的颜色:

after layer 1: (a1 * r1)
after layer 2: (a2 * r2 + (1.0 - a2) * a1 * r1)
after layer 3: (a3 * r3 + (1.0 - a3) * (a2 * r2 + (1.0 - a2) * a1 * r1)) =
               (a3 * r3 + (1.0 - a3) * a2 * r2 + (1.0 - a3) * (1.0 - a2) * a1 * r1)
Run Code Online (Sandbox Code Playgroud)

所以最后一个术语是我们最终结果的目标.现在,我们将第2层和第3层渲染为FBO.稍后我们将第1层渲染到帧缓冲区中,然后将FBO混合在其上.目标是获得相同的结果.

从现在开始,我们将应用开头列出的混合函数,并使用预乘的颜色.我们还需要计算alphas,因为DST_ALPHA在blend函数中使用了alphas .首先,我们将第2层和第3层渲染到FBO中:

after layer 2: (a2 * r2, a2)
after layer 3: (a3 * r3 + (1.0 - a3) * a2 * r2, (1.0 - a2) * a3 + a2)
Run Code Online (Sandbox Code Playgroud)

现在我们渲染到主帧缓冲区.由于我们不关心生成的alpha,我只会r再次计算组件:

after layer 1: (a1 * r1)
Run Code Online (Sandbox Code Playgroud)

现在我们将FBO的内容混合在一起.那么我们在FBO中为"后3层"计算的是我们的源颜色/ alpha,a1 * r1是目标颜色,并且GL_ONE, GL_ONE_MINUS_SRC_ALPHA仍然是混合函数.FBO中的颜色已经预先相乘,因此在混合FBO内容时,着色器中不会有预乘:

srcR = a3 * r3 + (1.0 - a3) * a2 * r2
srcA = (1.0 - a2) * a3 + a2
dstR = a1 * r1
ONE * srcR + ONE_MINUS_SRC_ALPHA * dstR
    = srcR + (1.0 - srcA) * dstR
    = a3 * r3 + (1.0 - a3) * a2 * r2 + (1.0 - ((1.0 - a2) * a3 + a2)) * a1 * r1
    = a3 * r3 + (1.0 - a3) * a2 * r2 + (1.0 - a3 + a2 * a3 - a2) * a1 * r1
    = a3 * r3 + (1.0 - a3) * a2 * r2 + (1.0 - a3) * (1.0 - a2) * a1 * r1
Run Code Online (Sandbox Code Playgroud)

将上一个术语与我们上面为标准混合情况计算的参考值进行比较,您可以看出它完全相同.

这个类似问题的答案GL_ONE_MINUS_DST_ALPHA, GL_ONE在混合函数方面有一些更多的背景:OpenGL ReadPixels(截图)Alpha.