Augmented Reality mit RealityKit & Reality Composer 11.09.2019, 08:43 Uhr

Realitätsnah

AR-Apps mit RealityKit & Reality Composer entwickeln.
(Quelle: Bild:  Shutterstock / Hadrian)
Das Thema Augmented Reality ist bei Apple weiter stark im Fokus. Neben dem schon in der letzten Ausgabe besprochenen Neuerungen in ARKit 3, gibt es zwei vollkommen neue Tools: Der 3D-Renderer RealityKit und der Szenen-Editor Reality Composer. Beide wollen wir uns im Folgenden einmal genauer anschauen.

RealityKit

Zwar hat Apple mit SceneKit schon einen 3D-Renderer in seinem Portfolio, doch ist dieser eher allgemein ausgelegt, was die Entwicklung von Augmented-Reality-Anwendungen unnötig komplex gestaltet. RealityKit wurde daher speziell dafür entwickelt, die Entwicklung von AR-Apps so einfach wie möglich zu gestalten. Hierfür unterstützt es fotorealistisches Rendering, welches sich nahtlos in das Kamerabild integriert. Dazu kommen weitere Features, wie Animation, Physik-Simulation, Audio und Synchronisation für Multiplayer-Spiele. Die API wurde speziell für Swift entworfen. RealityKit unterstützt die Plattformen iOS und macOS und verwendet Metal, wobei es speziell für die Apple-GPUs optimiert ist. Es unterstützt die schon bekannten USDZ-Dateien, bringt aber auch ein eigenes, neues Format mit, das Reality File Format.

Entity & Component

RealityKit ist, wie die meisten modernen 3D-Engines, ein Szenengraph-Renderer. Das bedeutet, dass die anzuzeigenden Elemente in Form eines Baums logisch strukturiert, verwaltet und gerendert werden. Die Lösung von RealityKit erinnert an die von UIKit, denn auch UIViews werden in einem Baum strukturiert. Bei RealityKit heißen diese Elemente Entity.
Ein Entity-Element stellt jeweils ein Objekt in der Szene dar und legt fest wer seine Vater- und Kindknoten sind, und wo es sich relativ zum Vaterknoten befindet.
Entities bilden die Basis und haben neben den Struktur-Aufgaben keine weiteren Fähigkeiten. Diese erhalten sie erst durch Komponenten (Components). Hierbei handelt es sich um ein modulares System, wobei unterschiedliche Komponenten nach Bedarf zu einer Einheit zusammengestellt werden (Bild 1).
Der Aufbau des Entity-Component-Systems (Bild 1)
Quelle: Klein
Diese Art der Komposition nennt Apple das Entity Component System. Es verfolgt die Absicht, bei einem komplexen System lange Ableitungshierarchien zu vermeiden. Im Vergleich zu anderen Systemen ist ihnen das auch gelungen.
Folgende Komponenten werden von RealityKit zur Verfügung gestellt:
  • AnchoringComponent
  • BodyTrackingComponent
  • CollisionComponent
  • DirectionalLightComponent
  • DirectionalLightComponent.Shadow
  • ModelComponent
  • PerspectiveCameraComponent
  • PhysicsBodyComponent
  • PhysicsMotionComponent
  • PointLightComponent
  • SpotLightComponent
  • SpotLightComponent.Shadow
  • SynchronizationComponent
  • Transform
 
