Cómo empezar con la realidad aumentada en Swift, la forma fácil

Si miras a tu alrededor, esta es la era dorada de la tecnología. Cada nota clave agrega algo nuevo a la pila de tecnologías existente. Es emocionante ver cómo estas tecnologías emergentes han mejorado los límites de nuestra imaginación. Como desarrollador, debemos estar orgullosos de ser los usuarios de primera mano de estas tecnologías.

Pero cada nueva tecnología viene con una curva de aprendizaje bastante pronunciada. Simplemente no puede ver un discurso o un video en Youtube y comenzar a desarrollar una aplicación. Pero la buena noticia es que, con AR en Swift, es muy fácil trabajar con aplicaciones AR básicas. Apple ha hecho la mayor parte del trabajo pesado por ti. Siga adelante y verá lo fácil que puede ser.

Vamos a profundizar en…

En este tutorial, aprenderemos las herramientas y técnicas necesarias de RA en Swift que nos permitirán crear una aplicación que decore tu suelo con unas bonitas baldosas y texturas de madera. La aplicación finalizada se verá así:

Comencemos por crear una aplicación de vista única en Xcode y asígnele el nombre Decoración para el hogar.

Agregar permisos de cámara

Ahora, lo primero que haremos es navegar hasta el archivo info.plist y habilitar el uso de la cámara. La capacidad de la cámara es lo primero que necesita para una aplicación AR. Busque la tecla Descripción de uso de la cámara, como la imagen de abajo, y envíele un mensaje adecuado. Este mensaje aparecerá en el primer lanzamiento de la aplicación mientras solicita los permisos de la cámara al usuario.

Agregar capacidades de ARKit a la aplicación

Vaya a Main.storyboard. Arrastre y suelte una vista de ARKit SceneKit en ViewController y fije ARSCNView a los bordes de ViewController.

Cree un IBOutlet para la clase ViewController y asígnele el nombre sceneView. Tan pronto como se hace eso, un error que indica ARSCNView no declarada , popup, como nuestro controlador de vista no reconoce nada de tipo ARSCNView. Para resolver esto y utilizar otras funciones de ARKit, necesitamos importar ARKit al controlador de vista.

Ahora pase del storyboard al archivo view controller.swift. Declare una propiedad de tipo ARWorldTrackingConfiguration antes del método viewDidLoad () y asígnele el nombre config. Y nuestro controlador de vista se verá así (eliminé el método didReceiveMemoryWarning):

import UIKitimport ARKit
class ViewController: UIViewController {
@IBOutlet weak var sceneView: ARSCNView!let config = ARWorldTrackingConfiguration()
override func viewDidLoad() {super.viewDidLoad()}

Permitir depuración

Esta variable de configuración determinará las configuraciones de la sesión de escena. Veremos su uso más adelante en la sección. Ahora, en el método viewDidLoad después de super.viewDidLoad (), agregue lo siguiente:

sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]

Aquí habilitamos las opciones de depuración para nuestro sceneView, que no es más que la vista de la cámara con las capacidades del marco de AR. ARSCNDebugOptions.showWorldOrigin mostrará el origen mundial en la pantalla. Esto nos ayudará a encontrar el punto de referencia de todas las demás posiciones. ARSCNDebugOptions.showFeaturePoints mostrará todos los puntos en la pantalla que la cámara AR ha reconocido en los alrededores.

Ahora, para iniciar la sesión de AR, necesitamos ejecutar una sesión en nuestro sceneView con las configuraciones mencionadas en la variable de configuración. Justo debajo de la línea sceneView.debugOptions, escriba:

sceneView.session.run(config)

Ahora ejecute la aplicación en su dispositivo (no en un simulador, ya que no tiene cámara). Aparecerá la alerta que solicita el permiso de la cámara con el mensaje que escribió, y debe permitirlo. Espere un poco mientras carga el origen mundial.

