gpt4 book ai didi

c++ - Opengl 渲染到具有部分透明度(半透明)的纹理,然后将其渲染到屏幕

转载 作者:可可西里 更新时间:2023-11-01 17:21:38 27 4
gpt4 key购买 nike

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

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

我希望能够将部分透明的重叠项目渲染到此纹理。我知道混合函数目前正在根据 Alpha 弄乱 RGB 值。我已经看到了一些使用“预乘 alpha”的模糊建议,但关于这意味着什么的描述很差。我在 photoshop 中制作 png 文件,我知道它们有一个半透明位,你不能像使用 TGA 那样轻松地独立编辑 alpha channel 。如有必要,我可以切换到 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 累积问题消失,它确实有效,但为什么呢?!我不想将相同的纹理渲染到屏幕数百次才能让它正确累积)

以下是一些详细说明问题的图像(多次渲染 channel 使用基本混合(GL_SRC_ALPHA、GL_ONE_MINUS_SRC_ALPHA),并且在纹理渲染步骤中多次渲染。右侧的 3 个框渲染为 100% 红色、绿色或蓝色(0-255) 但蓝色的 alpha 值为 50%,红色为 25%,绿色为 75%:
enter image description here

所以,我想知道的 segmentation :

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

  • 期望的行为是在该步骤结束时,最终像素结果与我这样做时的结果相同:
  • 我将混合模式设置为:(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  • 我将我的场景渲染到屏幕上。

  • 而且,为了完整起见,这是我最初的幼稚尝试(只是常规混合)的一些代码:
        //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()));
    }
    }

    这是我用来设置场景的代码:
        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.
    }

    以及渲染场景的代码:
    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);

    这是我的屏幕绘制:
    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();

    *编辑:帧缓冲区设置/拆卸代码:
    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);
    }
    }

    还有我的清晰屏幕代码:
    void Draw2D::clearScreen(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    }

    最佳答案

    根据我运行的一些计算和模拟,我想出了两个相当相似的解决方案,它们似乎可以解决问题。一种使用预乘颜色与单个(单独的)混合函数相结合,另一种不使用预乘颜色,但需要在此过程中多次更改混合函数。

    选项 1:单一混合函数,预乘

    这种方法在整个过程中使用单个混合函数。混合函数为:

    glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA,
    GL_ONE_MINUS_DST_ALPHA, GL_ONE);

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

    顺序是:
  • 将混合函数设置为 (GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE) .
  • 将渲染目标设置为 FBO。
  • 使用预乘颜色渲染要渲染到 FBO 的图层。
  • 将渲染目标设置为默认帧缓冲区。
  • 使用预乘颜色在 FBO 内容下方渲染您想要的图层。
  • 渲染 FBO 附件,不应用预乘,因为 FBO 中的颜色已经预乘。
  • 使用预乘颜色在 FBO 内容之上渲染您想要的图层。

  • 选项 2:切换混合函数,无需预乘

    这种方法不需要为任何步骤预先乘以颜色。缺点是在此过程中必须切换混合功能几次。
  • 将混合函数设置为 (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE) .
  • 将渲染目标设置为 FBO。
  • 渲染要渲染到 FBO 的图层。
  • 将渲染目标设置为默认帧缓冲区。
  • (可选)将混合函数设置为 (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) .
  • 在 FBO 内容下方渲染您想要的图层。
  • 将混合函数设置为 (GL_ONE, GL_ONE_MINUS_SRC_ALPHA) .
  • 渲染 FBO 附件。
  • 将混合函数设置为 (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) .
  • 在 FBO 内容之上渲染您想要的图层。

  • 说明与证明

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

    为了说明这是有效的,让我们通过一个渲染 3 层的示例。我会为 r 做所有的计算和 a成分。 g 的计算和 b将等同于 r 的那些.我们将按以下顺序渲染三层:
    (r1, a1)  pre-multiplied: (r1 * a1, a1)
    (r2, a2) pre-multiplied: (r2 * a2, a2)
    (r3, a3) pre-multiplied: (r3 * a3, a3)

    对于引用计算,我们将这 3 层与标准 GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 混合在一起。混合功能。由于 DST_ALPHA,我们不需要在这里跟踪生成的 alpha未在混合函数中使用,我们还没有使用预乘颜色:
    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)

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

    从现在开始,我们将应用开头列出的混合函数,并使用预乘颜色。我们还需要计算 alpha,因为 DST_ALPHA用于混合功能。首先,我们将第 2 层和第 3 层渲染到 FBO 中:
    after layer 2: (a2 * r2, a2)
    after layer 3: (a3 * r3 + (1.0 - a3) * a2 * r2, (1.0 - a2) * a3 + a2)

    现在我们渲染到主帧缓冲区。由于我们不关心由此产生的 alpha,我将只计算 r再次组件:
    after layer 1: (a1 * r1)

    现在我们在此之上混合 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

    将最后一项与我们上面为标准混合情况计算的引用值进行比较,您可以看出它完全相同。

    这个对类似问题的回答在 GL_ONE_MINUS_DST_ALPHA, GL_ONE 上有更多背景信息。混合函数的一部分: OpenGL ReadPixels (Screenshot) Alpha .

    关于c++ - Opengl 渲染到具有部分透明度(半透明)的纹理,然后将其渲染到屏幕,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24346585/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com