1. 개요
ARCore는 Android의 증강 현실 앱을 빌드할 수 있는 플랫폼입니다. 증강 이미지를 사용하면 현실에서 사전 등록된 2D 이미지를 인식하고 그 위에 가상 콘텐츠를 고정하는 AR 앱을 만들 수 있습니다.
이 Codelab에서는 기존 ARCore 샘플 앱을 수정하여 움직이거나 고정된 증강 이미지를 통합하는 과정을 안내합니다.
빌드할 항목
이 Codelab에서는 기존 ARCore 샘플 앱을 기반으로 빌드하겠습니다. Codelab을 마치면 앱에 다음 기능을 구현할 수 있습니다.
- 이미지 대상을 감지하고 대상에 가상 미로 연결
- 움직이는 대상이 카메라 뷰 안에 있을 때 대상 추적
ARCore 앱을 처음 만드시는 건가요?
이 Codelab에서 샘플 코드를 작성하실 건가요, 아니면 이 페이지를 읽기만 하실 건가요?
학습할 내용
- 자바로 ARCore에서 증강 이미지를 사용하는 방법
- ARCore의 이미지 인식 능력을 측정하는 방법
- 이미지에 가상 콘텐츠를 연결하고 이미지의 움직임을 추적하는 방법
기본 요건
이 Codelab을 완료하려면 특정 하드웨어와 소프트웨어가 필요합니다.
하드웨어 요구사항
- USB 케이블을 통해 개발 머신에 연결되는 ARCore 지원 기기
소프트웨어 요구사항
- ARCore APK 1.9.0 이상. 이 APK는 일반적으로 Play 스토어를 통해 기기에 자동으로 설치됩니다.
- Android 스튜디오(v3.1 이상)가 설치된 개발 머신
- 인터넷 액세스: 개발 중에 라이브러리를 다운로드할 때 필요합니다.
이제 모두 준비했으니 시작하겠습니다.
2. 개발 환경 설정
SDK 다운로드
먼저 GitHub에서 최신 ARCore Android SDK를 다운로드합니다. 원하는 위치에 압축 해제합니다. 이 Codelab의 경우 최초 SDK 버전은 1.18.1입니다. 폴더 이름은 arcore-android-sdk-x.xx.x
로 지정되며 정확한 값은 사용하는 SDK 버전입니다.
Android 스튜디오를 시작하고 기존 Android 스튜디오 열기를 클릭합니다.
다음의 압축 해제된 폴더로 이동합니다.
arcore-android-sdk-x.xx.x/samples/augmented_image_java
열기를 클릭합니다.
Android 스튜디오가 프로젝트 동기화를 마칠 때까지 기다립니다. Android 스튜디오에 필요한 구성요소가 없는 경우 Install missing platform and sync project
메시지와 함께 작업에 실패할 수 있습니다. 안내에 따라 문제를 해결합니다.
샘플 앱 실행
이제 작동하는 ARCore 앱 프로젝트가 있으므로 테스트를 실행해 봅니다.
ARCore 기기를 개발 머신에 연결하고 실행 > 앱 실행 메뉴를 사용하여 기기에서 디버그 버전을 실행합니다. 실행할 기기를 선택하라는 대화상자에서 연결된 기기를 선택하고 확인을 클릭합니다.
이 샘플 프로젝트는 targetSdkVersion 28
을 사용합니다. Failed to find Build Tools revision 28.0.3
과 같은 빌드 오류가 발생하는 경우 Android 스튜디오에서 설명하는 안내에 따라 필요한 Android 빌드 도구 버전을 다운로드하여 설치합니다.
여기까지 모두 성공하면 기기에서 샘플 앱이 실행되며 증강 이미지의 사진 및 동영상 촬영을 허용할 것을 요청하는 메시지가 표시됩니다. 허용을 탭하여 권한을 부여합니다.
샘플 이미지로 테스트
이제 개발 환경을 설정했으므로 표시할 이미지를 제공하여 앱을 테스트할 수 있습니다.
Android 스튜디오에서 프로젝트 창의 앱 > 애셋으로 이동하고 default.jpg
파일을 더블클릭하여 엽니다.
화면에 있는 지구 이미지에 기기 카메라를 대고 안내에 따라 스캔할 이미지를 십자선에 맞춥니다.
이미지 프레임이 다음과 같이 이미지 위에 오버레이됩니다.
다음으로 샘플 앱을 약간 개선합니다.
3. 2D 이미지에 미로 모델 표시
위에 3D 모델을 표시하여 증강 이미지로 플레이를 시작할 수 있습니다.
3D 모델 다운로드
이 Codelab에서는 게시자가 Evol이고 CC-BY 3.0에 따라 라이선스가 부여된 Circle Maze - Green을 사용합니다. 이 3D 모델의 사본을 이 Codelab의 GitHub 저장소에 저장했습니다. 사본은 여기에서 확인할 수 있습니다.
다음 단계에 따라 모델을 다운로드하여 Android 스튜디오에 포함하세요.
- 이 Codelab의 GitHub 저장소인 third_party 디렉터리로 이동합니다.
- GreenMaze_obj.zip을 클릭하고 다운로드 버튼을 클릭합니다.
그러면 GreenMaze_obj.zip
이라는 파일이 다운로드됩니다.
- Android 스튜디오의 앱 > 애셋 > 모델 아래에
green-maze
디렉터리를 만듭니다. GreenMaze_obj.zip
을 압축 해제하고 콘텐츠를arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/assets/models/green-maze
위치에 복사합니다.- Android 스튜디오에서 앱 > 애셋 > 모델 > green-maze로 이동합니다.
이 폴더에는 GreenMaze.obj
및 GreenMaze.mtl
의 두 파일이 있습니다.
미로 모델 렌더링
다음 단계에 따라 기존 2D 이미지 위에 GreenMaze.obj
3D 모델을 표시합니다.
미로 모델을 렌더링하도록 AugmentedImageRenderer.java
에서 mazeRenderer
라는 멤버 변수를 추가합니다. 미로를 이미지에 연결해야 하므로 mazeRenderer
를 AugmentedImageRenderer
클래스 안에 배치하는 것이 좋습니다.
AugmentedImageRenderer.java
// Add a member variable to hold the maze model.
private final ObjectRenderer mazeRenderer = new ObjectRenderer();
createOnGlThread()
함수에서 GreenMaze.obj
를 로드합니다. 여기서는 편의를 위해 동일한 프레임 텍스처를 텍스처로 사용합니다.
AugmentedImageRenderer.java
// Replace the definition of the createOnGlThread() function with the
// following code, which loads GreenMaze.obj.
public void createOnGlThread(Context context) throws IOException {
mazeRenderer.createOnGlThread(
context, "models/green-maze/GreenMaze.obj", "models/frame_base.png");
mazeRenderer.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);
}
draw()
함수의 정의를 다음과 같이 바꿉니다. 이렇게 하면 미로의 크기를 감지된 이미지의 크기에 맞춰 조정하고 화면에 렌더링합니다.
AugmentedImageRenderer.java
// Adjust size of detected image and render it on-screen
public void draw(
float[] viewMatrix,
float[] projectionMatrix,
AugmentedImage augmentedImage,
Anchor centerAnchor,
float[] colorCorrectionRgba) {
float[] tintColor =
convertHexToColor(TINT_COLORS_HEX[augmentedImage.getIndex() % TINT_COLORS_HEX.length]);
final float mazeEdgeSize = 492.65f; // Magic number of maze size
final float maxImageEdgeSize = Math.max(augmentedImage.getExtentX(), augmentedImage.getExtentZ()); // Get largest detected image edge size
Pose anchorPose = centerAnchor.getPose();
float mazeScaleFactor = maxImageEdgeSize / mazeEdgeSize; // scale to set Maze to image size
float[] modelMatrix = new float[16];
// OpenGL Matrix operation is in the order: Scale, rotation and Translation
// So the manual adjustment is after scale
// The 251.3f and 129.0f is magic number from the maze obj file
// You mustWe need to do this adjustment because the maze obj file
// is not centered around origin. Normally when you
// work with your own model, you don't have this problem.
Pose mazeModelLocalOffset = Pose.makeTranslation(
-251.3f * mazeScaleFactor,
0.0f,
129.0f * mazeScaleFactor);
anchorPose.compose(mazeModelLocalOffset).toMatrix(modelMatrix, 0);
mazeRenderer.updateModelMatrix(modelMatrix, mazeScaleFactor, mazeScaleFactor/10.0f, mazeScaleFactor); // This line relies on a change in ObjectRenderer.updateModelMatrix later in this codelab.
mazeRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);
}
이제 미로가 지구의 default.jpg
그림 위에 표시됩니다.
참고: 이 샘플 3D 모델을 완전히 제어할 수 없기 때문에 위의 코드에서는 몇 가지 '마법'의 숫자를 사용합니다. 미로 모델의 크기는 492.65 x 120 x 492.65이고 중심 위치는 (251.3, 60, -129.0)입니다. 꼭짓점 X, Y, Z 좌표값의 범위는 각각 [5.02, 497.67], [0, 120], [-375.17, 117.25]입니다. 따라서 미로 모델의 크기는 image_size / 492.65
여야 합니다. 미로의 3D 모델 중심이 원점 (0, 0, 0)에 맞춰지지 않기 때문에 mazeModelLocalOffset
이 도입됩니다.
미로의 벽이 여전히 너무 높아서 사진 위에 놓을 수 없습니다. X, Y, Z를 균등하지 않게 조정하여 미로의 높이를 0.1로 조정할 수 있는 도우미 함수 updateModelMatrix()
를 만듭니다. 기존 updateModelMatrix(float[] modelMatrix, float scaleFactor)
를 유지하고 함수 오버로드 updateModelMatrix(float[] modelMatrix, float scaleFactorX, float scaleFactorY, float scaleFactorZ)
를 새 함수로 추가해야 합니다.
common/rendering/ObjectRenderer.java
// Scale X, Y, Z coordinates unevenly
public void updateModelMatrix(float[] modelMatrix, float scaleFactorX, float scaleFactorY, float scaleFactorZ) {
float[] scaleMatrix = new float[16];
Matrix.setIdentityM(scaleMatrix, 0);
scaleMatrix[0] = scaleFactorX;
scaleMatrix[5] = scaleFactorY;
scaleMatrix[10] = scaleFactorZ;
Matrix.multiplyMM(this.modelMatrix, 0, modelMatrix, 0, scaleMatrix, 0);
}
코드를 실행합니다. 이제 미로가 이미지 위에 완벽하게 맞습니다.
4. 미로에 앤디 추가
이제 미로가 준비되었으므로 그 안에서 움직일 캐릭터를 추가합니다. ARCore Android SDK에 포함된 andy.obj
파일을 사용합니다. 이미지 위에 렌더링되는 녹색 미로와 다르게 보이기 때문에 이미지 프레임 텍스처를 텍스처로 유지합니다.
AugmentedImageRenderer.java
에서 비공개 ObjectRenderer
를 추가하여 앤디를 렌더링합니다.
AugmentedImageRenderer.java
// Render for Andy
private final ObjectRenderer andyRenderer = new ObjectRenderer();
다음으로 createOnGlThread()
의 끝에서 andyRenderer
를 초기화합니다.
AugmentedImageRenderer.java
public void createOnGlThread(Context context) throws IOException {
// Initialize andyRenderer
andyRenderer.createOnGlThread(
context, "models/andy.obj", "models/andy.png");
andyRenderer.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);
}
마지막으로 draw()
함수 끝에서 미로 위에 서 있는 앤디를 렌더링합니다.
AugmentedImageRenderer.java
public void draw(
float[] viewMatrix,
float[] projectionMatrix,
AugmentedImage augmentedImage,
Anchor centerAnchor,
float[] colorCorrectionRgba) {
// Render Andy, standing on top of the maze
Pose andyModelLocalOffset = Pose.makeTranslation(
0.0f,
0.1f,
0.0f);
anchorPose.compose(andyModelLocalOffset).toMatrix(modelMatrix, 0);
andyRenderer.updateModelMatrix(modelMatrix, 0.05f); // 0.05f is a Magic number to scale
andyRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);
}
코드를 실행합니다. 미로 위에 서 있는 앤디가 보입니다.
대상 이미지 품질 확인
ARCore는 시각적 특징을 사용하여 이미지를 인식합니다. 품질 차이로 인해 모든 이미지가 쉽게 인식되지는 않습니다.
arcoreimg
는 ARCore에서 이미지가 얼마나 잘 인식될지 판단할 수 있는 명령줄 도구입니다. 0에서 100 사이의 숫자를 출력합니다(100은 가장 쉽게 인식됨을 나타냄).
다음 예시를 참고하세요.
arcore-android-sdk-x.xx.x/tools/arcoreimg/macos$
$ ./arcoreimg eval-img --input_image_path=/Users/username/maze.jpg
100
maze.jpg
값은 100이므로 ARCore에서 쉽게 인식할 수 있습니다.
5. 선택사항: 앤디가 미로에서 움직이게 하기
마지막으로 몇 가지 코드를 추가하여 앤디가 미로에서 움직이게 할 수 있습니다. 예를 들어 오픈소스 물리학 엔진인 jBullet을 사용하여 물리학 시뮬레이션을 처리합니다. 이 부분을 건너뛰어도 괜찮습니다.
PhysicsController.java
를 다운로드하여 디렉터리의 프로젝트에 추가합니다.
arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/java/com/google/ar/core/examples/java/augmentedimage/
Android 스튜디오에서 GreenMaze.obj를 project assets 디렉터리에 추가하여 런타임에 로드할 수 있도록 합니다. GreenMaze.obj
를 앱 > 애셋 > 모델 > green-maze에서 앱 > 애셋으로 복사합니다.
앱의 build.gradle
파일에 다음 종속 항목을 추가합니다.
app/build.gradle
// jbullet library
implementation 'cz.advel.jbullet:jbullet:20101010-1'
// Obj - a simple Wavefront OBJ file loader
// https://github.com/javagl/Obj
implementation 'de.javagl:obj:0.2.1'
앤디의 현재 포즈의 위치를 저장할 andyPose
변수를 정의합니다.
AugmentedImageRenderer.java
// Create a new pose for the Andy
private Pose andyPose = Pose.IDENTITY;
새 andyPose
변수를 사용하여 앤디를 렌더링하도록 AugmentedImageRenderer.java
를 수정합니다.
AugmentedImageRenderer.java
public void draw(
float[] viewMatrix,
float[] projectionMatrix,
AugmentedImage augmentedImage,
Anchor centerAnchor,
float[] colorCorrectionRgba) {
// Use these code to replace previous code for rendering the Andy object
//
// Adjust the Andy's rendering position
// The Andy's pose is at the maze's vertex's coordinate
Pose andyPoseInImageSpace = Pose.makeTranslation(
andyPose.tx() * mazeScaleFactor,
andyPose.ty() * mazeScaleFactor,
andyPose.tz() * mazeScaleFactor);
anchorPose.compose(andyPoseInImageSpace).toMatrix(modelMatrix, 0);
andyRenderer.updateModelMatrix(modelMatrix, 0.05f);
andyRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);
}
새로운 유틸리티 함수 updateAndyPose()
를 추가하여 앤디 포즈 업데이트를 수신합니다.
AugmentedImageRenderer.java
// Receive Andy pose updates
public void updateAndyPose(Pose pose) {
andyPose = pose;
}
AugmentedImageActivity.java
에서 JBullet 물리학 엔진을 사용하여 모든 물리학 관련 함수를 관리하는 PhysicsController
객체를 만듭니다.
AugmentedImageActivity.java
import com.google.ar.core.Pose;
// Declare the PhysicsController object
private PhysicsController physicsController;
물리학 엔진에서는 실제로 단단한 공으로 앤디를 나타내고 공의 포즈를 통해 앤디의 포즈를 업데이트합니다. 앱에서 이미지를 인식할 때마다 물리학을 업데이트하려면 PhysicsController
를 호출하세요. 실제처럼 공을 옮기려면 실제 중력을 적용하여 미로에서 공을 움직이세요.
AugmentedImageActivity.java
// Update the case clause for TRACKING to call PhysicsController
// whenever the app recognizes an image
private void drawAugmentedImages(
...
case TRACKING:
// Switch to UI Thread to update View
this.runOnUiThread(
new Runnable() {
@Override
public void run() {
fitToScanView.setVisibility(View.GONE);
}
});
// Create a new anchor for newly found images
if (!augmentedImageMap.containsKey(augmentedImage.getIndex())) {
Anchor centerPoseAnchor = augmentedImage.createAnchor(augmentedImage.getCenterPose());
augmentedImageMap.put(
augmentedImage.getIndex(), Pair.create(augmentedImage, centerPoseAnchor));
physicsController = new PhysicsController(this);
} else {
Pose ballPose = physicsController.getBallPose();
augmentedImageRenderer.updateAndyPose(ballPose);
// Use real world gravity, (0, -10, 0), as gravity
// Convert to Physics world coordinate(maze mesh has to be static)
// Use the converted coordinate as a force to move the ball
Pose worldGravityPose = Pose.makeTranslation(0, -10f, 0);
Pose mazeGravityPose = augmentedImage.getCenterPose().inverse().compose(worldGravityPose);
float mazeGravity[] = mazeGravityPose.getTranslation();
physicsController.applyGravityToBall(mazeGravity);
physicsController.updatePhysics();
}
break;
앱을 실행하세요. 이제 이미지를 기울일 때 앤디가 현실적으로 움직입니다.
아래 예시에서는 다른 휴대전화를 사용하여 이미지를 표시합니다. 태블릿, 책 표지 또는 평평한 물체에 부착한 인쇄 용지 등 원하는 대로 자유롭게 사용하세요.
작업이 끝났습니다. 앤디가 미로를 통과할 수 있게 도와주세요. 힌트: 대상 이미지를 거꾸로 보이게 잡으면 더 쉽게 출구를 찾을 수 있습니다.
6. 축하합니다
축하합니다. 이 Codelab의 내용을 완료했으며 다음을 수행했습니다.
- ARCore AugmentedImage 자바 샘플을 빌드하고 실행했습니다.
- 이미지에 미로 모델을 적절한 크기로 표시하도록 샘플을 업데이트했습니다.
- 이미지의 위치를 활용하여 재미있는 효과를 만들었습니다.
전체 코드를 참고하려면 여기에서 다운로드하세요.