博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
VR全景图片浏览实现
阅读量:6219 次
发布时间:2019-06-21

本文共 7109 字,大约阅读时间需要 23 分钟。

本文章主要介绍关于VR全景图片浏览的实现,主要是基于OpenGL ES 2.0 / Swift3.0实现的代码,之后会放入OC版。(接下来会发布关于VR全景视频播放器文章,现在主要是在封装播放器)

实现思路:

  1. 创建一个球体模型
  2. 获取图片的纹理数据,通过着色器渲染到球体上
  3. 通过手势的变换,改变球体模型视图矩阵值
  4. VR模式,则通过拖陀螺仪获取用户的行为,调整视图矩阵。

一、文件介绍。

  1. Sphere.h: 引入C语言头文件 #include <stdio.h>。
  2. Sphere.c: 生成球体坐标的C语言方法。
  3. Bridging-Header.h: 桥接文件。
  4. MMPhotoView.swift: 继承于GLKView,用来渲染球体的。 注: 桥接文件路径。

二、VR全景图片浏览实现

  1. 属性一览。
/// 传过来的VR全景图片路径    public var photoURL: String? {                didSet {            guard let filePath = photoURL else {                return            }            /// 将图片转为纹理信息            photoToSwitchTexture(filePath)        }    }    /// 相机广角角度    fileprivate var overture: CGFloat = 0    /// 索引数    fileprivate var numIndices: Int = 0    /// 顶点索引缓存指针    fileprivate var vertexIndicesBufferID: GLuint = 0    /// 顶点缓存指针    fileprivate var vertexBufferID: GLuint = 0    /// 纹理缓存指针    fileprivate var vertexTexCoordID: GLuint = 0    /// 着色器    fileprivate var effect: GLKBaseEffect?    /// 图片纹理信息    fileprivate var textureInfo: GLKTextureInfo?    /// 模型坐标系    fileprivate var modelViewMatrix: GLKMatrix4 = GLKMatrix4Identity      /// 拖拽手势    fileprivate var panX: CGFloat = 0    fileprivate var panY: CGFloat = 0      let sphereSliceNum = 200 /// 每一帧片数    let sphereRadius = 1.0   /// 球体半径复制代码
  1. 初始化GLKView。
fileprivate func setupGLKView() {                /// 设置颜色格式和深度格式        drawableColorFormat = GLKViewDrawableColorFormat.RGBA8888        drawableDepthFormat = GLKViewDrawableDepthFormat.format24        self.delegate = self        context = EAGLContext.init(api: EAGLRenderingAPI.openGLES2)        //将此“EAGLContext”实例设置为OpenGL的“当前激活”的“Context”        EAGLContext.setCurrent(context)        /// 注意: 激活深度检测,设置深度检测一定要放在设置上一句的下面, 要不然context还没有激活        glEnable(GLenum(GL_DEPTH_TEST))    }复制代码
  1. 运行Sphere.c C语言文件获取球体索引坐标数据, 然后将索引坐标加载到GPU 中去。
fileprivate func setupBuffer() {                var vertices: UnsafeMutablePointer
? // 顶点 var texCoord: UnsafeMutablePointer
? // 纹理 var indices: UnsafeMutablePointer
? // 索引 var numVertices: Int32? = 0 /// 编译C文件 获取顶点/纹理/索引 numIndices = Int(GLuint(initSphere(Int32(sphereSliceNum), Float(sphereRadius), &vertices, &texCoord, &indices, &numVertices!))) /// 加载顶点索引数据 glGenBuffers(1, &vertexIndicesBufferID) // 申请内存 glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), vertexIndicesBufferID) // 将命名的缓冲对象绑定到指定的类型上去 glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER), numIndices * MemoryLayout
.size, indices, GLenum(GL_STATIC_DRAW)) /// 加载顶点坐标数据 glGenBuffers(1, &vertexBufferID) glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBufferID) glBufferData(GLenum(GL_ARRAY_BUFFER), Int(numVertices!) * 3 * MemoryLayout
.size, vertices, GLenum(GL_STATIC_DRAW)) /// 激活顶点位置属性 glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue)) glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout
.size * 3), nil) // 纹理 glGenBuffers(1, &vertexTexCoordID) glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexTexCoordID) glBufferData(GLenum(GL_ARRAY_BUFFER), Int(numVertices!) * 2 * MemoryLayout
.size, texCoord, GLenum(GL_DYNAMIC_DRAW)) glEnableVertexAttribArray(GLuint(GLint(GLKVertexAttrib.texCoord0.rawValue))) glVertexAttribPointer(GLuint(GLint(GLKVertexAttrib.texCoord0.rawValue)), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout
.size * 2), nil) }复制代码
  1. 初始化陀螺仪。
