1. Omówienie
ARCore to platforma do tworzenia aplikacji rzeczywistości rozszerzonej na Androida. Obrazy rozszerzone to funkcja, która umożliwia tworzenie aplikacji AR, które rozpoznają wstępnie zarejestrowane obrazy 2D w świecie rzeczywistym i zakotwiczają treści wirtualne.
Dzięki temu ćwiczeniu w Codelabs dowiesz się, jak zmodyfikować istniejącą przykładową aplikację ARCore, aby dodać obrazy rozszerzone, które poruszają się lub są stałe.
Co utworzysz
W tym ćwiczeniu w Codelabs utworzysz kontynuację istniejącej przykładowej aplikacji ARCore. Po zakończeniu ćwiczeń w Codelabs Twoja aplikacja będzie mogła:
- Wykryj cel obrazu i dołącz wirtualny labirynt na celowniku
- Śledź ruchomy cel, dopóki znajduje się w polu widzenia kamery
Czy tworzysz aplikację ARCore po raz pierwszy?
Planujesz napisać przykładowy kod w ramach tego ćwiczenia z programowania czy tylko przeczytać te strony?
Czego się nauczysz
- Jak korzystać z obrazów rozszerzonych w ARCore w Javie
- Jak ocenić zdolność obrazu do rozpoznania przez ARCore
- Jak załączyć treści wirtualne do obrazu i śledzić jego ruch
Wymagania wstępne
Do wykonania tego ćwiczenia z programowania potrzebne jest odpowiedni sprzęt i oprogramowanie.
Wymagania sprzętowe
- Urządzenie obsługujące ARCore podłączone do komputera kablem USB
Wymagania dotyczące oprogramowania
- Plik APK ARCore w wersji 1.9.0 lub nowszej. Ten plik APK jest zwykle automatycznie instalowany na urządzeniu ze Sklepu Play.
- Maszynę deweloperską z Android Studio (wersja 3.1 lub nowsza).
- dostęp do internetu, ponieważ biblioteki trzeba pobierać na etapie tworzenia aplikacji.
Skoro masz już wszystko gotowe, możemy zacząć!
2. Konfigurowanie środowiska programistycznego
Pobierz pakiet SDK
Zaczniemy od pobrania najnowszego pakietu ARCore Android SDK z GitHuba. Rozpakuj go do wybranej lokalizacji. Najwcześniejszą wersją pakietu SDK na potrzeby tego ćwiczenia w Codelabs jest 1.18.1. Folder będzie nazywać się arcore-android-sdk-x.xx.x
, a dokładną wartością będzie używana przez Ciebie wersja pakietu SDK.
Uruchom Android Studio i kliknij Otwórz istniejący projekt Android Studio.
Przejdź do tego rozpakowanego folderu:
arcore-android-sdk-x.xx.x/samples/augmented_image_java
Kliknij Otwórz.
Poczekaj, aż Android Studio zakończy synchronizację projektu. Jeśli Android Studio nie ma wymaganych komponentów, może wystąpić błąd i wyświetlić komunikat Install missing platform and sync project
. Postępuj zgodnie z instrukcjami, aby rozwiązać problem.
Uruchamianie przykładowej aplikacji
Masz już działający projekt aplikacji ARCore, czas więc na przetestowanie aplikacji.
Połącz urządzenie ARCore z maszyną programistyczną i użyj menu Uruchom > Uruchom „app”, aby uruchomić wersję do debugowania na urządzeniu. W oknie z prośbą o wybranie urządzenia, na którym chcesz uruchomić aplikację, wybierz połączone urządzenie i kliknij OK.
Ten przykładowy projekt używa targetSdkVersion 28
. Jeśli wystąpił błąd kompilacji, na przykład Failed to find Build Tools revision 28.0.3
, postępuj zgodnie z instrukcjami w Android Studio, aby pobrać i zainstalować wymaganą wersję Android Build Tools.
Jeśli wszystko będzie w porządku, przykładowa aplikacja uruchomi się na urządzeniu i poprosi o pozwolenie na robienie zdjęć i nagrywanie filmów w ramach obrazu rozszerzonego. Aby przyznać uprawnienia, kliknij ZEZWÓL.
Testowanie za pomocą przykładowego obrazu
Po skonfigurowaniu środowiska programistycznego możesz przetestować aplikację, nadając jej obraz.
Wróć do Android Studio i w oknie Projekt przejdź do sekcji aplikacja > zasoby i kliknij dwukrotnie plik default.jpg
, aby go otworzyć.
Skieruj aparat urządzenia na obraz Ziemi na ekranie i postępuj zgodnie z instrukcjami, aby dopasować do krzyżyka skanowanego obrazu.
Na obraz zostanie nałożona ramka, która wygląda tak:
Następnie wprowadzimy niewielkie ulepszenia w aplikacji próbnej.
3. Wyświetl model labiryntu na obrazie 2D
Aby zacząć korzystać z obrazów rozszerzonych, możesz wyświetlić na nich model 3D.
Pobierz model 3D
W tym ćwiczeniu w Codelabs użyjemy „Circle Maze – Green”. przez Evol. na licencji CC-BY 3.0. Zapisałam kopię tego modelu 3D w repozytorium GitHub dotyczącym tego ćwiczenia z programowania. Znajdziesz je tutaj.
Aby pobrać model i dołączyć go do Android Studio, wykonaj te czynności.
- Przejdź do repozytorium GitHub tego ćwiczenia z programowania, katalogu innej firmy.
- Kliknij GreenMaze_obj.zip i kliknij przycisk Pobierz.
Spowoduje to pobranie pliku o nazwie GreenMaze_obj.zip
.
- W Android Studio utwórz katalog
green-maze
w sekcji aplikacja > zasoby > modele - Rozpakuj plik
GreenMaze_obj.zip
i skopiuj zawartość do tej lokalizacji:arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/assets/models/green-maze
- W Android Studio przejdź do menu aplikacja > zasoby > modele > Green-maze.
W tym folderze powinny się znajdować 2 pliki: GreenMaze.obj
i GreenMaze.mtl
.
Renderowanie modelu labiryntu
Wykonaj te czynności, aby wyświetlić model 3D GreenMaze.obj
na istniejącym zdjęciu 2D.
W obiekcie AugmentedImageRenderer.java
dodaj zmienną składową o nazwie mazeRenderer
, aby wyrenderować model labiryntu. Ponieważ labirynt powinien zostać dołączony do obrazu, warto umieścić mazeRenderer
wewnątrz klasy AugmentedImageRenderer
.
AugmentedImageRenderer.java
// Add a member variable to hold the maze model.
private final ObjectRenderer mazeRenderer = new ObjectRenderer();
W funkcji createOnGlThread()
wczytaj GreenMaze.obj
. Dla uproszczenia użyj tej samej tekstury ramki co jej tekstura.
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);
}
Zastąp definicję funkcji draw()
podaną niżej. Spowoduje to dostosowanie rozmiaru labiryntu do rozmiaru wykrytego obrazu i wyświetlenie go na ekranie.
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);
}
Teraz labirynt powinien wyświetlić się na zdjęciu Ziemi na ekranie default.jpg
.
Uwaga: nie masz pełnej kontroli nad tym przykładowym modelem 3D, więc powyższy kod stosuje kilka „magicznych” funkcji liczby. Wymiary modelu labiryntu to 492,65 x 120 x 492,65, ze środkiem w wysokości (251,3, 60, -129,0). Zakres jego wierzchołków Wartości współrzędnych X, Y i Z to odpowiednio [5, 02; 497, 67], [0, 120] i [-375,17; 117, 25]. Dlatego skala modelu labiryntu musi wynosić image_size / 492.65
. Element mazeModelLocalOffset
został wprowadzony, ponieważ model 3D labiryntu nie jest wyśrodkowany wokół punktu początkowego (0, 0, 0).
Ściana labiryntu jest wciąż za wysoka, aby zmieścić się na zdjęciu. Utwórz funkcję pomocniczą updateModelMatrix()
, która może skalować X, Y i Z nierównomiernie, aby przeskalować wysokość labiryntu o 0,1. Zachowaj istniejącą funkcję updateModelMatrix(float[] modelMatrix, float scaleFactor)
i dodaj przeciążenie funkcji updateModelMatrix(float[] modelMatrix, float scaleFactorX, float scaleFactorY, float scaleFactorZ)
jako nową funkcję.
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);
}
Uruchom kod. Labirynt powinien teraz dokładnie mieścić się nad zdjęciem.
4. Dodaj Andy’ego do labiryntu
Gdy masz już labirynt, dodaj postać, która będzie się w nim poruszać. Użyj pliku andy.obj
dołączonego do pakietu ARCore Android SDK. Zachowaj teksturę ramki obrazu jako teksturę, ponieważ wygląda ona inaczej niż zielony labirynt renderowany na obrazie.
W pliku AugmentedImageRenderer.java
dodaj prywatny obiekt ObjectRenderer
, aby renderować Andy'ego.
AugmentedImageRenderer.java
// Render for Andy
private final ObjectRenderer andyRenderer = new ObjectRenderer();
Następnie zainicjuj andyRenderer
pod koniec createOnGlThread()
.
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);
}
Na koniec wyrenderuj Andy’ego stojącego na labiryncie na końcu funkcji 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);
}
Uruchom kod. Zobaczysz Andy’ego, który stoi na szczycie labiryntu.
Określanie docelowej jakości obrazu
ARCore rozpoznaje obrazy za pomocą funkcji wizualnych. Ze względu na różnice w jakości nie wszystkie obrazy są łatwo rozpoznawalne.
arcoreimg
to narzędzie wiersza poleceń, które pozwala określić, jak rozpoznawalny będzie obraz dla ARCore. Podaje liczbę od 0 do 100, przy czym 100 to najłatwiejsza do rozpoznania wartość.
, Oto przykład:
arcore-android-sdk-x.xx.x/tools/arcoreimg/macos$
$ ./arcoreimg eval-img --input_image_path=/Users/username/maze.jpg
100
maze.jpg
ma wartość 100, więc jest łatwo rozpoznawalny przez ARCore.
5. Opcjonalnie: niech Andy porusza się w labiryncie
Na koniec możesz dodać kod, który sprawi, że andy poruszają się po labiryncie. Na przykład do przeprowadzenia symulacji fizyki możesz użyć mechanizmu open source Physics, jBullet. Nic nie szkodzi, jeśli pominiesz tę część.
Pobierz PhysicsController.java
i dodaj go do swojego projektu w katalogu
arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/java/com/google/ar/core/examples/java/augmentedimage/
W Android Studio dodaj plik GreenMaze.obj do katalogu zasobów projektu, aby można go było załadować w czasie działania. Skopiuj GreenMaze.obj
z aplikacji > zasoby > modele > green-maze do app > zasobów.
Dodaj te zależności do pliku build.gradle
aplikacji.
app/build.gradle
// jbullet library
implementation 'cz.advel.jbullet:jbullet:20101010-1'
Zdefiniuj zmienną andyPose
, aby zapisać pozycję Andy’ego w bieżącej pozycji.
AugmentedImageRenderer.java
// Create a new pose for the Andy
private Pose andyPose = Pose.IDENTITY;
Zmodyfikuj AugmentedImageRenderer.java
, aby wyrenderować Andy za pomocą nowej zmiennej andyPose
.
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);
}
Dodaj nową funkcję narzędziową (updateAndyPose()
), aby otrzymywać informacje o pozycji Andy’ego.
AugmentedImageRenderer.java
// Receive Andy pose updates
public void updateAndyPose(Pose pose) {
andyPose = pose;
}
W AugmentedImageActivity.java
utwórz obiekt PhysicsController
, który używa silnika fizycznego JBullet do zarządzania wszystkimi funkcjami związanymi z fizyką.
AugmentedImageActivity.java
import com.google.ar.core.Pose;
// Declare the PhysicsController object
private PhysicsController physicsController;
W silniku fizyki używamy sztywnej piłki, aby przedstawić Andy’ego i zaktualizować jego pozycję, korzystając z jej pozycji. Wywołaj PhysicsController
, aby zaktualizować zasady fizyczne za każdym razem, gdy aplikacja rozpozna obraz. Aby poruszać piłką tak jak w świecie rzeczywistym, użyj rzeczywistej grawitacji, aby poruszać piłką w labiryncie.
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;
Uruchom aplikację. Andy powinien realistycznie się poruszać, gdy przechylisz obraz.
W przykładzie poniżej użyto innego telefonu do wyświetlenia obrazu. Możesz użyć dowolnego wygodnego rozwiązania, np. tabletu, okładki książki drukowanej lub wydrukowanego papieru przyklejonego na płaskim obiekcie.
Znakomicie. Dobrej zabawy! Spróbuj uwolnić Andy'ego przez labirynt. Wskazówka: wyjście łatwiej znaleźć, gdy przytrzymasz obraz docelowy do góry nogami.
6. Gratulacje
Gratulacje. To już koniec tego ćwiczenia z programowania. Dlatego:
- Utworzyliśmy i uruchomiliśmy przykładową aplikację ARCore w języku Java AugmentedImage.
- Zaktualizowano przykład, aby wyświetlić na obrazie model labiryntu w odpowiedniej skali.
- Wykorzystanie wizerunku w celu zrobienia czegoś zabawnego.
Pełny kod możesz pobrać tutaj.