iOS 使用 OpenGLES 渲染水印图片
前言
这次我们用 OpenGLES 将水印、贴图渲染到相机的预览画面上,代码跟上一篇 iOS 使用 OpenGLES 实现相机画面镜像 差不多。
实现思路
输入的纹理应该有两路,一路是相机回调的 Pixel Buffer,一路是水印图片纹理。相机 Buffer 在视口中的大小始终是不变的,但水印图片 Size 和 Position 都是不固定的,不能使用相同的顶点坐标和视口大小绘制这两个纹理,目前想到的有两种方式可以实现:一种是两个纹理采用两个不同的顶点坐标,同时共享屏幕视口大小;一种是两个纹理采用同一组顶点坐标,采用不同的视口大小。此例采用第二种方式。
代码
顶点坐标还是与上一篇的一致,由于是将渲染结果输出到纹理,因此不需要做翻转,顶点坐标纹理坐标一一对应即可:
1 | typedef struct { |
然后是着色器们,顶点着色器接受顶点坐标和纹理坐标转成 vec4 类型,赋值给 gl_Position,并将纹理坐标给到片段着色器。片段着色器拿到纹理坐标,通过纹理采样器计算像素颜色:
1 | // vertex shader |
着色器程序的创建、编译代码跟上一篇一致,就不再贴了,接着看下初始化方法:
1 | - (void)setupGL { |
两个纹理混合需要将 GL_BLEND 打开,通过 glBlendFunc 设置源因子和目标因子,不同的参数设置会产生不同的纹理叠加效果。FBO 和水印纹理的创建是动态的,所以放到渲染循环里处理:
1 | - (CVPixelBufferRef)process:(CVPixelBufferRef)pixelBuffer { |
process 方法实际是有两次绘制,先是渲染了相机 Buffer,然后调整视口大小为水印的 position 和 size,再渲染水印纹理。
创建输出 Buffer 代码:
1 | - (void)setupCVPixelBuffer:(CVPixelBufferRef *)pixelBuffer { |
创建 FBO 代码:
1 | - (void)setupFBO { |
创建水印图片纹理代码:
1 | - (void)setupWatermarkTexture { |
这里使用 GLKTextureLoader 生成纹理对象时发现一个问题,_watermarkImage.CGImage 明明有值但 textureWithCGImage 方法一直在报错,报错信息是 The operation couldn’t be completed. (GLKTextureLoaderErrorDomain error 8.) 。通过在每行 OpenGL 代码后增加 NSLog(@"GL Error = %u", glGetError()); 打印错误信息,发现是在给片段着色器的纹理采样器绑定纹理单元时方法用错了 glUniform1i 写成了 glUniform1f,修改之后运行没有问题,也算了解了一种 OpenGL 报错的排查方式。
后记
代码完成后就可以放到相机的回调方法里使用了。最近一直在看 OpenGLES 的代码,目前工程里还有一些相关的工具需要写,比如像素格式转换,Buffer 裁剪的功能,之后也会总结到博客上。哇今天还是比较高产,写了两篇博客,晚上可以加个鸡腿了,疫情期间鸡腿可是奢侈品呐。。。