Cómo crear una aplicación para Android de realidad aumentada con ARCore y Android Studio

Este artículo se publicó originalmente aquí.

En la publicación anterior, expliqué qué es ARCore y cómo ayuda a los desarrolladores a crear increíbles aplicaciones de realidad aumentada sin la necesidad de comprender las matemáticas de OpenGL o Matrix .

Si aún no lo ha revisado, le recomiendo que lo haga antes de seguir adelante con este artículo y sumergirse en el desarrollo de aplicaciones ARCore.

Visión general

Según Wikipedia, ARCore es un kit de desarrollo de software desarrollado por Google que permite construir aplicaciones de realidad aumentada.

ARCore utiliza tres tecnologías clave para integrar contenido virtual con el entorno real:

  1. Seguimiento de movimiento: permite que el teléfono comprenda su posición en relación con el mundo.
  2. Comprensión ambiental: esto permite que el teléfono detecte el tamaño y la ubicación de todo tipo de superficies, verticales, horizontales y en ángulo.
  3. Estimación de luz: permite al teléfono estimar las condiciones de iluminación actuales del entorno.

Empezando

Para comenzar con el desarrollo de aplicaciones ARCore , primero debe habilitar ARCore en su proyecto. Esto es simple ya que usaremos Android Studio y Sceneform SDK. Hay dos operaciones principales que Sceneform realiza automáticamente:

  1. Comprobando la disponibilidad de ARCore
  2. Pedir permiso a la cámara

No necesita preocuparse por estos dos pasos al crear una aplicación ARCore con Sceneform SDK. Pero es necesario que incluya Sceneform SDK en su proyecto.

Cree un nuevo proyecto de Android Studio y seleccione una actividad vacía.

Agrega la siguiente dependencia a tu archivo build.gradle a nivel de proyecto:

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

Agregue lo siguiente a su archivo build.gradle de nivel de aplicación:

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

Ahora sincronice el proyecto con los archivos de Gradle y espere a que finalice la compilación. Esto instalará el SDK Sceneform al proyecto y el plugin Sceneform a Android Studio . Le ayudará a ver el. archivos sfb . Estos archivos son los modelos 3D que se renderizan en su cámara. También le ayuda a importar, ver y crear activos 3D .

Construyendo su primera aplicación ARCore

Ahora, con nuestra configuración de Android Studio completa y Sceneform SDK instalado, podemos comenzar a escribir nuestra primera aplicación ARCore.

Primero, necesitamos agregar el fragmento de Sceneform a nuestro archivo de diseño. Esta será la Escena donde colocaremos todos nuestros modelos 3D. Se encarga de la inicialización de la cámara y el manejo de permisos.

Dirígete a tu archivo de diseño principal. En mi caso es activity_main.xml y agrego el fragmento Sceneform:

He configurado el ancho y el alto para que coincidan con el padre, ya que esto cubrirá toda mi actividad. Puede elegir las dimensiones según sus requisitos.

Verificación de compatibilidad

Esto es todo lo que necesita hacer en el archivo de diseño. Ahora dirígete al archivo java, en mi caso, que es MainActivity.java. Agregue el siguiente método en su clase:

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;}

Este método comprueba si su dispositivo es compatible con Sceneform SDK o no. El SDK requiere el nivel de API de Android 27 o más reciente y OpenGL ES versión 3.0 o más reciente. Si un dispositivo no admite estos dos, la escena no se renderizará y su aplicación mostrará una pantalla en blanco.

Sin embargo, puede seguir ofreciendo todas las demás funciones de su aplicación que no requieren el SDK de Sceneform.

Ahora, con la verificación de compatibilidad del dispositivo completa, construiremos nuestro modelo 3D y lo adjuntaremos a la escena.

Agregar los activos

Deberá agregar los modelos 3D que se renderizarán en su pantalla. Ahora puede construir estos modelos usted mismo si está familiarizado con la creación de modelos 3D. O puede visitar Poly.

Allí encontrará un enorme repositorio de activos 3D para elegir. Se pueden descargar gratis. Solo dale crédito al creador y listo.

En Android Studio, expanda la carpeta de su aplicación disponible en el panel del proyecto del lado izquierdo. Notará una carpeta de "datos de muestra " . Esta carpeta contendrá todos los activos de su modelo 3D. Cree una carpeta para su modelo dentro de la carpeta de datos de muestra.

Cuando descargue el archivo zip de poly, probablemente encontrará 3 archivos.

  1. archivo .mtl
  2. archivo .obj
  3. archivo .png

El más importante de estos 3 es el archivo .obj. Es tu modelo real. Coloque los 3 archivos dentro de sampledata -> "la carpeta r de su modelo ".

Ahora haga clic derecho en el archivo .obj . La primera opción sería Importar Activo de Sceneform. Haga clic en él, no cambie la configuración predeterminada, simplemente haga clic en finalizar en la siguiente ventana. Su gradle se sincronizará para incluir el activo en la carpeta de activos. Una vez que finalice la compilación de Gradle, estará listo.

Ha terminado de importar un recurso 3D utilizado por Sceneform en su proyecto. A continuación , construyamos el activo a partir de nuestro código e incluyémoslo en la escena.

Construyendo el modelo

Agregue el siguiente código a su archivo MainActivity.java (o lo que sea en su caso). No se preocupe, le explicaré todo el código línea por línea:

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.