fileprivate func startDeviceMotion() {                /**设置初始坐标系, 并开始监控         CMAttitudeReferenceFrameXArbitraryCorrectedZVertical: 描述的参考系默认设备平放(垂直于Z轴),在X轴上取任意值。实际上当你开始刚开始对设备进行motion更新的时候X轴就被固定了。不过这里还使用了罗盘来对陀螺仪的测量数据做了误差修正         使用pull形式获取数据         */        motionManager.startDeviceMotionUpdates(using: CMAttitudeReferenceFrame.xArbitraryCorrectedZVertical)        modelViewMatrix = GLKMatrix4Identity    }复制代码
  1. 添加定时器CADisplayLink。主要的目的是让它执行GLKView中的display()方法,让屏幕刷新率相同的频率相同。
fileprivate func addDisplayLink() {                let displayLink = CADisplayLink.init(target: self, selector: #selector(displayAction))        displayLink.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)    }        @objc fileprivate func displayAction() {                display() // 执行display() 不断刷新屏幕。    }复制代码
  1. 在GLKViewDelegate代理方法内进行绘制。
// MARK: - GLKViewDelegate    func glkView(_ view: GLKView, drawIn rect: CGRect) {                // 清除缓冲区的内容        glClearColor(0, 0, 0, 1)        // 清除颜色缓冲区与深度缓冲区内容        glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))        // 渲染着色器        effect?.prepareToDraw()        glDrawElements(GLenum(GL_TRIANGLES), GLsizei(numIndices), GLenum(GL_UNSIGNED_SHORT), nil)                update()    }        // MARK: - 生命周期方法        fileprivate func update() {                let aspect: Float = fabs(Float(bounds.size.width) / Float(bounds.size.height))        var projectionMatrix: GLKMatrix4 = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(85.0), aspect, 0.1, 400.0)        projectionMatrix = GLKMatrix4Scale(projectionMatrix, -1.0, 1.0, 1.0)                if motionManager.deviceMotion != nil {                        let w: Float = Float(motionManager.deviceMotion!.attitude.quaternion.w)            let x: Float = Float(motionManager.deviceMotion!.attitude.quaternion.x)            let y: Float = Float(motionManager.deviceMotion!.attitude.quaternion.y)            let z: Float = Float(motionManager.deviceMotion!.attitude.quaternion.z)                        projectionMatrix = GLKMatrix4RotateX(projectionMatrix, -(Float)(0.005 * panY))                        let quaternion: GLKQuaternion = GLKQuaternionMake(-x, y, z, w)            let rotation: GLKMatrix4 = GLKMatrix4MakeWithQuaternion(quaternion)                        projectionMatrix = GLKMatrix4Multiply(projectionMatrix, rotation)                        /// 为了保证在水平放置手机的时候, 是从下往上看, 因此首先坐标系沿着x轴旋转90度            projectionMatrix = GLKMatrix4RotateX(projectionMatrix, -Float(M_PI_2))            effect?.transform.projectionMatrix = projectionMatrix                        var modelViewMatrix: GLKMatrix4 = GLKMatrix4Identity            modelViewMatrix = GLKMatrix4RotateY(modelViewMatrix, Float(0.005 * panX))            effect?.transform.modelviewMatrix = modelViewMatrix        }    }复制代码
  1. 最后一步获取VR全景图片,将图片纹理信息添加到着色器中。
/// 传过来的VR全景图片路径    public var photoURL: String? {                didSet {                        guard let filePath = photoURL else {                                return            }            /// 将图片转为纹理信息            runningTexture(filePath)        }    }复制代码
fileprivate func runningTexture(_ filePath: String) {                // 获取图片纹理信息        textureInfo = try? GLKTextureLoader.texture(withContentsOfFile: filePath, options: [GLKTextureLoaderOriginBottomLeft: NSNumber(booleanLiteral: true)])                effect = GLKBaseEffect()        effect?.texture2d0.enabled = GLboolean(GL_TRUE)        effect?.texture2d0.name = textureInfo!.name    }复制代码
  1. 示例演示。(注意:要真机测试才可以)

注: 如果你喜欢OpenGL ES,想学习OpenGL ES的知识,可以去看和文章。

转载地址:http://jrrja.baihongyu.com/

你可能感兴趣的文章
吃鸡蛋引发的血案,详解内存中的字节序
查看>>
【1139】数据结构上机测试2-2:单链表操作B (逆序建表+重复元素删除)
查看>>
C++ 内存管理之三(栈和堆)
查看>>
Windows7 64bit 安装python3.3 & cx_Freeze-4.3.2
查看>>
手写web服务器
查看>>
也谈 Python 的中文编码处理
查看>>
[LeetCode] LRU Cache
查看>>
OpenStack若干概念
查看>>
AttributeToElement
查看>>
php使用循环创建任意长度数组
查看>>
站立会议03
查看>>
POJ3068:"Shortest" pair of paths——题解
查看>>
上传本地文件到github(码云)上(小乌龟方式,sourcetree方式)
查看>>
微软Holographic将更名为Windows Mixed Reality
查看>>
豪情哥的忠告 能做到这一条就够用了
查看>>
精彩的javascript对象和数组混合相加
查看>>
Markdown介绍及工具推荐
查看>>
面向对象软件设计原则(一) —— 引子
查看>>
EaseType 缓动函数
查看>>
Unity VR全景漫游
查看>>