Si está aquí, ya tiene una aplicación AR en ejecución. ¡Felicidades!

Cómo funcionan AR Axes

La barra roja o el eje X se utiliza para colocar objetos a la izquierda o derecha del origen mundial. La barra verde o el eje Y se utiliza para colocar objetos en la parte superior o inferior del origen mundial. Y la barra azul o el eje Z se usa para determinar qué tan cerca o lejos se colocará un objeto del origen mundial.

Un valor positivo de X colocará un objeto a la derecha del origen mundial y un valor negativo lo colocará a la izquierda. Positivo para Y lo colocará en la parte superior y negativo lo colocará en la parte inferior del origen mundial. Positivo para Z lo colocará más cerca y negativo lo colocará más lejos del origen mundial.

Agregar un objeto virtual

Agreguemos algunos objetos virtuales a la escena. La cápsula 3D sería una buena opción. Declare un capsuleNode de tipo SCNNode y asígnele una geometría de capsule. Dale una altura de 0,1 metros y un radio de 0,03 metros.

let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1

Ahora colóquelo a 0.1 metro a la izquierda del origen mundial, 0.1 metro por encima del origen mundial y a 0.1 metro de distancia del origen mundial:

capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)

Ahora, agregue el nodo a la escena:

sceneView.scene.rootNode.addChildNode(capsuleNode)

SceneView contiene una escena que se encarga de contener todos los objetos 3D en formato SCNNode que formarán la escena 3D. Estamos agregando la cápsula al nodo raíz de la escena. La posición del nodo raíz está exactamente alineada con la posición del origen mundial. Eso significa que su posición es (0,0,0).

Actualmente, nuestro método viewDidLoad se ve así:

override func viewDidLoad() {
super.viewDidLoad()
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
sceneView.session.run(config)
let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1))
capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)
sceneView.scene.rootNode.addChildNode(capsuleNode)
}

Ahora ejecuta la aplicación.

¡Frio! Acabamos de colocar un objeto virtual en el mundo real. Puedes jugar con diferentes posiciones y diferentes geometrías para explorar más. Ahora giremos la cápsula 90 grados alrededor del eje Z para que quede plana sobre el eje X y cambie su color a azul.

Ángulos de Euler

Los ángulos de Euler son responsables del ángulo de visualización de un SCNNode. Veremos cómo utilizarlo para rotar la cápsula.

Cada SCNGeometry puede tener materiales agregados, lo que define la apariencia de la geometría. Los materiales tienen una propiedad difusa que, cuando se establece, extiende su contenido por toda la geometría.

En viewDidLoad, agregue las siguientes líneas después de establecer la posición de la cápsula.

capsuleNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //1capsuleNode.eulerAngles = SCNVector3(0,0,Double.pi/2)//2

Aquí, en la primera línea, establecemos el color azul en el primer material del nodo que se extenderá por la cápsula y hará que se vea azul. En la línea 2, establecemos el ángulo Z Euler en radianes de 90 grados. Finalmente, nuestra vista se carga y se ve así:

override func viewDidLoad() {
super.viewDidLoad()
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
sceneView.session.run(config)
let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1))
capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)
capsuleNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //1
capsuleNode.eulerAngles = SCNVector3(0,0,Double.pi/2)//2
sceneView.scene.rootNode.addChildNode(capsuleNode)
}

Ahora ejecuta la aplicación.

¡Excelente! ¡Una cápsula para dormir de color azul en la pared! Incluso puede agregar texturas como contenido difuso para hacer que un objeto se vea más realista. Lo usaremos en la siguiente sección cuando coloquemos las texturas de las baldosas en el piso.

Ahora que hemos colocado correctamente los objetos virtuales en el mundo real, es hora de decorar nuestro suelo real con baldosas virtuales. Para lograr el efecto suelo, usaremos una geometría SCNPlane. SCNPlane no tiene ninguna profundidad como otras geometrías 3D, lo que lo hace perfecto para nuestra aplicación.