Alle Funktionen von RealityKit lassen sich auf Basis dieser Komponenten zusammenstellen und verändern. Um den Konfigurationsaufwand niedrig zu halten, gibt es jedoch konkrete Ableitungen von Entity, die häufig verwendete Pakete schnüren. Die AnchorEntity stellt so einen Ankerpunkt in der Szene dar. ModelEntity ein sichtbares Objekt mit Kollisionserkennung und Physik-Simulation. Bild 2 zeigt die jeweils dafür verwendeten Komponenten.
Die Komponenten von Entity, AnchorEntity und ModelEntity im Vergleich (Bild 2)
Quelle: Klein
Auch Lichter sind Entities. So gibt es AmbientLight für Umgebungslicht, DirectionalLight für gerichtetes Licht und SpotLight für Punktlicht. Sogar die Kamera (PerspectiveCamera) ist ein Entity.
Die am häufigsten verwendete Entity ist die ModelEntity. Mit ihr werden alle sichtbaren Elemente der Szene aufgebaut, wobei nur selten etwas an der Zusammenstellung der Komponenten verändert werden muss. Deshalb behandeln wir es hier auch nicht weiter.
RealityKit hat mit ARView seine eigene View-Komponente. Sie leitet wie üblich von UIView ab und kann an beliebiger Stelle in die View-Hierarchie von Anwendungen eingebaut werden. Das geht entweder manuell oder über das Storyboard, indem man eine Reality AR-View in die Szene zieht und ein Outlet erzeugt:
 
@IBOutlet var arView: ARView!
 
ARView kümmert sich dabei um globale Themen, wie das Handling von Gesten, das Fokus-Handling, Kamera-Effekte, usw. Außerdem verwaltet sie die Szene, also alles das, was angezeigt wird. Hierfür besitzt sie das scene-Property, welches bereits eine fertig initialisierte Instanz der Klasse Scene enthält. Sie verwaltet die erste Ebene der Entities der Szene, und damit effektiv den gesamten Baum.

Anker

Wie von ARKit bekannt, benötigt man Anker um virtuelle Objekte in der echten Welt positionieren zu können. Die Herangehensweise von RealityKit ist dabei von der anderen Seite. Hier formulieren wir mit einem AnchorEntity die Art des Ankers, mit dem wir uns verbinden wollen. Für eine horizontale Ebene beispielsweise so:
 
let planeAnchor = AnchorEntity(plane:
.horizontal)
 
Zur Laufzeit hält RealityKit dann Ausschau nach passenden Ankern, und bindet sich an den ersten, der die entsprechenden Bedingungen erfüllt. Diese Bedingungen lassen sich als optionale Parameter des Initializers auch genauer festlegen:
 
let planeAnchor = AnchorEntity(plane:
.horizontal, classification: .table,
minimumBounds: [1.0, 1.0])
 
Hier wird eine Fläche angefragt, die der Klassifikation (classification:) Tisch (.table) entspricht. Andere horizontale Flächen, wie Decken oder Fußböden, werden dann nicht beachtet. Über die Minimum Bounds (minimumBounds:) lässt sich außerdem noch die gewünschte Mindestgröße der Ebene festlegen. Wie bei ARKit üblich, wird hier im metrischen System gearbeitet und Werte in Metern übergeben. Die Fläche muss also eine Mindestgröße von einem Quadratmeter haben.
Um die AnchorEntity zu aktivieren, wird sie mit addAnchor() zur Szene der ARView hinzugefügt:
 
arView.scene.addAnchor(planeAnchor)
 
AnchorEntity fungiert nun als Wurzelelement für die weiteren Entities, die sich an diesem orientieren. Sie werden allerdings erst aktiviert, wenn auch ein passender Anker gefunden worden ist. Entsprechende Objekte hängen also nicht sinnlos in der Gegend herum, so lange noch kein passender Anker gefunden worden ist.
Mögliche weitere Anker sind die von ARKit bekannten Typen: Bild, Gesicht, Körper, Kamera, und alle vom Entwickler angelegten Instanzen von ARAnchor.

ModelEntity

ModelEntity-Objekte werden als visueller Teil der Szene in zwei Teilen beschrieben: Der Form und dem Material. Da USDZ-Dateien beides enthalten, können entsprechende Objekte mit einem simplen load() vollständig geladen werden:
 
let loadedEntity = try? ModelEntity.load(named: "Robot")
 
Die passende Datei (hier Robot.usdz) muss sich dabei in den Ressourcen befinden.
Anschließend muss das Modell nur noch mit addChild() zu einer Entity hinzugefügt werden. In diesem Falle direkt zum Anker (planeAnchor):
 
planeAnchor.addChild(loadedEntity)
 
