[英]Opengl Render To Texture With Partial Transparancy (Translucency) And Then Rendering That To The Screen
我找到了一些被问过的地方,但我还没有找到一个好的答案。
问题:我想要渲染到纹理,然后我想将渲染的纹理绘制到屏幕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%:
那么,我想知道的细分:
期望的行为是,在该步骤结束时,最终的像素结果与我刚刚执行此操作相同:
而且,为了完整性,这里有一些我的原始天真尝试的代码(只是定期混合):
//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();
*编辑: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);
}
}
我清晰的屏幕代码:
void Draw2D::clearScreen(){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
基于我运行的一些计算和模拟,我提出了两个相似的解决方案,似乎可以解决这个问题。 一个使用预乘颜色和单个(单独)混合功能,另一个使用没有预乘颜色,但需要在此过程中多次更改混合功能。
这种方法在整个过程中使用单一混合函数。 混合功能是:
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)
。 这种方法不需要为任何步骤预先乘以颜色。 缺点是在此过程中必须将混合功能切换几次。
(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE)
。 (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
。 (GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
。 (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
。 我认为选项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
混合函数混合。 我们不需要在此处跟踪生成的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混合在其上。 目标是获得相同的结果。
从现在开始,我们将应用开头列出的混合函数,并使用预乘的颜色。 我们还需要计算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中为“after layer 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(截图)Alpha 。
我实现了我的目标。 现在,让我与互联网分享这些信息,因为它无处可寻。
我在问题中包含的代码基本上没有被触及,除了我确保在我执行“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)? 因为原始颜色如下所示:
resultR = r * a,resultG = g * a,resultB = b * a,resultA = a * a
现在,如果我们要撤消,我们需要弄清楚它是什么。 最简单的方法是在这里解决“a”:
resultA = a * a
如果a在原始渲染时为.25,我们有:
resultA = .25 * .25
要么:
结果A = 0.0625
当纹理被绘制到屏幕时,我们不再有“a”了。 我们知道结果是什么,它是纹理的alpha通道。 所以我们可以将sqrt(resultA)恢复到.25。 现在有了这个值,我们可以分为撤消乘法:
textureColor / = SQRT(textureColor.a);
这解决了一切,取消混合!
*编辑:嗯......至少有点儿。 有一个花招不准确,在这种情况下,我可以被渲染了一个明确的颜色是不相同的帧缓冲明确的颜色表现出来。 一些alpha信息似乎丢失了,可能是在rgb频道中。 这对我来说仍然足够好 ,但是我想在退出之前跟进显示不准确的屏幕截图。 如果有人有解决方案,请提供!
我已经开了一笔赏金,让这个答案达到规范的100%正确的解决方案。 现在,如果我在现有透明度上渲染更多部分透明对象,则透明度的累积方式与右侧不同,导致最终纹理的亮度超出右侧显示的亮度。 同样,当在非黑色背景上渲染时,很明显现有解决方案的结果略有不同,如上所示。
在所有情况下,适当的解决方案都是相同的。 我现有的解决方案无法在着色器校正中考虑目标混合,只考虑源alpha。
为了在一次通过中执行此操作,您需要支持单独的颜色和Alpha混合功能。 首先渲染具有存储在Alpha通道中的前景贡献的纹理(即1 =完全不透明,0 =完全透明)和RGB颜色通道中的预乘源颜色值。 要创建此纹理,请执行以下操作:
要设置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)
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
将此纹理渲染到场景中
您的问题比OpenGL或个人计算机或任何活人更老。 你试图将两个图像混合在一起,使它看起来像根本没有混合。 印刷机面临这个确切的问题。 当墨水施加到纸张上时,结果是墨水颜色和纸张颜色之间的混合。
解决方案与OpenGL中的解决方案相同。 您必须更改源图像才能控制最终结果。 这很容易弄清楚你是否检查了用于混合的数学。
对于R,G,B中的每一个,得到的颜色是(old * (1-opacity)) + (new * opacity)
。 基本场景和您想要模拟的场景是在不透明度A处将颜色直接绘制到最终后台缓冲区上。
例如,不透明度为50%,绿色通道为0xFF
。 结果应该是黑色背景上的0x7F
(包括不可避免的舍入误差)。 您可能无法假设背景为黑色,因此期望绿色通道在0x7F
和0xFF
之间变化。
当您真正渲染到纹理,然后将纹理重新渲染到后台缓冲区时,您想知道如何模拟该结果。 事实证明,“使用'预乘alpha'的模糊建议”是正确的。 虽然您的解决方案是使用着色器在最后一步中混合先前的混合操作,但标准解决方案是将原始源纹理的颜色与Alpha通道(也称为预乘alpha)相乘。 在堆叠中间纹理时,RGB通道被混合而不乘以Alpha 。 将纹理渲染到后缓冲区时,将混合RGB通道而不乘以Alpha 。 因此,您可以整齐地避免多重乘法问题。
请参阅 这些 资源以便更好地理解。 我和其他大多数人在DirectX中更熟悉这种技术,因此您可能必须搜索适当的OGL标志。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.