本篇主要讲解GPUImage底层是如何渲染的,GPUImage底层使用的是OPENGL,操控GPU来实现屏幕展示
由于网上OpenGL实战资料特别少,官方文档对一些方法也是解释不清楚,避免广大同学再次爬坑,本篇讲解了不少OpenGL的知识,并且还讲解了花了大量时间解决bug的注意点
可以捕获采集的视频数据
关键是捕获到一帧一帧视频数据如何展示?
通过这个方法可以获取采集的视频数据
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
[videoConnection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];
01-自定义图层类型
02-初始化CAEAGLLayer图层属性
1.不透明度(opaque)=YES,CALayer默认是透明的,透明性能不好,最好设置为不透明.
2.设置绘图属性
其实设置不设置都无所谓,默认也是这个值
03-创建EAGLContext
04-创建渲染缓冲区
有了上下文,openGL还需要在一块buffer进行描绘,这块buffer就是RenderBuffer
OpenGLES 总共有三大不同用途的color buffer,depth buffer 和 stencil buffer.
最基本的是color buffer,创建它就好了
函数glGenRenderbuffers
函数glBindRenderbuffer
函数renderbufferStorage
实际代码:
#pragma mark - 4、创建渲染缓存
- (void)setupRenderBuffer
{
glGenRenderbuffers(1, &_colorRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
// 把渲染缓存绑定到渲染图层上CAEAGLLayer,并为它分配一个共享内存。
// 并且会设置渲染缓存的格式,和宽度
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_openGLLayer];
}
05-创建帧缓冲区
它相当于buffer(color, depth, stencil)的管理者,三大buffer可以附加到一个framebuffer上
本质是把framebuffer内容渲染到屏幕
函数glFramebufferRenderbuffer
#pragma mark - 5、创建帧缓冲区
- (void)setupFrameBuffer
{
glGenFramebuffers(1, &_framebuffers);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffers);
// 把颜色渲染缓存 添加到 帧缓存的GL_COLOR_ATTACHMENT0上,就会自动把渲染缓存的内容填充到帧缓存,在由帧缓存渲染到屏幕
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
}
06-创建着色器
着色器
07-创建着色器程序
第3步和第5步,绑定属性,必须有顺序,否则绑定不成功,造成黑屏
#pragma mark - 7、创建着色器程序
- (void)setupProgram
{
// 创建着色器程序
_program = glCreateProgram();
// 绑定着色器
// 绑定顶点着色器
glAttachShader(_program, _vertShader);
// 绑定片段着色器
glAttachShader(_program, _fragShader);
// 绑定着色器属性,方便以后获取,以后根据角标获取
// 一定要在链接程序之前绑定属性,否则拿不到
glBindAttribLocation(_program, ATTRIB_POSITION, "position");
glBindAttribLocation(_program, ATTRIB_TEXCOORD, "inputTextureCoordinate");
// 链接程序
glLinkProgram(_program);
// 获取全局参数,注意 一定要在连接完成后才行,否则拿不到
_luminanceTextureAtt = glGetUniformLocation(_program, "luminanceTexture");
_chrominanceTextureAtt = glGetUniformLocation(_program, "chrominanceTexture");
_colorConversionMatrixAtt = glGetUniformLocation(_program, "colorConversionMatrix");
// 启动程序
glUseProgram(_program);
}
08-创建纹理对象
采集的是一张一张的图片,可以把图片转换为OpenGL中的纹理, 然后再把纹理画到OpenGL的上下文中
什么是纹理?一个纹理其实就是一幅图像。
纹理映射,我们可以把这幅图像的整体或部分贴到我们先前用顶点勾画出的物体上去.
比如绘制一面砖墙,就可以用一幅真实的砖墙图像或照片作为纹理贴到一个矩形上,这样,一面逼真的砖墙就画好了。如果不用纹理映射的方法,则墙上的每一块砖都必须作为一个独立的多边形来画。另外,纹理映射能够保证在变换多边形时,多边形上的纹理图案也随之变化。
纹理映射是一个相当复杂的过程,基本步骤如下:
注意:纹理映射只能在RGBA方式下执行
函数glTexParameter
函数glPixelStorei
GL_PACK_ALIGNMENT
,用于将像素数据打包,一般用于压缩。GL_UNPACK_ALIGNMENT
,用于将像素数据解包,一般生成纹理对象,就需要用到解包.函数CVOpenGLESTextureCacheCreateTextureFromImage
根据图片生成纹理
参数allocator kCFAllocatorDefault,默认分配内存
参数textureCache 纹理缓存
参数sourceImage 图片
参数textureAttributes NULL
参数target , GL_TEXTURE_2D(创建2维纹理对象)
参数internalFormat GL_LUMINANCE,亮度格式
参数width 图片宽
参数height 图片高
参数format GL_LUMINANCE 亮度格式
参数type 图片类型 GL_UNSIGNED_BYTE
参数planeIndex 0,切面角标,表示第0个切面
参数textureOut 输出的纹理对象
实战代码
#pragma mark - 7、创建纹理对象,渲染采集图片到屏幕
- (void)setupTexture:(CMSampleBufferRef)sampleBuffer
{
// 获取图片信息
CVImageBufferRef imageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer);
// 获取图片宽度
GLsizei bufferWidth = (GLsizei)CVPixelBufferGetWidth(imageBufferRef);
_bufferWidth = bufferWidth;
GLsizei bufferHeight = (GLsizei)CVPixelBufferGetHeight(imageBufferRef);
_bufferHeight = bufferHeight;
// 创建亮度纹理
// 激活纹理单元0, 不激活,创建纹理会失败
glActiveTexture(GL_TEXTURE0);
// 创建纹理对象
CVReturn err;
err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCacheRef, imageBufferRef, NULL, GL_TEXTURE_2D, GL_LUMINANCE, bufferWidth, bufferHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0, &_luminanceTextureRef);
if (err) {
NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
}
// 获取纹理对象
_luminanceTexture = CVOpenGLESTextureGetName(_luminanceTextureRef);
// 绑定纹理
glBindTexture(GL_TEXTURE_2D, _luminanceTexture);
// 设置纹理滤波
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 激活单元1
glActiveTexture(GL_TEXTURE1);
// 创建色度纹理
err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, _textureCacheRef, imageBufferRef, NULL, GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, bufferWidth / 2, bufferHeight / 2, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 1, &_chrominanceTextureRef);
if (err) {
NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
}
// 获取纹理对象
_chrominanceTexture = CVOpenGLESTextureGetName(_chrominanceTextureRef);
// 绑定纹理
glBindTexture(GL_TEXTURE_2D, _chrominanceTexture);
// 设置纹理滤波
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
09-YUV转RGB绘制纹理
glDrawArrays如果要绘制着色器上的点和片段,必须和着色器赋值代码放在一个代码块中,否则找不到绘制的信息,就绘制不上去,造成屏幕黑屏
函数glUniform1i
函数glEnableVertexAttribArray
开启顶点属性数组,只有开启顶点属性,才能给顶点属性信息赋值
函数glVertexAttribPointer
函数glBindAttribLocation
函数glDrawArrays
实战代码:
// YUV 转 RGB,里面的顶点和片段都要转换
- (void)convertYUVToRGBOutput
{
// 在创建纹理之前,有激活过纹理单元,就是那个数字.GL_TEXTURE0,GL_TEXTURE1
// 指定着色器中亮度纹理对应哪一层纹理单元
// 这样就会把亮度纹理,往着色器上贴
glUniform1i(_luminanceTextureAtt, 0);
// 指定着色器中色度纹理对应哪一层纹理单元
glUniform1i(_chrominanceTextureAtt, 1);
// YUV转RGB矩阵
glUniformMatrix3fv(_colorConversionMatrixAtt, 1, GL_FALSE, _preferredConversion);
// 计算顶点数据结构
CGRect vertexSamplingRect = AVMakeRectWithAspectRatioInsideRect(CGSizeMake(self.bounds.size.width, self.bounds.size.height), self.layer.bounds);
CGSize normalizedSamplingSize = CGSizeMake(0.0, 0.0);
CGSize cropScaleAmount = CGSizeMake(vertexSamplingRect.size.width/self.layer.bounds.size.width, vertexSamplingRect.size.height/self.layer.bounds.size.height);
if (cropScaleAmount.width > cropScaleAmount.height) {
normalizedSamplingSize.width = 1.0;
normalizedSamplingSize.height = cropScaleAmount.height/cropScaleAmount.width;
}
else {
normalizedSamplingSize.width = 1.0;
normalizedSamplingSize.height = cropScaleAmount.width/cropScaleAmount.height;
}
// 确定顶点数据结构
GLfloat quadVertexData [] = {
-1 * normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
normalizedSamplingSize.width, -1 * normalizedSamplingSize.height,
-1 * normalizedSamplingSize.width, normalizedSamplingSize.height,
normalizedSamplingSize.width, normalizedSamplingSize.height,
};
// 确定纹理数据结构
GLfloat quadTextureData[] = { // 正常坐标
0, 0,
1, 0,
0, 1,
1, 1
};
// 激活ATTRIB_POSITION顶点数组
glEnableVertexAttribArray(ATTRIB_POSITION);
// 给ATTRIB_POSITION顶点数组赋值
glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, 0, 0, quadVertexData);
// 激活ATTRIB_TEXCOORD顶点数组
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData);
// 给ATTRIB_TEXCOORD顶点数组赋值
glEnableVertexAttribArray(ATTRIB_TEXCOORD);
// 渲染纹理数据,注意一定要和纹理代码放一起
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
10-渲染缓冲区到屏幕
必须设置窗口尺寸glViewport
渲染代码必须调用[EAGLContext setCurrentContext:_context]
每次创建纹理前,先把之前的纹理引用清空[self cleanUpTextures],否则卡顿
函数glViewport
方法presentRenderbuffer
是将指定renderbuffer呈现在屏幕上
#pragma mark - 10.渲染帧缓存
- (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer
{
// 因为是多线程,每一个线程都有一个上下文,只要在一个上下文绘制就好,设置线程的上下文为我们自己的上下文,就能绘制在一起了,否则会黑屏.
if ([EAGLContext currentContext] != _context) {
[EAGLContext setCurrentContext:_context];
}
// 清空之前的纹理,要不然每次都创建新的纹理,耗费资源,造成界面卡顿
[self cleanUpTextures];
// 创建纹理对象
[self setupTexture:sampleBuffer];
// YUV 转 RGB
[self convertYUVToRGBOutput];
// 设置窗口尺寸
glViewport(0, 0, self.bounds.size.width, self.bounds.size.height);
// 把上下文的东西渲染到屏幕上
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
11-清理内存
只要有Ref结尾的,都需要自己手动管理,清空
函数glClearColor
设置一个RGB颜色和透明度,接下来会用这个颜色涂满全屏.
函数glClear
#pragma mark - 11.清理内存
- (void)dealloc
{
// 清空缓存
[self destoryRenderAndFrameBuffer];
// 清空纹理
[self cleanUpTextures];
}
#pragma mark - 销毁渲染和帧缓存
- (void)destoryRenderAndFrameBuffer
{
glDeleteRenderbuffers(1, &_colorRenderBuffer);
_colorRenderBuffer = 0;
glDeleteBuffers(1, &_framebuffers);
_framebuffers = 0;
}
// 清空纹理
- (void)cleanUpTextures
{
// 清空亮度引用
if (_luminanceTextureRef) {
CFRelease(_luminanceTextureRef);
_luminanceTextureRef = NULL;
}
// 清空色度引用
if (_chrominanceTextureRef) {
CFRelease(_chrominanceTextureRef);
_chrominanceTextureRef = NULL;
}
// 清空纹理缓存
CVOpenGLESTextureCacheFlush(_textureCacheRef, 0);
}