Die load()-Methode hat jedoch einen kleinen Haken, und zwar lädt sie Ressourcen synchron, was besonders auf dem Main-Thread äußerst ungünstig werden kann, da die Anwendung so lange nicht mehr reagiert. Bei einem kleinen Modell kann das noch vertretbar sein, ansonsten empfiehlt es sich eher das asynchrone Laden, das über die Methode loadAsync() zur Verfügung gestellt wird:
 
let resourceLoader = ModelEntity.loadAsync(named:
"Robot").sink(receiveCompletion: { error in
   // Error...
}, receiveValue:
{ loadedEntity in
   planeAnchor.addChild(loadedEntity)
})
 
Der Vorgang gestaltet sich naturgemäß etwas komplexer, dafür kann es aber auch große Modelle störungsfrei in den Speicher bringen.
Der von loadAsync() zurückgegebene LoadRequest ist ein Publisher für das neue Combine Framework. Über die sink()-Methode abonnieren wir das Ergebnis, das im Fehlerfall ein error und im Erfolgsfall das geladene Modell (loadedEntity) übergibt. Auch dort muss dieses nur noch mit addChild() zu einer anderen Entity hinzugefügt werden.

MeshResource

Alternativ zum Laden aus Dateien können die Form (shape) und das Material auch manuell erzeugt werden. RealityKit hat hierfür die Klassen MeshResource und SimpleMaterial. MeshResource ist die Repräsentation einer Kollektion von Punkten (edges) und Kanten (vertices), die zusammen die Form (shape) eines 3D-Objekts definieren. Diese können entweder manuell definiert oder als Würfel (box), Ebene (plane), Kugel (sphere) oder Text über entsprechende Generator-Funktionen erzeugt werden. Ein Würfel erzeugt man mit generateBox() beispielsweise so:
 
let boxShape = MeshResource.generateBox(size: 0.1,
cornerRadius: 0.01)
 
Dabei beschreibt size: die Kantenlänge des Würfels in Metern. cornerRadius: ist optional und rundet die Ecken ab.
Ein Text wird ebenso unkompliziert über die Methode generateText() erzeugt:
 
let textShape = MeshResource.generateText("w&m")
 
MeshResources können dabei von mehreren Entities gleichzeitig verwendet werden. Bei komplexen Formen kann das spürbar Speicher sparen.

SimpleMaterial

Materialien werden manuell über den Typ SimpleMaterial erzeugt:
 
let boxMaterial = SimpleMaterial(color: .purple,
roughness: 0, isMetallic: true)
 
Das Material wird dabei über drei Parameter beschrieben: Die Farbe (color:) ist ein UIColor/NSColor, die Rauheit (roughness) wird im Bereich 0 bis 1 angegeben, und isMetallic: gibt an, ob das Material metallisch sein soll. Im Beispiel wird so ein violettes (.purple), metallisches und blank poliertes (Rauheit 0) Material erzeugt.
Außerdem ist es möglich für SimpleMaterials Texturen zu verwenden. Hierfür wird mit dem parameterlosen Initializer eine Instanz von SimpleMaterialerzeugt, da der oben gezeigte Initializer nur UIColor-Farben erlaubt:
 
var textMaterial = SimpleMaterial()
textMaterial.baseColor = try! .texture(.load
(named: "yellow wall"))
 
Anschließend kann als baseColor eine Textur (.texture()) gesetzt werden, die wiederum mit .load(named:) geladen wird. Dieses Enum verhält sich dabei wie beim Laden eines Bildes über UIImage(named:) und lädt das entsprechende Bild aus dem Asset Catalog.
Hat man Form und Material zusammen, dann ist es ein leichtes daraus ein ModelEntity-Objekt zu erzeugen:
 
let boxEntity = ModelEntity(mesh:
boxShape, materials: [boxMaterial])
 
Dieses muss nur noch zu einem anderen Entity (hier planeAnchor) hinzugefügt werden:
 
planeAnchor.addChild(boxEntity)
 
