티스토리 뷰
<fragment
android:id="@+id/sceneFormFragment"
android:layout_width="0dp"
android:layout_height="0dp"
class="com.google.ar.sceneform.ux.ArFragment"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
지난 글에서는 fragment에서 class를 통해 google에서 제공하는 ArFragment를 사용하여 제작하였습니다.
<com.google.ar.sceneform.ArSceneView
android:id="@+id/arView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
이번에는 ARScenceView를 통해 ARCore를 사용해보려고 합니다.
private fun checkCameraPermission(){
TedPermission.with(this)
.setPermissionListener(object: PermissionListener {
override fun onPermissionGranted() {
createSession()
}
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
finish()
}
})
.setDeniedMessage(R.string.permission_denied_message)
.setPermissions(Manifest.permission.CAMERA)
.check()
}
ArFragment와 달리 자동으로 권한 요청을 해주지 않기 때문에 우선 권한 요청을 먼저 진행합니다.
편하게 사용하기 위해 TedPermission을 이용하였습니다.
https://github.com/ParkSangGwon/TedPermission
ParkSangGwon/TedPermission
Easy check permission library for Android Marshmallow - ParkSangGwon/TedPermission
github.com
private fun createSession(){
try {
session = Session(this)
configSession()
binding.arView.setupSession(session)
session.resume()
binding.arView.resume()
}catch (e : UnavailableArcoreNotInstalledException){
e.printStackTrace()
}catch (e : UnavailableApkTooOldException){
e.printStackTrace()
}catch (e : UnavailableSdkTooOldException){
e.printStackTrace()
}catch (e : UnavailableDeviceNotCompatibleException){
e.printStackTrace()
}catch (e : CameraNotAvailableException){
e.printStackTrace()
}catch (e : Exception){
e.printStackTrace()
}
}
private fun configSession(){
val config = Config(session)
config.updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE
config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
session.configure(config)
}
override fun onPause() {
super.onPause()
if(this::session.isInitialized){
session.pause()
binding.arView.pause()
}
}
override fun onDestroy() {
super.onDestroy()
session.close()
}
카메라 권한 요청 후 Session을 구성해줍니다.
Session 생성 후 binding.arView.setupSession(session) 를 해주면 기본적인 ARCore사용 준비는 완료가 됩니다.
Confing를 통해 [조명 추정, 클라우드 앵커, Augmented Images, Agumented Faces, 깊이 API, 즉시 배치]와 같은 각종 기능을 추가 설정이 가능합니다.
이번 예제에서는 업데이트 모드와 즉시 배치 모드를 활성화하였습니다.
private fun getAugmentImage() : Bitmap? {
try {
val inputStream = assets.open("qr.png")
val bitmap = BitmapFactory.decodeStream(inputStream)
binding.img.setImageBitmap(bitmap)
return bitmap
}catch (e : Exception){
e.printStackTrace()
}
return null
}
이번 예제에서는 Augmented Images를 진행할 예정이기 때문에 Bitmap 이미지를 준비합니다.
private fun buildDatabase(config : Config) : Boolean{
val bitmap = getAugmentImage() ?: return false
val augmentedImageDatabase = AugmentedImageDatabase(session)
augmentedImageDatabase.addImage("qr", bitmap)
config.augmentedImageDatabase = augmentedImageDatabase
return true
}
val augmentedImageDatabase = AugmentedImageDatabase(session) 를 통해 비어있는 데이터베이스를 생성합니다.
.addImage("qr", bitmap) 를 통해 새 이미지를 등록합니다. "qr"은 나중에 이미지 이름으로 사용할 것입니다.
그 후 confing에 추가 후 위에처럼 session에 등록하면 됩니다.
private fun initSceneView(){
binding.arView.scene.addOnUpdateListener(this)
}
override fun onUpdate(frameTime: FrameTime?) {
val frame = binding.arView.arFrame ?: return
frame.getUpdatedTrackables(AugmentedImage::class.java).forEach { augmentedImage ->
if(augmentedImage.trackingState == TrackingState.TRACKING){
if(augmentedImage.name == "qr"){
if(isAgumentedImageVisible.not()){
isAgumentedImageVisible = true
val anchorNode = AnchorNode()
anchorNode.renderable = renderable
binding.arView.scene.addChild(anchorNode)
}
}
}
}
}
scence의 업데이트 정보를 받기 위해 Scene.OnUpdateListener를 상속받아서 등록해줍니다.
augmentedImage가 추적 중 데이터베이스에 저장된 "qr"과 3D 모델이 나오도록 설정했습니다.
저는 고정 이미지를 사용했기 때문에 [ augmentedImage.trackingState == TrackingState.TRACKING ]이 조건만 넣었는데
움직이는 이미지 사용할 경우에는 아래의 조건을 추가해주면 됩니다.
if(augmentedImage.trackingMethod == AugmentedImage.TrackingMethod.FULL_TRACKING)
전체 코드
activity_arcore_augmented.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ARCoreAugmentedActivity">
<com.google.ar.sceneform.ArSceneView
android:id="@+id/arView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/img"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
ARCoreAugmentedActivity
class ARCoreAugmentedActivity : AppCompatActivity(), Scene.OnUpdateListener{
private val binding : ActivityArcoreAugmentedBinding by lazy { ActivityArcoreAugmentedBinding.inflate(layoutInflater) }
private lateinit var session : Session
private val fileName = "out.glb"
private var isAgumentedImageVisible : Boolean = false
private var renderable : ModelRenderable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// 카메라 권환 확인
checkCameraPermission()
}
private fun checkCameraPermission(){
TedPermission.with(this)
.setPermissionListener(object: PermissionListener {
override fun onPermissionGranted() {
initRenderableFile()
createSession()
initSceneView()
}
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
finish()
}
})
.setDeniedMessage(R.string.permission_denied_message)
.setPermissions(Manifest.permission.CAMERA)
.check()
}
// 3D 모델 파일 설정
private fun initRenderableFile(){
try {
val splitFileName = fileName.split(".")
val file = File.createTempFile(splitFileName[0], splitFileName[1])
assets.open(fileName).use { input->
file.outputStream().use { output ->
input.copyTo(output)
buildModel(file)
}
}
toast("3D 모델 준비 완료")
}catch (e: IOException){
e.printStackTrace()
toast("파일 다운로드중 오류가 발생하였습니다.")
}catch (e: java.lang.Exception){
e.printStackTrace()
toast("파일 다운로드중 오류가 발생하였습니다.")
}
}
private fun createSession(){
try {
// Create a new ARCore session
session = Session(this)
// Configure the session
configSession()
binding.arView.setupSession(session)
session.resume()
binding.arView.resume()
}catch (e : UnavailableArcoreNotInstalledException){
e.printStackTrace()
}catch (e : UnavailableApkTooOldException){
e.printStackTrace()
}catch (e : UnavailableSdkTooOldException){
e.printStackTrace()
}catch (e : UnavailableDeviceNotCompatibleException){
e.printStackTrace()
}catch (e : CameraNotAvailableException){
e.printStackTrace()
}catch (e : Exception){
e.printStackTrace()
}
}
private fun configSession(){
val config = Config(session)
if(!buildDatabase(config)){
Toast.makeText(this, "Error database", Toast.LENGTH_SHORT).show()
}
config.updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE
// 즉시 배치 모드를 설정합니다.
config.instantPlacementMode = Config.InstantPlacementMode.LOCAL_Y_UP
session.configure(config)
}
private fun buildDatabase(config : Config) : Boolean{
val bitmap = getAugmentImage() ?: return false
val augmentedImageDatabase = AugmentedImageDatabase(session)
augmentedImageDatabase.addImage("qr", bitmap)
config.augmentedImageDatabase = augmentedImageDatabase
return true
}
private fun getAugmentImage() : Bitmap? {
try {
val inputStream = assets.open("qr.png")
val bitmap = BitmapFactory.decodeStream(inputStream)
binding.img.setImageBitmap(bitmap)
return bitmap
}catch (e : Exception){
e.printStackTrace()
}
return null
}
private fun initSceneView(){
binding.arView.scene.addOnUpdateListener(this)
}
override fun onUpdate(frameTime: FrameTime?) {
val frame = binding.arView.arFrame ?: return
frame.getUpdatedTrackables(AugmentedImage::class.java).forEach { augmentedImage ->
if(augmentedImage.trackingState == TrackingState.TRACKING){
if(augmentedImage.name == "qr"){
if(isAgumentedImageVisible.not()){
isAgumentedImageVisible = true
val anchorNode = AnchorNode()
anchorNode.renderable = renderable
binding.arView.scene.addChild(anchorNode)
}
}
}
}
}
private fun buildModel(file: File){
val renderableSource =
RenderableSource
.builder()
.setSource(this, Uri.parse(file.path), RenderableSource.SourceType.GLB)
.setRecenterMode(RenderableSource.RecenterMode.ROOT)
.build()
ModelRenderable
.builder()
.setSource(this, renderableSource)
.setRegistryId(file.path)
.build()
.thenAccept{ modelRenderable ->
toast("다운로드 완료")
renderable = modelRenderable
}
.exceptionally {
toast("${it.message}")
return@exceptionally null
}
}
override fun onPause() {
super.onPause()
if(this::session.isInitialized){
session.pause()
binding.arView.pause()
}
}
override fun onDestroy() {
super.onDestroy()
session.close()
}
}
등록한 이미지 (qr코드)를 발견하면 오른쪽 사진과 같이 모델이 나오게 됩니다.
화면 오른쪽 화단은 그냥 예시 이미지로 넣어 둔 거고 필수는 아닙니다.
근데 이게 한 번 인식이 안되면 쫌 잘 안 되는 느낌이 있습니다.
저번 글에 올린 내용에 추가 제작을 하였습니다.
https://alanboyce.tistory.com/4
ARCore
이번 주는 ARCore에 대해서 공부를 진행하였습니다. 이름에서도 알 수 있듯이 ARCore는 AR의 필수 기능(모션 추적, 환경 이해 및 조명 추정 등)의 API를 제공해주는 SDK입니다. https://developers.google.com/ar.
alanboyce.tistory.com
지난 글에서 즉시 배치만 진행하였고 이번에 이름표 단것과 위에서 한 Agumented Image를 하려고 하였으나
Agumented Image는 아직 테스트 진행 중입니다.
시작하기 전에 앞서 지난번의 ArFragment 설정하는 부분을 수정하였습니다.
private fun settingArFragment() = with(binding){
arFragment = supportFragmentManager.findFragmentById(R.id.sceneFormFragment) as ArFragment
arFragment.setOnTapArPlaneListener { hitResult, plane, _ ->
when (radioGroup.checkedRadioButtonId) {
// 즉시 배치
radioInstantPlacement.id -> {
settingInstantPlacement(hitResult)
}
// 이름표
radioNameCard.id -> {
settingNameCard(hitResult)
}
}
}
}
private fun settingInstantPlacement(hitResult: HitResult) {
val anchorNode = AnchorNode(hitResult.createAnchor())
anchorNode.renderable = renderable
anchorNode.setOnTouchListener { _, _ ->
anchorNode.setParent(null)
return@setOnTouchListener true
}
arFragment.arSceneView.scene.addChild(anchorNode)
}
anchorNode.setOnTouchListener를 추가하여 등록한 anchor를 누르면 지울 수 있도록 설정하였습니다.
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/txtName"
android:background="@drawable/bg_round_teal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="20dp"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/white"
tools:text="고양이"/>
이름표를 위해 위의 xml을 만들었습니다.
private fun settingNameCard(hitResult: HitResult){
val anchorNode =AnchorNode(hitResult.createAnchor())
anchorNode.setParent(arFragment.arSceneView.scene)
val transFromAbleNode = TransformableNode(arFragment.transformationSystem)
transFromAbleNode.setParent(anchorNode)
transFromAbleNode.renderable = renderable
transFromAbleNode.select()
addName(anchorNode, transFromAbleNode, cardName)
}
private fun addName(anchorNode: AnchorNode, node: TransformableNode, name: String?) {
ViewRenderable.builder().setView(this, R.layout.name_card)
.build()
.thenAccept { viewRenderable ->
val nameView = TransformableNode(arFragment.transformationSystem)
nameView.localPosition = Vector3(0f, node.localPosition.y + 0.5f, 0f)
nameView.setParent(anchorNode)
nameView.renderable = viewRenderable
nameView.select()
val txtName = viewRenderable.view as TextView
txtName.text = name
txtName.setOnClickListener {
anchorNode.setParent(null)
}
}
}
이번에는 기본 AnchorNode 위에 TransformableNode를 올렸습니다.
이름 그대로 움직임이 가능한 노드입니다.
저 하얀색 점을 인식한 곳이면 이동이 가능한 AnchorNode입니다.
다음 주에는 AnchorNode 종류들과 Config 셋팅들 테스트 진행과 클라우드 앵커 부분 진행할 계획입니다.
'메타버스 스터디' 카테고리의 다른 글
ARCore 정리 (0) | 2021.08.06 |
---|---|
AR Core 4주차 (0) | 2021.07.16 |
ARCore 3주차 (0) | 2021.07.11 |
ARCore (0) | 2021.06.27 |
구글 글라스 학습 (구글 문서) (0) | 2021.06.20 |
- Total
- Today
- Yesterday
- Duplicate class found error
- WebView
- Android
- Pokedex
- Compose 네이버 지도
- Compose ConstraintLayout
- Compose Naver Map
- LazyColumn
- Compose 네이버 지도 api
- Row
- Compose QRCode Scanner
- Worker
- Compose BottomSheetDialog
- Compose BottomSheetScaffold
- 포켓몬 도감
- 안드로이드
- Compose BottomSheet
- Fast api
- WorkManager
- column
- Kotlin
- Compose MotionLayout
- 안드로이드 구글 지도
- Compose ModalBottomSheetLayout
- 웹뷰
- Duplicate class fond 에러
- Gradient
- compose
- Android Compose
- Retrofit
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |