SceneKit结合CMMotionManager实现旋转全景图/视频(UIKit版)

        项目中要用到这项功能,去网上搜了一下,大部分都是用Object—C完成的,所以写下这篇用UIKit完成的代码,方便大家使用。

如何将照片渲染到球体上,整体思路如下:

1.全景照片的制作

2.将全景照片作为球体渲染的对象渲染出来
3将球体添加到SCNNode上
4.将SCNNode添加到场景上

具体代码如下:

import UIKit
import SceneKit
import CoreMotion
import GLKit
class ViewController: UIViewController{
    var sceneView = SCNView()
    // 相机Node
    var cameraNode = SCNNode()
    // 球体
    var sphere = SCNSphere()
    //陀螺仪实体
    var camera = SCNCamera()
    var motionManager = CMMotionManager()
    var gq1: GLKQuaternion!
    var gq2: GLKQuaternion!
    var qp: GLKQuaternion!
    var attitude: CMAttitude!
    var quaternion: CMQuaternion!
    var time: Timer!
    var sphereNode: SCNNode!
    override func viewDidAppear(_ animated: Bool) {
     
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.scene = SCNScene.init()
        sceneView.frame = self.view.frame
        self.view.addSubview(sceneView)
        sphere.radius = 10
        // 只渲染一面,从球体里面看,外面就不用渲染了
        sphere.firstMaterial?.isDoubleSided = false
        // 剔除外面
        sphere.firstMaterial?.cullMode = .front
        // 把全景图“贴”到球体上
        sphere.firstMaterial?.diffuse.contents = UIImage.init(named: "star.jpg")
        // 球体Node,位置放到场景原点
        sphereNode = SCNNode.init(geometry: sphere)
        sphereNode.position = SCNVector3Make(0, 0, 0)
        sceneView.scene?.rootNode.addChildNode(sphereNode)
        
        // 相机Node,位置放到场景原点
        camera.automaticallyAdjustsZRange = true
        camera.focalBlurRadius = 0
        cameraNode.camera = camera
        cameraNode.position = SCNVector3Make(0, 0, 0)
        //球面纹理翻转
        sphere.firstMaterial?.diffuse.contentsTransform = SCNMatrix4MakeScale(-1, 1, 1)
        sphere.firstMaterial?.diffuse.wrapS = SCNWrapMode.repeat
        sphere.firstMaterial?.diffuse.wrapT = SCNWrapMode.repeat
        judgeMent()
        sceneView.scene?.rootNode.addChildNode(cameraNode)
    }
    //获取陀螺仪信息
    @objc func getData(){
        if(motionManager.isGyroActive){
            if(motionManager.deviceMotion != nil){
                attitude = motionManager.deviceMotion?.attitude
                quaternion = attitude.quaternion
                cameraNode.orientation = orientationFromCMQuaternion(quaternion: quaternion)
            }else{
                print("未能获取到陀螺仪数据,请进行相关检查")
            }
        }
    }
    //这里x轴要同时旋转90度,这是因为手机陀螺仪的坐标系不一致:a手机正放于桌面上的坐标为(0,0,0);而scnView坐标系是手机正立的时候为(0,0,0)
    func orientationFromCMQuaternion(quaternion:CMQuaternion) -> SCNVector4{
        gq1 = GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(-90), 1, 0, 0)
        gq2 = GLKQuaternionMake(Float(quaternion.x), Float(quaternion.y), Float(quaternion.z), Float(quaternion.w))
        qp = GLKQuaternionMultiply(gq1!, gq2!)
        return SCNVector4Make(qp.x, qp.y, qp.z, qp.w)
    }
    func judgeMent(){
        if(motionManager.isGyroAvailable){
            motionManager.showsDeviceMovementDisplay = true
            motionManager.startGyroUpdates()
            motionManager.startDeviceMotionUpdates()
            //人眼的帧率是60,我弄了个120的高刷
            motionManager.deviceMotionUpdateInterval = 1/120
            time = Timer.scheduledTimer(timeInterval: 1/120, target: self, selector: #selector(getData), userInfo: nil, repeats: true)
        }
    }
}

        我用的是四元素的旋转方法,因为三元素的旋转存在万向节死锁问题,解决起来太麻烦了,有兴趣可以去b站搜一下万向节死锁,挺有意思的。

        既然能够实现全景照片,那么全景视频是不是也可以实现呢?答案是肯定的。