Da Würfel mit dem Ursprung des Koordinatensystems genau in der Mitte erzeugt werden, würden sie zur hälfte in der Fläche stecken, was sich verhindern lässt, indem man das Objekt um die Hälfte seiner Größe nach oben schiebt. Das erreicht man, indem man einen Float-Vektor mit 3 Komponenten (SIMD3<Float>) erzeugt und dem position-Property der ModelEntity-Instanz zuweist:
 
boxEntity.position = [0,
boxShape.bounds.extents.y/2, 0]
 
Der Vektor kann dabei als Array geschrieben werden. Die Dimension des Objekts steht über das bounds-Property von MeshResource zur Verfügung, welches im extents-Property die Ausdehnung des Objekts als Vektor beschreibt. Da wir den Würfel um die Hälfte hochschieben wollen, nehmen wir die Hälfte der Y-Achse als neue Position auf der Y-Achse (zweites Element im Array) für boxEntity.

Relationen

Ein wichtiges Merkmal von Szenengraph-Systemen ist die relative Positionierung von Kind-Elementen, so dass man sich immer nur um die Positionierung relativ zum Vaterknoten kümmern muss. Wo man sich konkret in der Welt befindet, ist dabei egal. Auf dieselbe Art werden Entities bei RealityKit miteinander verbunden. Erzeugen wir hierfür eine weitere ModelEntity mit der oben erzeugten Text-Form (textShape) und dem Material mit der Textur (textMaterial):
 
let textEntity = ModelEntity(mesh: textShape, materials: [textMaterial])
 
Dieses Element (textEntity) fügen wir nun mit addChild() zur eben erzeugten boxEntity hinzu, was die Relation herstellt:
 
boxEntity.addChild(textEntity)
 
Der Text von textEntity hat enorme Dimensionen, weshalb er zuerst einmal auf ein brauchbares Maß reduziert werden muss. Das erledigen wir über die Transformationsmatrix von textEntity, dessen Skalierungskomponente wir über das scale-Property mit einem einfachen Vektor für die drei Achsen konfigurieren können:
 
textEntity.scale = [0.01, 0.01, 0.01]
 
Der Text von textEntity beginnt am Nullpunkt und läuft dann nach rechts. Wir wollen ihn aber zentriert über dem Würfel sehen, weshalb wir textEntity um die hälfte der Breite des Texts nach links schieben wollen. Das machen wir so, wie wir das oben schon getan haben, indem wir die Dimensionen (extends) von MeshResource und davon die X-Achse abfragen:
 
let textWidthHalf = textEntity.model!.mesh.bounds.extents.x/2 * textEntity.scale.x
textEntity.position = [-textWidthHalf, 0.1, 0]
 
Hier fragen wir die MeshResource über das model-Property von textEntity ab, über das es ebenfalls verfügbar ist. Außerdem muss noch der Skalierungsfaktor mit einbezogen werden, den wir über das scale-Property festgelegt haben. Abschließend schieben wir dann textEntity über das position-Property um die halbe Textbreite (textWidthHalf) nach links und um 10 Zentimeter nach oben, damit es über dem Würfel erscheint. Um den Text besser lesbar zu machen, entfernen wir die Text-Entity aus der Beleuchtungsberechnung, wodurch es uniform beleuchtet wird, und nicht mehr von den in der Umgebung verfügbaren Lichtquellen abhängt. Wir machen das, indem wir die entsprechende Komponente zur Beleuchtungsberechnung (DirectionalLightComponent) der Instanz entfernen:
 
textEntity.components.remove
(DirectionalLightComponent.self)
 
