本工程完全采用OpenGL ES 3.0的API进行实现,目的是更好的使用OpenGL相关功能并移植到移动端项目。
以示例为主,可以作为调参工具,也可以作为学习参考。
- 将需要显示的图片转成纹理
- 纹理序号将会在渲染线程中传给GPU进行缓存
- OpenGL程序绘制纹理并光栅化显示到窗口
// 图片指定存储路径
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"yourname" ofType:@"png"];
// 转换图片为OpenGL纹理ID
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:imagePath options:@{GLKTextureLoaderOriginBottomLeft : @true, GLKTextureLoaderGenerateMipmaps : @true} error:NULL];
// 存储纹理
self.mTextureId = textureInfo.name;
// 纹理尺寸
_imageSize = CGSizeMake(textureInfo.width, textureInfo.height);
图例一
- 通过绘制两张不同的图像
- 在切换图像过程中,编写shader,添加一些行为(如:旋转、渐变、缩放、移动等)
- 按指定帧率渲染每一帧图像,得到流畅的动画
图例二
- 多张图片绘制时,可能需要修改透明度(需要开启混合模式, 会占用一定资源)
- 将时间作为参数传入shader,用于控制显示时长及效果
- 绘制第二张图片到窗口,用于替代第一张图,实现转场效果
绘制第一张图像, 如果在后台绘制,需要使用FBO机制,此处暂时不做讲解
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
// 清理颜色缓冲区,防止渲染花屏不同步导致撕裂等问题
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
// 自定义参数传入
glUniform2f(uniforms[UNIFORM_PIXEL_SIZE], _imageSize.width, _imageSize.height);
glUniform1f(uniforms[UNIFORM_ZOOM], _zoom);
glUniform1f(uniforms[UNIFORM_ANGLE_X], (_rotationAngle * M_PI) / 180.0);
glUniform1f(uniforms[UNIFORM_ANGLE_Y], (_verticalRotationAngle * M_PI) / 180.0);
glUniform2f(uniforms[UNIFORM_OFFSET], _offsetPoint.x, _offsetPoint.y);
// 激活纹理标号绑定到当前纹理ID,会将当前纹理对象传入shader中的inputImageTexture,此处使用的是2D图像,只包含x、y轴
if (_mTextureId) {
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, _mTextureId);
glUniform1i(_textureUniform, 2);
}
// 进行等比例裁剪显示,防止图像填充到屏幕出现拉伸现象
[self cropTextureCoordinateForRect:rect];
// 传入VAO数据,用于显示指定的顶点坐标以及纹素数据提取位置信息
glVertexAttribPointer(_positionAttribute, 2, GL_FLOAT, 0, 0, _texturePosition);
glVertexAttribPointer(_coordinateAttibute, 2, GL_FLOAT, 0, 0, textureCoordinates);
// 同步绘制
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[self redrawTexture];
}
绘制第二张图 为了保证alpha通道不受第一张图的影响,需要开启混合模式
- (void)redrawTexture
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
timeElapsed += [[NSString stringWithFormat:@"%f", self.timeSinceLastDraw] doubleValue];
glUniform1f(_timeUniform, timeElapsed);
glUniform1f(_chatrletUniform, 1.0);
if (_textureId)
{
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, _textureId);
glUniform1i(_textureUniform, 3);
}
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_BLEND);
}
模型使用google开源项目MediaPipe中的faceblaze 支持亚毫秒级的人脸检测 人脸关键点定位使用腾讯优图工作室开源模型文件
以鼻尖关键点作为中心,添加贴图在视频上 根据人脸定位,计算当前人脸角度、焦距大小来进行贴图旋转和缩放
为了方便demo使用,本处没有使用opengl进行绘制,可以结合上文进行处理
添加图片
self.imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"emoji.png"]];
self.imageView.frame = CGRectMake(-50, 0, 50, 50);
[self.view addSubview:self.imageView];
初始化
// 初始化识别器,指定最大支持人脸个数及预览视频尺寸,用于适配点位
self.predictManager = [[WDPredictManager alloc] initWithFaceNumber:12 withInputSize:self.renderView.bounds.size];
// 显示关键点,用于demo查看
[self.predictManager showFaceLandmarkOnView:self.renderView];
// 设置代理
self.predictManager.delegate = self;
实现代理
- (void)predict:(WDPredictManager *)predict didFinished:(BOOL)hasFace
{
if (hasFace) {
// 鼻尖位置
CGPoint point = [predict pointForIndex:16];
NSLog(@"point = %@, 角度 = %.2f", NSStringFromCGPoint(point), predict.angle);
self.imageView.center = point;
self.imageView.transform = CGAffineTransformMakeRotation(predict.angle);
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, predict.scale, predict.scale);
}
}