零. 前言
绿幕视频,顾名思义就是指将一个视频的背景弄成纯绿色,而对视频中的元素进行抠图。在影视界非常常见,我们看到的许多在虚拟背景的动作场面,都是先让演员基于绿幕进行拍摄,再用后期进行抠图和渲染的。如下所示。
这次我们以海绵宝宝的绿幕视频为实例,将视频中的海绵宝宝抠到水母田去,以下是原素材:
以下是效果:
一. YUV转RGB
在之前的Metal与图形渲染三:透明通道视频也提到过,视频是YUV格式的,如果需要提取RGB颜色值,则需要转换,故有:
float3 rgbFromYuv(float2 textureCoor,
texture2d <float> textureY,
texture2d <float> textureUV,
constant CCAlphaVideoMetalConvertMatrix *convertMatrix) {
constexpr sampler textureSampler (mag_filter::linear,
min_filter::linear);
float3 yuv = float3(textureY.sample(textureSampler, textureCoor).r,
textureUV.sample(textureSampler, textureCoor).rg);
return convertMatrix->matrix * (yuv + convertMatrix->offset);
}
fragment float4 movieFragment(SingleInputVertexIO input [[ stage_in ]],
texture2d <float> textureY [[ texture(0) ]],
texture2d <float> textureUV [[ texture(1) ]],
constant CCAlphaVideoMetalConvertMatrix *convertMatrix [[ buffer(0) ]]) {
float3 rgb = rgbFromYuv(input.textureCoordinate, textureY, textureUV, convertMatrix);
return float4(rgb, 1.0);
}
二. 将绿幕视频缩小一点
由于原视频的海绵宝宝太大只了,因此我们需要编写一个Filter,用于纹理的放缩,CATransform3D
是一个很有用的缩放、平移矩阵,因此该Filter可以基于CATransform3D
来编写,让变换矩阵直接乘以顶点值即可:
#import "HobenMetalTransformFilter.h"
@interface HobenMetalTransformFilter ()
@property (nonatomic, strong) id <MTLBuffer> transformBuffer;
@end
@implementation HobenMetalTransformFilter
- (instancetype)initWithRenderContext:(HobenMetalRenderContext *)renderContext {
if (self = [super initWithVertexName:@"perspectiveVertex" renderContext:renderContext]) {
self.transform3D = CATransform3DIdentity;
}
return self;
}
- (void)renderToTextureWithVertices:(NSArray *)vertices textureCoordinates:(NSArray *)textureCoordinates {
CATransform3D transform3D = _transform3D;
matrix_float4x4 mapMatrix = {
.columns[0] = { (float)transform3D.m11, (float)transform3D.m12, (float)transform3D.m13, (float)transform3D.m14},
.columns[1] = { (float)transform3D.m21, (float)transform3D.m22, (float)transform3D.m23, (float)transform3D.m24},
.columns[2] = { (float)transform3D.m31, (float)transform3D.m32, (float)transform3D.m33, (float)transform3D.m34},
.columns[3] = { (float)transform3D.m41, (float)transform3D.m42, (float)transform3D.m43, (float)transform3D.m44},
};
self.transformBuffer = [_renderContext.device newBufferWithBytes:&mapMatrix
length:sizeof(matrix_float4x4)
options:MTLResourceStorageModeShared];
[super renderToTextureWithVertices:vertices textureCoordinates:textureCoordinates verticeBuffers:@[self.transformBuffer]];
}
@end
修改顶点着色器:
vertex SingleInputVertexIO perspectiveVertex(const device packed_float2 *position [[buffer(0)]],
const device matrix_float4x4 &perspectiveMatrix [[buffer(1)]],
const device packed_float2 *texturecoord [[buffer(2)]],
uint vid [[vertex_id]])
{
SingleInputVertexIO outputVertices;
outputVertices.position = perspectiveMatrix * float4(position[vid], 0, 1.0);
outputVertices.textureCoordinate = texturecoord[vid];
return outputVertices;
}
三. 提取绿幕视频元素
提取绿幕视频元素的Shader,输入两个纹理,一个是视频的RGB,一个是水母田的图片RGB,当为绿色时(RGB = 0, 255, 255),说明要提取水母田的RGB;否则提取视频的RGB,因为读取色素值可能不那么准确,所以加了0.05的容错。
另外,由于还需要将海绵宝宝缩小一点,加了一个缩小的Filter,产生的多出部分的RGBA均为0,会影响绿幕的判断,因此还需要加个a == 0.0的判断。
得到Shader如下:
fragment float4
greenScreenFragment(TwoInputVertexIO input [[ stage_in ]],
texture2d<float> greenScreenTexture [[ texture(0) ]],
texture2d<float> backgroundTexture [[ texture(1) ]]) {
constexpr sampler s;
float4 greenScreenColor = greenScreenTexture.sample(s, input.textureCoordinate);
float4 backgroundColor = backgroundTexture.sample(s, input.textureCoordinate2);
if ((greenScreenColor.g >= 0.95 && greenScreenColor.r <= 0.05 && greenScreenColor.b <= 0.05) || greenScreenColor.a == 0.0) {
// 绿幕背景,取背景色
return backgroundColor;
} else {
return greenScreenColor;
}
}
四. 外层调用
绿幕视频 => 矩阵变换Filter
矩阵变换Filter + 背景Picture => RenderView
self.renderView = [[HobenMetalRenderView alloc] initWithRenderContext:_renderContext];
self.picture = [[HobenMetalPicture alloc] initWithImage:image renderContext:_renderContext];
self.greenScreenFilter = [[HobenMetalFilter alloc] initWithVertexName:@"twoInputVertex" fragmentName:@"greenScreenFragment" numberOfInputs:2 renderContext:_renderContext];
self.cropFilter = [[HobenMetalCropFilter alloc] initWithRenderContext:_renderContext];
self.greenScreenReader = [[HobenMetalMovieReader alloc] initWithRenderContext:_renderContext];
self.transformFilter = [[HobenMetalTransformFilter alloc] initWithRenderContext:_renderContext];
self.transformFilter.transform3D = CATransform3DMakeScale(1, 0.8, 1);
[self.greenScreenReader addTarget:self.transformFilter];
[self.transformFilter addTarget:self.greenScreenFilter];
[self.picture addTarget:self.greenScreenFilter];
[self.greenScreenFilter addTarget:self.renderView];
五. 总结
本文主要介绍了绿幕视频的原理和如何将绿幕视频的元素抠出来,叠放到其他纹理上面。