Swift/Swift 개발 노트

[iOS/SceneKit] SCNView

힛해 2024. 8. 18. 01:44
728x90

https://developer.apple.com/documentation/scenekit/scnview

 

SCNView | Apple Developer Documentation

A view for displaying 3D SceneKit content.

developer.apple.com

 

SCNView란?

3D SceneKit 콘텐츠를 표시하기 위한 뷰.

 

자료형

// iOS, iPad, Mac Catalyst,
class SCNView : UIView

// macOS
class SCNView : NSView

 

개요

  • macOS에서는 SCNView가 NSView의 서브클래스입니다. iOS tvOS에서는 SCNView가 UIView의 서브클래스입니다. 각 운영 체제의 뷰 계층 구조의 일부로서, SCNView 객체는 앱의 사용자 인터페이스에서 SceneKit 콘텐츠를 표시할 수 있는 공간을 제공합니다.
  • SCNView를 생성하려면 init(frame:options:) 메서드를 사용하거나, 닙 파일 또는 스토리보드에 추가할 수 있습니다. SceneKit 뷰에 콘텐츠를 제공하려면, SCNScene 객체를 뷰의 scene 속성에 할당하면 됩니다.
  • SceneKit 뷰와 작업할 때 중요한 추가 메서드와 속성에 대해서는 SCNSceneRenderer 프로토콜을 참조하세요. (또한 SCNRenderer 클래스를 사용하여 임의의 Metal 명령 큐나 OpenGL 컨텍스트에 SceneKit 콘텐츠를 렌더링하거나, macOS에서 SCNLayer 클래스를 사용하여 Core Animation 레이어에 렌더링할 수도 있습니다. SCNSceneRenderer 프로토콜은 이 세 가지 SceneKit 렌더링 클래스에 공통된 기능을 정의합니다.

 

기능

더보기

SceneKit 뷰 초기화하기

init(frame: CGRect, options: [String : Any]?)

  • 설명: 지정된 프레임 사각형과 옵션으로 새로운 SceneKit 뷰 객체를 초기화하고 반환합니다.

뷰 속성 설정

  • scene: SCNScene?
    • 설명: 뷰에서 표시할 장면을 설정합니다.
  • backgroundColor: NSColor
    • 설명: 뷰의 배경 색상을 설정합니다.
  • preferredFramesPerSecond: Int
    • 설명: 뷰가 장면을 렌더링하는 애니메이션 프레임 속도를 설정합니다.
  • rendersContinuously: Bool
    • 설명: 뷰가 항상 선호하는 프레임 속도로 렌더링할지 여부를 결정합니다. 이 속성이 true인 경우, 뷰는 콘텐츠가 변경될 때만 렌더링을 업데이트합니다.
  • antialiasingMode: SCNAntialiasingMode
    • 설명: 뷰의 장면 렌더링에 사용되는 앤티앨리어싱 모드를 설정합니다.
    SCNAntialiasingMode (앨리어싱 모드 열거형)
    • 설명: 뷰의 장면 렌더링을 위한 앤티앨리어싱 모드입니다. 이 속성은 SCNView에서 사용됩니다.

카메라 제어 구성

  • allowsCameraControl: Bool
    • 설명: 사용자가 현재 장면의 카메라 시점을 조작할 수 있는지 여부를 결정합니다.
  • cameraControlConfiguration: any SCNCameraControlConfiguration
    • 설명: 카메라 컨트롤러의 이벤트 처리 동작에 대한 현재 구성을 설정합니다.
    SCNCameraControlConfiguration (카메라 제어 구성 프로토콜)
    • 설명: 카메라 컨트롤러의 동작에 영향을 미치는 속성들입니다.
  • defaultCameraController: SCNCameraController
    • 설명: 기본 카메라 컨트롤러를 제공합니다.
    SCNCameraController (카메라 컨트롤러 클래스)
    • 설명: SceneKit 뷰의 카메라 제어를 담당하는 클래스입니다.

장면의 액션 및 애니메이션 관리

  • func pause(Any?)
    • 설명: 뷰의 장면에서 액션과 애니메이션의 재생을 일시 정지합니다.
  • func play(Any?)
    • 설명: 뷰의 장면에서 액션과 애니메이션의 재생을 재개합니다.
  • func stop(Any?)
    • 설명: 뷰의 장면에서 액션과 애니메이션의 재생을 중지하고 장면 시간을 시작 시간으로 리셋합니다.

뷰 스냅샷 캡처

  • func snapshot() -> UIImage
    • 설명: 뷰의 장면을 새 이미지 객체로 렌더링합니다.

OpenGL ES 및 OpenGL 컨텍스트와의 작업

  • var eaglContext: EAGLContext? (Deprecated)
    • 설명: 뷰가 콘텐츠를 렌더링하는 데 사용하는 OpenGL ES 컨텍스트입니다.
  • var openGLContext: NSOpenGLContext? (Deprecated)
    • 설명: 뷰가 콘텐츠를 렌더링하는 데 사용하는 OpenGL 컨텍스트입니다.
  • var pixelFormat: NSOpenGLPixelFormat? (Deprecated)
    • 설명: 뷰의 OpenGL 픽셀 포맷입니다.
  • var drawableResizesAsynchronously: Bool
    • 설명: 뷰의 드로우어블이 비동기적으로 크기 조절되는지 여부를 설정합니다.

 

직접 만들어보자

이전에 포스팅한 SCNScene을 사용해 모델을 불러오고 SCNView의 scene에 할당하는 방식으로 만들어보자.

import SwiftUI
import SceneKit

struct ContentView: View {
    var body: some View {
        VStack{
            Text("3D Test")
            
            SceneView(
                scene: loadScene(),
                options: [
                .autoenablesDefaultLighting,
                // 현재 시점을 사용자가 조작할 수 있게 할지 여부
                .allowsCameraControl,
                //  뷰가 항상 선호하는 프레임 속도로 렌더링 되는지 아니면 표시되는 콘텐츠가 변경될 때만 렌더링 되는지
                .rendersContinuously
                ],
                // 뷰가 장면을 렌더링 하는데 사용하는 애니메이션 프레임 속도, 작게 할수록 느려짐
                preferredFramesPerSecond: 60
            )// SceneView
            .frame(height: 400)
            
            Text("3D Test")
        }.background(Color.green)
        
    }
    
    func loadScene() -> SCNScene {
        let scene = SCNScene(named: "Dols.scnassets/cupid.scn") ?? SCNScene()
        
        // 모든 노드의 이름을 출력
        printAllNodeNames(from: scene.rootNode)
        
       
        
        return scene
    }

    func printAllNodeNames(from node: SCNNode) {
        if let nodeName = node.name {
            print("Node name: \(nodeName)")
        }
        
        
        for childNode in node.childNodes {
            printAllNodeNames(from: childNode)
        }
    }
}

 

이렇게 만들면 잘 만들어지는 결과를 확인할 수 있다.

 

그러나 문제가 있다.

 

공식문서에 있는 몇몇 메서드들은 위와같은 방식으로 구현이 되지 않는다.

ex)backgroundColor

 

