项目中要用到这项功能,去网上搜了一下,大部分都是用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
}
}
在做全景视频的时候还找到了另一种思路,全景照片的制作是比较简单的事情,视频又是由一帧一帧的照片构成的,如果我们能够获取视频中每一帧的照片,并放在球体上进行渲染,就相当于实现了全景视频。所以我们需要写一个能够获取视频每一帧照片的代码块,然后配合我们的全景照片代码块,两者一结合,也能够实现全景视频,蛮好玩的哈哈哈😸