日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2025/5/22 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【AR实验室】OpenGL ES绘制相机(OpenGL ES 1.0版本) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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.0if (!_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:480];}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_RGBAglTexImage2D(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,480,0,480,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(480, 0, _backingHeight*480/_backingWidth, 0, 0, 1); // 852 = 568*480/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, 480, 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-效果顯示


因為我使用的為iPhone5s,分辨率為320x568,而相機圖像分辨率為480x640。所以為了讓圖像全部能顯示在屏幕上,我選擇了等寬顯示。

為了方便大家使用代碼,現已將代碼提交到GitHub上了,請猛戳此處。

0x05-參考資料


  • ObjC中國 - 在 iOS 上捕獲視頻

轉載于:https://www.cnblogs.com/polobymulberry/p/6130376.html

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的【AR实验室】OpenGL ES绘制相机(OpenGL ES 1.0版本)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 亚洲va欧美va天堂v国产综合 | 久久人体视频 | 日韩一区二区三区高清 | 五月天久久综合 | 五月激情丁香网 | 国产无限资源 | 男生和女生一起差差差很痛的视频 | 美女伊人网 | 天堂无乱码| 强行挺进皇后紧窄湿润小说 | 久久伊人爱 | 中文字幕第二区 | 久久久久久亚洲精品中文字幕 | 轮乱 | 操久久| 激情综合网av | 97超碰伊人| av在线短片 | 蜜桃又黄又粗又爽av免 | 小柔好湿好紧太爽了国产网址 | 久久久一二三 | 国产精品欧美激情在线 | 激情视频区 | 欧美日本高清视频 | 亚洲精品av中文字幕在线在线 | 露胸app| 红桃视频网站 | 日本在线一区 | 久久精品视频99 | 九九热九九热 | 日韩欧美亚洲一区 | 古装做爰无遮挡三级聊斋艳谭 | 国产男女猛烈无遮挡a片漫画 | 亚洲一区二区国产精品 | www.男人的天堂 | 麻豆激情视频 | 美女扒开腿男人爽桶 | 成人午夜视频免费在线观看 | 欧美一区二区三区色 | 亚洲不卡在线视频 | www午夜| 五月情婷婷 | 无限资源日本好片 | 夜夜夜操 | 精品亚洲国产成人av制服丝袜 | 无码精品一区二区三区AV | 五月综合激情 | 日本护士做爰视频 | 国产亚洲一区二区三区不卡 | 亚洲免费精品 | 欧美一区二区在线免费观看 | 婷婷超碰| www亚洲一区 | 国产日韩免费 | 亚洲伦理在线 | 国产不卡在线观看视频 | 国色天香一区二区 | 黄色网址你懂的 | 午夜在线播放视频 | 久久综合桃花网 | 久久久久久久久久一区二区 | 有码视频在线观看 | 我们的生活第五季在线观看免费 | 黑人大群体交免费视频 | 国产精品网站入口 | 在线免费观看视频你懂的 | 国产农村妇女精品一区 | 在线欧美一区 | 欧美激情视频一区 | 精品动漫一区 | 日日日日日日bbbbbb | 人人搞人人插 | 中文字幕在线观看亚洲 | 亚洲4438| 四虎精品在永久在线观看 | 老太脱裤让老头玩ⅹxxxx | 成年人免费黄色 | 蜜色视频 | 狼人伊人干 | 一道本在线视频 | 极品粉嫩小仙女高潮喷水久久 | 欧美成人黄色 | 免费成人美女在线观看. | 久久在线视频精品 | 精品无码久久久久久国产 | 污片免费看 | 欧美在线日韩 | 欧美日韩国产大片 | 国产亚洲综合精品 | 绿色地狱在线观看 | 俄罗斯毛片基地 | 亚洲一区二区三区在线视频 | 亚洲欧美中文日韩在线观看 | 日韩欧美国产精品 | 一区三区在线 | 飘花影院伦理片 | 极品尤物魔鬼身材啪啪仙踪林 | 一区视频 | 动漫美女被吸奶 |