
В предыдущей статье я уже объяснил, что такое 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 и добавлю больше функциональности в приложение.
