Як створити додаток Android для доповненої реальності за допомогою ARCore та Android Studio

Ця стаття була спочатку розміщена тут

У попередньому дописі я пояснив, що таке ARCore і як він допомагає розробникам створювати чудові програми з доповненою реальністю без необхідності розуміти математику OpenGL або Matrix .

Якщо ви ще не перевірили його, я настійно рекомендую зробити це, перш ніж продовжувати роботу з цією статтею та заглибитися в розробку додатка ARCore.

Огляд

Згідно з Вікіпедією, ARCore - це набір для розробки програмного забезпечення, розроблений Google, який дозволяє створювати додатки з доповненою реальністю.

ARCore використовує три ключові технології для інтеграції віртуального контенту з реальним середовищем:

  1. Відстеження руху: це дозволяє телефону зрозуміти своє положення щодо світу.
  2. Розуміння навколишнього середовища: це дозволяє телефону визначати розмір та розташування всіх типів поверхонь, вертикальних, горизонтальних та кутових.
  3. Оцінка освітленості: це дозволяє телефону оцінювати поточні умови освітлення навколишнього середовища.

Починаємо

Щоб розпочати розробку додатків ARCore , спочатку потрібно увімкнути ARCore у своєму проекті. Це просто, оскільки ми будемо використовувати Android Studio та Sceneform SDK. Існує дві основні операції, які Sceneform виконує автоматично:

  1. Перевірка наявності ARCore
  2. Прохання дозволу камери

Вам не потрібно турбуватись цими двома кроками під час створення програми ARCore за допомогою Sceneform SDK. Але вам потрібно включити Sceneform SDK у свій проект.

Створіть новий проект Android Studio і виберіть порожнє заняття.

Додайте таку залежність до файлу build.gradle рівня проекту:

dependencies { classpath 'com.google.ar.sceneform:plugin:1.5.0'}

Додайте до файлу build.gradle рівень вашого додатка:

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

Тепер синхронізуйте проект з файлами Gradle і дочекайтеся завершення збірки. Це встановить Sceneform SDK до проекту та плагін Sceneform до AndroidStudio . Це допоможе вам переглянути. sfb файли. Ці файли - це тривимірні моделі, які відображаються у вашій камері. Це також допомагає імпортувати, переглядати та створювати тривимірні об’єкти .

Створення першого додатку ARCore

Тепер, коли ми завершили налаштування Android Studio та встановили Sceneform SDK, ми можемо почати писати наш перший додаток ARCore.

Спочатку нам потрібно додати фрагмент Sceneform до нашого файлу макета. Це буде сцена, де ми розміщуємо всі наші 3D-моделі. Він дбає про ініціалізацію камери та обробку дозволів.

Перейдіть до основного файлу макета. У моєму випадку це activity_main.xml і додайте фрагмент Sceneform:

Я встановив ширину та висоту, щоб вони відповідали батьківським, оскільки це охоплюватиме всю мою діяльність. Ви можете вибрати розміри відповідно до ваших вимог.

Перевірка сумісності

Це все, що вам потрібно зробити у файлі макета. Тепер перейдіть до файлу Java, у моєму випадку це MainActivity.java. Додайте метод нижче у своєму класі:

public static boolean checkIsSupportedDeviceOrFinish(final Activity activity) { 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; } 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;}

Цей метод перевіряє, чи може ваш пристрій підтримувати Sceneform SDK чи ні. Для SDK потрібен Android API рівня 27 або новішої та OpenGL ES версії 3.0 або новішої. Якщо пристрій не підтримує ці два, сцена не відображатиметься, і ваша програма відобразить порожній екран.

Тим не менш, ви все ще можете продовжувати надавати всі інші функції свого додатка, для яких не потрібен Sceneform SDK.

Тепер, коли перевірка сумісності пристрою завершена, ми створимо нашу 3D-модель і прикріпимо її до сцени.

Додавання активів

