先直接看效果
需求
就是水印视频和目标视频叠加融合,水印视频在上层,非内容部分不展示不遮挡目标视频显示。
方案 (注:这里对OpenGL和GPUImage需要有一定的了解)
- 用提供好的带alpha通道的视频,利用AVFoundation的接口将水印视频与目标视频直接合成。
但是报错
Error Domain=AVFoundationErrorDomain Code=-11838 "Operation Stopped" UserInfo={NSLocalizedFailureReason=The operation is not supported for this media., NSLocalizedDescription=Operation Stopped, NSUnderlyingError=0x6000025abd50 {Error Domain=NSOSStatusErrorDomain Code=-16976 "(null)"
我用是素材是.mov 的,这个CFHD的编解码格式是不支持的
后面也用了工具转成了带alpha通道的MP4格式的素材,同样报这样的错误,看属性编解码格式也是个没见过的格式
- 用绿幕抠图的方案
1.水印视频素材中非内容区域全部用绿色像素;
2.将水印视频和目标视频初始化两个 GPUImageMovie 作为 GPUImage 的 输入源;
3.以GPUImageTwoInputFilter为父类创建一个子类,重写FragmentShader,这个shader作用是扣除水印视频纹理绿色+纹理融合;- 水印视频的 GPUImageMovie 和 目标视频 GPUImageMovie 都添加 上述 GPUImageTwoInputFilter子类filter为targer;
- 最后以 GPUImageMovieWriter 作为最后的输出节点写文件到沙河;
子类filterFragmentShader
/*
计算当前像素点RGB值对应的HSV值
设定HSV三个分量的权重,根据权重计算当前像素点的HSV值到给定背景色的HSV值的欧式距离
将欧式距离用smoothstep做平滑,0.5以下的一定要滤掉
将原图和背景图用平滑值混合
*/
NSString *const kGPUImageGreenScreenFragmentShaderString = SHADER_STRING
(
varying highp vec2 textureCoordinate;
varying highp vec2 textureCoordinate2;
uniform sampler2D inputImageTexture;
uniform sampler2D inputImageTexture2;
void main()
{
lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
lowp vec4 textureColor2 = texture2D(inputImageTexture2, textureCoordinate2);
lowp float rbAverage = textureColor.r * 0.5 + textureColor.b * 0.5;
lowp float gDelta = textureColor.g - rbAverage;
textureColor.a = 1.0 - smoothstep(0.0, 0.25, gDelta);
if(textureColor.a < 0.5){
gl_FragColor = textureColor2;
}else{
gl_FragColor = textureColor;
}
}
);
但是最后的效果似乎不是很好:
可以看到水印的边上有明显的锯齿感,水印是细小的文字更是明显。也很容易理解:无论怎么如何做平滑,都会或多或少有像素点会误过滤掉
- opengl渲染rgba再同目标视频混合,流程还是同抠图,对素材特殊处理
方案是根据下面的blog来的,
感谢:
http://llyblog.com/2020/01/04/iOS%E5%9F%BA%E4%BA%8EGPUImage%E5%B8%A6Alpha%E7%9A%84mp4%E6%92%AD%E6%94%BE%E6%96%B9%E6%A1%88/
opengl是能渲染rgba,但是像AVAsset,AVAssetRead都无法解码RGBA,只能解码RGB,那就用骚操作搞到alpha
下半部分是rgb像素点都存alpha,上半部分正常rgb视频,合成一个视频是为了alpha和rgb同步
流程:
1.将水印视频和目标视频初始化两个 GPUImageMovie 作为 GPUImage 的 输入源;
2.水印视频 GPUImageMovie 添加一个 filter 用来 将alpha和rgb提取并混合。
3.水印视频 GPUImageMovie 通过混合后 和 目标视频 GPUImageMovie 都添加一个 filter:GPUImageTwoInputFilter 用来将 两个纹理混合
水印视频 GPUImageMovie 添加一个 filter 用来 将alpha和rgb提取并混
这个filter的 FragmentShader
NSString *const kGPUImageVideoAlphaShaderString = SHADER_STRING
(
varying highp vec2 textureCoordinate;//这个对应下面的纹理坐标,即渲染的视频的坐标
uniform sampler2D inputImageTexture;//这个是完整视频的纹理
const lowp vec2 bottomW = vec2(0.0,0.5);
void main()
{
//从上边的视频中拿RGB值,从下边的视频中拿Alpha值,然后返回一个新的RGBA.
lowp vec4 topTextureColor = texture2D(inputImageTexture, textureCoordinate);
lowp vec2 bottomTextureCoordinate = bottomW + textureCoordinate;
lowp vec4 bottomTextureColor = texture2D(inputImageTexture, bottomTextureCoordinate);
gl_FragColor = vec4(topTextureColor.rgb,bottomTextureColor.r);
}
);
这里要注意纹理顶点的设置:
//这里注意,取上半部分纹理贴图纹理顶点要拿下半部分的,因为是颠倒的
static const GLfloat textVertices[] = {
0.0, 0.5,
1.0, 0.5,
0.0, 0.0,
1.0, 0.0,
};
这样是将上下纹理区分开
这里渲染完水印视频RGBA后,接着和目标视频传入下一个target:GPUImageTwoInputFilter
该filter的FragmentShader需要修改一下
NSString *const kGPUImageAlphaVideoMixFragmentShaderString = SHADER_STRING
(
varying highp vec2 textureCoordinate;
varying highp vec2 textureCoordinate2;
uniform sampler2D inputImageTexture;
uniform sampler2D inputImageTexture2;
uniform lowp float mixturePercent;
void main()
{
lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
lowp vec4 textureColor2 = texture2D(inputImageTexture2, textureCoordinate2);
gl_FragColor = textureColor * (1.0 - textureColor2.a) + textureColor2;
}
);
最后用GPUImageInput(GPUImageMovieWriter、GPUImageView或者其他)展示或保存
最后
这里只是学习记录,也是参考网上各位大佬实现的,没有上具体代码。
需要请email: 13048914897@163.com