12

これが尋ねられた場所をいくつか見つけましたが、まだ良い答えを見つけていません。

問題: テクスチャにレンダリングしたいのですが、そのレンダリングされたテクスチャを、テクスチャへのレンダリングのステップをスキップして画面に直接レンダリングした場合と同じように画面に描画したいと考えています。現在、ブレンド モード glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) を使用しています。私もglBlendFuncSeparateを使って遊んでいます。

部分的に透明な重なり合うアイテムをこのテクスチャにレンダリングできるようにしたいと考えています。ブレンド関数が現在、アルファに基づいて RGB 値を台無しにしていることを知っています。「事前に乗算されたアルファ」を使用するという漠然とした提案を見たことがありますが、それが何を意味するかについての説明は貧弱です。私は Photoshop で png ファイルを作成します。それらには半透明ビットがあり、TGA のようにアルファ チャネルを個別に簡単に編集することはできません。PNG の方が便利ですが、必要に応じて TGA に切り替えることもできます。

今のところ、この質問のために、画像を使用していないと仮定します。代わりに、アルファ付きのフルカラー クワッドを使用しています。

シーンをテクスチャにレンダリングしたら、そのテクスチャを別のシーンにレンダリングする必要があります。また、部分的な透明度を想定してテクスチャをブレンドする必要があります。ここで物事がバラバラになります。前のブレンディング手順では、アルファに基づいて RGB 値を明確に変更しました。アルファが 0 または 1 の場合は再度変更しても問題ありませんが、その間にある場合は、部分的に半透明のピクセルがさらに暗くなります。

ブレンドモードで遊んでいて、運がほとんどありませんでした。私ができる最善の方法は、次の方法でテクスチャにレンダリングすることです。

glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) を使用して複数回レンダリングすると、適切な色に近づくことがわかりました (重複しない限り)。しかし、それは完全ではありません (次の画像でわかるように、緑/赤/青のボックスが重なっている部分が暗くなるか、アルファが蓄積されます。(編集: 画面部分へのレンダリングで複数の描画を行うと、テクスチャに 1 回レンダリングすると、アルファ蓄積の問題がなくなり、機能しますが、なぜ?! 同じテクスチャを画面に何百回もレンダリングして適切に蓄積する必要はありません)

問題の詳細を示すいくつかの画像を次に示します (複数のレンダー パスは基本的なブレンディング (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()));
        }
    }

シーンをセットアップするために使用しているコードは次のとおりです。

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

4 に答える 4

17

私が実行したいくつかの計算とシミュレーションに基づいて、うまくいくと思われる 2 つの非常によく似たソリューションを思いつきました。1 つは事前に乗算された色を 1 つの (個別の) ブレンド関数と組み合わせて使用​​し、もう 1 つは事前に乗算された色なしで動作しますが、処理中にブレンド関数を数回変更する必要があります。

オプション 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)します。フラグメント シェーダーで事前乗算を実行できます。

シーケンスは次のとおりです。

  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 の計算はほとんど同じです。唯一の実際の違いは、オプション 2GL_SOURCE_ALPHAが必要に応じて事前乗算を実行するためにブレンド関数の最初の項を使用することです。オプション 1 では、事前乗算された色がブレンド関数に入ることを期待しています。

これが機能することを説明するために、3 つのレイヤーがレンダリングされる例を見てみましょう。rおよびコンポーネントのすべての計算を行いaます。との計算は の計算gb同等ですr。次の順序で 3 つのレイヤーをレンダリングします。

(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ブレンド関数で使用されていないため、ここで結果のアルファを追跡する必要はありません。また、事前に乗算された色はまだ使用していません。

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 をブレンドします。目標は、同じ結果を得ることです。

これからは、最初にリストされたブレンド関数を適用し、事前に乗算された色を使用します。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)

次に、プライマリ フレームバッファにレンダリングします。結果のアルファは気にしないので、rコンポーネントのみを再度計算します。

after layer 1: (a1 * r1)

これに FBO のコンテンツをブレンドします。したがって、FBO の「レイヤー 3 の後」について計算したのは、ソース カラー/アルファで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

于 2014-06-24T06:57:24.390 に答える
2

目標を達成しました。では、この情報をインターネットで共有させてください。私が見つけることができる他の場所には存在しないからです。