如何将视频渲染到球体上,整体思路如下:

1.通过AVPlayer 获取视频流
2.通过SKVedioNode 渲染视频
3.将SKVedioNode添加到SKScene场景上
4.将场景作为球体渲染的对象渲染出来

下面是如何实现全景视频的代码

//
//  ViewController.swift
//  video
//
//  Created by Down on 2023/2/10.
//

import UIKit
import SceneKit
import SpriteKit
import AVFoundation
class ViewController: UIViewController {
    override func viewDidLoad() {
        let scnView = SCNView(frame: self.view.bounds);
        scnView.scene = SCNScene()
        self.view.addSubview(scnView);
        
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        cameraNode.camera?.automaticallyAdjustsZRange = true;// 自动调节可视范围
        cameraNode.position = SCNVector3Make(0, 0, 0);// 把摄像机放在中间
        scnView.scene?.rootNode.addChildNode(cameraNode);
        
        let panoramaNode = SCNNode()
        panoramaNode.geometry = SCNSphere(radius: 10);
        panoramaNode.geometry?.firstMaterial?.isDoubleSided = true
        panoramaNode.position = SCNVector3Make(0, 0, 0);
        scnView.scene?.rootNode.addChildNode(panoramaNode);
        
        let url = Bundle.main.url(forResource: "SampleVideo_1280x720_1mb", withExtension: "mp4")
        let player = AVPlayer(url: url!)
         let videoNode = SKVideoNode(avPlayer: player)
         videoNode.size = CGSize(width: 1600, height: 900)
         videoNode.position = CGPoint(x: videoNode.size.width/2, y: videoNode.size.height/2)
         videoNode.zRotation = CGFloat(M_PI)
         let skScene = SKScene()
         skScene.addChild(videoNode)
         skScene.size = videoNode.size
         panoramaNode.geometry?.firstMaterial?.diffuse.contents = skScene
         player.play()
         scnView.allowsCameraControl = true
    }
}

        在做全景视频的时候还找到了另一种思路,全景照片的制作是比较简单的事情,视频又是由一帧一帧的照片构成的,如果我们能够获取视频中每一帧的照片,并放在球体上进行渲染,就相当于实现了全景视频。所以我们需要写一个能够获取视频每一帧照片的代码块,然后配合我们的全景照片代码块,两者一结合,也能够实现全景视频,蛮好玩的哈哈哈😸 

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要从全景图片中生成视频,您需要使用图像处理和计算机视觉技术,以及视频编码知识。以下是可能的实现步骤: 1. 将全景图像切割成多个覆盖相机的子图像。这些子图像可以是球面、柱面或立方体投影。 2. 对每个子图像应用图像拼接算法,将其转换为平面图像。这些平面图像将是您生成视频的帧。 3. 将帧序列编码为视频格式,如MP4或AVI。 以下是一个示例Python代码,该代码使用OpenCV库实现全景图像到视频的转换: ```python import cv2 import numpy as np # 读入全景图像 panorama = cv2.imread('panorama.jpg', cv2.IMREAD_COLOR) # 将全景图像切割成多个子图像,这里使用立方体投影 # 具体实现可以使用OpenCV的cv2.CubeMap或cv2.CubeMapCamera # 进行立方体投影,也可以使用其他库或算法实现 sub_images = cube_map_projection(panorama) # 对每个子图像进行图像拼接,生成平面图像 # 具体实现可以使用OpenCV的cv2.stitcher或cv2.createStitcher # 进行图像拼接,也可以使用其他库或算法实现 plane_images = stitch_images(sub_images) # 将平面图像序列编码为视频 # 具体实现可以使用OpenCV的cv2.VideoWriter或FFmpeg # 进行视频编码,也可以使用其他库或算法实现 video_writer = cv2.VideoWriter('panorama.mp4', cv2.VideoWriter_fourcc(*'mp4v'), 30, (640, 480)) for image in plane_images: video_writer.write(image) video_writer.release() ``` 需要注意的是,具体的实现方式会因为数据类型和算法的不同而有所差异,上述代码仅供参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值