跨平臺ffmpeg播放器開發(四)OpenGL ES顯示yuv數據(附改動過的他人iOS代碼)

前言

說來慚愧,本來是打算使用SDL的,網上的SDL資料也是相當的多,但是由于編譯一直不成功,所以轉向了另外一個跨平臺,使用OpenGL,以后等OpenGL做完后我還是會對SDL渲染進行嘗試,OpenGL我打算寫三篇博客來試著對FFmpeg解碼后的yuv420p數據進行渲染,今天的這一篇是參考自別人的代碼,我對別人的代碼進行了相當程度的注釋,注釋如果有不對的地方歡迎探討,在下一期打算使用雷博的這篇文章作為資料來寫一個demo,后期可能還要更深入的學習OpenGL的相關知識,這方面的學習成本還是很高的,歡迎大家一起交流,一起進步.

抽取類的百度云盤下載地址

鏈接: https://pan.baidu.com/s/1nuUp9nb 密碼: u242

github demo地址

使用前切記要給對幀的寬和高,否則會提示參數錯誤以及視頻顯示不對,由于時間過的就遠了一些,所以一些參數不對就被我改掉了,同時加入了像素對齊,否則看到的圖像就會感覺只渲染出了Y數據一樣.這是原文鏈接,需要登錄查看.

源代碼以及注釋

  • 使用前需要導入相關依賴庫

基本上每一步都已經在代碼中注釋了,所以我在此便不再仔細敘述了.

  • 這是頭文件外界可以調用的三個接口:
- (void)displayYUV420pData:(void *)data width:(NSInteger)w height:(NSInteger)h;
- (void)setVideoSize:(GLuint)width height:(GLuint)height;

/** 
 清除畫面
 */
- (void)clearFrame;
  • 源碼
