具有 alpha 透明度的高斯模糊句柄
Gaussian blur handle with alpha transparency
描述
- 我在 android 中使用 OpenGL es 2.0 和 glsl 着色器来做高斯模糊效果。
- 如果没有 alpha 通道,它工作正常,但是当我有带有 png 的 alpha 通道时,结果会变得太暗,它将与 link 相同:http://www.jhlabs.com/ip/premultiplied_blur.jpg(中间一种效果,有些暗)。
- 所有文章都说使用预乘 alpha,但我不知道如何使用它,同样的可以帮助我,谢谢。
我是怎么做模糊的
- 加载 png 纹理
- 创建 FBO 以渲染水平模糊
- 创建 FBO 以渲染垂直模糊
- 将结果纹理渲染到屏幕
我的着色器文件
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";
是的,不幸的是,透明度存在整体问题,当像素透明时其他颜色通道会发生什么。如果您尝试在没有 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...) 这样更容易解释。
我遇到了同样的问题,尤其是在模糊被 alpha=0.0 像素包围的文本时。文字会逐渐消失,以至于看不见(中图)。
但是,在模糊之后添加 'Gamma Correction' 步骤后,我设法获得了更好的结果。
因此,对于您的情况,在模糊完成后,根据如下内容调整 RGBA 值:
color = pow(color, vec4(1.0/fGamma))
其中 fGamma
是 0.0
和 1.0
之间的 float
(我在示例中使用了 fGamma=5.0
)。
这里有一个link很好的解释:
https://learnopengl.com/#!Advanced-Lighting/Gamma-Correction
描述
- 我在 android 中使用 OpenGL es 2.0 和 glsl 着色器来做高斯模糊效果。
- 如果没有 alpha 通道,它工作正常,但是当我有带有 png 的 alpha 通道时,结果会变得太暗,它将与 link 相同:http://www.jhlabs.com/ip/premultiplied_blur.jpg(中间一种效果,有些暗)。
- 所有文章都说使用预乘 alpha,但我不知道如何使用它,同样的可以帮助我,谢谢。
- 我在 android 中使用 OpenGL es 2.0 和 glsl 着色器来做高斯模糊效果。
我是怎么做模糊的
- 加载 png 纹理
- 创建 FBO 以渲染水平模糊
- 创建 FBO 以渲染垂直模糊
- 将结果纹理渲染到屏幕
我的着色器文件
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";
是的,不幸的是,透明度存在整体问题,当像素透明时其他颜色通道会发生什么。如果您尝试在没有 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...) 这样更容易解释。
我遇到了同样的问题,尤其是在模糊被 alpha=0.0 像素包围的文本时。文字会逐渐消失,以至于看不见(中图)。 但是,在模糊之后添加 'Gamma Correction' 步骤后,我设法获得了更好的结果。
因此,对于您的情况,在模糊完成后,根据如下内容调整 RGBA 值:
color = pow(color, vec4(1.0/fGamma))
其中 fGamma
是 0.0
和 1.0
之间的 float
(我在示例中使用了 fGamma=5.0
)。
这里有一个link很好的解释: https://learnopengl.com/#!Advanced-Lighting/Gamma-Correction