Вам потрібно буде додати 3D-моделі, які відображатимуться на екрані. Тепер ви можете створити ці моделі самостійно, якщо ви знайомі зі створенням 3D-моделі. Або ви можете відвідати Полі.

Там ви знайдете величезне сховище 3D-об’єктів на вибір. Їх можна безкоштовно завантажити. Просто надайте творцеві кредит, і ви готові піти.

В Android Studio розгорніть папку програми, доступну на лівій панелі проекту. Ви помітите папку sampledata . У цій папці будуть розміщені всі об’єкти вашої 3D-моделі. Створіть папку для своєї моделі всередині зразка папки даних.

Коли ви завантажуєте zip-файл із полі, ви, швидше за все, знайдете 3 файли.

  1. файл .mtl
  2. .obj файл
  3. файл .png

Найважливішим із цих 3 є файл .obj. Це ваша реальна модель. Помістіть усі 3 файли всередину зразків даних -> “список вашої моделі r”.

Тепер клацніть правою кнопкою миші файл .obj . Першим варіантом було б імпортувати Sceneform Asset. Натисніть на нього, не змінюйте налаштування за замовчуванням, просто натисніть кнопку Готово у наступному вікні. Ваш gradle синхронізується, щоб включити об’єкт у папку об’єктів. Після завершення побудови gradle, ви готові піти.

Ви закінчили імпортувати 3D-об’єкт, що використовується Sceneform у вашому проекті. Далі , давайте побудуємо актив із нашого коду та включимо його в сцену.

Побудова моделі

Додайте наступний код у файл MainActivity.java (або будь-який інший, що є у вашому випадку). Не хвилюйся, я поясню весь код рядок за рядком:

private static final String TAG = MainActivity.class.getSimpleName();private static final double MIN_OPENGL_VERSION = 3.0;
ArFragment arFragment;ModelRenderable lampPostRenderable;
@[email protected]({"AndroidApiChecker", "FutureReturnValueIgnored"})
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!checkIsSupportedDeviceOrFinish(this)) { return; } setContentView(R.layout.activity_main); arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
 ModelRenderable.builder() .setSource(this, Uri.parse("LampPost.sfb")) .build() .thenAccept(renderable -> lampPostRenderable = renderable) .exceptionally(throwable -> { Toast toast = Toast.makeText(this, "Unable to load andy renderable", Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); return null; });
}

First, we find the arFragment that we included in the layout file. This fragment is responsible for hosting the scene. You can think of it as the container of our scene.

Next, we are using the ModelRenderable class to build our model. With the help of setSource method, we load our model from the .sfb file. This file was generated when we imported the assets. thenAccept method receives the model once it is built. We set the loaded model to our lampPostRenderable.

For error handling, we have .exceptionally method. It is called in case an exception is thrown.

All this happens asynchronously, hence you don’t need to worry about multi-threading or deal with handlers XD

With the model loaded and stored in the lampPostRenderable variable, we’ll now add it to our scene.

Adding the Model to Scene

The arFragment hosts our scene and will receive the tap events. So we need to set the onTap listener to our fragment to register the tap and place an object accordingly. Add the following code to onCreate method:

arFragment.setOnTapArPlaneListener( (HitResult hitresult, Plane plane, MotionEvent motionevent) -> { if (lampPostRenderable == null){ return; }
 Anchor anchor = hitresult.createAnchor(); AnchorNode anchorNode = new AnchorNode(anchor); anchorNode.setParent(arFragment.getArSceneView().getScene());
 TransformableNode lamp = new TransformableNode(arFragment.getTransformationSystem()); lamp.setParent(anchorNode); lamp.setRenderable(lampPostRenderable); lamp.select(); });

We set the onTapArPlaneListener to our AR fragment. Next what you see is the Java 8 syntax, in case you are not familiar with it, I would recommend checking out this guide.

First, we create our anchor from the HitResult using hitresult.createAnchor() and store it in an Anchor object.