ここに画像の説明を入力

  1. フレームバッファを作成します (blindframebuffer など)
  2. フレームバッファを 0、0、0、0 にクリアします
  3. ビューポートを適切に設定します。これは、質問で当然のことと思っていたすべての基本的なものですが、ここに含めたいと思います。
  4. 次に、glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) を使用して、シーンを通常のフレーム バッファにレンダリングします。シーンがソートされていることを確認します (通常と同じように)。
  5. 含まれているフラグメント シェーダーをバインドします。これにより、ブレンド関数によって画像の色の値に与えられたダメージが取り消されます。
  6. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) を使用してテクスチャを画面にレンダリングします。
  7. 通常のシェーダーで通常のレンダリングに戻ります。

質問に含めたコードは基本的に変更されていませんが、自分の小さなフレームワークに固有の「preDraw」機能を実行するときに、以下にリストするシェーダーをバインドしていることを確認しますが、基本的には「画面への描画」ですレンダリングされたテクスチャを呼び出します。

私はこれを「アンブレンド」シェーダーと呼んでいます。

#version 330 core

smooth in vec4 color;
smooth in vec2 uv;

uniform sampler2D texture;

out vec4 colorResult;

void main(){
    vec4 textureColor = texture2D(texture, uv.st);

    textureColor/=sqrt(textureColor.a);

    colorResult = textureColor * color;
}

なぜ textureColor/=sqrt(textureColor.a) を行うのですか? 元の色は次のように計算されるためです。

結果R = r * a、結果G = g * a、結果B = b * a、結果A = a * a

ここで、それを元に戻したい場合は、a が何であるかを理解する必要があります。見つける最も簡単な方法は、ここで "a" を解くことです。

結果 A = a * a

最初のレンダリング時に a が .25 の場合、次のようになります。

結果A = .25 * .25

または:

結果A = 0.0625

ただし、テクスチャが画面に描画されているときは、「a」はもうありません。resultA が何であるかはわかっています。これはテクスチャのアルファ チャネルです。したがって、sqrt(resultA) を実行して .25 を返すことができます。その値を使用して、乗算を元に戻すために除算できます。

textureColor/=sqrt(textureColor.a);

そして、それはブレンドを元に戻すことですべてを修正します!

*編集: うーん...少なくともちょっと。ちょっとした不正確さがあります。この場合、フレーム バッファのクリア カラーとは異なるクリア カラーをレンダリングすることでそれを示すことができます。おそらく RGB チャンネルで、一部のアルファ情報が失われているようです。これでも十分ですが、サインアウトする前に、不正確さを示すスクリーンショットをフォローアップしたいと思いました. 誰かが解決策を持っている場合は、それを提供してください!

この回答を正規の 100% 正しい解決策にするために、賞金をオープンしました。現在、既存の透明度の上に部分的に透明なオブジェクトをレンダリングすると、右側とは異なる方法で透明度が蓄積され、右側に表示されているものを超えて最終的なテクスチャが明るくなります。同様に、黒以外の背景にレンダリングすると、上記のように既存のソリューションの結果がわずかに異なることは明らかです。

適切な解決策は、すべての場合で同じです。私の既存のソリューションでは、シェーダー補正で宛先ブレンディングを考慮することができず、ソース アルファのみが考慮されます。

ここに画像の説明を入力

于 2014-06-22T03:12:01.707 に答える
1

これを 1 回のパスで行うには、個別のカラー & アルファ ブレンディング機能をサポートする必要があります。最初に、アルファ チャネルに格納されたフォアグラウンド コントリビューション (つまり、1 = 完全に不透明、0 = 完全に透明) と、RGB カラー チャネルで事前に乗算されたソース カラー値を持つテクスチャをレンダリングします。このテクスチャを作成するには、次の操作を行います。

  1. テクスチャを RGBA=[0, 0, 0, 0] にクリアします
  2. カラー チャネルのブレンドを src_color*src_alpha+dst_color*(1-src_alpha) に設定します
  3. アルファ チャネルのブレンドを src_alpha*(1-dst_alpha)+dst_alpha に設定します
  4. シーンをテクスチャにレンダリングする

2) と 3) で指定されたモードを設定するには、次のようにしますglBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE)glBlendEquation(GL_FUNC_ADD)

次に、カラー ブレンディングを src_color+dst_color*(1-src_alpha) に設定して、このテクスチャをシーンにレンダリングしますglBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)glBlendEquation(GL_FUNC_ADD)

于 2014-06-25T03:10:11.577 に答える