Delegados de ARSCENEView

Antes de comenzar la detección del piso, exploraremos algunos métodos delegados de nuestro sceneView para comprender con qué capacidades estamos equipados para interactuar con una sesión de AR en curso.

func renderer(SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor)

Siempre que movemos o inclinamos nuestro dispositivo con una sesión de AR, el ARKit intenta encontrar diferentes ARAnchors en los alrededores. Un ARAnchor contiene información sobre una posición y orientación en el mundo real que se puede utilizar para colocar un objeto.

Una vez que se encuentra un ancla diferente, se agrega un nuevo nodo a la escena con la misma información para acomodar este ancla recién encontrada. Este método de delegado nos informará sobre eso. Lo usaremos para encontrar todas las posiciones en el suelo para colocar las baldosas.

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor)

La mayoría de las veces, todos los nodos que se agregan desde los anclajes pertenecen al mismo objeto. Supongamos que se mueve por el suelo y el dispositivo encuentra varios anclajes en diferentes posiciones. Intenta agregar todos los nodos para esos anclajes, ya que cree que todos estos anclajes pertenecen a diferentes objetos.

Pero ARKit finalmente reconoce que todos pertenecen al mismo piso, por lo que actualiza el nodo del primer piso agregando dimensiones de otros nodos duplicados. Este método de delegado nos informará sobre eso.

func renderer(SCNSceneRenderer, didRemove: SCNNode, for: ARAnchor)

Después de actualizar el primer nodo único con las dimensiones de todos los demás nodos duplicados, ARKit elimina todos los nodos duplicados y el método delegado nos lo notifica. Usaremos todos los métodos delegados anteriores en nuestra aplicación (y su propósito será más claro).

Detección de aviones

Actualmente, nuestra escena está tratando de reunir todos los anclajes con los que se encuentra, ya que ese es el comportamiento predeterminado. Pero dado que un piso es una superficie horizontal, solo nos interesan los anclajes que están en planos horizontales. Por lo tanto, regrese a nuestro método viewDidLoad y escriba el siguiente código antes de ejecutar la sesión (es decir, antes de la línea sceneView.session.run (config)).

config.planeDetection = .horizontal

En el método viewDidLoad, puede eliminar todo después de sceneView.session.run (config) ya que eso fue para colocar la cápsula en la pantalla y ya no lo necesitamos. Dado que usaremos todos los métodos delegados mencionados anteriormente, debemos hacer que nuestro viewController sea un delegado de sceneView. Antes de la llave de cierre del método viewDidLoad (), agregue la siguiente línea.

sceneView.delegate = self

Debería recibir un error ahora, ya que nuestro controlador de vista aún no cumple con el delegado de sceneView. Para implementar esto, creemos una extensión del controlador de vista al final del archivo ViewController.swift.

extension ViewController:ARSCNViewDelegate{}

El método de delegado didAdd SCNNode se activará cada vez que se descubra una parte del piso y se agregue un nuevo nodo a la escena según el ancla. Dentro de este método, crearemos un nodo de piso y lo agregaremos como hijo del nodo agregado recientemente en la posición del ancla.

ARArchor puede ser de cuatro tipos diferentes para resolver cuatro propósitos diferentes. Aquí solo estamos interesados ​​en ARPlaneAnchor que detecta los planos horizontales o verticales.

Creación de nodos de suelo AR

Creemos una función que reciba un ARPlaneAnchor como parámetro, creemos un nodo de piso en la posición del ancla y lo devuelva.

func createFloorNode(anchor:ARPlaneAnchor) ->SCNNode{
let floorNode = SCNNode(geometry: SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))) //1
floorNode.position=SCNVector3(anchor.center.x,0,anchor.center.z) //2
floorNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //3
floorNode.geometry?.firstMaterial?.isDoubleSided = true //4
floorNode.eulerAngles = SCNVector3(Double.pi/2,0,0) //5
return floorNode //6
}

