В предыдущей статье я уже объяснил, что такое ARCore и как он помогает разработчикам создавать удивительные приложения дополненной реальности без необходимости понимания математики и OpenGL.
Если вы ещё не читали её, я настоятельно рекомендую это сделать, прежде чем перейти к этой статье и начать разработку ARCore-приложений.
Начало работы
Чтобы начать разработку ARCore-приложений, сначала необходимо добавить поддержку ARCore в свой проект. Это очень просто, так как мы будем использовать Android Studio и Sceneform SDK. Есть две основные операции, которые благодаря Sceneform выполняются автоматически:
- Проверка наличия ARCore.
- Запрос на разрешение использования камеры.
Вам не нужно беспокоиться об этих двух шагах при создании ARCore-приложения с помощью Sceneform SDK. Вам просто нужно добавить Sceneform SDK в ваш проект.
Создайте новый проект Android Studio с пустой Activity.
Добавьте следующую зависимость в файл 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 для Android Studio. Это позволит вам просматривать файлы с разрешением .sfb
, которые представляют собой 3D-модели, которые будут рендериться в вашей камере, а также поможет вам импортировать, просматривать и создавать 3D-ресурсы.
Создание вашего первого ARCore-приложения
Теперь, когда настройка Android Studio завершена и SDK Sceneform установлен, мы можем начать создание нашего первого ARCore-приложения.
Во-первых, нужно добавить Sceneform-фрагмент в наш layout. Это так называемая сцена, где будут размещаться все наши 3D-модели. Фрагмент самостоятельно позаботится об инициализации камеры и обработке разрешений.
Перейдите к своему основному layout файлу. В моём случае это файл activity_main.xml
. И добавьте туда Sceneform-фрагмент:
<?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>
Я установил значения ширины и высоты match_parent
, чтобы сцена занимала весь экран. Вы можете выбрать размеры в соответствии с вашими требованиями.
Проверка совместимости
Это всё, что нужно сделать в layout файле. Теперь переходим к Activity, в моём случае это MainActivity
. Добавьте в Activity метод:
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-модель и прикрепить её к сцене.
Добавление assets
Теперь нужно добавить в проект 3D-модели, которые будут отображаться на вашем экране. Вы можете создавать эти модели самостоятельно, если вы знакомы с процессом их создания. Или же вы можете зайти на Poly.
Там вы найдете огромный репозиторий 3D-ресурсов на выбор. Кроме того, они бесплатны для скачивания.
В Android Studio откройте папку своего приложения в панели слева. Вам нужна папка sampledata. Эта папка будет содержать все ваши 3D-модели. Внутри этой папки создайте папку с названием своей модели.
В архиве, который вы скачаете с Poly, вы, скорее всего, найдёте 3 файла:
.mtl
-файл.obj
-файл.png
-файл
Наиболее важным из этих трёх файлов является файл .obj
. Это и есть ваша модель. Поместите все 3 файла в sampledata -> «папка вашей модели».
Теперь щёлкните правой кнопкой мыши на файле .obj
. Первым вариантом будет Import Sceneform Asset. Нажмите на него, не меняйте настройки по умолчанию, просто нажмите Finish в следующем окне. После этого синхронизируйте проект с Gradle-файлами.
Импорт 3D-ресурса, который будет использован в вашем проекте, завершён. Далее давайте используем 3D-модель в нашем коде и включим его в сцену.
Создание модели
Добавьте следующий код в вашу Activity, а я объясню его построчно:
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
, который мы ранее добавили в layout. Этот фрагмент отвечает за хранение и работу сцены. Вы можете представить его в виде контейнера для нашей сцены.
Далее мы используем класс ModelRenderable
для построения нашей модели. С помощью метода setSource
мы загружаем нашу модель из .sfb
-файла, который был сгенерирован при импорте ресурсов. Метод thenAccept
получает модель после её создания, и мы устанавливаем загруженную модель в нашу переменную lampPostRenderable
.
Для обработки ошибок у нас есть метод exceptionally
, который вызывается в случае возникновения исключения.
Всё это происходит асинхронно, поэтому вам не нужно беспокоиться о многопоточности.
Теперь, когда модель загружена и сохранена в переменной lampPostRenderable
, мы добавим её в нашу сцену.
Добавление модели в сцену
В arFragment
находится наша сцена, и он будет получать события пользовательских касаний. Поэтому нам нужно установить слушатель onTap
для нашего фрагмента, чтобы обрабатывать касания и размещать объекты, там, где это потребуется. Добавьте следующий код в метод onCreate
:
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();
}
);
Мы устанавливаем слушатель onTapArPlaneListener
для нашего AR-фрагмента. Далее используется синтаксис лямбда-выражений. Если вы с ним не знакомы, то ознакомьтесь с этим небольшим гайдом по этой теме.
Сначала мы создаем якорь из HitResult
с помощью hitresult.createAnchor()
и сохраняем его в объекте Anchor
.
Затем создаём узел из этого якоря. Он будет называться AnchorNode
и будет прикреплён к сцене при помощи метода setParent
.
Далее мы создаём TransformableNode
, который и будет являться нашей моделью, и привязываем его к нашему узлу. TransformableNode
по-прежнему не имеет никакой информации об объекте, который он должен отобразить. Мы передадим ему этот объект с помощью метода setRenderable
, который в качестве параметра принимает объект типа ModelRenderable (помните, мы получили такой объект и назвали его lampPostRenderable
?). И, наконец, вызоваем метод lamp.select()
;
Ох! Слишком много терминологии. Не волнуйтесь, сейчас всё объясню:
-
Сцена: это место, где будут отображаться все ваши 3D-объекты. Эта сцена размещена в AR-фрагменте, который мы добавили в layout.
-
HitResult: это воображаемая линия (или луч), идущая из бесконечности, которая даёт точку пересечения себя с объектом реального мира.
-
Якорь: это фиксированное местоположение и ориентация в реальном мире. Его можно понимать как координаты (x, y, z) в трехмерном пространстве. Поза — это положение и ориентация объекта на сцене. Она используется для преобразования локального координатного пространства объекта в реальное координатное пространство.
-
Якорный узел: это узел, который автоматически позиционирует себя в реальном мире. Это первый узел, который устанавливается при обнаружении плоскости.
-
TransformableNode: это узел, с которым можно взаимодействовать. Его можно перемещать, масштабировать, поворачивать и так далее. В этом примере мы можем масштабировать наш объект и вращать его. Отсюда и название Transformable.
Здесь нет никакого ракетостроения. Это действительно относительно просто. Всю сцену можно просмотреть в виде графа, в котором родительским объектом является сцена, а дочерними — якорные узлы, которые затем разветвляются в различные другие узлы и объекты, которые будут отображаться на экране.
По итогу ваша Activity должна выглядеть следующим образом:
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;
}
}
Поздравляю! Вы только что завершили создание своего первого ARCore-приложения. Начните добавлять в него объекты и вы увидите, как они начнут оживать в реальном мире.
Это был ваш первый взгляд на то, как создать простое ARCore-приложение с нуля в Android Studio. В следующем уроке я углублюсь в ARCore и добавлю больше функциональности в приложение.
Автор: Devcolibri