具有alpha透明度的高斯模糊处理

时间:2016-02-18 08:19:10

标签: opengl-es glsl shader blur

  • 描述

    1. 我在android中使用OpenGL es 2.0和glsl着色器来做高斯模糊效果。
    2. 如果没有alpha通道,它可以正常工作,但是当我有png的alpha通道时,结果会变得太暗,它会与链接相同:http://www.jhlabs.com/ip/premultiplied_blur.jpg(中间的一个效果有些暗)点。
    3. 所有文章都说使用预乘alpha,但我不知道如何使用它,同样可以帮助我,谢谢。
  • 我如何进行模糊

    1. 加载png纹理
    2. 创建FBO以渲染水平模糊
    3. 创建FBO以渲染垂直模糊
    4. 将结果纹理渲染到屏幕
  • 我的着色器文件

	static const char* ULBlurShader_vertexShader =
		"uniform mat4 fpsWorldViewProjectionMatrix;\n"
		"attribute vec4 gInPosition;\n"
		"attribute vec2 gInTex0;\n"
		"varying vec2 varTex0;\n"
		"void main() {\n"
		"	gl_Position = fpsWorldViewProjectionMatrix * gInPosition;\n"
		"	varTex0 = gInTex0.xy;\n"
		"}\n";

	static const char* ULBlurShaderHor_pixelShader =
		"uniform sampler2D sampler0; \n"
		"uniform vec4 fpsGaussBlur; \n"
		"uniform vec4 fpsGaussWeights[65]; \n"
		"varying vec2 varTex0;\n"
		"vec4 gaussBlurHorizontal(const int anRadius,vec2 avBaseCoo, vec2 avSamplerRes)"
		"{ \n"
		"	float fStartX = avBaseCoo.x - anRadius*avSamplerRes.x; \n"
		"	vec4 color = vec4(0, 0, 0, 0); \n"
		"	for (int x = 0; x < anRadius * 2; x++) \n"
		"	{ \n"
		"		color += texture2D(sampler0, vec2(fStartX + x*avSamplerRes.x, avBaseCoo.y))*fpsGaussWeights[x / 4][x % 4]; \n"
		"	} \n"
		"	return color; \n"
		"}\n"
		"void main() {\n"
		"	gl_FragColor.rgba = gaussBlurHorizontal(int(fpsGaussBlur.y), varTex0, vec2(fpsGaussBlur.z, fpsGaussBlur.w)); \n"
		"}\n";

	static const char* ULBlurShaderVert_pixelShader =
		"uniform sampler2D sampler0; \n"
		"uniform vec4 fpsGaussBlur; \n"
		"uniform vec4 fpsGaussWeights[65]; \n"
		"varying vec2 varTex0;\n"
		"vec4 gaussBlurVertical(const int anRadius,vec2 avBaseCoo, vec2 avSamplerRes)"
		"{ \n"
		"	float fStartY = avBaseCoo.y - (anRadius*avSamplerRes.y); \n"
		"	vec4 color = vec4(0, 0, 0, 0); \n"
		"	for(int y=0; y<anRadius*2; y++) \n"
		"	{ \n"
		"		color += texture2D(sampler0, vec2(avBaseCoo.x, fStartY+y*avSamplerRes.y))*fpsGaussWeights[y/4][y%4]; \n"
		"	} \n"
		"	return color; \n"
		"}\n"
		"void main() {\n"
		"	gl_FragColor.rgba = gaussBlurVertical(int(fpsGaussBlur.y), varTex0, vec2(fpsGaussBlur.z, fpsGaussBlur.w));\n"
		"}\n";

2 个答案:

答案 0 :(得分:1)

我遇到了同样的问题,尤其是在模糊由alpha = 0.0的像素包围的文本时。文字会消失,直至无法看到(中间图像)。 然而,一旦我在模糊之后添加了“Gamma校正”步骤,我设法得到了更好的结果。

Left: original, Middle: Blur only (practically invisible), Right: Blur+Gamma Correction

因此,对于您的情况,在模糊完成后,根据以下内容调整RGBA值:

color = pow(color, vec4(1.0/fGamma))

