본문 바로가기



관련 기술/AR(증강현실)

Sceneform을 적용하여 기본 AR앱을 만들어보자 (1)

이번 글에서는 Sceneform을 사용하여 간단히 평면을 인식하고 안드로보이 3D 객체를 배치하는 방법을 살펴보겠습니다.
이번에 사용하는 소스코드들은 구글의  Introduction to Sceneform (codelabs.developers.google.com/codelabs/sceneform-intro/index.html#0)과 개발자 문서, 그리고 GitHub의 Sceneform 코드(특히 hellosceneform, https://github.com/google-ar/sceneform-android-sdk/tree/v1.15.0/samples/hellosceneform)를 참고하였습니다.

 

개발하면서 안드로이드 디바이스 또는 에뮬레이터를 사용할 수 있는데 저는 안드로이드 기반의 스마트폰을 사용하고 있으므로 에뮬레이터가 아닌 실제 장비를 사용하겠습니다.
에뮬레이터를 사용하시려면 개발자 문서 '장치 또는 에뮬레이터 준비 (developers.google.com/sceneform/develop/android-quickstart)'를 참고하세요.

 

 

 

 

 

먼저 build.gradle (Module: app) 파일에 개발환경을 설정해줍니다.

 

첫번째로 자바 버전을 1.8 이상 사용할 것을 등록해 줍니다.
이 항목은 따로 추가하지 않아도 별 문제는 일으키지 않던데 이미 개발 PC에 1.8버전이 깔려있어서 그런지도 모릅니다.
다른 환경에서는 어떻게 동작할지 모르니까 일단 선언해 줍시다.

compileOptions {
	sourceCompatibility JavaVersion.VERSION_1_8
	targetCompatibility JavaVersion.VERSION_1_8
}

다음으로 dependencies에 ARCore를 사용할 것을 선언해 줍니다.

implementation 'com.google.ar:core:1.18.0'

이제 Sceneform을 사용할 것을 선언해 줍시다.
참고로 1.16.0 버전부터 오픈소스로 전환되었다고 소개되고 있으며 GitHub에도 1.16.0 버전이 제공되고 있지만 개발자 문서에는 아직 1.15.0 버전에 대해서만 다루고 있습니다.

implementation 'com.google.ar.sceneform:core:1.15.0'
implementation "com.google.ar.sceneform.ux:sceneform-ux:1.15.0"

그런데 dependencies에 이 설정들을 적용하라고 개발자 문서에 여기저기 흩어져 있습니다만.. 

제공되는 샘플코드 중에는

implementation "com.google.ar.sceneform.ux:sceneform-ux:1.15.0"

이것만 달랑 등록되어 있는 코드가 많습니다.
Sync Now 버튼을 통해 다운로드할 때 저녀석만 포함되어 있으면 다 물고 들어오는지는 잘 모르겠지만.. 동작은 제대로 하더군요.

예전처럼 "Sync Now" 버튼을 눌러서 필요한 라이브러리 등 의존형 모듈들을 다운로드 합니다.

 

 

다음으로 해야 할 작업은 AndroidManifest.xml 파일의 수정이죠.


카메라를 사용하기 위한 권한을 등록하고 해당 특성을 등록합니다.

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera2" android:required="true" />

그리고 ARCore를 사용하겠다는 메타정보를 등록하죠.

<meta-data android:name="com.google.ar.core" android:value="required" />

여기까지는 지난 글에서 다룬 ARCore를 사용하기 위한 기본 내용과 같습니다.

 

 

그럼 이제 MainActivity.java 파일을 작성해 줄 차례입니다.

 

자동으로 생성된 파일에 아래와 같이 코드를 입력합니다.

package com.aidalab.proparsf;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;

import com.google.ar.core.ArCoreApk;
import com.google.ar.core.Session;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = MainActivity.class.getSimpleName();

    private Session mSession;
    private boolean mUserRequestInstall = true;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onResume() {
        super.onResume();
        requestCameraPermission();

        try {
            if(mSession == null) {
                switch(ArCoreApk.getInstance().requestInstall(this, mUserRequestInstall)) {
                    case INSTALLED:
                        mSession = new Session(this);
                        Toast.makeText(this, "Session is created.", Toast.LENGTH_LONG).show();
                        break;

                    case INSTALL_REQUESTED:
                        mUserRequestInstall = false;
                        Toast.makeText(this, "Session creation is failed", Toast.LENGTH_LONG).show();
                        return;
                }
            }
        } catch(Exception e) {
            Toast.makeText(this, "TODO: Handle exception " + e, Toast.LENGTH_LONG).show();
            return;
        }
    }

    private void requestCameraPermission(){
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 0);
        }
    }
}

