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

article/2025/8/22 5:18:42

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

1594482-5d0b713d064e5fd8.jpg
让学习成为一种习惯

实战2中,详细介绍了多屏显示的原理和实现过程,今天我们继续我们的OpenGL 旅程!技术再牛逼也要学习!

1594482-b48c01309fc13518.PNG
学习是一件开心的额事情

学习目标

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

你应该知道的

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

准备工作

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

  • 实现步骤
    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;  // 纹理采样器Yuniform sampler2D sam2DUV;// 纹理采样器UVvoid 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的世界,都太重要了。今天先说的这里,给几张展示图欣赏一下。

1594482-da4c0d320a8d1295.jpg
全景模式下

1594482-e165bd2f3e33b7cd.jpg
普通视频
1594482-2866b90b20f898fe.jpg
普通视频双屏展示
1594482-709ec9fdcf23e05c.jpg
全景视频VR模式
1594482-167695d33c966c22.gif
全景.gif

需要代码在这里和这里

全景播放器-实现方案2

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

1594482-094fac1d6e8803a4.png
加群了

http://chatgpt.dhexx.cn/article/MXV2PurL.shtml

相关文章

播放全景视频【一】:用unity Video Player视频播放器来播放360全景视频

先上图为敬 本文测试环境&#xff1a; Win10 Unity 2020.3.40 Pico G2 4k VR一体机 一、使用Video Player心理负担比较小 使用Unity自带的【视频播放器&#xff08;Video Player&#xff09;】来播放360全景视频的【好处】&#xff1a; 1、控制逻辑与2D视频相同&#xff0c…

利用FFmpeg和OpenGL ES 实现 3D 全景播放器

前言 我们已经利用 FFmpeg OpenGLES OpenSLES 实现了一个多媒体播放器&#xff0c;本文将基于此播放器实现一个酷炫的 3D 全景播放器。 全景播放器原理 全景视频是由多台摄像机在一个位置同时向四面八方拍摄&#xff0c;最后经过后期拼接处理生成的。 用普通的多媒体播放器播…

航空客运订票系统(C语言,软件用的DEV)

这两天整理之前的作业代码&#xff0c;把自己一点一点敲出来的系统又看了一下&#xff0c;挑几个发出来供大家参考。想要源码、报告可以找我啦&#xff0c;代码的注释之前写的都是非常详细的&#xff01; 但是不是无偿的啦&#xff08;不坑&#xff0c;一杯奶茶喽&#xff0c;不…

数据结构_航空客运订票系统(C实现)

文章目录 总述代码粗糙版修理版 还可以修正的点:余票不足时仍然提示还剩下几张(而不是直接拒绝该用户的订票操作)对于购票者的id 不单单是说约定一个可以不重复的主键(命名规则),而且还要辅以必要的检查违约功能 总述 在这里插入代码片 1&#xff0e; 问题描述&#xff1a;(题…

航空机票订票系统

项目介绍 主要功能是使订票系统可以录入航班情况&#xff0c;查询某个航线的情况、办理订票、办理退票、修改航班信息、查询订票信息等。完成此系统&#xff0c;需要综合运用数据结构课程中学到的几种典型数据结构&#xff0c;以及程序设计语言&#xff08;C语言&#xff09;&…

【数据结构应用】航空客运订票系统

目录 前言 一、作业要求介绍 二、各个函数的实现 1.头文件总结需要的功能 &#xff08;1&#xff09;结构体的定义 &#xff08;2&#xff09;各个功能的函数 2.各个函数的具体实现 &#xff08;1&#xff09;初始化 &#xff08;2&#xff09;打印航班信息表 &#xff08;4&…

Java实现航空机票订票系统

1、要求&#xff1a; &#xff08;1&#xff09;设计每条航线所涉及的信息&#xff0c;如终点站名、航班号、飞机号、飞机周日&#xff08;星期几&#xff09;、乘员定额、余票量、订定票的客户名单&#xff08;包括姓名、订票量、舱位等级1&#xff0c;2或3&#xff09;等&…

c语言航空订票系统程序设计,C语言航空订票系统

C语言航空订票系统 这 是 一 篇 用 C 语 言 编 写 的 航 空 订 票 系 统 的 论 文 。 该 系 统 使 用 的 是十 字 链 表 结 构 &#xff0c; 包 含 有 订 票 &#xff0c; 退 票 &#xff0c; 录 入 航 班 信 息 &#xff0c; 查 询 航 班 余 票 &#xff0c; 查询 个 人 订 票…