SceneView를 인스턴스로 생성해주어야 메서드를 사용할 수 있었다.

 

변경된 코드

struct ContentView: View {
    var body: some View {
        VStack{
            Text("3D Test")
            
           DolView()
            
            Text("3D Test")
        }.background(Color.green)
        
    }
}

struct DolView : UIViewRepresentable {
    
    // UI가 만들어질때 생성
    func makeUIView(context: Context) -> SCNView {
        let scnView = SCNView()
        scnView.scene = loadScene()
        scnView.backgroundColor = UIColor.clear // SCNView의 배경을 투명하게 설정
        scnView.allowsCameraControl = true
        scnView.autoenablesDefaultLighting = true
        
        return scnView
    }
    
    func updateUIView(_ uiView: SCNView, context: Context) {
    }
    
    func loadScene() -> SCNScene {
        let scene = SCNScene(named: "Dols.scnassets/cupid.scn") ?? SCNScene()
               
        return scene
    }
}

 

인스턴스화 해서 생성해주면 이전에는 선언으로만 구현한 변수들에 직접 값을 넣어 변경해줄 수 있다.

ex) allowsCameraControl

 

그리고 초기 카메라 위치를 구현하는 코드다

let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 100)
cameraNode.camera?.automaticallyAdjustsZRange = true // Z축 클리핑 범위를 자동으로 조정

 

이렇게 만든 카메라 노드는 SCNScene에 넣어준다.

 

 func loadScene() -> SCNScene {
        let scene = SCNScene(named: "Dols.scnassets/cupid.scn") ?? SCNScene()
               
        
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        // z값을 높일수록 멀어진다
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 4)
        //SceneKit에서 카메라의 Z축 범위를 자동으로 조정하도록 설정하는 속성
//        cameraNode.camera?.automaticallyAdjustsZRange = true
        scene.rootNode.addChildNode(cameraNode)
        
        return scene
    }

 

각 파일별로 카메라 노드를 확인해서 넣어줄 수 있지만 파일별로 다른 뿐더러 메서드를 통해 카메라노드를 가져오는 것을 찾지 못해 새로 생성해준뒤 SCNScene의 rootNode에 추가해주는 방식으로 구현했다.