博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS 使用 SceneKit 实现全景图
阅读量:6080 次
发布时间:2019-06-20

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

项目里碰到了展示全景图的需求,以前没做过,google了一下已经有不少现成的库可以拿来用了,不过有前辈提到可以用SceneKit来实现,刚好去年做AR的时候用过SceneKit,一下子来了思路,在这里记录一下。

思路

思路就是在SceneKit的场景里放置一个球体,把全景图贴在球体表明,然后把相机放置在球体中心不就得了。

实现

实现起来很简单,往 ViewController 放一个 SCNView,一个相机,一个球体就好。

class PanoramaVC: UIViewController {        let sceneView = SCNView()    // 相机Node    let cameraNode = SCNNode()    // 球体    let sphere = SCNSphere()        ...        override func viewDidLoad() {        super.viewDidLoad()                sceneView.scene = SCNScene.init()        sceneView.frame = self.view.frame        self.addSubView(sceneView)                sphere.radius = 50        // 只渲染一面,从球体里面看,外面就不用渲染了        sphere.firstMaterial?.isDoubleSided = false        // 剔除外面        sphere.firstMaterial?.cullMode = .front        // 把全景图“贴”到球体上        sphere.firstMaterial?.diffuse.contents = UIImage.init(named: "全景图名")                // 球体Node,位置放到场景原点        let sphereNode = SCNNode.init(geometry: sphere)        sphereNode.position = SCNVector3Make(0, 0, 0)        sceneView.scene?.rootNode.addChildNode(sphereNode)                // 相机Node,位置放到场景原点        cameraNode.camera = SCNCamera.init()        cameraNode.position = SCNVector3Make(0, 0, 0)        sceneView.scene?.rootNode.addChildNode(cameraNode)    }}复制代码

到这里就成功了。但是这样有个问题:全景图左右是反的。这是因为全景图是从球体外面“贴”到球体上的,所以从球体内部看到的全景图是左右反向的。那我就把全图片给它左右反向就好啦。使用下面这个函数可以把图片左右反向。

func flipImageLeftRight(_ image: UIImage) -> UIImage? {    UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)    let context = UIGraphicsGetCurrentContext()!    context.translateBy(x: image.size.width, y: image.size.height)    context.scaleBy(x: -image.scale, y: -image.scale)    context.draw(image.cgImage!, in: CGRect(origin:CGPoint.zero, size: image.size))    let newImage = UIGraphicsGetImageFromCurrentImageContext()    UIGraphicsEndImageContext()    return newImage}复制代码

此时把左右反向的图片“贴”到球体上

sphere.firstMaterial?.diffuse.contents = flipImageLeftRight(UIImage.init(named: "全景图名"))复制代码

全景图长这样:

快看下效果吧(实际还是很流畅的,gif 效果有卡顿):

悬浮广告

除了全景图,还有个需求就是在全景图适当的位置显示广告图标,点击后会出现一张海报。要求广告图标在全景图视角变化的时候跟着动。

这个其实也不难,只要知道需要放置广告的位置坐标,在全景图视角变化的同时移动图标让图标和目标位置同步就好。

但是我怎么知道全景图视角怎样变化呢(其实是相机Node旋转角度在变化)?我想通过相机旋转时监听旋转角度但是没找到监听方法,如果有谁知道望告知,十分感谢!

那我干脆自己控制相机旋转角度。使用这句代码禁止通过 sceneView 来控制相机旋转:

sceneView.allowsCameraControl = false复制代码

然后监听滑动手势来控制相机和广告图标旋转:

