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

本文共 7161 字,大约阅读时间需要 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的知识,可以去看和文章。

转载于:https://juejin.im/post/5a30fd04f265da4332279184

你可能感兴趣的文章
2016年第七届蓝桥杯java B组省赛试题
查看>>
听闰土大话前端之ES6是怎么来的
查看>>
MySQL学习——优化
查看>>
【自己读源码】Netty4.X系列(四) Netty中的异步调用
查看>>
Spring Boot Logback 通用配置文件
查看>>
实现一个spring webservice服务端三:实现一个有复杂返回值的spring-ws服务
查看>>
linux安装tensorflow搭配那个版本的cuda和cudnn?
查看>>
商城系统建设,怎样做才符合客户需求?
查看>>
Is Subsequence
查看>>
慎用!BLEU评价NLP文本输出质量存在严重问题
查看>>
关于HTTPOXY漏洞的分析说明
查看>>
从蚂蚁金服实践入手,带你深入了解 Service Mesh
查看>>
Mac下brew方式安装mysql
查看>>
开源的任务队列服务HTQ
查看>>
通过微信小程序看前端
查看>>
[LeetCode] Rotate Function
查看>>
iOS - 更轻量级的 AppDelegate - 面向服务设计
查看>>
什么样的爬虫才是好爬虫:Robots协议探究
查看>>
CentOS6.6设置静态ip
查看>>
tomcat 部署多个项目的技巧
查看>>