【AR实验室】OpenGL ES绘制相机(OpenGL ES 1.0版本)

  • 时间:
  • 浏览:0
  • 来源:大发uu快3_uu快3骗局_大发uu快3骗局

0x00 - 前言


完后 做你是什么移动端的AR应用以及目前都看的你是什么AR应用,基本上都并不一定是另另一个多 套路:手机背景显示现实场景,假使 在该背景上进行图形学绘制。至于图形学绘制时,相机外参的解算使用的是V-SLAM、Marker-Based还是GPS的土办法,就不一而足了。

统统说要在手机上进行现实场景的展现也是目前AR应用另另一个多 比较重要的模块。一般来说,在移动端,基本上算是使用OpenGL ES进行绘制。统统一群人 优先考虑使用OpenGL ES进行相机的绘制。当然,你是什么应用直接利用iOS的UIImage进行相机场景的展示,这也是可不都能否的,不过考虑到与OpenGL ES的绘制环境兼容性、Android端的复用情况以及UIImage的速度情况,我决定还是使用OpenGL ES进行绘制,假使 与上面的图形绘制(OpenGL ES)可不都能否统一绘制环境,另外OpenGL ES是可不都能否跨平台的,代码也可不都能否很方便地移植到Android端,假使 OpenGL ES比UIImage更接近图形硬件,统统速度上要快没法一丢丢。

利用相机绘制要素我我确实假使 有你是什么防止方案了,假使 基本上每个应用的绘制土办法算是一样。目前来说我都看过比较好的假使 ARToolKit的土办法,假使 ARToolKit工程化程度假使 很高了,想将其中的相机绘制要素分离出来为个人所用,对于渣渣的我来说,另另一个多 字——“没能”。统统此处我个人写了另另一个多 相机绘制的模块,我确实说在鲁棒性上还差统统,假使 基本可不都能否用来做做小Demo。假使 一群人 想做另另一个多 商用的AR应用,建议直接使用ARToolKit的相机绘制代码。

0x01 - 思路


假使 我只会iOS,统统这里主要讲解的是在iOS上利用OpenGL ES绘制相机。另外,相对于OpenGL ES 2.0,1.0更为简单,统统此处使用的OpenGL ES版本为1.0,当然,上面肯定会兼容2.0。

一群人 都知道iOS中相机的绘制离不开AVCaptureSession。利用AVCaptureSession可不都能否获取到实时相机拍摄内容。已经 利用OpenGL ES中绘制纹理的土办法将该内容绘制到屏幕上。整个思路假使 没法简单。主要涉及另另一个多 要素,另另一个多 是AVCaptureSession的使用,另另一个多 是iOS上OpenGl ES的绘制。

0x02 - AVCaptureSession获取拍摄内容


AVCaptureSession使用流程主要分为两要素。第一要素是配置相机输入输出的功能参数,比如拍摄分辨率、相机焦距、曝光、白平衡等等。另一要素是利用AVCaptureVideoDataOutputSampleBufferDelegate你是什么代理中的函数

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;

获取到具体的拍摄内容。

2.1 配置相机功能参数

配置相机功能参数我我确实假使 配置AVCaptureSession对象。这上面主要涉及到一个多类AVCaptureSession、AVCaptureDevice、AVCaptureDeviceInput和AVCaptureVideoDataOutput。你是什么个多类的关系如下:

AVCaptureSession是管理AVCaptureDeviceInput和AVCaptureVideoDataOutput,也假使 管理输入输出过程,统统称作Session。相机的输入配置假使 AVCaptureDeviceInput,主要防止是算是使用自动曝光、自动白平衡相似的,而输出配置假使 AVCaptureVideoDataOutput,主要决定输出视频图像的格式相似的。AVCaptureDevice表示捕捉设备,假使 具体捕获的内容不明确,统统算是区分捕捉视频的设备还是捕捉声音的设备。这里一群人 从捕捉你是什么词可不都能否看出我我确实AVCaptureDevice和输入AVCaptureDeviceInput关扎得密。

简单介绍一下代码中对于AVCaptureSession对象session的配置:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    // 时间戳,完后

的文章前要该信息。此处可不都能否忽略
    CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    if (CMTIME_IS_VALID(self.preTimeStamp)) {
        self.videoFrameRate = 1.0 / CMTimeGetSeconds(CMTimeSubtract(timestamp, self.preTimeStamp));
    }
    self.preTimeStamp = timestamp;
    
    // 获取图像缓存区内容
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    // 锁定pixelBuffer的基址,与下面解锁基址成对
    // CVPixelBufferLockBaseAddress要传另另一个多