이전 글(ARCore 프로젝트 생성 및 초기설정 하기, https://aidalab.tistory.com/60)에서 다룬 코드와 거의 동일합니다.

 

그런데 아래에 requestCameraPermission() 메소드를 추가해 둔 것이 보입니다. 

 

처음 개발환경을 설정할 때, 분명히 카메라 사용 권한을 지정했지만 실제로 빌드 후 실행해 보면 카메라의 권한을 가져오지 못하는 경우가 많습니다.

그럴때는 스마트폰에서 빌드 후 설치된 프로그램의 설정으로 들어가 카메라 허용을 해 주면 됩니다만.. 귀찮습니다.

그래서 requestCameraPermission() 메소드를 사용해서 권한을 받지 못한 경우에는 직접 권한을 주도록 합니다.

 

 

 

 

 

이제 이 코드에 Sceneform을 사용하기 위한 코드로 변형하도록 하겠습니다.

 

기존의 코드에서는 세션을 사용하기 위하여 mSession 변수를 사용하였고, ARCore 라이브러리의 인스톨 여부를 확인하기 위하여 mUserRequestInstall 변수를 사용하였습니다.

그런데 Sceneform에서는 해당 작업들을 내부적으로 처리하기 때문에 필요없는 코드가 되었습니다.

대신 ArFragment라는 것을 사용합니다.

ArFragment는 AR 시스템의 상태를 관리하고 세션의 수명주기를 처리하는 ARCore API의 주요 진입점의 역할을 합니다.

우리는 ArFragment 클래스를 사용하여 세션의 생성, 구성, 시작 및 중지가 가능하며, 카메라 이미지와 장치, 포즈에 접근할 수 있는 프레임을 수신합니다.

import com.google.ar.sceneform.ux.ArFragment;

...

private ArFragment arFragment;

 

다음으로 onCreate 메소드를 고쳐보겠습니다.

  @Override
  @SuppressWarnings({"AndroidApoChecker", "FutureReturnValueIgnored"})
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if(!checkIsSupportedDeviceOrFinish(this)){
      return;
    }
    setContentView(R.layout.activity_main);
  }

먼저 기본적인 코드 외에 checkIsSupportedDeviceOrFinish() 함수를 사용하고 있습니다.

시스템이 Sceneform SDK를 실행할 수 있는지 없는지 확인해서 결과를 돌려보내주는 역할을 합니다.

Sceneform SDK는 기본적으로 Java 1.8 버전 이상, OpenGL 3.0 버전 이상에서 동작합니다.

위에서 설정했던 자바 버전이 여기에서 사용되네요.

그리고 OpenGL 최소버전을 상수로 하나 정의해 두죠.

private static final double MIN_OPENGL_VERSION = 3.0;

...

	/**
     * Sceneform을 실행할 수 없으면 false 반환
     *
     * @param activity
     * @return
     */
    public static boolean checkIsSupportedDeviceOrFinish(final Activity activity){
        // SDK 최소 버전을 만족하지 못하면 False Return
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N){
            Log.e(TAG, "Sceneform requires Android N or later");
            Toast.makeText(activity, "Sceneform requires Android N or later.", Toast.LENGTH_LONG).show();
            activity.finish();
            return false;
        }

        // OpenGL 최소 버전을 만족하지 못하면 False Return
        String openGlVersionString =
                ((ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE))
                        .getDeviceConfigurationInfo()
                        .getGlEsVersion();

        if (Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) {
            Log.e(TAG, "Sceneform requires OpenGL ES 3.0 later");
            Toast.makeText(activity, "Sceneform requires OpenGL ES 3.0 or later", Toast.LENGTH_LONG)
                    .show();
            activity.finish();
            return false;
        }
        return true;
    }

여기까지 해서 빌드를 해 보죠.

문제 없이 잘 실행됩니다.

 

그럼 다음으로 화면을 그리기 위한 레이아웃을 잡아주도록 합시다.

activity_main.xml 파일을 열어서 기존 코드에 포함된 Hello World를 출력하는 TextView를 지우고, Fragment 요소를 등록해 줍니다.

Fragment 요소에 영상, 객체를 그려넣게 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:name="com.google.ar.sceneform.ux.ArFragment"
        android:id="@+id/ux_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

 

그럼 다시 빌드해 보겠습니다.

Sceneform 적용 앱 화면 (1)

잘 나오는군요.

평면을 인식하기 위하여 스마트폰을 이리저리 움직여보라는 아이콘이 화면에 떠 있습니다.

스마트폰을 이리저리 움직여보면 평면을 인식한 포인트 클라우드가 화면에 표시됩니다.

Sceneform 적용 앱 화면 (2)

아주 간단하게 Sceneform을 적용한 앱이 완성되었습니다.

 

Sceneform을 사용하지 않고 직접 제어하는 방식으로도 만들어보았는데 생각보다 잘 동작하지 않더군요.

평면을 인식하고 평면에 색깔을 입혀서 그리는 동작 자체는 문제없이 수행되지만 속도가 좀 느리고 평면을 그릴 때 외곽을 제대로 인식하지 못하여 겹치게 되거나 이전에 그린 평면이 확장되다가 갑자기 다른 위치의 평면을 인식하면서 사라져버리거나.. 하는 경우가 생겼습니다.

Sceneform을 적용하지 않은 앱 화면

세부적인 구현까지 직접하는 것이 좋긴 하지만 프레임워크, 라이브러리가 이미 제공하는 기능은 잘 활용하는 것도 좋은 방법일 것입니다.

 

다음 글에서는 3D 객체를 평면에 배치하는 기능을 다루겠습니다.

 

 

 

 

반응형