시작
오늘은 SceneKit
으로 이미지를 인식하여 그 위에 캐릭터를 올려볼 예정입니다.
간단히 설명할 예정이라 자세한 내용은 이전 글을 참고하시길 바랍니다.
SceneKit의 사용법 (1) - 정육면체와 달을 만들어 보자
SceneKit의 사용법 (3) - 카메라 줄자, 거리 측정하기
준비물
1. 이미지를 인식할 수 있는 스티커 or 카드
2. 스티커 모양에 맞는 3D 모델을 다운로드
3D 모델 다운로드하는 사이트들
https://www.turbosquid.com/ko/
위에서 원하는 모델을 다운로드합니다.
모델의 파일 확장자는 dae, obj 둘 중 하나를 다운로드하여 주세요.
만약 dae 파일을 다운로드 시
다운로드한 dae파일을 art.scnassets
에 넣어줍니다.
dae 파일을 Xcode 내에서 Editor -> Convert를 통해 scn파일로 변환을 해주세요
만약 obj 파일을 다운로드 시
obj 파일을 다운로드하였다면 usdz파일로 변환시켜줘야 합니다.
여기서 USDZ는 애플과 픽사가 같이 만든 AR content를 표시하는 3D file format으로
ARKit
의 모델들과 호환이 가능하다고 하네요.
변환을 하기 위하여 아래 공식 홈페이지에서
Reality Converter 다운로드를 해주세요.
Reality Converter 사용법
Reality Converter 실행 후 드래그로 obj파일 넣은 후
내보내기(command + E)를 통하여 usdz파일로 변환을 시켜 줍니다.
생성된 usdz파일을 art.scnassets
폴더에 넣어줍시다.
usdz파일을 Xcode 내에서 Editor -> Convert를 통해 scn파일로 변환해 주세요
인식할 이미지를 추가하기
자 이제 이미지 준비하기 단계는 끝났습니다.
한번 준비한 이미지를 넣어봅시다.
Assets.xcassets
에서 AR Resource 폴더를 추가합니다.
폴더명은 원하는 이름으로 지어주세요. (저는 "Cards"라고 지었습니다.)
인식시키고 싶은 이미지를 생성한 폴더에 넣어줍니다.
느낌표 에러가 생겼네요
에러가 발생하는 이유는 인식할 사진의 크기를 정해주지 않아서 발생합니다.
실제 스티커의 크기로 설정해 줍시다. 여기서 기본 단위는 미터(m)입니다
(width만 설정해 주면 자동으로 Height는 잡아줍니다.)
Configuration 설정
이미지를 인식해야 하므로 ARImageTrackingConfiguration
을 사용합니다.
Asset에 넣어둔 이미지를
ARReferenceImage.referenceImages(inGroupNamed:bundle:)
로 불러와 줍니다.
referenceImages(inGroupNamed:)
에는
바로 위에 생성한 AR Resource 폴더 이름인 "Cards"를 넣어 줍시다.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// ARImageTrackingConfiguration으로 특정 이미지를 찾는다.
let configuration = ARImageTrackingConfiguration()
// Asset에서 이미지를 인식할 이미지 폴더를 선택합니다..
// inGroupNamed: 생성한 AR Resource Group의 폴더의 이름
// bundle: 번들의 위치를 나타낸다.(main에서 실행되므로 Bundle.main)
guard let imageToTrack = ARReferenceImage.referenceImages(inGroupNamed: "Cards", bundle: Bundle.main) else { return }
// ARKit에서 어떤 이미지를 추적할 것인가?
configuration.trackingImages = imageToTrack
// 몇 개의 이미지를 추적할 것인가?
configuration.maximumNumberOfTrackedImages = 2
// Run the view's session
sceneView.session.run(configuration)
}
참고
ARImageTrackingConfiguration
과 ARWorldTrackingConfiguration
둘 다 이미지를 인식할 수 있습니다.
// 한 두개의 이미지 또는 움직이는 상황에도 오직 이미지만 인식하여 인식률이 높다.
let configuration = ARImageTrackingConfiguration()
configuration.trackingImages = referenceImages
// 여러 이미지들을 추척해야 할 때 안정적으로 사용 가능합니다.
let configuration = ARWorldTrackingConfiguration()
configuration.detectionImages = referenceImages
각각의 장단점이 존재하는데 자세한 내용은 공식문서를 참고해 주세요.
이미지 인식시키기
이미지를 추가하였으니 이제 인식을 시켜보겠습니다.
순서
1. 이미지를 인식하기
2. 인식된 이미지 위에 평면을 올리기
3. 평면 위에 3D모델 올리기
먼저 renderer(_:nodeFor:)
메서드 간단 설명
renderer
메서드는 anchor
의 위치에 따라 새로운 노드를 추가할 수 있습니다.
anchor
는 화면에 감지된 이미지를 말합니다. 그리고 결과 값으로 3D객체(node
)를 리턴하죠.
이제 인식한 이미지 위에 반투명한 평면을 올려보겠습니다.
extension ViewController: ARSCNViewDelegate {
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
// 빈 노드를 생성시킵니다.
let node = SCNNode()
// 이미지를 추적해야 하므로 감지된 anchor를 ARImageAnchor로 형변환을 시켜줍니다.
// imageAnchor.referenceImage.name로 접근하여 지금 인식되고 있는 사진의 이름도 알 수 있습니다.
guard let imageAnchor = anchor as? ARImageAnchor else { return node }
// 카드를 인식해야 하므로 감지된 카드의 크기를 입력해 준다.(하드코딩 할 필요 X)
// 카드위에 3D객체 형상(plane)을 렌더링을 시킨다.
let plane = SCNPlane(width: imageAnchor.referenceImage.physicalSize.width,
height: imageAnchor.referenceImage.physicalSize.height)
// plane을 투명하게 만들기
plane.firstMaterial?.diffuse.contents = UIColor(white: 1.0, alpha: 0.5)
// 뼈대 설정 : 위에 생성한 plane
let planeNode = SCNNode(geometry: plane)
// plane은 처음 생성시 수직평면으로 생성이 되므로 우리는 스티커와 동일하게 90도로 눞여 줍니다.
// eulerAngles은 라디안 각도를 표현하기 위함. : -90°
planeNode.eulerAngles.x = -(Float.pi / 2)
node.addChildNode(planeNode)
// 감지된 사진의 이름을 갖고 모델을 만들어 준다. (감지된 이름 : AR Resource폴더의 이미지 이름)
if let imageName = imageAnchor.referenceImage.name {
makeModel(on: planeNode, name: imageName)
}
return node
}
makeModel(on planeNode: SCNNode, name: String)
라는 함수를 생성해 모델을 만들어 보겠습니다.
여기서 사용할 이미지는 2개입니다.
간편하게 사용하기 위해 Enum
으로 구성해 줬습니다.
enum Card {
case Ghost
case Squidward
// 파일 이름
var name: String {
switch self {
case .Ghost: return "Ghost"
case .Squidward: return "Squidward"
}
}
// 파일이 있는 위치
var assetLocation: String {
switch self {
case .Ghost:
return "art.scnassets/Ghost.scn"
case .Squidward:
return "art.scnassets/SquidwardTentacles.scn"
}
}
}
3D모델 생성 관련 자세한 내용은 정육면체와 달을 만들어 보자, 주사위 만들기 를 참고해 주세요.
func makeModel(on planeNode: SCNNode, name: String) {
switch name {
case Card.Ghost.name:
guard let ghostScene = SCNScene(named: Card.Ghost.assetLocation) else { return }
guard let ghostNode = ghostScene.rootNode.childNodes.first else { return }
// 생성된 3D 모델의 각도를 조정
ghostNode.eulerAngles.x = Float.pi/2
ghostNode.eulerAngles.z = -(Float.pi/2)
// plane위에다 올려야 하므로 planeNode.addChildNode에 추가
planeNode.addChildNode(ghostNode)
case Card.Squidward.name:
guard let squidwardScene = SCNScene(named: Card.Squidward.assetLocation) else { return }
guard let squidwardNode = squidwardScene.rootNode.childNodes.first else { return }
// 생성된 3D 모델의 각도와 위치를 조정
squidwardNode.eulerAngles.x = Float.pi/2
squidwardNode.position.z = -(squidwardNode.boundingBox.min.y * 6)/1000
// plane위에다 올려야 하므로 planeNode.addChildNode에 추가
planeNode.addChildNode(squidwardNode)
default: break
}
}
완성
신기하고 재밌는 경험이었습니다. ㅎ
이미지 인식률이 낮아서 애를 먹었.. 사진만 100장 이상 찍었던...
아무튼 팁으로는 처음 이미지를 만들 때
최대한 이미지를 꽉 채워서 사진을 찍도록 합시다.
완성된 코드를 보고 싶다면 Github 를 참고하세요.
ScnenKit의 다른 예제를 보고 싶다면 아래 링크를 참고하세요
참고
'Xcode > Framework' 카테고리의 다른 글
[iOS/Swift] SceneKit의 사용법 (5) - 이미지를 인식하고 그 위에 동영상을 재생시켜 보자 (0) | 2023.01.11 |
---|---|
[iOS/Swift] SceneKit의 사용법 (3) - 카메라 줄자, 거리 측정하기 (0) | 2023.01.10 |
[iOS/Swift] SceneKit의 사용법 (2) - 주사위 만들기 (0) | 2023.01.08 |
[iOS/Swift] SceneKit의 사용법 (1) - 정육면체와 달을 만들어 보자 (0) | 2023.01.07 |
[iOS/Swift] ARKit의 종류 (0) | 2023.01.07 |