#import "OpenglView.h"

    enum AttribEnum
    {
        ATTRIB_VERTEX,
        ATTRIB_TEXTURE,
        ATTRIB_COLOR,
    };

    //YUV數據枚舉
    enum TextureType
    {
        TEXY = 0,
        TEXU,
        TEXV,
        TEXC
    };

    @implementation OpenglView

    #pragma mark -  初始化等操作
    - (BOOL)doInit{
        //用來顯示opengl的圖形
        CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer;
        //eaglLayer.opaque = YES;
        
        //設為不透明
        eaglLayer.opaque = YES;
        
        //設置描繪屬性
        //    [eaglLayer setDrawableProperties:[NSDictionary dictionaryWithObjectsAndKeys:
        //                                     [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
        //                                     kEAGLColorFormatRGB565, kEAGLDrawablePropertyColorFormat,
        //                                     //[NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking,
        //                                      nil]];
        
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
                                        kEAGLColorFormatRGB565, kEAGLDrawablePropertyColorFormat,
                                        //[NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking,
                                        nil];
        
        //設置分辨率
        self.contentScaleFactor = [UIScreen mainScreen].scale;
        _viewScale = [UIScreen mainScreen].scale;
        
        //創建上下文
        _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        
        //[self debugGlError];
        
        //上下文創建失敗則直接返回no
        if(!_glContext || ![EAGLContext setCurrentContext:_glContext])
        {
            return NO;
        }
        
        //創建紋理
        [self setupYUVTexture];
        
        //加載著色器
        [self loadShader];
        
        //像素數據對齊,第二個參數默認為4,一般為1或4
        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
        
        //使用著色器
        glUseProgram(_program);
        
        //獲取一致變量的存儲位置
        GLuint textureUniformY = glGetUniformLocation(_program, "SamplerY");
        GLuint textureUniformU = glGetUniformLocation(_program, "SamplerU");
        GLuint textureUniformV = glGetUniformLocation(_program, "SamplerV");
        
        //對幾個紋理采樣器變量進行設置
        glUniform1i(textureUniformY, 0);
        glUniform1i(textureUniformU, 1);
        glUniform1i(textureUniformV, 2);
        
        return YES;
    }

    -(instancetype)initWithFrame:(CGRect)frame{
        
        self = [super initWithFrame:frame];
        
        if (self) {
            
            //沒有初始化成功
            if (![self doInit]) {
                
                self = nil;
            }
        }
        
        return self;
    }

    -(instancetype)initWithCoder:(NSCoder *)aDecoder{
        
        self = [super initWithCoder:aDecoder];
        
        if (self) {
            
            //沒有初始化成功
            if (![self doInit]) {
                
                self = nil;
            }
        }
        
        return self;
    }

    -(void)layoutSubviews{
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            //互斥鎖
            @synchronized (self) {
                
                [EAGLContext setCurrentContext:_glContext];
                
                //清除緩沖區
                [self destoryFrameAndRenderBuffer];
                
                //創建緩沖區
                [self createFrameAndRenderBuffer];
                
            }
            
            //把數據顯示在這個視窗上
            glViewport(1, 1, self.bounds.size.width*_viewScale - 2, self.bounds.size.height*_viewScale - 2);
        });
    }

    #pragma mark -  設置opengl

    /**
     不寫的話,設置描繪屬性會崩潰
     
     @return <#return value description#>
     */
    + (Class)layerClass
    {
        return [CAEAGLLayer class];
    }

    /**
     創建緩沖區
     
     @return <#return value description#>
     */
    - (BOOL)createFrameAndRenderBuffer
    {
        //創建幀緩沖綁定
        glGenFramebuffers(1, &_framebuffer);
        //創建渲染緩沖
        glGenRenderbuffers(1, &_renderBuffer);
        
        //將之前用glGenFramebuffers創建的幀緩沖綁定為當前的Framebuffer(綁定到context上?).
        glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
        //Renderbuffer綁定到context上,此時當前Framebuffer完全由renderbuffer控制
        glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
        
        //分配空間
        if (![_glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer])
        {
            NSLog(@"attach渲染緩沖區失敗");
        }
        
        //這個函數看起來有點復雜,但其實它很好理解的。它要做的全部工作就是把把前面我們生成的深度緩存對像與當前的FBO對像進行綁定,當然我們要注意一個FBO有多個不同綁定點,這里是要綁定在FBO的深度緩沖綁定點上。
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
        
        //檢查當前幀緩存的關聯圖像和幀緩存參數
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        {
            NSLog(@"創建緩沖區錯誤 0x%x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
            return NO;
        }
        return YES;
    }

    /**
     清除緩沖區
     */
    - (void)destoryFrameAndRenderBuffer
    {
        if (_framebuffer)
        {
            //刪除FBO
            glDeleteFramebuffers(1, &_framebuffer);
        }
        
        if (_renderBuffer)
        {
            //刪除渲染緩沖區
            glDeleteRenderbuffers(1, &_renderBuffer);
        }
        
        _framebuffer = 0;
        _renderBuffer = 0;
    }
    /**
     創建紋理
     */
    - (void)setupYUVTexture{
        
        if (_textureYUV[TEXY])
        {
            //刪除紋理
            glDeleteTextures(3, _textureYUV);
        }
        
        //生成紋理
        glGenTextures(3, _textureYUV);
        if (!_textureYUV[TEXY] || !_textureYUV[TEXU] || !_textureYUV[TEXV])
        {
            NSLog(@"<<<<<<<<<<<<紋理創建失敗!>>>>>>>>>>>>");
            return;
        }
        
        //選擇當前活躍單元
        glActiveTexture(GL_TEXTURE0);
        //綁定Y紋理
        glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXY]);
        //紋理過濾函數
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//放大過濾
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//縮小過濾
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);//水平方向
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);//垂直方向
        
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXU]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXV]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        
    }

    #define FSH @"varying lowp vec2 TexCoordOut;\
    \
    uniform sampler2D SamplerY;\
    uniform sampler2D SamplerU;\
    uniform sampler2D SamplerV;\
    \
    void main(void)\
    {\
    mediump vec3 yuv;\
    lowp vec3 rgb;\
    \
    yuv.x = texture2D(SamplerY, TexCoordOut).r;\
    yuv.y = texture2D(SamplerU, TexCoordOut).r - 0.5;\
    yuv.z = texture2D(SamplerV, TexCoordOut).r - 0.5;\
    \
    rgb = mat3( 1,       1,         1,\
    0,       -0.39465,  2.03211,\
    1.13983, -0.58060,  0) * yuv;\
    \
    gl_FragColor = vec4(rgb, 1);\
    \
    }"

    #define VSH @"attribute vec4 position;\
    attribute vec2 TexCoordIn;\
    varying vec2 TexCoordOut;\
    \
    void main(void)\
    {\
    gl_Position = position;\
    TexCoordOut = TexCoordIn;\
    }"

    /**
     加載著色器
     */
    - (void)loadShader{
        /**
         1 編譯著色
         */
        GLuint vertexShader = [self compileShader:VSH withType:GL_VERTEX_SHADER];
        GLuint fragmentShader = [self compileShader:FSH withType:GL_FRAGMENT_SHADER];
        
        /**
         2
         */
        //創建程序容器
        _program = glCreateProgram();
        //綁定shader到program
        glAttachShader(_program, vertexShader);
        glAttachShader(_program, fragmentShader);
        
        /**
         綁定需要在link之前
         */
        //把頂點屬性索引綁定到頂點屬性名
        glBindAttribLocation(_program, ATTRIB_VERTEX, "position");
        glBindAttribLocation(_program, ATTRIB_TEXTURE, "TexCoordIn");
        
        //鏈接
        glLinkProgram(_program);
        
        /**
         3
         */
        GLint linkSuccess;
        //查詢相關信息,并將數據返回到linkSuccess
        glGetProgramiv(_program, GL_LINK_STATUS, &linkSuccess);
        if (linkSuccess == GL_FALSE) {
            GLchar messages[256];
            glGetProgramInfoLog(_program, sizeof(messages), 0, &messages[0]);
            NSString *messageString = [NSString stringWithUTF8String:messages];
            NSLog(@"<<<<著色器連接失敗 %@>>>", messageString);
            //exit(1);
        }
        
        if (vertexShader)
        /*
         釋放內存不能立刻刪除
         If a shader object to be deleted is attached to a program object, it will be flagged for deletion, but it will not be deleted until it is no longer attached to any program object…
         shader 刪除該著色器對象(如果一個著色器對象在刪除前已經鏈接到程序對象中,那么當執行glDeleteShader函數時不會立即被刪除,而是該著色器對象將被標記為刪除,器內存被釋放一次,它不再鏈接到其他任何程序對象)
         */
            glDeleteShader(vertexShader);
        if (fragmentShader)
            glDeleteShader(fragmentShader);
    }

    /**
     編譯著色代碼,使用著色器
     
     @param shaderString 代碼
     @param shaderType   類型
     
     @return 成功返回著色器,失敗返回-1
     */
    - (GLuint)compileShader:(NSString*)shaderString withType:(GLenum)shaderType
    {
        
        /**
         1
         */
        if (!shaderString) {
            //        NSLog(@"Error loading shader: %@", error.localizedDescription);
            exit(1);
        }
        else
        {
            //NSLog(@"shader code-->%@", shaderString);
        }
        
        /**
         2 分別創建一個頂點著色器對象和一個片段著色器對象
         */
        GLuint shaderHandle = glCreateShader(shaderType);
        
        /**
         3 分別將頂點著色程序的源代碼字符數組綁定到頂點著色器對象,將片段著色程序的源代碼字符數組綁定到片段著色器對象
         */
        const char * shaderStringUTF8 = [shaderString UTF8String];
        int shaderStringLength = [shaderString length];
        glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
        
        /**
         4 分別編譯頂點著色器對象和片段著色器對象
         */
        glCompileShader(shaderHandle);
        
        /**
         5
         */
        GLint compileSuccess;
        
        //獲取編譯情況
        glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
        if (compileSuccess == GL_FALSE) {
            GLchar messages[256];
            glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
            NSString *messageString = [NSString stringWithUTF8String:messages];
            NSLog(@"%@", messageString);
            exit(1);
        }
        
        return shaderHandle;
    }

    #pragma mark -  接口

    /**
     設置大小
     
     @param width  界面寬
     @param height 界面高
     */
    -(void)setVideoSize:(GLuint)width height:(GLuint)height{
        
        //給寬高賦值
        _videoH = height;
        _videoW = width;
        
        //開辟內存空間
        //為什么乘1.5而不是1: width * hight =Y(總和) U = Y / 4   V = Y / 4
        void *blackData = malloc(width * height * 1.5);
        
        if (blackData) {
            
            /**
             對內存空間清零,作用是在一段內存塊中填充某個給定的值,它是對較大的結構體或數組進行清零操作的一種最快方法
             
             @param __b#>   源數據 description#>
             @param __c#>   填充數據 description#>
             @param __len#> 長度 description#>
             
             @return <#return value description#>
             */
            memset(blackData, 0x0, width * height * 1.5);
        }
        /*
         Apple平臺不允許直接對Surface進行操作.這也就意味著在Apple中,不能通過調用eglSwapBuffers函數來直接實現渲染結果在目標surface上的更新.
         在Apple平臺中,首先要創建一個EAGLContext對象來替代EGLContext (不能通過eglCreateContext來生成), EAGLContext的作用與EGLContext是類似的.
         然后,再創建相應的Framebuffer和Renderbuffer.
         Framebuffer象一個Renderbuffer集(它可以包含多個Renderbuffer對象).
         
         Renderbuffer有三種:  color Renderbuffer, depth Renderbuffer, stencil Renderbuffer.
         
         渲染結果是先輸出到Framebuffer中,然后通過調用context的presentRenderbuffer,將Framebuffer上的內容提交給之前的CustumView.
         */
        
        //設置當前上下文
        [EAGLContext setCurrentContext:_glContext];
        
        
        /*
         target —— 紋理被綁定的目標,它只能取值GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D或者GL_TEXTURE_CUBE_MAP;
         texture —— 紋理的名稱,并且,該紋理的名稱在當前的應用中不能被再次使用。
         glBindTexture可以讓你創建或使用一個已命名的紋理,調用glBindTexture方法,將target設置為GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D或者GL_TEXTURE_CUBE_MAP,并將texture設置為你想要綁定的新紋理的名稱,即可將紋理名綁定至當前活動紋理單元目標。當一個紋理與目標綁定時,該目標之前的綁定關系將自動被打破。紋理的名稱是一個無符號的整數。在每個紋理目標中,0被保留用以代表默認紋理。紋理名稱與相應的紋理內容位于當前GL rendering上下文的共享對象空間中。
         */
        
        //綁定Y紋理
        glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXY]);
        
        
        /**
         根據像素數據,加載紋理
         
         @param target#>         指定目標紋理,這個值必須是GL_TEXTURE_2D。 description#>
         @param level#>          執行細節級別。0是最基本的圖像級別,n表示第N級貼圖細化級別 description#>
         @param internalformat#> 指定紋理中的顏色格式。可選的值有GL_ALPHA,GL_RGB,GL_RGBA,GL_LUMINANCE, GL_LUMINANCE_ALPHA 等幾種。 description#>
         @param width#>          紋理的寬度 description#>
         @param height#>         高度 description#>
         @param border#>         紋理的邊框寬度,必須為0 description#>
         @param format#>         像素數據的顏色格式, 不需要和internalformatt取值必須相同。可選的值參考internalformat。 description#>
         @param type#>           指定像素數據的數據類型。可以使用的值有GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT_5_6_5,GL_UNSIGNED_SHORT_4_4_4_4,GL_UNSIGNED_SHORT_5_5_5_1等。 description#>
         @param pixels#>         指定內存中指向圖像數據的指針 description#>
         
         @return <#return value description#>
         */
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED_EXT, width, height, 0, GL_RED_EXT, GL_UNSIGNED_BYTE, blackData);
        
        //綁定U紋理
        glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXU]);
        //加載紋理
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED_EXT, width/2, height/2, 0, GL_RED_EXT, GL_UNSIGNED_BYTE, blackData + width * height);
        
        //綁定V數據
        glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXV]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED_EXT, width/2, height/2, 0, GL_RED_EXT, GL_UNSIGNED_BYTE, blackData + width * height * 5 / 4);
        
        //釋放malloc分配的內存空間
        free(blackData);
    }

    /**
     清除畫面
     */
    -(void)clearFrame{
        
        if ([self window])
        {
            [EAGLContext setCurrentContext:_glContext];
            glClearColor(0.0, 0.0, 0.0, 1.0);
            glClear(GL_COLOR_BUFFER_BIT);
            glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
            [_glContext presentRenderbuffer:GL_RENDERBUFFER];
        }
    }

    /**
     顯示YUV數據
     
     @param data YUV數據
     @param w    <#w description#>
     @param h    <#h description#>
     */
    -(void)displayYUV420pData:(void *)data width:(NSInteger)w height:(NSInteger)h{
        
        if (!self.window) {
            return;
        }
        
        //加互斥鎖,防止其他線程訪問
        @synchronized (self) {
            
            if (w != _videoW || h != _videoH) {
                [self setVideoSize:(GLuint)w height:(GLuint)h];
            }
            
            //設置當前上下文
            [EAGLContext setCurrentContext:_glContext];
            
            //綁定
            glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXY]);
            
            /**
             更新紋理
             https://my.oschina.net/sweetdark/blog/175784
             @param target#>  指定目標紋理,這個值必須是GL_TEXTURE_2D。 description#>
             @param level#>   執行細節級別。0是最基本的圖像級別,n表示第N級貼圖細化級別 description#>
             @param xoffset#> 紋理數據的偏移x值 description#>
             @param yoffset#> 紋理數據的偏移y值 description#>
             @param width#>   更新到現在的紋理中的紋理數據的規格寬 description#>
             @param height#>  高 description#>
             @param format#>  像素數據的顏色格式, 不需要和internalformatt取值必須相同。可選的值參考internalformat。 description#>
             @param type#>    顏色分量的數據類型 description#>
             @param pixels#>  指定內存中指向圖像數據的指針 description#>
             
             @return <#return value description#>
             */
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (GLsizei)w, (GLsizei)h, GL_RED_EXT, GL_UNSIGNED_BYTE, data);
            
            glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXU]);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (GLsizei)w/2, (GLsizei)h/2, GL_RED_EXT, GL_UNSIGNED_BYTE, data + w * h);
            
            glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXV]);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (GLsizei)w/2, (GLsizei)h/2, GL_RED_EXT, GL_UNSIGNED_BYTE, data + w * h * 5 / 4);
            
            //渲染
            [self render];
        }
        
    #ifdef DEBUG
        
        GLenum err = glGetError();
        if (err != GL_NO_ERROR)
        {
            printf("GL_ERROR=======>%d\n", err);
        }
        struct timeval nowtime;
        gettimeofday(&nowtime, NULL);
        if (nowtime.tv_sec != _time.tv_sec)
        {
            printf("視頻 %ld 幀率:   %d\n", self.tag, _frameRate);
            memcpy(&_time, &nowtime, sizeof(struct timeval));
            _frameRate = 1;
        }
        else
        {
            _frameRate++;
        }
    #endif
    }

    /**
     渲染
     */
    -(void)render{
        
        //設置上下文
        [EAGLContext setCurrentContext:_glContext];
        
        CGSize size = self.bounds.size;
        
        //把數據顯示在這個視窗上
        glViewport(1, 1, size.width * _viewScale -2, size.height * _viewScale -2);
        
        /*
         我們如果選定(0, 0), (0, 1), (1, 0), (1, 1)四個紋理坐標的點對紋理圖像映射的話,就是映射的整個紋理圖片。如果我們選擇(0, 0), (0, 1), (0.5, 0), (0.5, 1) 四個紋理坐標的點對紋理圖像映射的話,就是映射左半邊的紋理圖片(相當于右半邊圖片不要了),相當于取了一張320x480的圖片。但是有一點需要注意,映射的紋理圖片不一定是“矩形”的。實際上可以指定任意形狀的紋理坐標進行映射。下面這張圖就是映射了一個梯形的紋理到目標物體表面。這也是紋理(Texture)比上一篇文章中記錄的表面(Surface)更加靈活的地方。
         */
        static const GLfloat squareVertices[] = {
            -1.0f, -1.0f,
            1.0f, -1.0f,
            -1.0f,  1.0f,
            1.0f,  1.0f,
        };
        
        
        static const GLfloat coordVertices[] = {
            0.0f, 1.0f,
            1.0f, 1.0f,
            0.0f,  0.0f,
            1.0f,  0.0f,
        };
        
        //更新屬性值
        glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices);
        //開啟定點屬性數組
        glEnableVertexAttribArray(ATTRIB_VERTEX);
        
        
        glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, coordVertices);
        glEnableVertexAttribArray(ATTRIB_TEXTURE);
        
        //繪制
        
        //當采用頂點數組方式繪制圖形時,使用該函數。該函數根據頂點數組中的坐標數據和指定的模式,進行繪制。
        //繪制方式,從數組的哪一個點開始繪制(一般為0),頂點個數
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        
        //將該渲染緩沖區對象綁定到管線上
        glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
        
        //把緩沖區(render buffer和color buffer)的顏色呈現到UIView上
        [_glContext presentRenderbuffer:GL_RENDERBUFFER];
        
    }
    @end
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容