模糊(双遍着色器)具有透明背景的对象?

时间:2017-09-12 21:35:50

标签: opengl-es shader

我有一个我试图模糊的对象。

  1. 将其渲染为透明(带有1,1,1,0)FBO的glClear。
  2. 使用垂直模糊着色器将其渲染到第二个透明FBO。
  3. 使用水平模糊着色器将其渲染到屏幕。
  4. 以下是一个示例看起来没有模糊,然后用这种技术模糊:

    enter image description here

    显然问题是模糊物体周围的白光。

    我想我掌握了为什么会这样的基本概念。虽然FBO中对象周围的像素是透明的,但它们仍保持颜色(1,1,1),因此,该颜色会混合到模糊中。

    我只是不知道我会采取什么措施来解决这个问题?

    这是我的水平模糊着色器,垂直方向大致相同:

    hBlur.vert

    uniform mat4 u_projTrans;
    uniform float u_blurPixels;
    uniform float u_texelWidth;
    
    attribute vec4 a_position;
    attribute vec2 a_texCoord0;
    attribute vec4 a_color;
    
    varying vec2 v_texCoord;
    varying vec2 v_blurTexCoords[14];
    
    void main()
    {
        v_texCoord = a_texCoord0;
        gl_Position = u_projTrans * a_position;
    
        float blurDistance6 = u_blurPixels * u_texelWidth;
        float blurDistance5 = blurDistance6 * 0.84;
        float blurDistance4 = blurDistance6 * 0.70;
        float blurDistance3 = blurDistance6 * 0.56;
        float blurDistance2 = blurDistance6 * 0.42;
        float blurDistance1 = blurDistance6 * 0.28;
        float blurDistance0 = blurDistance6 * 0.14;
    
        v_blurTexCoords[ 0] = v_texCoord + vec2(-blurDistance6, 0.0);
        v_blurTexCoords[ 1] = v_texCoord + vec2(-blurDistance5, 0.0);
        v_blurTexCoords[ 2] = v_texCoord + vec2(-blurDistance4, 0.0);
        v_blurTexCoords[ 3] = v_texCoord + vec2(-blurDistance3, 0.0);
        v_blurTexCoords[ 4] = v_texCoord + vec2(-blurDistance2, 0.0);
        v_blurTexCoords[ 5] = v_texCoord + vec2(-blurDistance1, 0.0);
        v_blurTexCoords[ 6] = v_texCoord + vec2(-blurDistance0, 0.0);
        v_blurTexCoords[ 7] = v_texCoord + vec2( blurDistance0, 0.0);
        v_blurTexCoords[ 8] = v_texCoord + vec2( blurDistance1, 0.0);
        v_blurTexCoords[ 9] = v_texCoord + vec2( blurDistance2, 0.0);
        v_blurTexCoords[10] = v_texCoord + vec2( blurDistance3, 0.0);
        v_blurTexCoords[11] = v_texCoord + vec2( blurDistance4, 0.0);
        v_blurTexCoords[12] = v_texCoord + vec2( blurDistance5, 0.0);
        v_blurTexCoords[13] = v_texCoord + vec2( blurDistance6, 0.0);
    }
    

    blur.frag

    uniform sampler2D u_texture;
    
    varying vec2 v_texCoord;
    varying vec2 v_blurTexCoords[14];
    
    void main()
    {
        gl_FragColor = vec4(0.0);
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 0]) * 0.0044299121055113265;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 1]) * 0.00895781211794;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 2]) * 0.0215963866053;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 3]) * 0.0443683338718;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 4]) * 0.0776744219933;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 5]) * 0.115876621105;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 6]) * 0.147308056121;
        gl_FragColor += texture2D(u_texture, v_texCoord         ) * 0.159576912161;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 7]) * 0.147308056121;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 8]) * 0.115876621105;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[ 9]) * 0.0776744219933;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[10]) * 0.0443683338718;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[11]) * 0.0215963866053;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[12]) * 0.00895781211794;
        gl_FragColor += texture2D(u_texture, v_blurTexCoords[13]) * 0.0044299121055113265;
    }
    
    如果我说我完全确定这段代码在做什么,我会撒谎。但总的来说,它只是从半径u_blurPixels内采样像素,并使用预先确定的高斯权重来总结gl_FragColor的结果颜色。

    如何修改此项以防止因透明背景而产生白光?

2 个答案:

答案 0 :(得分:3)

此模糊程序实际上并不适用于透明图像,因此需要进行一些调整。

要查看你在代码中做了什么,你正在应用周围的像素生效取决于他们的距离,然后你将它们标准化,取其平均值,无论你想要什么叫它......我所说的是那个您的因素0.00442991210551132650.00895781211794已归一化,因此它们的总和始终为1.更自然地,这些值可能是(例如,仅使用3个像素)scales = [1, 5, 1],其中结果为{ {1}}。

因此,如果我们退一步,您的代码可以转换为:

(pix[0]*scales[0] + pix[1]*scales[1] + pix[2]*scales[2])/(scales[0]+scales[1]+scales[2])

现在除非我犯了一些错误,否则这段代码应该与你的代码完全相同。它有点灵活,因为如果你在范围( int offset = 7; // Maximum range taking effect float offsetScales[offset+1] = { // +1 is for the zero offset 0.159576912161, 0.147308056121, ... }; float sumOfScales = offsetScales[0]; for(int i=0; i<offset; i++) sumOfScales += offsetScales[i+1]*2.0; gl_FragColor = texture2D(u_texture, v_texCoord)*offsetScales[0]; for(int i=0; i<offset; i++) { gl_FragColor += texture2D(u_texture, v_blurTexCoords[6-i]) * offsetScales[i+1]; gl_FragColor += texture2D(u_texture, v_blurTexCoords[7+i]) * offsetScales[i+1]; } gl_FragColor /= sumOfScales; // sumOfScales in current case is always 1.0 )中添加了另一个像素,你可以简单地添加它并将其值设置为offset = 8,你的颜色永远不会溢出。在您的情况下,您需要调整所有比例,使其总和等于0.0022。但是没关系,你的方式更接近最优,所以继续使用它,我正在解释这一点,退后一步,找到问题的解决方案......

好的,现在代码更易于维护,让我们看看当alpha通道需要生效时会发生什么。当像素是透明或半透明时,在计算整体颜色时应该花费较少或没有效果。它应该在结束alpha上生效。这意味着除了这些尺度之外,我们还需要采用alpha尺度。但这样做我们需要调整应用颜色的总和:

1.0

此代码可能仍需要进行一些调整,但我希望这能让您走上正确的轨道。在你完成它的工作后,我建议你再次失去所有的循环并像你最初那样(使用新的逻辑)。如果您发布了最终的代码,也会很好。

随意提出任何问题......

答案 1 :(得分:1)

问题在于OpenGL ES使用后倍增alpha(它在硬件上更便宜),而“正确”执行它需要预乘alpha。

您可以在着色器中为每个模糊的样本执行预乘数学运算:

premult.rgb = source.rgb * source.a;

...但是您要混合的每个纹理样本都会产生运行时成本。通常,在纹理创建/压缩过程中,将输入艺术资产预先离线。

如果您需要用于照明计算等的后乘数据,则可以通过将对象颜色挤压到相邻的“透明”像素中来减少错误。 E.g:

extruded color

请注意,如果着色器正在发射预乘的alpha fragmetn颜色,则需要修复OpenGL混合方程(对GL_ONE使用srcAlpha,而不是alpha值)。