如何为片段着色器添加遮罩实现自拍相机背景虚化效果
方案可行性判断
你的思路完全可行,这也是目前移动端实时人像背景虚化的主流实现方案,只需要对现有流程做少量优化就能落地。
现有流程的优化调整建议
- 遮罩数据传参优化
你现在的流程里通过ByteBuffer生成Bitmap再转纹理的方式可以正常运行,但实时性表现一般,生成Bitmap的过程会带来额外的内存拷贝和CPU开销,动态更新频率高的话容易掉帧。建议直接把ByteBuffer的分割数据上传为OpenGL纹理,省去中间Bitmap的转换步骤。
你当前的maskColorsFromByteBuffer方法里实际只用到了背景置信度对应的alpha值,完全可以直接把backgroundLikelihood的值作为单通道灰度值存入纹理,不需要封装成ARGB颜色,能进一步减少数据传输量。 - 片段着色器修改
不需要在着色器里判断坐标跳过模糊,直接用遮罩的权重值混合原图和模糊结果即可,还能实现边缘的自然过渡,避免硬边问题。修改后的着色器参考如下:
precision mediump float; uniform sampler2D uSampler; uniform sampler2D uMaskSampler; // 新增遮罩纹理 uniform float uBlur; uniform float uRadius; varying vec2 vTextureCoord; // 注意要传入遮罩和相机纹理对齐后的纹理坐标,避免错位 varying vec2 vMaskTextureCoord; void main() { // 先采样原图 vec3 originColor = texture2D(uSampler, vTextureCoord).rgb; // 计算模糊结果 vec3 blurColor = originColor; if (uBlur > 0.0) { vec3 sum = vec3(0); for (float i = -uBlur; i < uBlur; i++) { for (float j = -uBlur; j < uBlur; j++) { sum += texture2D(uSampler, vTextureCoord + vec2(i, j) * (uRadius / uBlur)).rgb / pow(uBlur * 2.0, 2.0); } } blurColor = sum; } // 采样遮罩获取背景权重 float bgWeight = texture2D(uMaskSampler, vMaskTextureCoord).r; // 混合结果,bgWeight越高越偏向模糊效果 gl_FragColor = vec4(mix(originColor, blurColor, bgWeight), 1.0); }
- 坐标对齐注意事项
MLKit输出的分割Mask的宽高比例、旋转方向可能和相机预览纹理不一致,需要先对Mask做对应的旋转、缩放、翻转处理,保证遮罩和人像位置完全对齐,否则会出现虚化错位的问题。
性能优化建议
你当前使用的盒式模糊是二维循环实现,模糊半径较大的时候性能消耗很高,建议做两个优化:
- 先把相机输入纹理降采样到1/2或者1/4分辨率,再做模糊处理,最后上采样回原分辨率,模糊效果损失很小但性能提升非常明显
- 把二维盒式/高斯模糊拆分为横向、纵向两个一维的 pass 实现,时间复杂度从O(n²)降到O(n),能大幅提升大半径模糊的运行效率
替代实现路径
如果你的渲染管线更方便分层渲染,也可以用双Pass的方式实现:
- 第一个Pass把整个相机画面做模糊,输出到离屏纹理
- 第二个Pass先绘制模糊后的全画面,再用遮罩的alpha值作为混合权重,把人像区域的原图绘制上去,最终效果和单Pass混合完全一致
内容的提问来源于stack exchange,提问作者3akat
相关产品推荐
相关产品推荐