//相机旋转角度(单位是弧度,转一圈是 2pi)var rotateAngle = CGPoint.zero...override func touchesMoved(_ touches: Set
, with event: UIEvent?) { if touches.count == 1 { let touch = touches.first // 手指在x、y方向滑动距离 let offsetX = (touch?.location(in: self.view).x)! - (touch?.previousLocation(in: self.view).x)! let offsetY = (touch?.location(in: self.view).y)! - (touch?.previousLocation(in: self.view).y)! // 手指滑动的距离和相机旋转弧度比值约等于800(这个值是我测试得到的可能不精确) rotateAngle.x += offsetX / 800 rotateAngle.y += offsetY / 800 // 把rotateAngle.x按2?取模 rotateAngle.x = rotateAngle.x.truncatingRemainder(dividingBy: CGFloat.pi * 2) // 控制全景图上下旋转不颠倒 if rotateAngle.y > 1 { rotateAngle.y = 1 } if rotateAngle.y < -1 { rotateAngle.y = -1 } // 旋转相机 rotateCamera() // 旋转广告 rotateAdvertisement() }}/// 旋转相机func rotateCamera() { let rotation = SCNAction.rotateTo(x: rotateAngle.y, y: rotateAngle.x, z: 0, duration: 0) cameraNode.runAction(rotation)}复制代码

要旋转广告图标就要先添加一个广告图标,这里使用一个 UIButton 作为示例。我想把广告图标放在右边的狮子头上,而狮子头的位置的x、y坐标与图片宽高比值为0.570和0.456。

... var adPosition = CGPoint.init(x: 0.570, y: 0.456)var adButton = UIButton()    func addAdversisement() {    adButton.setTitle("我是广告图标", for: .normal)    adButton.backgroundColor = UIColor.init(white: 1, alpha: 0.5)    adButton.layer.cornerRadius = 10    adButton.frame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: 80, height: 40))    self.view.addSubview(adButton)    // 把图标放在屏幕上对应的位置    adButton.center = positionInScreenOfAd(position: adPosition)}/// 从广告图标在图片上的位置计算出在屏幕上的位置////// - Parameter position: 图片上的位置/// - Returns: 屏幕上的位置func positionInScreenOfAd(position: CGPoint) -> CGPoint {    //广告点相对于图片原点的偏移角度    let diffToOrigin = CGPoint.init(x: position.x * CGFloat.pi, y: (position.y - 0.5) * CGFloat.pi)    //转动过程中,广告点相对于屏幕中心点的偏移角度    var offsetAngle = CGPoint.init(x: diffToOrigin.x + rotateAngle.x / 2, y: diffToOrigin.y + rotateAngle.y)    offsetAngle.x = offsetAngle.x.truncatingRemainder(dividingBy: CGFloat.pi)    //在显示范围内,就按角度比例显示在屏幕上    let viewSize = self.view.frame.size        // 全景图显示在屏幕上的范围为 -0.20453 < x < 0.20453, -0.53996 < y < 0.53996(单位为弧度),超出范围就不显示,其中 x 值正负要分开处理    //(这个值是我测试得到的可能不精确)    if abs(offsetAngle.y) > 0.53996 {        return CGPoint.zero    }    if abs(offsetAngle.x) < 0.20453 {        return CGPoint.init(x: viewSize.width / 2 * (1 + offsetAngle.x / 0.20453), y: viewSize.height / 2 * (1 + offsetAngle.y / 0.53996))    }    if abs(offsetAngle.x) >= CGFloat.pi - 0.20453 && abs(offsetAngle.x) < CGFloat.pi {        return CGPoint.init(x: viewSize.width / 2 * (1 + (abs(offsetAngle.x) - CGFloat.pi) / 0.20453), y: viewSize.height / 2 * (1 + offsetAngle.y / 0.53996))    }    return CGPoint.zero}复制代码

接下来就是旋转广告图标:

/// 旋转广告func rotateAdvertisement() {    // 实时计算旋转过程中广告图标在屏幕上的位置    let newP = positionInScreenOfAd(position: adPosition)    // newP == CGPoint.zero 表示图标位置在屏幕之外    adButton.isHidden = newP == CGPoint.zero    adButton.center = positionInScreenOfAd(position: adPosition)}复制代码

到这里就可以看到广告图标随全景图滑动了:

大功告成!

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

你可能感兴趣的文章
无敌删除
查看>>
我的友情链接
查看>>
LinuxCast 远程管理 视频教程笔记
查看>>
zabbix 监控日志关键字
查看>>
Docker之weave工具
查看>>
lvm,磁盘配额
查看>>
政府网站群建设关注点
查看>>
深入理解计算机操作系统--读书笔记-第八章异常
查看>>
我的友情链接
查看>>
Windows 2016 容錯移轉叢集安裝 (1) 叢集安裝
查看>>
Servlet 工作原理解析
查看>>
【Java】按行写入文件
查看>>
官方Guide-python版本
查看>>
目前可用的中国maven2镜像(2013-05-07)
查看>>
海明校验码--确定校验位
查看>>
tomcat目录学习
查看>>
Unit19 What brings you to BeiJing?
查看>>
MySQL数据库参数设置不当导致应用不能连接问题
查看>>
足以代替Apache的Nginx
查看>>
普通大学物理【杨雅玲】--流体力学--做题笔记1
查看>>