Dafür greifen wir mit dem components-Property auf das Set der Komponenten des Entities zu und entfernen die gewünschte Komponente mit der remove()-Methode, indem wir sie über den Typ (DirectionalLightComponent.self) identifizieren. Da jede Komponente nur einmal pro Entity existieren darf, lässt sie sich so immer einfach finden. Startet man nun das Beispiel und findet eine horizontale Fläche, dann wird dort die Objekt-Gruppe angezeigt. Bild 3 zeigt die Szene.
Die erstellte Objekt-Gruppe wird auf der horizontalen Fläche positioniert und angezeigt (Bild 3)
Quelle: Klein
Es gibt Fälle, wo es in Augmented Reality-Anwendungen zu Störungen in der Darstellung kommt, wenn virtuelle Objekte eigentlich in realen Objekten verschwinden oder von diesen verdeckt werden würden. Solch ein Fall entsteht beispielsweise, wenn wir den Würfel oben nicht auf der Y-Achse nach oben schieben würden (Bild 4).
Der Würfel versinkt im Tisch, wird aber nicht korrekt dargestellt (Bild 4)
Quelle: Klein
RealityKit hat hierfür ein besonderes Material, mit dem in solchen Fällen gearbeitet werden kann, und zwar das Occlusion Material. Es funktioniert wie ein Unsichtbarkeitsumhang für Objekte. Alle Objekte, die sich in oder hinter ein Objekt mit diesem Material bewegen, werden verdeckt und sind nicht mehr zu sehen.
Um eine Tischoberfläche auf diese Art undurchlässig zu machen, erzeugen wir einen neuen Würfel, der als Stellvertreter für den Tisch in der virtuellen Scene agiert:
 
let occlusionBox = MeshResource.generateBox(size: 0.5)
 
Er erhält eine Kantenlänge von einem halben Meter. Dann erzeugen wir das OcclusionMaterial:
 
let occlusionMaterial = OcclusionMaterial()
 
Und kombinieren alles in Form eines ModelEntity-Objekts:
 
let occlusionEntity = ModelEntity(mesh: occlusionBox, materials: [occlusionMaterial])
planeAnchor.addChild(occlusionEntity)
 
Welches wir mit addChild() zum planeAnchor hinzufügen. Der Würfel ist nun wieder um den Ursprung des Vater-Knotens und damit dem Tisch-Anker positioniert. Um den Effekt richtig umzusetzen, müssen wir den Würfel daher um seine halbe Höhe nach unten schieben, damit die obere Fläche des Würfels an der Stelle der Tischplatte des Tisches steht. Dafür fragen wir wieder die extents der Box für die Y-Achse ab und verschieben den Würfel über das position-Property:
 
occlusionEntity.position = [0,
-(occlusionBox.bounds.extents.y/2) + 0.001, 0]
 
Hierbei verschieben wir den Würfel um ein kleines bisschen mehr, damit flach auf dem Tisch aufliegende Entities nicht auch schon abgeschnitten werden. Der Wert von 0.001 ist durch ausprobieren entstanden. Bild 5 zeigt, wie der Würfel an der Tischplatte nun verdeckt wird.
Der Würfel wird an der Tischplatte nun verdeckt (Bild 5)
Quelle: Klein
Aus Performance-Gründen ist die Physik-Simulation für Entities standardmäßig erst einmal ausgeschaltet, was in der Nomenklatur von RealityKit heißt, dass die Physik-Komponente (PhysicsBodyComponent) nicht konfiguriert ist. Sie kann aber unkompliziert erzeugt und dann hinzugefügt werden:
 
boxEntity.physicsBody =
PhysicsBodyComponent(massProperties: .init(mass: 1.0), mode: .dynamic)
 
Hierfür erzeugen wir eine Instanz von PhysicsBodyComponent, wobei wir die Masse und den Simulations-Modus für das Objekt festlegen. Die Masse wird in Form eines PhysicsMassProperties-Typs angegeben, den wir hier inline mit einer Masse von einem Kilogramm erzeugen. Für den Modus stehen drei Varianten zur Auswahl, die vom PhysicsBodyMode-Enum definiert werden:
  • .static: Der Körper bewegt sich nicht, nimmt aber an der Simulation teil
  • .kinematic: Der Körper wird vom Nutzer bewegt
  • .dynamic: Der Körper wird von der Physik-Simulation bewegt
 
.dynamic steht dabei für das typische Verhalten von physikalisch korrekt bewegten Objekten, welches wir hier auch verwenden.
Damit die Physik-Simulation funktioniert, muss für die beteiligten Elemente noch die Kollisionserkennung eingeschaltet werden. Das wird mit einem Aufruf der generateCollisionShapes(recursive:)-Methode erreicht:
 
