iPhone OpenGL ES 2.0 与 Cocos2D 的混合产生了意想不到的结果

Posted

技术标签:

【中文标题】iPhone OpenGL ES 2.0 与 Cocos2D 的混合产生了意想不到的结果【英文标题】:iPhone OpenGL ES 2.0 blending with Cocos2D gives unexpected results 【发布时间】:2013-09-11 07:51:21 【问题描述】:

我有非常简单的 CCScene,只有 1 个 CCLayer 包含:

    具有标准混合模式的背景 CCSprite CCRenderTexture 用于绘制画笔,其精灵附加到背景精灵上方的根 CCLayer:
_bgSprite = [CCSprite spriteWithFile:backgroundPath]; _renderTexture = [CCRenderTexture renderTextureWithWidth:self.contentSize.width height:self.contentSize.height]; [_renderTexture.sprite setBlendFunc:(ccBlendFunc)GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA]; [self addChild:_bgSprite z:-100]; [self addChild:_renderTexture];

画笔渲染代码:

[_renderTexture begin];
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ONE); // 1.
// calculate vertices code,etc...
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)count);
[_renderTexture end];

当用户使用第一个彩色画笔刷时,它按预期与背景混合。 但是,当继续在前一个画笔上用另一种颜色刷时,它会出错(当 2 个画笔相互重叠时,软 alpha 边缘会失去不透明度):

我尝试了许多混合选项,但不知何故我找不到正确的选项。

CCRenderTexture 有什么特别之处,它不会像预期的那样与自身(与先前绘制的内容)融合吗?

我用于刷亮的片段着色器只是标准纹理着色器,稍作更改以保留纹理中的输入颜色 Alpha:

无效的主要() gl_FragColor = texture2D(u_texture, v_texCoord); gl_FragColor.a = v_fragmentColor.a;

更新 - 几乎完美的解决方案: by jozxyqk

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
                        GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

在渲染代码中(代替// 1.

[_renderTexture.sprite setBlendFunc:(ccBlendFunc)GL_ONE, GL_ONE_MINUS_SRC_ALPHA];

这很好用,给了我想要的东西......

...但仅当 _rederTexture 完全不透明时。

_rendertexture.sprite 的不透明度降低时,画笔会变亮而不是像预期的那样淡出:

为什么当父纹理完全不透明时,画笔的 alpha 与背景正确混合,而当不透明度降低时却变成香蕉?如何使画笔与背景正确融合?

【问题讨论】:

【参考方案1】:

编辑

混合画笔 -> 图层 -> 背景

好的,glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 正在将画笔笔触混合到画笔纹理中,但纹理中生成的 alpha 值是错误的。每个添加的片段需要 1. 将它的 alpha 添加到最终的 alpha 值 - 它必须为交互移除恰好那么多的光 2. 按余数缩放前一个 alpha - 之前的表面将光减少前一个值,但是因为添加了一个新的表面,它们减少的光线更少。我不确定这是否有意义,但它导致了这个......

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
                    GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

现在画笔纹理的颜色通道包含要与背景混合的总颜色(预乘 alpha),而 alpha 通道给出权重(或颜色遮盖背景的量)。由于颜色预先与 alpha 相乘,默认的 RenderTexture 混合 GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 会随着 alpha 缩放再次,从而使整体颜色变暗。您现在需要使用以下函数将画笔纹理与背景混合,我认为这必须在 Cocos2D 中设置:

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

希望这是可能的。我还没有考虑过如何管理设置画笔纹理以与GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 混合的可能性,但它可能需要浮点纹理和/或额外的通道来划分/标准化 alpha,这听起来痛苦。

或者,在绘制之前将背景添加到渲染纹理中,并在不混合任何图层的情况下保留该背景。

这对我有用:

glDisable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);

fbo.bind();
glClear(GL_COLOR_BUFFER_BIT);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
                    GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
drawTexture(brush1);
drawTexture(brush2);
fbo.unbind();