fGamma位于float0.0之间1.0的位置(我在我的示例中使用了fGamma=5.0)。

以下是一个非常好的解释链接: https://learnopengl.com/#!Advanced-Lighting/Gamma-Correction

答案 1 :(得分:0)

是的,不幸的是,透明度存在整体问题,当像素透明时,其他颜色通道会发生什么。如果您尝试绘制没有Alpha透明度的图像(使用RGB1),您将看到透明像素为黑色。对于至少某些情况,这似乎是自然的,但对所有情况都不是。想象一下,你有一个图像具有漂亮的纯白色渐变,从完全不透明度下降到包括完全透明。然后第一个像素将是(1,1,1,1),某个像素将是(1,1,1,0.5)并且接近结束它可能(1,1,1,0.00X)但是然后是下一个像素alpha为0的像素将变为(0,0,0,0)而不是(1,1,1,0)。所以这是你混合的黑色,你会看到黑色边框。

因此看起来png导出器可能会出错,因为所有透明像素仍应为红色但情况并非如此。如果图像具有不同的颜色(在您的情况下不仅仅是白色或红色),那么导出器可能无法预测这些像素的颜色。所以最后这是一个正确的状态。

因此,在这种情况下,您需要重新平衡透明像素的颜色混合。它还意味着更改模糊着色器的工作方式。在计算颜色时(但不是alpha),您需要忽略透明像素,或者根据透明度,Alpha通道,您需要根据颜色缩放效果。

我会在一个类似于模糊的特定权重中求和5个像素时给你一个例子。让我们说相对比例是1,2,5,2,1。你现在正在做的是color = (c1*1 + c2*2 + c3*5 + c4*2 + c5*1)/(1+2+5+2+1)但你在这个等式中有一个优化,你已经有一个标准值,你调用fpsGaussWeights在我的例子中是1/11, 2/11, 5/11 ...(通过汇总(1+2+5+2+1)收到了11个)所以你以c1*1/11 + c2*2/11结束...现在让我们改变方程以包括alpha值。现在原来是

color = (c1*1*c1.a + c2*2*c2.a + c3*5*c3.a + c4*2*c4.a + c5*1*c5.a)/(1*c1.a+2*c2.a+5*c3.a+2*c4.a+1*c5.a)

但是你只需要在颜色部分这样做

color.rgb = (c1.rgb*1*c1.a + c2.rgb*2*c2.a + c3.rgb*5*c3.a + c4.rgb*2*c4.a + c5.rgb*1*c5.a)/(1*c1.a+2*c2.a+5*c3.a+2*c4.a+1*c5.a)

而alpha通道保留先前的模糊方程。因此,您可以看到您可能不再包含权重的标准化值,因为当乘以Alpha通道时,权重值的总和不会是1.0。

现在我希望您可以自己在for循环中优化此等式。如果使用低精度,还要注意溢出。

看看这个作为参考(甚至可能只是工作,但我没有测试它甚至仔细检查它):

    int sampleCount = 5;
    float weights[sampleCount];

    vec4 color = vec4(.0);
    float fullWeight = .0;
    for(int i=0; i<sampleCount; i++) {
        float actualWeight = weights[i] * textureColor.a;
        vec4 textureColor = ; // fetch the pixel
        color.rgb += textureColor.rgb * actualWeight; // can produce overflow
        /*
            We may do the computation on the fly so we do not need to divide in the end. This will remove the overflow issue
        color.rgb = (color.rgb*fullWeight + textureColor.rgb*actualWeight)/(fullWeight + actualWeight);
            Leads to:
        float newWeightResult = (fullWeight + actualWeight);
        float leftWeight = fullWeight/(fullWeight + actualWeight); //will be less or equal then 1
        float rightWeight = actualWeight/(fullWeight + actualWeight); //will be less or equal then 1
        color.rgb = color.rgb*leftWeight + textureColor.rgb*rightWeight; // no overflow
            The color.rgb /= fullWeight line needs to be removed when using this
         */

        color.a += textureColor.a * weights[i];
        fullWeight += actualWeight;
    }
    color.rgb /= fullWeight;

您使用的重量仍可按原样保留。我只使用(1,2,5 ......)所以更容易解释。