[ARchive] sceneView으로 AR 기능 구현하기

love&peace·2024년 9월 28일

ARchive

목록 보기
1/2
post-thumbnail

2023.10 기준..

What is SceneView?!

  • 3D and AR View for Android, Flutter and React Native working with ARCore and Google Filament
  • SceneView are open source projects freely available under the GPL and developed by a dedicated team of passionate individuals.
  • ScencView

결론은 ARCore를 쉽게 실행 시키는 라이브러리입니다

또한, ARCore는 openGL이 필요하다보니 실행되지 않은 기기가 생겨서 SceneView를 사용하기로 결졍했습니다~
쉽게 구현 가능 하기도하고..ㅎㅎ

AR을 어디에 사용하나요?

ARchive의 핵심 기능인 AR을 통해 타임캡슐을 찾는 기능에 필요하기 때문에 반드시 반드시 구현해야 합니다..
특이점은 AR의 모델이 3D가 아닌 사용자의 그림에 AI를 적용해 움직이는 스킨(?)을 AR 모델로 사용하기 때문에 이미지(gif)를 캡슐 생성한 gps좌표에 해당하는 위치에 띄우는 것을 목표입니다..⭐️

문제 발생🚨

(2023.10 기준입니다)
최근에 SceneView의 버전이 2.0.0 버전으로 바뀌면서 scencView 공식문서를 업데이트 해주지 않았다.............🫠
그래서 원하는 기능을 구현하기 위해 SceneView깃헙에 있는 예제, closed issue 그리고 거기에 있는 comment들을 샅샅이 뒤져가며 기능을 구현했습니다..여름이였다

지금부터 만약에 2D 사진(gif)을 모델로 AR을 구현해야 하는 분을 위해 글을 쓰겠습니다 ㅎㅎ

SceneView 사용하기

초기 세팅 - sceneView깃헙에에 잘 나와있기 때문에 pass~

.xml

  <io.github.sceneview.ar.ARSceneView
            android:id="@+id/sceneView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

특정 좌표(GPS)에 AR 모델 생성하기

CameraFragment.kt

// 좌표는 http 통신을 통해 서버에서 가져옵니다.
 visibleLifecycleOwner.lifecycleScope.launch {
            visibleLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.capsuleList.collect { capsuleList ->
                    arSceneView.onSessionUpdated = { session, frame ->
                        if (viewModel.capsuleList.value.isNotEmpty() && !viewModel.isCapsulesAdded) {
                            val earth = session.earth
                            if (earth == null) {
                                Log.d("CameraFragmentAR", "earth is null")
                            } else {
                                if (earth.trackingState == TrackingState.TRACKING) {
                                    capsuleList.forEach { capsule ->
                                        val latitude = capsule.latitude
                                        val longitude = capsule.longitude
                                        val altitude = earth.cameraGeospatialPose.altitude

                                        val earthAnchor = earth.createAnchor(latitude, longitude, altitude, 0f, 0f, 0f, 0f)

                                        addAnchorNode(earthAnchor, capsule)
                                    }
                                }
                            }
                        }

                    }
                }
            }
        }
        
   // 각 Anchor 별로 Node 구현하는 함수
   private fun addAnchorNode(anchor: Anchor, capsule: CapsuleAnchor) {
        arSceneView.let { sceneView ->
            viewAttachmentManager.let { attachManager ->
                ARContentNode(sceneView, attachManager, this,
                    capsule, layoutInflater, requireContext(),
                    onLoaded = { viewNode ->
                        sceneView.engine.let {
                            AnchorNode(it, anchor).apply {
                                isEditable = true
                                lifecycleScope.launch {
                                    addChildNode(viewNode)
                                }
                                viewModel.addAnchorNode(this)
                                keyMaps[capsule.id] = this
                            }
                        }.let {
                            sceneView.addChildNode(it)
                        }
                    }
                )
            }
        }
    }

✨session.earth.createAnchor( ~~ )를 통해 위치가 고정된 Anchor 를 생성

이미지(gif)를 모델(Node)에 불러오기

ARContentNode.kt

class ARContentNode(
    val arscene: ARSceneView,
    val viewAttManager: ViewAttachmentManager,
    val fragmentManagerProvider: FragmentManagerProvider,
    val capsule: CapsuleAnchor,
    val layoutInflater: LayoutInflater,
    val context: Context,
    val onLoaded: (node: ViewNode) -> Unit
) {

    init {
        renderContent()
    }

    private fun renderContent() {
   		 //스킨 이미지 추가
        val capsuleSkin = ItemCapsuleSkinBinding.inflate(layoutInflater)

        ViewRenderable.builder()
            .setView(arscene.context, capsuleSkin.root)
            .build(arscene.engine).thenAccept { r: ViewRenderable? ->
                val viewNode = ViewNode(
                    engine = arscene.engine,
                    modelLoader = arscene.modelLoader,
                    viewAttachmentManager = viewAttManager
                ).apply {
                    if (r != null) {
                        setRenderable(r)
                        isEditable = true
                    }
                    onSingleTapConfirmed = {
                        val existingDialog = fragmentManagerProvider.provideFragmentManager()
                            .findFragmentByTag(CapsulePreviewDialogFragment.TAG) as DialogFragment?
                        if (existingDialog == null) {
                            val dialog = CapsulePreviewDialogFragment.newInstance(
                                "-1",
                                capsule.id.toString(),
                                capsule.capsuleType.toString(),
                                true
                            )
                            dialog.show(
                                fragmentManagerProvider.provideFragmentManager(),
                                CapsulePreviewDialogFragment.TAG
                            )
                        }
                        true
                    }
                }
                onLoaded(viewNode)
            }
    }
}

ViewRenderable.builder()을 통해 item_capsule_skin.xml 바인딩하여 이미지(gif) 추가

  • onSingleTapConfirmed = { ~ } 으로 Node clickEvent 구현

결과😎

40초 부터 보시면 됩니다, 하하
Video Label

[ARchive 깃헙 - AR부분]
https://github.com/tukcomCD2024/DroidBlossom/tree/master/frontend/ARchive/app/src/main/java/com/droidblossom/archive/presentation/ui/camera

0개의 댓글