参数
    // 第另另一个多


参数是我前要锁定的buffer的基址,第一个多参数目前还未定义,直接传'0'即可
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    
    // 获取图像缓存区的宽高
    int buffWidth = static_cast<int>(CVPixelBufferGetWidth(pixelBuffer));
    int buffHeight = static_cast<int>(CVPixelBufferGetHeight(pixelBuffer));
    // 你是什么步有点要,将图像缓存区的内容转化为C语言中的unsigned char指针
    // 假使

一群人

在相机设置时,图像格式为BGRA,而上面OpenGL ES的纹理格式为RGBA
    // 这里使用OpenCV转换格式,当然,你也可不都能否不让OpenCV,手动直接交换R和B另另一个多


分量即可
    unsigned char* imageData = (unsigned char*)CVPixelBufferGetBaseAddress(pixelBuffer);
    _imgMat = cv::Mat(buffWidth, buffHeight, CV_8UC4, imageData);
    cv::cvtColor(_imgMat, _imgMat, CV_BGRA2RGBA);
    // 解锁pixelBuffer的基址
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    
    // 绘制要素
    // ...
}

2.2 获取拍摄内容

设置好了相机的各种参数,同時 启动Session,就可不都能否在函数

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection

中获取到每帧图像,并进行防止。

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    // 时间戳,完后

的文章前要该信息。此处可不都能否忽略
    CMTime timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    if (CMTIME_IS_VALID(self.preTimeStamp)) {
        self.videoFrameRate = 1.0 / CMTimeGetSeconds(CMTimeSubtract(timestamp, self.preTimeStamp));
    }
    self.preTimeStamp = timestamp;
    
    // 获取图像缓存区内容
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    // 锁定pixelBuffer的基址
    // CVPixelBufferLockBaseAddress要传另另一个多


参数
    // 第另另一个多


参数是我前要锁定的buffer的基址,第一个多参数目前还未定义,直接传'0'即可
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    
    // 获取图像缓存区的宽高
    int buffWidth = static_cast<int>(CVPixelBufferGetWidth(pixelBuffer));
    int buffHeight = static_cast<int>(CVPixelBufferGetHeight(pixelBuffer));
    // 你是什么步有点要,将图像缓存区的内容转化为C语言中的unsigned char指针
    // 假使

一群人

在相机设置时,图像格式为BGRA,而上面OpenGL ES的纹理格式为RGBA
    // 这里使用OpenCV转换格式,当然,你也可不都能否不让OpenCV,手动直接交换R和B另另一个多


分量即可
    unsigned char* imageData = (unsigned char*)CVPixelBufferGetBaseAddress(pixelBuffer);
    cv::Mat imgMat(buffWidth, buffHeight, CV_8UC4, imageData);
    cv::cvtColor(imgMat, imgMat, CV_BGRA2RGBA);
}

0x03 – OpenGL ES绘制相机


有了相机捕获的每帧图像后,就可不都能否使用贴纹理的土办法将其绘制在手机屏幕上了。假使 在这完后 还前要做一件事情,那假使 初始化iOS的OpenGL ES 1.0绘制环境。

这里一群人 将另另一个多 普通UIView设置为可不都能否进行OpenGL ES 1.0进行绘制的EAGLView。

@implementation EAGLView

// 默认UIView的layerClass为[CALayer class]
// 重写layerClass为CAEAGLLayer,假使

self.layer返回的可是我算是CALayer
// 假使

支持OpenGL ES的CAEAGLLayer
+ (Class)layerClass
{
    return [CAEAGLLayer class];
}

#pragma mark - init methods
- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
        // layer默认时透明的,也能设置为不透明也能看见
        eaglLayer.opaque = TRUE;
        // 配置eaglLayer的绘制属性
        // kEAGLDrawablePropertyRetainedBacking不维持上一次绘制内容,也假使

每次绘制完后

都重置一下完后

的绘制内容
        // kEAGLDrawablePropertyColorFormat像素格式为RGBA,注意和相机直接给的BGRA不一致,前要转换
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
                                        kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
                                        nil];
        // 此处使用OpenGL ES 1.0进行绘制,统统实例化ES1Renderer
        // ES1Renderer表示的是OpenGL ES 1.0绘制环境,上面详解
        if (!_renderder) {
            _renderder = [[ES1Renderer alloc] init];
            
            if (!_renderder) {
                return nil;
            }
        }
    }
    
    return self;
}