boxEntity.generateCollisionShapes(recursive: true)
 
Der Parameter recursive: gibt dabei an, ob auch alle Kinder der Entity die Kollisionserkennung einschalten sollen. Üblicherweise ist das das gewünschte verfahren, es kann aber vorkommen, dass man nicht alle Kinder miteinbeziehen möchte.
Führt man die Anwendung nun aus, dann verhält sich boxEntity physikalisch korrekt, und das auch "AR-Enabled", er interagiert also auch mit seinem Anker, der horizontalen Fläche korrekt.

Gesten

Doch der Spaß ist schnell vorbei, wenn der Würfel dann einmal auf der Fläche zum Liegen gekommen ist. Und hier kommen Gesten ins Spiel, mit einer solchen kann nämlich neues Leben in die Szene gehaucht werden. Wir erzeugen einen gewohnten UITapGestureRecognizer und fügen ihn mit addGestureRecognizer() zu arView hinzu:
 
let tapRecognizer = UITapGestureRecognizer(target: self,
action: #selector(handleTap(_:)))
self.arView.addGestureRecognizer
(tapRecognizer)
 
Als target: übergeben wir dabei self und als action: den Selektor der handleTap(_:)-Methode, die wir im Folgenden so definieren:
 
@objc func handleTap(_ sender:
UIGestureRecognizer) {
   let pointInView = sender.location
  (in: self.arView)
   if let tappedEntity =
  self.arView.entity(at: pointInView) {
      if let tappedModelEntity =
      tappedEntity as? ModelEntity {
      tappedModelEntity.
      applyLinearImpulse([0, 2, 0],
      relativeTo: tappedModelEntity) }
   }
}
 
Als erstes rechnen wir hier mit location(in:) die Position des Taps auf den Bildschirm in die entsprechende 2D-Koordinate von arView um. Dann fragen wir mit der entity(at:)-Methode von ARView ab, welche Entity sich an diesem Punkt am vordersten befindet, und stellen dann sicher, dass es sich um ein ModelEntity-Objekt handelt.
Nun fügen wir diesem Objekt einen linearen Impuls hinzu, indem wir applyLinearImpulse(_:relativeTo:) aufrufen. Der erste Parameter gibt dabei die Richtung des Impulses als Vektor an, der zweite Parameter (relativeTo:) legt den Ursprung des Impulses fest. In diesem Fall geht er vom angetippten Objekt aus, welches daher nach oben schießt und dann natürlich auch wieder herunterfällt.

Audio

Da geräuschlos herumspringende Objekte keinen Spaß machen, bringt RealityKit außerdem noch Audio-Unterstützung mit. Und hierbei wird der Ton nicht einfach nur abgespielt, sondern mittels Head Related Transfer Functions (HRTF) auch korrekt im Raum positioniert.
Hierfür muss zuerst einmal ein Sound aus den Ressourcen geladen werden:
 
let pingSound = try! AudioFileResource.load
(named: "ping")
 
Unterstützt werden dafür lediglich AIFF-Dateien, die allerdings mit jedem Sound-Editor erzeugt werden können.
Nun sucht man sich das Entity der Szene aus, von dem der Sound kommen soll, und ruft auf diesem playAudio() auf:
 
tappedModelEntity.playAudio(pingSound)
 
Fügt man die zwei Zeilen zur handleTap(_:)-Methode hinzu, dann springt das Objekt nicht nur hoch, sondern macht auch noch ein Geräusch dabei.

Animation

Animationen gibt es in RealityKit in zwei Varianten: Einfachen Transformationen und Skeletal Animation.
Änderungen der Transformationsmatrix einer Entity ermöglichen Positionsänderung und Skalierung mit nur wenigen Zeilen Code. Für eine Skalierung nimmt man beispielsweise die existierende Tranformationsmatrix und verändert ausgehend von den vorhandenen Werten die Skalierung durch Multiplikation:
 
var newTransform = tappedEntity.transform
newTransform.scale.x *= 1.25
newTransform.scale.y *= 1.25
newTransform.scale.z *= 1.25
 
Anschließend verwendet man dann die move()-Methode von Entity um die Änderung durchzuführen:
 
tappedEntity.move(to: newTransform, relativeTo:
tappedEntity, duration: 1.0, timingFunction: .easeInOut)
 
Der Übergang wird dabei sauber mit den angegebenen Werten animiert. Hier also mit einer Dauer von einer Sekunde (duration:) und der Timing-Funktion (timingFunction:) .easeInOut, die wir schon von UIKit/Core Animation kennen.Skeletal Animations beschreiben die Animation eines Skelettsystems, wie sie 3D-Editoren definiert werden können. Entsprechende Animationen können mit zum Beispiel einer Figur in USDZ-Dateien gespeichert und zur Laufzeit ganz einfach mit playAnimation(named:) abgespielt werden:
 
robot.playAnimation(named: "dance")
 
Ähnlich einfach ist es die mit ARKit 3 neu gekommenen Bewegungsdaten einer Person (Motion Capturing) live auf eine passende Figur zu übertragen. Hierfür wird zuerst ein Body-Anchor erzeugt und zur Szene hinzugefügt:
 
let bodyAnchor = AnchorEntity(.body)
arView.scene.addAnchor(bodyAnchor)
 
Anschließend lädt man mit der loadBodyTracked(named:)-Methode der Klasse BodyTrackedEntity das Modell und fügt es mit der addChild()-Methode zum bodyAnchor hinzu:
 
let robot = try! BodyTrackedEntity.loadBodyTracked(named: "robot")
bodyAnchor.addChild(robot)
 
Nachdem eine Person erkannt worden ist, wird nun das Modell angezeigt und live mit den Bewegungen der Person synchronisiert.

Reality Composer

Ergänzend zu RealityKit gibt es dieses Jahr ebenfalls neu den Reality Composer. Er ist für Mac und iOS (iPad & iPhone) verfügbar. Auf dem Mac wird er mit Xcode 11 ausgeliefert und ist im Xcode-Menü unter Open Developer Tool zu finden. Unter iOS steht er regulär im App Store zur Verfügung.
Reality Composer erlaubt es unkompliziert AR-Szenen grafisch zu entwerfen. Dabei bietet er Szenen für die bekannten Anker-Typen horizontale Fläche, vertikale Fläche, Bild, Gesicht und (3D-)Objekt an (Bild 6).
Die Anker-Auswahl des Reality Composers (Bild 6)
Quelle: Klein
Eine eingebaute Objektbibliothek liefert genug Futter für erste Experimente (Bild 7), ergänzend lassen sich aber auch USDZ-Dateien einbinden.
Bibliothek: Die eingebaute Objektbibliothek (Bild 7)
Quelle: Klein
Die Szene bearbeitet man nun in einem Editor, in dem die Objekte frei und in drei Dimensionen platziert, verschoben, skaliert und gedreht werden können. Ein Eigenschaften-Editor, der ein wenig an den Storyboard-Editor erinnert, erlaubt es weitere Details von Objekten einzustellen (Bild 8).
Der Szenen-Editor des Reality Composers (Bild 8)
Quelle: Klein
Ein besonderes Schmankerl ist der AR-Modus des Szenen-Editors auf dem iPad. Er erlaubt es, die Szene Live und in Echtzeit in einer AR-Ansicht zu sehen und zu bearbeiten (Bild 9). Ein AR-WYSIWYG-Editor, gewissermaßen.
Der Szenen-Editor im AR-Modus auf dem iPad (Bild 9)
Quelle: Klein
Arbeitet man bevorzugt auf dem Mac, dann gibt es von dort aus jederzeit die Möglichkeit das Projekt auf ein iOS-Gerät zu schieben, dort zu testen, und dann zur Bearbeitung auf dem Mac zurückzukehren.
Neben der Szene lässt sich in einem gesonderten Editor außerdem noch Verhalten programmieren (Bild 10). Hierbei wird jeweils ein Auslöser mit einer Aktion kombiniert. Als Aktionen stehen tippen, Szenenstart, Entfernung zur Kamera und Kollidieren zur Auswahl.
Der Verhaltens-Editor im Einsatz (Bild 10)
Quelle: Klein
Die Liste der Aktionen ist lang und geht vom Verschieben, Drehen und Rotieren von Objekten, über das Anzeigen oder Ausblenden, Ton abspielen und der Musikwiedergabe, über das Abspielen von Animationen aus USDZ-Dateien, bis hin zum Wechsel zu einer neuen Szene.

Xcode

Um die Projekte des Reality Composers (.rcproject) in Xcode zu verwenden, zieht man sie einfach per Drag & Drop in ein Projekt. Es wird dann zu diesem hinzugefügt und auch eine Vorschau angezeigt (Bild 11).
Das Reality Composer-Projekt in Xcode (Bild 11)
Quelle: Klein
Zum Bearbeiten des Projekts springt man dann wieder in den Reality Composer. Beim Import fügt Xcode allerdings nicht nur die Datei zum Projekt hinzu, sondern erzeugt auch gleich die passende Schnittstelle, so dass die Szene mit nur einer Zeile Code geladen werden kann:
 
let myScene = try! SomeStuff.loadSzene()
 
Da die zurückgelieferte Szene selbst gleich den Anker der Szene darstellt, kann sie ohne weitere Konfiguration mit addAnchor() zur Szene von ARView hinzugefügt werden:
 
arView.scene.addAnchor(myScene)
 
Der erzeugte Szenen-Typ enthält außerdem Properties, mit denen direkt auf die einzelnen Objekte zugegriffen werden kann. Über das burger-Property erhält man so Zugriff auf den Burger der Szene. Dieser ist vom Typ Entity und kann zu einem existierenden Anker hinzugefügt werden:
 
planeAnchor.addChild(burger)
 
Im Build-Prozess von Xcode wird nun das Reality Composer-Projekt in das neue Reality File Format (.reality) überführt, welches in den Ressourcen der Anwendung landet und von den Geräten zur Laufzeit effizient geladen werden kann.
Zum Abschluss noch ein praktisches Goodie für Entwickler, das Reality Composer noch mitbringt. Und zwar kann dieser AR-Sitzungen aufzeichnen. Öffnen Sie das …-Menü und gehen Sie dort auf Entwickler und dann auf AR-Sitzung aufnehmen (Bild 12). Dann erhält man eine AR-Ansicht und kann nach einer weiteren Bestätigung loslegen wie üblich, wobei allerdings ein Video und die Sensorinformationen des Geräts aufgezeichnet werden. Drücken Sie den Stop-Button um die Sitzung zu beenden. Im folgenden Dialog kann die Aufnahme dann gleich via AirDrop an den Mac übertragen werden.
Reality Composer kann AR-Sitzungen aufzeichnen (Bild 12)
Quelle: Klein
Das übertragene Video im MOV-Format enthält dabei nicht nur das Video, sondern als eingebettete Metadaten auch die Sensordaten des Geräts. Die Datei reicht aus, um die Szene für ARKit vollständig nachzustellen. Und das kann mit Xcode 11 auch gemacht werden. Im Schema Editor des Projekts findet sich im Options-Tab jetzt der Eintrag ARKit. Setzen Sie dort das Häkchen an Replay data und wählen Sie das Video aus (Bild 13).
Im Schema-Editor wird die Sitzungs-Aufzeichnung eintragen (Bild 13)
Quelle: Klein
Startet man die App, dann wird bei Start der AR-Session statt den Live-Daten das Video mit seinen Sensorinformationen eingespielt. Auf diese Weise erhält man eine reproduzierbare Situation, die man am Rechner unkompliziert immer  wieder testen kann. Leider funktioniert das derzeit nur mit echten Geräten. Die Simulatoren sind also weiterhin außen vor.

Fazit

RealityKit und Reality Composer sind vielversprechende neue Tools, mit denen die Entwicklung von AR-Apps so einfach wie möglich gemacht wird. Bei der Menge an Arbeit, die Apple in das Thema steckt, drängt sich der Verdacht auf, dass wir uns auf etwas größeres zubewegen.



Das könnte Sie auch interessieren