drawTexture(grassTex); //tex alpha is 1.0, so blending doesn't affect background
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
drawTexture(fbo.getColour(0)); //blend in the brush layer

画笔图层不透明度

使用GL_ONE, GL_ONE_MINUS_SRC_ALPHA 会导致库在图层混合中实现不透明度的问题,因为它假定颜色乘以 alpha。通过减小opacity 的值,在混合过程中画笔层的 alpha 会按比例缩小。 GL_ONE_MINUS_SRC_ALPHA 然后会导致背景颜色的数量增加,但是 GL_ONE 将 100% 的画笔层相加并使图像过饱和。

imo最简单的解决办法就是自己想办法通过全局图层不透明度来缩小颜色,继续使用GL_ONE, GL_ONE_MINUS_SRC_ALPHA

如果库支持它,实际上使用GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_ALPHA 可能是一个答案,但显然它不支持。 您可以使用固定管道渲染来缩放颜色:glColor4f(opacity, opacity, opacity, opacity),但这将需要第二个渲染目标并手动进行混合,类似于上面的代码,您在其中为背景绘制一个全屏四边形,然后再次为画笔层。 如果您手动进行混合,使用片段着色器而不是glColor 方法会更可靠。如果您想使用更复杂的混合功能,这将允许更大的控制,特别是在涉及 0 到 1 范围之外的划分和临时: gl_FragColour = texture(brushTexture, coord) * layerOpacity;

结束编辑


标准的 alpha 混合函数是 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);,而不是 GL“初始”/默认函数。

像在 glBlendFuncSeparate 中那样对 alpha 值求和会使 alpha 过度饱和,并且下面的颜色会被完全替换。饱和度混合可能会产生不错的结果:glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE)。如果支持,也可能值得尝试 glBlendEquationSeparate 和 MAX 混合。使用 MAX 的好处是减少线条绘图代码中的重叠伪影(硬三角形位) - 例如替换颜色,但仅在达到总 alpha 值 X 之前。 编辑:这两种情况都需要在每次笔画后进行混合和清除

我只能假设将渲染纹理混合到背景上确实有效。(不适用于当前图层值)

在旁注中,基本上不相关,还有“Under Blending”,您可以在其中保留透射率值而不是 alpha/不透明度(来自 here):

glBlendEquation(GL_FUNC_ADD); 
glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA); 

【讨论】:

glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE) 不起作用 - 请参阅问题的更新部分(在底部)。 我假设您指的是边缘变暗。混合纹理时标准化(将颜色除以 alpha 值)可以解决这个问题,但我猜你无法控制它。如果您可以将画笔纹理与背景混合并保存,那就太好了 - 允许笔划之间的 alpha 混合,但笔划内的饱和度。为什么标准 Alpha 混合不起作用? 是的,我指的是边缘变暗。为什么阿尔法混合不起作用?好吧 - 如果我知道我不会问这个问题。我是 Cocos2D 和 OpenGL 的新手,并尝试在这个非常简单的场景中使用标准混合模式。我不知道 Cocos2D 是否在幕后改变了某些东西,或者我做错了什么。 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 听起来就是你想要的。我提到它的唯一原因是因为您的代码有 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA),它会使颜色过度饱和 @Lukasz 查看编辑。笔刷渲染纹理的混合功能是否容易更改(例如cocos2d-iphone.org/forums/topic/…)

以上是关于iPhone OpenGL ES 2.0 与 Cocos2D 的混合产生了意想不到的结果的主要内容,如果未能解决你的问题,请参考以下文章

iPad 或 iPhone 是不是支持 OpenGL ES 2.0?

iPhone 上的 OpenGL ES 2.0:无法超过小索引/顶点数

iphone opengl es 2.0,如何使用objective-c类来存储原始数据?

是否可以在顶点着色器内持续更改 iPhone OpenGL ES 2.0 上的 VBO 值?

如何在 iPhone OpenGL ES 2.0 中在渲染和呈现帧缓冲区之间切换?

iPhone OpenGL ES 2.0 奇怪的图案在 COLLADA 文件中的球体上渲染纹理