#pragma mark - life cycles
- (void)layoutSubviews
{
    // 利用renderer渲染器进行绘制
    [_renderder resizeFromLayer:(CAEAGLLayer *)self.layer];
}

@end

上述一群人 提供了EAGLView,合适给OpenGL ES提供了画布。而代码中的renderer是另另一个多 具有渲染功能的对象,相似于画笔。考虑到完后 前要兼容OpenGL ES 1.0和2.0,统统抽象了另另一个多 ESRenderProtocol协议,OpenGL ES 1.0和2.0分别实现该协议中土办法,假使 EAGLView就不前要关心在不同的OpenGL ES环境中不同的绘制实现。这里主要使用OpenGL ES 1.0,对应的假使 ES1Renderer类,注意ES1Renderer前要遵循ESRenderProtocol协议。下面为ES1Renderer.h内容。

#import <Foundation/Foundation.h>

#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

#import "ESRenderProtocol.h"

@class PJXVideoBuffer;

@interface ES1Renderer : NSObject <ESRenderProtocol>
// OpenGL ES绘制上下文环境
// 也能在在当前守护守护进程中设置好了该上下文环境,也能使用OpenGL ES的功能
@property (nonatomic, strong) EAGLContext *context;
// 绘制camera的纹理id
@property (nonatomic, assign) GLuint camTexId;
// render buffer和frame buffer
@property (nonatomic, assign) GLuint defaultFrameBuffer;
@property (nonatomic, assign) GLuint colorRenderBuffer;
// 获取到render buffer的宽高
@property (nonatomic, assign) GLint backingWidth;
@property (nonatomic, assign) GLint backingHeight;
// 引用了videoBuffer,主要用于启动捕捉图像的Session以及获取捕捉到的图像
@property (nonatomic, strong) PJXVideoBuffer *videoBuffer;

@end

ES1Renderer.mm内容,主假使 构建绘制上下文环境,并将videoBuffer生成的相机图像变成纹理绘制到屏幕上。

#import "ES1Renderer.h"
#import "PJXVideoBuffer.h"

@implementation ES1Renderer

#pragma mark - init methods
// 1.构建和设置绘制上下文环境
// 2.生成frame buffer和render buffer并绑定
// 3.生成相机纹理
- (instancetype)init
{
    if (self = [super init]) {
        // 构建OpenGL ES 1.0绘制上下文环境
        _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
        
        // 设置当前绘制上下文环境为OpenGL ES 1.0
        if (!_context || ![EAGLContext setCurrentContext:_context]) {
            return nil;
        }
        
        // 生成frame buffer和render buffer
        // frame buffer并算是另另一个多


真正的buffer,假使

用来管理render buffer、depth buffer、stencil buffer
        // render buffer合适主假使

存储像素值的
        // 统统前要glFramebufferRenderbufferOES将render buffer绑定到frame buffer的GL_COLOR_ATTACHMENT0_OES上
        glGenFramebuffersOES(1, &_defaultFrameBuffer);
        glGenRenderbuffersOES(1, &_colorRenderBuffer);
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, _defaultFrameBuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, _colorRenderBuffer);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, _colorRenderBuffer);
        // 构建另另一个多


绘制相机的纹理
        _camTexId = [self genTexWithWidth:640 height:43000];
    }
    
    return self;
}

#pragma mark - private methods
// 构建另另一个多