Next, create a node out of this anchor. It will be called AnchorNode. It will be attached to the scene by calling the setParent method on it and passing the scene from the fragment.

Now we create a TransformableNode which will be our lamppost and set it to the anchor spot or our anchor node. The node still doesn’t have any information about the object it has to render. We’ll pass that object using lamp.setRenderable method which takes in a renderable as it’s parameter. Finally call lamp.select();

Phew!! Too much terminology there, but don’t worry, I’ll explain it all.

  1. Scene: This is the place where all your 3D objects will be rendered. This scene is hosted by the AR Fragment which we included in the layout. An anchor node is attached to this screen which acts as the root of the tree and all the other objects are rendered as its objects.
  2. HitResult: This is an imaginary line (or a ray) coming from infinity which gives the point of intersection of itself with a real-world object.
  3. Anchor: An anchor is a fixed location and orientation in the real world. It can be understood as the x,y,z coordinate in the 3D space. You can get an anchor’s post information from it. Pose is the position and orientation of the object in the scene. This is used to transform the object’s local coordinate space into real-world coordinate space.
  4. AnchorNode: This is the node that automatically positions itself in the world. This is the first node that gets set when the plane is detected.
  5. TransformableNode: It is a node that can be interacted with. It can be moved around, scaled rotated and much more. In this example, we can scale the lamp and rotate it. Hence the name Transformable.

There is no rocket science here. It’s really simple. The entire scene can be viewed as a graph with Scene as the parent, AnchorNode as its child and then branching out different nodes/objects to be rendered on the screen.

Your final MainActivity.java must look something like this:

package com.ayusch.arcorefirst;
import android.app.Activity;import android.app.ActivityManager;import android.content.Context;import android.net.Uri;import android.os.Build;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.Gravity;import android.view.MotionEvent;import android.widget.Toast;
import com.google.ar.core.Anchor;import com.google.ar.core.HitResult;import com.google.ar.core.Plane;import com.google.ar.sceneform.AnchorNode;import com.google.ar.sceneform.rendering.ModelRenderable;import com.google.ar.sceneform.ux.ArFragment;import com.google.ar.sceneform.ux.TransformableNode;
public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private static final double MIN_OPENGL_VERSION = 3.0;
 ArFragment arFragment; ModelRenderable lampPostRenderable;
 @Override @SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"}) protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!checkIsSupportedDeviceOrFinish(this)) { return; } setContentView(R.layout.activity_main); arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
 ModelRenderable.builder() .setSource(this, Uri.parse("LampPost.sfb")) .build() .thenAccept(renderable -> lampPostRenderable = renderable) .exceptionally(throwable -> { Toast toast = Toast.makeText(this, "Unable to load andy renderable", Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); return null; });
 arFragment.setOnTapArPlaneListener( (HitResult hitresult, Plane plane, MotionEvent motionevent) -> { if (lampPostRenderable == null){ return; }
 Anchor anchor = hitresult.createAnchor(); AnchorNode anchorNode = new AnchorNode(anchor); anchorNode.setParent(arFragment.getArSceneView().getScene());
 TransformableNode lamp = new TransformableNode(arFragment.getTransformationSystem()); lamp.setParent(anchorNode); lamp.setRenderable(lampPostRenderable); lamp.select(); } );
 }
 public static boolean checkIsSupportedDeviceOrFinish(final Activity activity) { 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; } 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; }}

Congratulations!! You’ve just completed your first ARCore app. Start adding objects and see them come alive in the real world!

This was your first look into how to create a simple ARCore app from scratch with Android studio. In the next tutorial, I would be going deeper into ARCore and adding more functionality to the app.

If you have any suggestions or any topic you would want a tutorial on, just mention in the comments section and I’ll be happy to oblige.

Like what you read? Don’t forget to share this post on Facebook, Whatsapp and LinkedIn.

You can follow me on LinkedIn, Quora, Twitter and Instagram where I answer questions related to Mobile Development, especially Android and Flutter.