iOS 使用 SceneKit 实现全景图

发布于:2025-06-29 ⋅ 阅读:(14) ⋅ 点赞:(0)

新项目为购车App涉及到全景图功能,经过搜索和咨询得到全景图的实现原理为从球内看球上的图片这一原理

思路就是在SceneKit的场景里放置一个球体,把全景图贴在球体表明,然后把相机放置在球体中心,通过旋转相机的角度得到全景图旋转的效果。

现将demo放在下方以供参考

import UIKit
import SceneKit

class FullImageViewController: BaseViewController {
    // 全景图
    lazy var sceneView = {
        let sv = SCNView()
        sv.scene = SCNScene.init()
        return sv
    }()
    // 相机
    lazy var cameraNode = {
        let node = SCNNode()
        node.camera = SCNCamera.init()
        node.eulerAngles = currentEulerAngles
        node.position = SCNVector3Make(0, 0, 0)
        node.camera?.fieldOfView = 90
        return node
    }()
    // 球体
    lazy var sphere = {
        let sp = SCNSphere()
        // 半径
        sp.radius = 50
        // 只渲染一面,从球体里面看,外面就不用渲染了
        sp.firstMaterial?.isDoubleSided = true
        // 剔除外面
        sp.firstMaterial?.cullMode = .front
        return sp
    }()
    
    // 当前相机的旋转角度(用于手势持续累加角度)
    private var currentEulerAngles = SCNVector3(0, Float.pi, 0)

    override func viewDidLoad() {
        super.viewDidLoad()

        sceneView.frame = CGRectMake(0, 90, view.frame.width, view.frame.width)
        view.addSubview(sceneView)
        
        // 把全景图“贴”到球体上
        sphere.firstMaterial?.diffuse.contents = flipImageLeftRight(UIImage.init(named: "full_image")!)
        
        // 球体Node,位置放到场景原点
        let sphereNode = SCNNode.init(geometry: sphere)
        sphereNode.position = SCNVector3Make(0, 0, 0)
        sceneView.scene?.rootNode.addChildNode(sphereNode)
        
        // 相机Node,位置放到场景原点
        sceneView.scene?.rootNode.addChildNode(cameraNode)
        sceneView.allowsCameraControl = false
        
        // 添加拖动手势识别器,用于控制视角旋转
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        sceneView.addGestureRecognizer(panGesture)
        
    }
    // 手势
    @objc func handlePan(_ gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: sceneView)

        //视角向右(正方向),向左(负方向)
        let deltaYaw = Float(translation.x) * (Float.pi / 180) * 0.5
        //视角往上(正方向), 视角往下(负方向)
        let deltaPitch = Float(translation.y) * (Float.pi / 180) * 0.5
        if gesture.state == .changed {
            var newX = currentEulerAngles.x + deltaPitch
            let newY = currentEulerAngles.y + deltaYaw
            
            // 限制上下角度在 ±90° 内,防止翻转
            newX = max(min(newX, Float.pi / 2), -Float.pi / 2)
            cameraNode.eulerAngles = SCNVector3(newX, newY, 0)
        }
        
        if gesture.state == .ended {
            currentEulerAngles = cameraNode.eulerAngles
        }
        
    }

    // 图片翻转
    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
    }

}

import UIKit

import SceneKit

class FullImageViewController: BaseViewController {

    // 全景图

    lazy var sceneView = {

        let sv = SCNView()

        sv.scene = SCNScene.init()

        return sv

    }()

    // 相机

    lazy var cameraNode = {

        let node = SCNNode()

        node.camera = SCNCamera.init()

        node.eulerAngles = currentEulerAngles

        node.position = SCNVector3Make(0, 0, 0)

        node.camera?.fieldOfView = 90

        return node

    }()

    // 球体

    lazy var sphere = {

        let sp = SCNSphere()

        // 半径

        sp.radius = 50

        // 只渲染一面,从球体里面看,外面就不用渲染了

        sp.firstMaterial?.isDoubleSided = true

        // 剔除外面

        sp.firstMaterial?.cullMode = .front

        return sp

    }()

    // 当前相机的旋转角度(用于手势持续累加角度)

    private var currentEulerAngles = SCNVector3(0, Float.pi, 0)

    override func viewDidLoad() {

        super.viewDidLoad()

        sceneView.frame = CGRectMake(0, 90, view.frame.width, view.frame.width)

        view.addSubview(sceneView)

        // 把全景图“贴”到球体上

        sphere.firstMaterial?.diffuse.contents = flipImageLeftRight(UIImage.init(named: "full_image")!)

        // 球体Node,位置放到场景原点

        let sphereNode = SCNNode.init(geometry: sphere)

        sphereNode.position = SCNVector3Make(0, 0, 0)

        sceneView.scene?.rootNode.addChildNode(sphereNode)

        // 相机Node,位置放到场景原点

        sceneView.scene?.rootNode.addChildNode(cameraNode)

        sceneView.allowsCameraControl = false

        // 添加拖动手势识别器,用于控制视角旋转

        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))

        sceneView.addGestureRecognizer(panGesture)

    }

    // 手势

    @objc func handlePan(_ gesture: UIPanGestureRecognizer) {

        let translation = gesture.translation(in: sceneView)

        //视角向右(正方向),向左(负方向)

        let deltaYaw = Float(translation.x) * (Float.pi / 180) * 0.5

        //视角往上(正方向), 视角往下(负方向)

        let deltaPitch = Float(translation.y) * (Float.pi / 180) * 0.5

        if gesture.state == .changed {

            var newX = currentEulerAngles.x + deltaPitch

            let newY = currentEulerAngles.y + deltaYaw

            // 限制上下角度在 ±90° 内,防止翻转

            newX = max(min(newX, Float.pi / 2), -Float.pi / 2)

            cameraNode.eulerAngles = SCNVector3(newX, newY, 0)

        }

        if gesture.state == .ended {

            currentEulerAngles = cameraNode.eulerAngles

        }

    }

    // 图片翻转

    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

    }

}

参考文献:iOS 使用 SceneKit 实现全景图项目里碰到了展示全景图的需求,以前没做过,google了一下已经有不少现成的库 - 掘金


网站公告

今日签到

点亮在社区的每一天
去签到