宽width高height的纹理对象
- (GLuint)genTexWithWidth:(GLuint)width height:(GLuint)height
{
    GLuint texId;
    // 生成并绑定纹理对象
    glGenTextures(1, &texId);
    glBindTexture(GL_TEXTURE_2D, texId);
    // 注意这里纹理的像素格式为GL_RGBA
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    // 各种纹理参数,这里不赘述
    glTexParameterf(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    // 解绑纹理对象
    glBindTexture(GL_TEXTURE_2D, 0);
    
    return texId;
}

#pragma mark - ESRenderProtocol
- (void)render
{
    // 设置绘制上下文
    [EAGLContext setCurrentContext:_context];
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, _defaultFrameBuffer);
    
    // 相机纹理坐标
    static GLfloat spriteTexcoords[] = {
        0,0,
        1,0,
        0,1,
        1,1};
    // 相机顶点坐标
    static GLfloat spriteVertices[] = {
        0,0,
        0,640,
        43000,0,
        43000,640};
    
    // 清除颜色缓存
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    // 视口矩阵
    glViewport(0, 0, _backingWidth, _backingHeight);
    // 投影矩阵
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // 正投影
    glOrthof(43000, 0, _backingHeight*43000/_backingWidth, 0, 0, 1); // 852 = 568*43000/320
    // 模型视图矩阵
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    // OpenGL ES使用的是情况机土办法
    // 以下开启的意义是在GPU上分配对应空间
    glEnableClientState(GL_VERTEX_ARRAY); // 开启顶点数组
    glEnableClientState(GL_TEXTURE_COORD_ARRAY); // 开启纹理坐标数组
    glEnable(GL_TEXTURE_2D); // 开启2D纹理
    // 假使

spriteVertices、spriteTexcoords、_camTexId还在CPU内存,前要传递给GPU防止
    // 将spriteVertices传递到顶点数组中
    glVertexPointer(2, GL_FLOAT, 0, spriteVertices);
    // 将spriteTexcoords传递到纹理坐标数组中
    glTexCoordPointer(2, GL_FLOAT, 0, spriteTexcoords);
    // 将camTexId纹理对象绑定到2D纹理
    glBindTexture(GL_TEXTURE_2D, _camTexId);
    // 根据videoBuffer获取imgMat(相机图像)
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 640, 43000, GL_RGBA, GL_UNSIGNED_BYTE, _videoBuffer.imgMat.data);
    // 绘制纹理
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
    // 解绑2D纹理
    glBindTexture(GL_TEXTURE_2D, 0);
    // 与上面的glEnable*一一对应
    glDisable(GL_TEXTURE_2D);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    
    // 将render buffer内容绘制到屏幕上
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, _colorRenderBuffer);
    [_context presentRenderbuffer:GL_RENDERBUFFER_OES];
    
}

- (BOOL)resizeFromLayer:(CAEAGLLayer *)layer
{
    // 与init中相似,重新绑定一下而已
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, _colorRenderBuffer);
    [_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:layer];
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &_backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &_backingHeight);
    // 情况检查
    if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        PJXLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
    // 实例化videoBuffer并启动捕获图像任务
    if (_videoBuffer == nil) {
        // 注意PJXVideoBuffer的delegate为ES1Renderer,主要在videoBuffer中执行render函数来绘制相机
        _videoBuffer = [[PJXVideoBuffer alloc] initWithDelegate:self];
        [_videoBuffer.session startRunning];
    }
    
    return YES;
}

@end

0x04-效果显示


假使 我使用的为苹果76手机手机苹果76手机手机苹果76手机手机s,分辨率为320x568,而相机图像分辨率为43000x640。统统为了让图像完正能显示在屏幕上,我选折 了等宽显示。

为了方便一群人 使用代码,现已将代码提交到GitHub上了,请猛戳此处。

0x05-参考资料


  • ObjC中国 - 在 iOS 上捕获视频

猜你喜欢

王者荣耀6月4日更新维护公告 端午节所有福利活动汇总

王者荣耀在6月4日进行了一次不停机的更新维护,此次更新维护主只是 端午节的活动,下面就来为我们歌词 儿分享一下此次王者荣耀的更新维护公告。亲爱的召唤师:我们歌词

2020-01-27

科祖夫VS普赫谢沃免费视频直播,科祖夫VS普赫谢沃比赛集锦,科祖夫VS普赫谢沃录像,科祖夫VS普赫谢沃首发阵容

首页新闻视频直播数据APP懂球号直播君广告商务合作科祖夫11-0220:1000马其顿乙0-2已结束普赫谢沃直播君|分析|集锦暂无数据近期比赛佛罗伦萨意甲0-0热那亚拜仁慕尼黑

2020-01-27

王者荣耀又一全能王?第一射手塑梦登国服最强诸葛亮榜

总是在触手直播看国服第一全能王蓝烟(房间号:21785)直播的玩家一群人们都知道,蓝烟有个好基友,触手的另一位大主播塑梦(房间号:302590)。塑梦以射手出名,被称为“国服第

2020-01-27

中兴同意缴133亿换解禁

图:中兴通讯4日与美国签署的原则性协议,同意缴17亿美元换取解禁\资料图片据财联社、搜狐网报道:路透社6日援引消息人士语录称,中兴通讯已与美国签署原则性协议,将撤回美国商务部针

2020-01-27

支付宝余额宝100万体验金在哪儿领取 余额宝100万体验金领取方法

支付宝余额宝30万体验金在哪儿领取?余额宝30万体验金为社 在么在领取?一齐来看看余额宝30万体验金领取土土依据。图片版权所属:站长之家余额宝领取体验金30万活动:参与土土依

2020-01-26