Repasemos la función línea por línea y analicémosla con más detalle. Siga la descripción de cada línea, ya que es la parte más complicada.

1. Estamos creando un nodo con una geometría de SCNPlane que tiene el tamaño del ancla. La extensión de ARPlaneAnchor contiene la información de la posición. El hecho de que la extensión.z se haya utilizado como altura y no extensión.y, puede resultar un poco confuso. Si visualiza que un cubo 3D está colocado en un piso y desea hacerlo plano a lo largo de una superficie 2D, cambiaría la y a cero y se volvería plano. Ahora, para obtener la longitud de esta superficie 2D, consideraría la z, ¿no es así? Nuestro piso es plano, por lo que necesitamos un nodo plano, no un cubo.

2. Establecemos la posición del nodo. Como no necesitamos ninguna elevación, hacemos y cero.

3. Establezca el color del suelo en azul.

4. El color del material se mostrará solo en un lado a menos que mencionemos específicamente que es de doble cara.

5. Por defecto, el plano se colocará verticalmente. Para hacerlo horizontal, debemos rotarlo 90 grados.

Implementar los métodos delegados

Ahora, implementemos el método de delegado didAdd SCNNode.

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {return} //1
let planeNode = createFloorNode(anchor: planeAnchor) //2
node.addChildNode(planeNode) //3
}

En la línea 1, estamos comprobando si el ancla es un ARPlaneAnchor, ya que solo trataríamos este tipo de ancla.

En la línea 2, se crea un nuevo nodo basado en el ancla. En la línea 3, se agrega al nodo.

Ahora, en el delegado didUpdate SCNNode, eliminaremos todos nuestros nodos de piso. Haremos esto porque las dimensiones del nodo actual se han cambiado y los nodos del piso antiguo no coincidirán. Luego agregaremos nuevamente un nuevo nodo de piso a este nodo actualizado.

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {return}
node.enumerateChildNodes { (node, _) in
node.removeFromParentNode()
}
let planeNode = createFloorNode(anchor: planeAnchor)
node.addChildNode(planeNode)
}

En el método de delegado didRemove SCNNode, queremos limpiar todos nuestros nodos basura de una manera civilizada.

func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
guard let _ = anchor as? ARPlaneAnchor else {return}
node.enumerateChildNodes { (node, _) in
node.removeFromParentNode()
}
}

¡Uf! ¡Eso es! Ejecute la aplicación.

Añadiendo el efecto mosaico

¿Esperar lo? ¿Un suelo azul? No, todavía no hemos terminado. ¡Solo un pequeño cambio y tendremos un piso impresionante!

Para cambiar el piso azul a baldosas, necesitamos una textura. Busquemos en Google una textura de baldosa. Busqué "textura de piso de madera" y encontré algunas imágenes hermosas de textura. Guarde cualquiera de ellos en su Mac y arrástrelo a Assets.xcassets.

Lo llamé WoodenFloorTile. Puedes nombrarlo como quieras. Vuelva al archivo ViewController.swift nuevamente. En la función createFloorNode, en lugar de configurar UIColor.blue como contenido difuso, conviértalo en un UIImage con el nombre que le ha dado a la imagen en la carpeta de activos.

floorNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "WoodenFloorTile")

Ahora ejecute la aplicación y espere hasta que se cargue el origen mundial. Una vez que se detecta el piso, muévase para actualizar la información del nodo.

¡Vaya, realmente tienes un piso precioso! Puede descargar varias texturas y colocarlas en un listView. Esto le permite cambiar el piso en base a la textura seleccionada, como se mostró en la primera parte.

Descarga el proyecto completo de GitHub aquí.

Ahora que tiene un piso bonito, debe faltar algunos muebles bonitos para darle a su habitación un aspecto excelente. Trabajaremos en eso más adelante.