航空订票系统C++课程设计

航空订票系统 项目实践完整源码 前言一、功能演示二、代码总结 提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 编写程序模拟航空订票系统&#xff0c;要求实现以下功能&#xff1a; ① 允许增、删、改航班信息&#…

数据结构课设-航空客运订票系统(C语言实现)

航空客运订票系统&#xff08;C语言实现&#xff09; 系统框架已完成功能用户功能管理员功能其他 运行结果管理员添加航班客户订票添加候补客户退票 代码 系统框架 已完成功能 用户功能 查询航线&#xff1a;根据旅客提出的终点站名输出航班的信息。订票业务&#xff1a;根据…

C++课程设计:航空客运订票系统

航空客运订票系统 选题背景 方案论证 过程论述 运行结果 完整代码 选题背景 ①背景: 现在人们更多的使用飞机作为出行交通工具&#xff0c;因此机票票务市场也在快速发展。国内外航空事业在飞速发展&#xff0c;各航空公司对票务管理的要求也在不断的提高&#xff0c;对…

基于ssm的航空订票系统

基于ssm的航空订票系统 一、技术栈 ​ 前端 ​ vue全家桶、element-ui组件库、moment.js插件 ​ 后端 ​ springboot springmvc mybatis 二、功能描述 本系统是基于B/S架构的航空订票系统 系统分为三大用户–乘客、航空公司、后台管理员&#xff0c;本次课程设计主要实现…

课程设计之航空客运订票系统

/***************************************************** * 版权所有&#xff08;C&#xff09;2016&#xff0c;王力源 * *文件名称&#xff1a;A.C 航空售票系统 *文件标识&#xff1a;无 *内容摘要&#xff1a;航空售票系统 *其他说明&#xff1a;无 *当前版本&#xff1…

航空客运订票系统(数据结构课设)

前言&#xff1a; 广工数据结构课设&#xff0c;基本需求和选做内容以及一些小扩展均已实现&#xff0c;此博客仅展示一部分&#xff0c;实验报告文档、源代码和可运行程序&#xff08;.exe文件&#xff09;等可以去我的github或者码云上下载&#xff0c;如果对您有帮助&#…

python-数据结构-大学生-航空订票系统

python-数据结构-大学生-航空订票系统 1.问题描述及任务描述 1.1问题描述 航空订票系统&#xff1a;可以实现航空客运订票的主要业务活动 基本要求&#xff1a; &#xff08;1&#xff09;航线管理&#xff1a;每条航线所涉及的信息由&#xff1a;终点站名、航班号、飞机号、…

C语言航空订票系统课程设计

目录 1.设计目的&#xff1a; 2总体设计和功能&#xff1a; 3.菜单设计 4.各功能代码详解&#xff08;闲话少扯&#xff09;&#xff1a; 4.1.C语言文件的操作&#xff1a; 4.2.读取航班信息&#xff1a; C语言知识回顾 4.3.打印航班信息 5.根据要求查找航班&#xf…

C语言 数据结构课设 航空订票系统

目录 实现功能 部分功能测试截图 ​ 代码展示 实现功能 1.订票 2.退票 3.查询航班信息 4.修改航班信息 5.录入航班信息 6.打印订票信息 部分功能测试截图 功能选择界面 查询航班功能测试 订票功能测试 打印订票信息 代码展示 代码中需要的二个文件内容如下 第一个是代码中的…

【计算机毕业设计】41.航空订票系统

摘 要 网络的广泛应用给生活带来了十分的便利。所以把航空订票与现在网络相结合&#xff0c;利用JSP技术建设航空订票系统&#xff0c;实现航空订票的信息化。则对于进一步提高航班公司的发展&#xff0c;丰富航空订票经验能起到不少的促进作用。 航空订票系统能够通过互联网…

航空订票系统(javaweb项目)

航空订票系统课程设计 一、项目选题 航空订票系统 二、项目背景 当今社会知识经济高速发展&#xff0c;信息化在各个行业内正在被越来越广泛的应用。人们生活水平的不断提高&#xff0c;使得出行的交通工具也跟着发展起来。如今乘飞机出行的人越来越多&#xff0c;这方面的市…

数据结构课设——航空航天订票系统

文章目录 一、系统简介    1、业务活动    2、操作和功能二、系统功能模块图    1、系统功能模块图    2、函数功能模块三、详细描述    1、数据结构    2、设计思路四、运行结果    1、查询所有航班    2、根据起点终点搜索航班    3、订…