美文网首页IOS个人开发平时生活和工作中的iOS直播
OpenGL ES_手把手教你打造VR全景播放器

OpenGL ES_手把手教你打造VR全景播放器

作者: 酷走天涯 | 来源:发表于2016-08-27 12:25 被阅读9482次

    OpenGL ES _ 入门_01
    OpenGL ES _ 入门_02
    OpenGL ES _ 入门_03
    OpenGL ES _ 入门_04
    OpenGL ES _ 入门_05
    OpenGL ES _ 入门练习_01
    OpenGL ES _ 入门练习_02
    OpenGL ES _ 入门练习_03
    OpenGL ES _ 入门练习_04
    OpenGL ES _ 入门练习_05
    OpenGL ES _ 入门练习_06
    OpenGL ES _ 着色器 _ 介绍
    OpenGL ES _ 着色器 _ 程序
    OpenGL ES _ 着色器 _ 语法
    OpenGL ES_着色器_纹理图像
    OpenGL ES_着色器_预处理
    OpenGL ES_着色器_顶点着色器详解
    OpenGL ES_着色器_片断着色器详解
    OpenGL ES_着色器_实战01
    OpenGL ES_着色器_实战02
    OpenGL ES_着色器_实战03

    让学习成为一种习惯
    实战2中,详细介绍了多屏显示的原理和实现过程,今天我们继续我们的OpenGL 旅程!技术再牛逼也要学习! 学习是一件开心的额事情

    学习目标

    打造全景视频,以及VR 眼镜专用的双屏显示框架!

    你应该知道的

    网络截图
    • 全景显示的原理
      通俗的将,好比红色区域就是你的手机屏幕,当你旋转手机的时候,我们球体向相反的方向旋转,这样,你就可以看到球体上的画面了.

    准备工作

    找一个全景视频,添加到项目中去。

    • 实现步骤
      1.创建一个球体模型
      2.获取视频数据的每一帧数据 转换成RGB 格式,渲染到球体上
      3.通过手势的变换,改变球体模型视图矩阵值
      4.如果是VR模式,则通过角度传感器获取用户的行为,调整视图矩阵。

    实现了那些功能

    • 支持普通视频播放
    • 支持全景视频播放
    • 支持VR 双屏显示模式
    • 支持快进,快退
    • 支持播放,暂停
    • 支持暂停广告功能

    核心代码讲解

    如果你想要和我一样,能够从零开始把代码敲出来,请确保自己有OpenGL ES 2.0 的基础知识 和 GLSL 的简单基本知识,如果你不具备这方面的知识,没关系,我已经写好了OpenGL学习教程GLSL教程,请移步开始学习。下面开始我们的内容讲解.

    • 视频采集

    <p>工程中的两个文件 XJVRPlayerViewController.h和XJVRPlayerController.m主要负责视频数据采集,界面布局在XJVRPlayerViewController中可以更改,主要使用AVFoundation框架这部分内容今天咱不讲解,后面我会写关于视频采集的教程</p>

    • 模型创建
      a.全景播放器生成球体的顶点坐标和纹理坐标
      b.普通播放器生成长方形的顶点坐标和纹理坐标
      两个生成函数在OSShere.h中

    • 将数据加载到GPU中去

      // 加载顶点索引数据
       glGenBuffers(1, &_indexBuffer);
       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
       glBufferData(GL_ELEMENT_ARRAY_BUFFER, _numIndices*sizeof(GLushort), _indices, GL_STATIC_DRAW);
      
      // 加载顶点坐标
      glGenBuffers(1, &_vertexBuffer);
      glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
      glBufferData(GL_ARRAY_BUFFER,   numVertices*strideNum*sizeof(GLfloat), _vertices, GL_STATIC_DRAW);
      glEnableVertexAttribArray(GLKVertexAttribPosition);
      glVertexAttribPointer(GLKVertexAttribPosition, strideNum, GL_FLOAT, GL_FALSE, strideNum*sizeof(GLfloat), NULL);
      
      //加载纹理坐标
      glGenBuffers(1, &_textureCoordBuffer);
      glBindBuffer(GL_ARRAY_BUFFER, _textureCoordBuffer);
      glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*2*numVertices, _texCoords, GL_DYNAMIC_DRAW);
      glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
      glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), NULL);
      

    以上函数的具体用法,在之前的教程中都讲过,这里就不赘述了。

    • 着色器程序
      我把着色器分为两种类型,一种是渲染全景视频的,一种是渲染普通视频的,两个没有多大区别,只是在全景着色器中添加了一个视图转换矩阵 (全景着色器:ShadePanorama,普通着色器:ShaderNormal)
      下面给出的是全景的着色器的代码:
      a.顶点着色器

      attribute vec4 position; // 顶点坐标属性
      attribute vec2 texCoord0;// 纹理坐标
      varying  vec2 texCoordVarying;// 片段着色器输入变量,负责获取纹理坐标的值
      uniform mat4 modelViewProjectionMatrix;//视图变换矩阵
      void main (){
      texCoordVarying = texCoord0;
      gl_Position = modelViewProjectionMatrix*position;
      }
      

    b.片段着色器

      precision mediump float;//设置float精度
      varying  vec2 texCoordVarying;
      uniform sampler2D sam2DY;  // 纹理采样器Y
      uniform sampler2D sam2DUV;// 纹理采样器UV
      void main(){
      mediump vec3 yuv;
      lowp vec3 rob;
      // YUV  转RGB 的转换矩阵 
      mediump mat3 convert = mat3(1.164,  1.164, 1.164,
                                0.0, -0.213, 2.112,
                                1.793, -0.533,   0.0);
      yuv.x = texture2D(sam2DY,texCoordVarying).r - (16.0/255.0);
      yuv.yz = texture2D(sam2DUV,texCoordVarying).rg - vec2(0.5, 0.5);
      rgb = convert*yuv;
      gl_FragColor = vec4(rgb,1);
      }
    

    如果想要了解更多关于着色器语言的知识,请猛戳我

    • 创建着色器程序
      创建着色器程序的目的是编译刚才我们编写好的着色器源代码,以及将着色器的变量和我们的应用程序代码相关联

      /**
       *  创建编译shader程序
       *
       *  @param vshName 顶点着色器文件名称
       *  @param fshName 片段着色器文件名称
       */
       -(void)createShaderProgramVertexShaderName:  (NSString*)vshName FragmentShaderName:  (NSString*)fshName{
        self.shaderManager = [[OSShaderManager alloc]init];
        // 编译连个shader 文件
        GLuint vertexShader,fragmentShader;
        NSURL *vertexShaderPath = [[NSBundle mainBundle]URLForResource:vshName withExtension:@"vsh"];
        NSURL *fragmentShaderPath = [[NSBundle mainBundle]URLForResource:fshName withExtension:@"fsh"];
        if (![self.shaderManager compileShader:&vertexShader type:GL_VERTEX_SHADER URL:vertexShaderPath]||!  [self.shaderManager compileShader:&fragmentShader type:GL_FRAGMENT_SHADER URL:fragmentShaderPath]){
          return ;
        }
      
        // 注意获取绑定属性要在连接程序之前 location 随便你写,如果你随便写请记住他,后面要用到
        [self.shaderManager   bindAttribLocation:GLKVertexAttribPosition   andAttribName:"position"];
        [self.shaderManager bindAttribLocation:GLKVertexAttribTexCoord0 andAttribName:"texCoord0"];
      
        // 将编译好的两个对象和着色器程序进行连接
        if(![self.shaderManager linkProgram]){
          [self.shaderManager deleteShader:&vertexShader];
          [self.shaderManager deleteShader:&fragmentShader];
        }
        _textureBufferY = [self.shaderManager   getUniformLocation:"sam2DY"];
        _textureBufferUV = [self.shaderManager getUniformLocation:"sam2DUV"];
        _modelViewProjectionMatrixIndex = [self.shaderManager getUniformLocation:"modelViewProjectionMatrix"];
      
        [self.shaderManager detachAndDeleteShader:&vertexShader];
        [self.shaderManager detachAndDeleteShader:&fragmentShader];
        // 启用着色器
        [self.shaderManager useProgram];
        }
      

    // 上面的OSShaderManager 这个类,我把着色器程序编译链接的一些方法简单的封装了一下,具体的方向看下面

        /**
         *  编译shader程序
         *  @param shader shader名称
         *  @param type   shader 类型
         *  @param URL    shader 本地路径
         *  @return 是否编译成功
       */
        - (BOOL)compileShader:(GLuint *)shader type:(GLenum)type URL:(NSURL *)URL;
       /**
       *  连接程序
       *  @return 连接程序是否成功
       */
      - (BOOL)linkProgram;
      /**
       *  验证程序是否成功
       *  @param prog 程序标示
       *  @return 返回是否成功标志
       */
      - (BOOL)validateProgram;
      /**
       *  绑定着色器的属性
       *  @param index 属性在shader 程序的索引位置
       *  @param name  属性名称
       */
      - (void)bindAttribLocation:(GLuint)index andAttribName:  (GLchar*)name;
      /**
       *  删除shader
       */
      - (void)deleteShader:(GLuint*)shader;
      /**
       *  获取属性值索引位置
       *  @param name 属性名称
       *  @return 返回索引位置
       */
      - (GLint)getUniformLocation:(const GLchar*) name;
      /**
       * 释放, 删除shader
       *  @param shader 着色器名称
       */
      -(void)detachAndDeleteShader:(GLuint*)shader;
      /**
       *  使用程序
       */
      -(void)useProgram;
    

    方法的具体实现请阅读工程文件

    • 纹理采样器指向

      glUniform1i(_textureBufferY, 0); // 0 代表GL_TEXTURE0
      glUniform1i(_textureBufferUV, 1); // 1 代表GL_TEXTURE1
      

    在这里我有必要提醒你,这两个方法,一定要放在着色器程序链接成功之后,不然你调用这个两个方法,没有效果。

    • 如何将YUV 数据分离,并且加载到两个着色器中去, 这里我们又要用到之前我们使用过的框架了CoreVideo. 干涉么的呢,专门处理我们的像素数据的。我们从视频采集到的视频是CVPixelBufferRef 类型的
      下面我们先看一下我们像素数据的格式

        <CVPixelBuffer 0x7fa27962c9c0 width=2048     height=1024 pixelFormat=420v iosurface=0x0 planes=2>
        <Plane 0 width=2048 height=1024 bytesPerRow=2048>
        <Plane 1 width=1024 height=512 bytesPerRow=2048>
        <attributes=<CFBasicHash 0x7fa279623910 [0x10296ba40]>{type = immutable dict, count = 4,
        entries =>
      1 : <CFString 0x102d183b8 [0x10296ba40]>{contents = "PixelFormatType"} = <CFArray 0x7fa27c414bc0   [0x10296ba40]>{type = mutable-small, count = 1, values = (
      0 : <CFNumber 0xb000000343230763 [0x10296ba40]>{value = +875704438, type = kCFNumberSInt64Type}
      )}
      2 : <CFString 0x102d17e78 [0x10296ba40]>{contents = "Height"} = <CFNumber 0xb000000000004002 [0x10296ba40]>{value = +1024, type = kCFNumberSInt32Type}
      5 : <CFString 0x102d17d38 [0x10296ba40]>{contents = "PropagatedAttachments"} = <CFBasicHash 0x7fa27c51c590 [0x10296ba40]>{type = mutable dict, count = 4,
        entries =>
      0 : <CFString 0x102d18058 [0x10296ba40]>{contents = "CVImageBufferYCbCrMatrix"} = <CFString 0x102d18098 [0x10296ba40]>{contents = "ITU_R_601_4"}
      1 : <CFString 0x102d181b8 [0x10296ba40]>{contents = "CVImageBufferTransferFunction"} = <CFString 0x102d18078 [0x10296ba40]>{contents = "ITU_R_709_2"}
      2 : <CFString 0x106eadc88 [0x10296ba40]>{contents = "ColorInfoGuessedBy"} = <CFString 0x106eadca8 [0x10296ba40]>{contents = "VideoToolbox"}
      5 : <CFString 0x102d18138 [0x10296ba40]>{contents = "CVImageBufferColorPrimaries"} = <CFString 0x102d18178 [0x10296ba40]>{contents = "SMPTE_C"}
          }
      6 : <CFString 0x102d17e58 [0x10296ba40]>{contents = "Width"} = <CFNumber 0xb000000000008002 [0x10296ba40]>{value = +2048, type = kCFNumberSInt32Type}
          }
           propagatedAttachments=<CFBasicHash 0x7fa27962caa0 [0x10296ba40]>{type = mutable dict, count = 10,
          entries =>
      0 : <CFString 0x106eadc88 [0x10296ba40]>{contents = "ColorInfoGuessedBy"} = <CFString 0x106eadca8 [0x10296ba40]>{contents = "VideoToolbox"}
      1 : <CFString 0x102d18058 [0x10296ba40]>{contents = "CVImageBufferYCbCrMatrix"} = <CFString 0x102d18098 [0x10296ba40]>{contents = "ITU_R_601_4"}
      2 : <CFString 0x102d17ed8 [0x10296ba40]>{contents = "CVFieldCount"} = <CFNumber 0xb000000000000012 [0x10296ba40]>{value = +1, type = kCFNumberSInt32Type}
      3 : <CFString 0x102d17f98 [0x10296ba40]>{contents = "CVPixelAspectRatio"} = <CFBasicHash 0x7fa279728c10 [0x10296ba40]>{type = immutable dict, count = 2,
          entries =>
      1 : <CFString 0x102d17fb8 [0x10296ba40]>{contents = "HorizontalSpacing"} = <CFNumber 0xb000000000000012 [0x10296ba40]>{value = +1, type = kCFNumberSInt32Type}
      2 : <CFString 0x102d17fd8 [0x10296ba40]>{contents = "VerticalSpacing"} = <CFNumber 0xb000000000000012 [0x10296ba40]>{value = +1, type = kCFNumberSInt32Type}
          }
      4 : <CFString 0x102d17d78 [0x10296ba40]>{contents = "QTMovieTime"} = <CFBasicHash 0x7fa27c51db40 [0x10296ba40]>{type = immutable dict, count = 2,
          entries =>
      0 : <CFString 0x102d17d98 [0x10296ba40]>{contents = "TimeValue"} = <CFNumber 0xb000000000000003 [0x10296ba40]>{value = +0, type = kCFNumberSInt64Type}
      1 : <CFString 0x102d17db8 [0x10296ba40]>{contents = "TimeScale"} = <CFNumber 0xb000000000075302 [0x10296ba40]>{value = +30000, type = kCFNumberSInt32Type}
            }
      5 : <CFString 0x102d18138 [0x10296ba40]>{contents = "CVImageBufferColorPrimaries"} = <CFString 0x102d18178 [0x10296ba40]>{contents = "SMPTE_C"}
      8 : <CFString 0x102d181b8 [0x10296ba40]>{contents = "CVImageBufferTransferFunction"} = <CFString 0x102d18078 [0x10296ba40]>{contents = "ITU_R_709_2"}
      9 : <CFString 0x102d18318 [0x10296ba40]>{contents = "CVImageBufferChromaSubsampling"} = <CFString 0x102d18278 [0x10296ba40]>{contents = "TopLeft"}
      10 : <CFString 0x102d18218 [0x10296ba40]>{contents = "CVImageBufferChromaLocationBottomField"} = <CFString 0x102d18338 [0x10296ba40]>{contents = "4:2:0"}
      12 : <CFString 0x102d181f8 [0x10296ba40]>{contents = "CVImageBufferChromaLocationTopField"} = <CFString 0x102d18338 [0x10296ba40]>{contents = "4:2:0"}
          }
         nonPropagatedAttachments=<CFBasicHash 0x7fa27962ca60 [0x10296ba40]>{type = mutable dict, count = 0,
        entries =>
      }
      >
      

    我们从上面的日志输出找到了下面的东西

        <CVPixelBuffer 0x7fa27962c9c0 width=2048     height=1024 pixelFormat=420v iosurface=0x0 planes=2>
        <Plane 0 width=2048 height=1024 bytesPerRow=2048>
        <Plane 1 width=1024 height=512 bytesPerRow=2048>
    

    我们能得到的信息是:
    像素格式: 420v
    数据通道: 2 个
    通道1: width=2048 height=1024
    通道2: width=1024 height=512

    从上面信息可以得出我们数据的排列方式为YY....YY....UV.....UV,
    2048\1024 个Y 数据,1024\512 从 bytesPerRow 可以看出每个Y、U、V 各占一个字节.
    接下来就是如何将数据加载到我们的纹理缓冲区去了

    CVReturn CVOpenGLESTextureCacheCreateTextureFromImage(
    CFAllocatorRef CV_NULLABLE allocator,
    CVOpenGLESTextureCacheRef CV_NONNULL textureCache,
    CVImageBufferRef CV_NONNULL sourceImage,
    CFDictionaryRef CV_NULLABLE textureAttributes,
    GLenum target,
    GLint internalFormat,
    GLsizei width,
    GLsizei height,
    GLenum format,
    GLenum type,
    size_t planeIndex,
    CV_RETURNS_RETAINED_PARAMETER CVOpenGLESTextureRef CV_NULLABLE * CV_NONNULL textureOut ) 
    

    这个函数作用是: 通过CVImageBufferRef 创建一个纹理对象
    allocator : 写默认值就可以了 kCFAllocatorDefault
    textureCache:我们需要手动创建一个纹理缓冲对象,
    sourceImage:传我们的CVImageBufferRef 数据
    textureAttributes:纹理属性,可以为NULL
    target:纹理的类型(GL_TEXTURE_2D 和GL_RENDERBUFFER)
    internalFormat:数据格式,就是这个数据步伐的意思
    width:纹理的高度
    height : 纹理的长度
    format: 像素数据的格式
    type: 数据类型
    planeIndex: 通道索引

    接下来看我们的代码:

     // 启用纹理缓冲区0
    glActiveTexture(GL_TEXTURE0);
    err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                       _videoTextureCache,
                                                       pixelBuffer,
                                                       NULL,
                                                       GL_TEXTURE_2D,
                                                       GL_RED_EXT,
                                                       width,
                                                       height,
                                                       GL_RED_EXT,
                                                       GL_UNSIGNED_BYTE,
                                                       0,
                                                       &_lumaTexture);
    if (err) {
        NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
        
    }
    
    glBindTexture(CVOpenGLESTextureGetTarget(_lumaTexture), CVOpenGLESTextureGetName(_lumaTexture));
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    // UV-plane.
    // 启用纹理缓冲区1
    glActiveTexture(GL_TEXTURE1);
    err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                       _videoTextureCache,
                                                       pixelBuffer,
                                                       NULL,
                                                       GL_TEXTURE_2D,
                                                       GL_RG_EXT,
                                                       width /2,
                                                       height /2,
                                                       GL_RG_EXT,
                                                       GL_UNSIGNED_BYTE,
                                                       1,
                                                       &_chromaTexture);
    if (err) {
        NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
    }
    
    
    glBindTexture(CVOpenGLESTextureGetTarget(_chromaTexture), CVOpenGLESTextureGetName(_chromaTexture));
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    

    GL_RED_EXT 代表 1位数据 GL_RG_EXT 代表2位数据 。UV 就是两位数据 所以我们选择GL_RG_EXT。

    刚才说了,参数中需要一个纹理缓冲TextureCacha,接下来我们就自己创建一个.

      CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, self.eagContext, NULL, &_videoTextureCache);
    

    以上基本的工作都做完了,接下来,我们就只剩下显示了

    • 渲染绘制

        // 清除颜色缓冲区
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        if (_isVR){
        // 渲染双屏
        glViewport(0, 0, self.view.bounds.size.width, self.view.bounds.size.height*2);
        glDrawElements(GL_TRIANGLES, _numIndices, GL_UNSIGNED_SHORT, 0);
        glViewport(self.view.bounds.size.width, 0, self.view.bounds.size.width, self.view.bounds.size.height*2);
        glDrawElements(GL_TRIANGLES, _numIndices, GL_UNSIGNED_SHORT, 0);
        }else{
        // 渲染单屏
        glViewport(0, 0, self.view.bounds.size.width*2, self.view.bounds.size.height*2);
        glDrawElements(GL_TRIANGLES, _numIndices, GL_UNSIGNED_SHORT, 0);
        }
      

    到这里,视频已经可以显示了。

    • 视图矩阵初始化

      -(void)initModelViewProjectMatrix{
      // 创建投影矩阵
      float aspect = fabs(self.view.bounds.size.width / self.view.bounds.size.height);
      _projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(OSVIEW_CORNER), aspect, 0.1f, 400.0f);
      _projectionMatrix = GLKMatrix4Rotate(_projectionMatrix, ES_PI, 1.0f, 0.0f, 0.0f);
      
      // 创建模型矩阵
      _modelViewMatrix = GLKMatrix4Identity;
      float scale = OSSphereScale;
      _modelViewMatrix = GLKMatrix4Scale(_modelViewMatrix, scale, scale, scale);
      
      // 最终传入到GLSL中去的矩阵
      _modelViewProjectionMatrix = GLKMatrix4Multiply(_projectionMatrix, _modelViewMatrix);
      glUniformMatrix4fv(_modelViewProjectionMatrixIndex, 1, GL_FALSE, _modelViewProjectionMatrix.m);
      }
      
    • 全景单屏模式
      手势操纵矩阵

      - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
      if(self.isVR || self.vedioType == OSNormal ) return;
      UITouch *touch = [touches anyObject];
      float distX = [touch locationInView:touch.view].x - [touch previousLocationInView:touch.view].x;
      float distY = [touch locationInView:touch.view].y - [touch previousLocationInView:touch.view].y;
      distX *= -0.005;
      distY *= -0.005;
      self.fingerRotationX += distY *  OSVIEW_CORNER / 100;
      self.fingerRotationY -= distX *  OSVIEW_CORNER / 100;
      _modelViewMatrix = GLKMatrix4Identity;
      float scale = OSSphereScale;
      _modelViewMatrix = GLKMatrix4Scale(_modelViewMatrix, scale, scale, scale);
      _modelViewMatrix = GLKMatrix4RotateX(_modelViewMatrix, self.fingerRotationX);
      _modelViewMatrix = GLKMatrix4RotateY(_modelViewMatrix, self.fingerRotationY);
      _modelViewProjectionMatrix = GLKMatrix4Multiply(_projectionMatrix, _modelViewMatrix);
      glUniformMatrix4fv(_modelViewProjectionMatrixIndex, 1, GL_FALSE, _modelViewProjectionMatrix.m);
      }
      
      - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent   *)event {
        if (self.isVR || self.vedioType == OSNormal) return;
        for (UITouch *touch in touches) {
        [self.currentTouches removeObject:touch];
        }
      }
      - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
      for (UITouch *touch in touches) {
        [self.currentTouches removeObject:touch];
      }
      }
      
    • 全景 VR模式
      使用角度传感器

      -(void)startMotionManager{
      self.motionManager = [[CMMotionManager alloc]init];
      self.motionManager.deviceMotionUpdateInterval = 1.0 / 60.0;
      self.motionManager.gyroUpdateInterval = 1.0f / 60;
      self.motionManager.showsDeviceMovementDisplay = YES;
      [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical];
      self.referenceAttitude = nil;
      [self.motionManager startGyroUpdatesToQueue: [[NSOperationQueue alloc]init] withHandler:^(CMGyroData * _Nullable gyroData, NSError * _Nullable error) {
       if(self.isVR) {
        
          [self calculateModelViewProjectMatrixWithDeviceMotion:self.motionManager.deviceMotion];
        }
        
      }];
      self.referenceAttitude = self.motionManager.deviceMotion.attitude;
      }
      -(void)calculateModelViewProjectMatrixWithDeviceMotion:(CMDeviceMotion*)deviceMotion{
      
      _modelViewMatrix = GLKMatrix4Identity;
      float scale = OSSphereScale;
      _modelViewMatrix = GLKMatrix4Scale(_modelViewMatrix, scale, scale, scale);
        if (deviceMotion != nil) {
            CMAttitude *attitude = deviceMotion.attitude;
            
            if (self.referenceAttitude != nil) {
                [attitude multiplyByInverseOfAttitude:self.referenceAttitude];
                
            } else {
                self.referenceAttitude = deviceMotion.attitude;
            }
            
            float cRoll = attitude.roll;
            float cPitch = attitude.pitch;
            
            _modelViewMatrix = GLKMatrix4RotateX(_modelViewMatrix, -cRoll);
            _modelViewMatrix = GLKMatrix4RotateY(_modelViewMatrix, -cPitch*3);
      
            _modelViewProjectionMatrix = GLKMatrix4Multiply(_projectionMatrix, _modelViewMatrix);
            // 下边这个方法必须在主线程中完成.
            dispatch_async(dispatch_get_main_queue(), ^{
                 glUniformMatrix4fv(_modelViewProjectionMatrixIndex, 1, GL_FALSE, _modelViewProjectionMatrix.m);
            });
        }}
      

    操作矩阵这里,暂时不想讲,后面我会专门来讲矩阵变换和角度传感器的使用,因为这两个东西在游戏和VR,还是AR的世界,都太重要了。今天先说的这里,给几张展示图欣赏一下。

    全景模式下
    普通视频 普通视频双屏展示 全景视频VR模式 全景.gif

    需要代码在这里这里

    全景播放器-实现方案2

    使用SceneKit 也可以实现全景播放器,需要了解的朋友请查看这里

    加群了

    相关文章

      网友评论

      • logozph:请问下,怎么解决vr视频平行转动90度导致图像上下颠倒呢?
      • 7e70e3784a48:畸变怎么解决呢?
        酷走天涯:@flyiron 你先看看书把基础学好了!然后到群里讨论
      • b95f0ca6ca78:正是在下所需学习的东西,感谢分享!
      • b54f4d9e5481:我点击全景播放,反复点击十次。然后就会抱解码的错误。第二次进入画面有重叠,需要如何修改呢?
        酷走天涯:@冰_0af4 比较大,可以考虑scenekit
        b54f4d9e5481:@酷走天涯 公司需要在手机端播放VR视频,目前使用的utovr分辨率有限制,所以在找有没有合适。要是自己开发一个VR播放器工程量大吗?对这方面不太了解。
        酷走天涯:@冰_0af4 你要做全景吗?可以考虑scenekit
      • akali:我怎么修改纹理坐标才能只贴到一半的球面上啊,因为我的视频源只是半景的视频
      • akali:楼主,能解释下你的普通播放器顶点数组是怎么索引的吗
      • akali:不能直接从视频里拿出RGB格式的吗 ,为什么非要用YUV
        akali:@酷走天涯 我指定yuv和RGB转换公式
        YUV与RGB相互转换的公式如下(RGB取值范围均为0-255):
        Y = 0.299R + 0.587G + 0.114B
        U = -0.147R - 0.289G + 0.436B
        V = 0.615R - 0.515G - 0.100B
        R = Y + 1.14V
        G = Y - 0.39U - 0.58V
        B = Y + 2.03U
        这样,你shader代码里是怎样操作的没看懂
        酷走天涯:@akali 你先把yuv和rgb对应的关系搞清楚
        akali:使用cv是因为cv对纹理的创建和缓冲有优化,但是我还是没理解在shader中sam2DY和sam2DUV2个纹理里面代表什么信息,怎么减一下就得到了YUV格式的
      • akali:楼主,看了你的demo,感觉画面变形了,是因为球半径太小的缘故吗,http://www.utovr.com/,你看下这个sdk的效果,怎么把球半径变大一点呢
        akali:变形是因为glViewport窗口跟视频宽高比例不对应,改一下窗口大小就不会变形了
      • 83b39b94597a:太好了,很感谢!
      • 豆宝的老公:回去研究
      • 14de75cd4f5f:我想做到像UtoVR的镜头移动,该怎么实现呢
      • 范少:楼主请问3d模型有什么解析库吗,能解析出顶点,法线等数组,还能解析出动画的
        酷走天涯:@范少 不太清楚
      • blue_lights:楼主你好,这个全景视频播放器里添加3d模型有好的思路么
        酷走天涯:@想象的产儿 有问题就交流探讨
        blue_lights:@酷走天涯 恩,好的,正在看你的代码
        酷走天涯:@想象的产儿 全景模型其实是一个球体模型,你把你的3d 模型放在这个球体里面,然后通过混合,就可以呈现出来了。你可以试试
      • akali:楼主,对这两个纹理进行合成之后,怎么再转回CVPixelBuffer啊
        酷走天涯:@akali 到群里来讨论吧!
      • c7d69b3fd2fb:代码Github找不到了,01
        c7d69b3fd2fb:@酷走天涯 说下你Github代码仓库地址吧
        酷走天涯:@Curry1988 我删了,你在我仓库去找
      • 4118d10da922:很详细呀!
        酷走天涯:@伊莎贝拉UID 必须的嘛!不然同行看不懂
      • 落影loyinglin:还是你写的详细。
        酷走天涯:@落影loyinglin 公司最近没啥事干

      本文标题:OpenGL ES_手把手教你打造VR全景播放器

      本文链接:https://www.haomeiwen.com/subject/yvnnsttx.html