Fallstudie : Erstellen einer Galaxie in Mixed Reality

Bevor Microsoft HoloLens ausgeliefert werden, haben wir unsere Entwicklercommunity gefragt, welche Art von App sie sich für einen erfahrenen internen Teambuild für das neue Gerät wünschen würde. Mehr als 5000 Ideen wurden geteilt, und nach einer 24-stündigen Twitter-Umfrage war der Gewinner eine Idee namens Galaxy Explorer.

Andy Zibits, der Künstlerische Leiter des Projekts, und Karim Luccin, der Grafikingenieur des Teams, sprechen über die Zusammenarbeit zwischen Kunst und Technik, die zur Schaffung einer genauen, interaktiven Darstellung der Milchstraße in Galaxy Explorer geführt hat.

Die Technik

Unser Team - bestehend aus zwei Designern, drei Entwicklern, vier Künstlern, einem Produzenten und einem Tester - hatte sechs Wochen Zeit, um eine voll funktionsfähige App zu erstellen, die es den Menschen ermöglichte, mehr über die Weite und Schönheit unserer Milchstraße zu erfahren und zu erkunden.

Wir wollten die Fähigkeit von HoloLens nutzen, 3D-Objekte direkt in Ihrem Lebensraum zu rendern, also beschlossen wir, eine realistisch aussehende Galaxie zu schaffen, in der menschen in der Lage sein würden, nah zu zoomen und einzelne Sterne auf ihren eigenen Flugbahnen zu sehen.

In der ersten Woche der Entwicklung haben wir uns ein paar Ziele für unsere Darstellung der Milchstraße entwickelt: Es brauchte Tiefe, Bewegung und volumetrisches Gefühl – voller Sterne, die dazu beitragen würden, die Form der Galaxie zu schaffen.

Das Problem bei der Erstellung einer animierten Galaxie mit Milliarden von Sternen war, dass die schiere Anzahl einzelner Elemente, die aktualisiert werden müssen, pro Frame zu groß wäre, um HoloLens mit der CPU zu animieren. Unsere Lösung beinhaltete eine komplexe Mischung aus Kunst und Wissenschaft.

Abläufe im Hintergrund

Um den Menschen die Erforschung einzelner Sterne zu ermöglichen, bestand unser erster Schritt darin, herauszufinden, wie viele Partikel wir gleichzeitig rendern konnten.

Rendern von Partikeln

Aktuelle CPUs eignen sich hervorragend für die gleichzeitige Verarbeitung von seriellen Aufgaben und bis zu einigen parallelen Aufgaben (je nachdem, wie viele Kerne sie haben), aber GPUs sind bei der parallelen Verarbeitung von Tausenden von Vorgängen viel effektiver. Da sie jedoch in der Regel nicht denselben Arbeitsspeicher wie die CPU nutzen, kann der Austausch von Daten zwischen cpu-GPU<>schnell zu einem Engpass werden. Unsere Lösung bestand darin, eine Galaxie auf der GPU zu erstellen, und sie musste vollständig auf der GPU leben.

Wir haben Stresstests mit Tausenden von Punktpartikeln in verschiedenen Mustern gestartet. Dies ermöglichte es uns, die Galaxie auf HoloLens zu bringen, um zu sehen, was funktionierte und was nicht.

Erstellen der Position der Sterne

Eines unserer Teammitglieder hatte bereits den C#-Code geschrieben, der Sterne an seiner Ausgangsposition generierte. Die Sterne befinden sich auf einer Ellipse und ihre Position kann durch (curveOffset, ellipseSize, elevation) beschrieben werden, wobei curveOffset der Winkel des star entlang der Ellipse, EllipseSize ist die Dimension der Ellipse entlang X und Z, und die richtige Höhe der star innerhalb der Galaxie. Daher können wir einen Puffer (ComputeBuffer von Unity) erstellen, der mit jedem star-Attribut initialisiert wird, und ihn an die GPU senden, an der er sich für den Rest der Benutzeroberfläche befindet. Um diesen Puffer zu zeichnen, verwenden wir DrawProcedural von Unity , der es ermöglicht, einen Shader (Code auf einer GPU) auf einer beliebigen Gruppe von Punkten auszuführen, ohne über ein tatsächliches Gitter zu verfügen, das die Galaxie darstellt:

CPU:

GraphicsDrawProcedural(MeshTopology.Points, starCount, 1);

GPU:

v2g vert (uint index : SV_VertexID)
{

 // _Stars is the buffer we created that contains the initial state of the system
 StarDescriptor star = _Stars[index];
 …

}

Wir begannen mit rohen kreisförmigen Mustern mit Tausenden von Partikeln. Dies gab uns den Beweis, dass wir viele Partikel bewältigen UND mit leistungsstarken Geschwindigkeiten ausführen konnten, aber wir waren mit der Gesamtform der Galaxie nicht zufrieden. Um die Form zu verbessern, versuchten wir verschiedene Muster und Partikelsysteme mit Drehung. Diese waren zunächst vielversprechend, weil die Anzahl der Partikel und die Leistung konstant blieben, aber die Form brach in der Nähe des Zentrums ab und die Sterne gaben nach außen ab, was nicht realistisch war. Wir brauchten eine Emission, die es uns ermöglichte, die Zeit zu manipulieren und die Partikel realistisch bewegen zu lassen und immer näher an das Zentrum der Galaxie zu schlingen.

Wir haben verschiedene Muster und Partikelsysteme versucht, die sich wie diese drehten.

Wir haben verschiedene Muster und Partikelsysteme versucht, die sich wie diese drehten.

Unser Team hat einige Untersuchungen zur Funktionsweise von Galaxien durchgeführt und wir haben ein benutzerdefiniertes Partikelsystem speziell für die Galaxie entwickelt, damit wir die Teilchen auf Ellipsen auf der Grundlage der "Dichtewellentheorie" bewegen konnten, die besagt, dass die Arme einer Galaxie Bereiche mit höherer Dichte, aber in ständigem Fluss sind, wie ein Stau. Es erscheint stabil und fest, aber die Sterne bewegen sich tatsächlich in und aus den Armen, während sie sich entlang ihrer jeweiligen Auslassungspunkte bewegen. In unserem System sind die Partikel nie auf der CPU vorhanden – wir generieren die Karten und richten sie alle an der GPU aus, sodass das gesamte System einfach Erstzustand + Zeit ist. Der Vorgang ist wie folgt verlaufen:

Fortschritt des Partikelsystems mit GPU-Rendering

Fortschritt des Partikelsystems mit GPU-Rendering

Sobald genügend Ellipsen hinzugefügt wurden und sich drehen, begannen die Galaxien, "Arme" zu bilden, wo die Bewegung der Sterne konvergieren. Der Abstand der Sterne entlang jedes elliptischen Pfads wurde mit einer gewissen Zufallszufälligkeit versehen, und jeder star wurde ein wenig Positionszufälligkeit hinzugefügt. Dadurch entstand eine viel natürlichere Verteilung von star Bewegung und Armform. Schließlich haben wir die Möglichkeit hinzugefügt, Farben basierend auf der Entfernung von der Mitte zu steuern.

Erstellen der Bewegung der Sterne

Um die allgemeine star Bewegung zu animieren, mussten wir einen konstanten Winkel für jeden Frame hinzufügen und Sterne mit einer konstanten Radialgeschwindigkeit entlang ihrer Ellipsen bewegen. Dies ist der Hauptgrund für die Verwendung von curveOffset. Dies ist technisch nicht korrekt, da Sterne sich schneller entlang der langen Seiten der Ellipsen bewegen werden, aber die allgemeine Bewegung fühlte sich gut an.

Sterne bewegen sich schneller auf dem langen Bogen, langsamer an den Rändern.

Sterne bewegen sich schneller auf dem langen Bogen, langsamer an den Rändern.

Damit wird jeder star vollständig durch (curveOffset, ellipseSize, elevation, Age) beschrieben, wobei Age eine Akkumulation der Gesamtzeit ist, die seit dem Laden der Szene verstrichen ist.

float3 ComputeStarPosition(StarDescriptor star)
{

  float curveOffset = star.curveOffset + Age;
  
  // this will be coded as a “sincos” on the hardware which will compute both sides
  float x = cos(curveOffset) * star.xRadii;
  float z = sin(curveOffset) * star.zRadii;
   
  return float3(x, star.elevation, z);
  
}

Dies ermöglichte es uns, einmal zu Beginn der Anwendung Zehntausende von Sternen zu generieren, dann animierten wir eine einzelne Reihe von Sternen entlang der etablierten Kurven. Da sich alles auf der GPU befindet, kann das System alle Sterne parallel ohne Kosten für die CPU animieren.

So sieht es beim Zeichnen von weißen Quads aus.

So sieht es beim Zeichnen von weißen Quads aus.

Um jedes Quad zur Kamera zu machen, haben wir einen Geometrieshader verwendet, um jede star Position in ein 2D-Rechteck auf dem Bildschirm zu transformieren, das unsere star Textur enthält.

Diamanten anstelle von Quads.

Diamanten anstelle von Quads.

Da wir die Überzeichnung (Anzahl der Verarbeitungen eines Pixels) so weit wie möglich einschränken wollten, haben wir unsere Quads gedreht, sodass sie weniger Überlappungen aufweisen.

Hinzufügen von Clouds

Es gibt viele Möglichkeiten, ein volumetrisches Gefühl mit Partikeln zu bekommen – vom Strahl, der innerhalb eines Volumens marschiert, bis hin zum Zeichnen so vieler Partikel wie möglich, um eine Wolke zu simulieren. Das Strahlmarschen in Echtzeit war zu teuer und schwer zu erstellen, also haben wir zuerst versucht, ein Hochstaplersystem mit einer Methode zum Rendern von Wäldern in Spielen zu erstellen – mit vielen 2D-Bildern von Bäumen, die der Kamera gegenüberstehen. Wenn wir dies in einem Spiel tun, können wir Strukturen von Bäumen von einer Kamera rendern lassen, die sich umdreht, alle diese Bilder speichern und zur Laufzeit für jede Billboard-Karte das Bild auswählen, das der Ansichtsrichtung entspricht. Dies funktioniert nicht so gut, wenn es sich bei den Bildern um Hologramme handelt. Der Unterschied zwischen dem linken Auge und dem rechten Auge macht es so, dass wir eine viel höhere Auflösung benötigen, oder es sieht nur flach, aliashaft oder sich wiederholend aus.

Beim zweiten Versuch haben wir versucht, so viele Partikel wie möglich zu haben. Die besten Visuellen wurden erreicht, wenn wir Partikel additiv gezeichnet und verschwommen haben, bevor wir sie der Szene hinzufügen. Die typischen Probleme bei diesem Ansatz hängen davon ab, wie viele Partikel wir auf einmal zeichnen konnten und wie viel Bildschirmfläche sie bedeckten, während sie weiterhin 60fps beibehalten. Das daraus resultierende Bild zu verwischen, um dieses Cloudgefühl zu erhalten, war in der Regel ein sehr kostspieliger Vorgang.

Ohne Textur würden die Clouds mit einer Deckkraft von 2 % aussehen.

Ohne Textur würden die Clouds mit einer Deckkraft von 2 % aussehen.

Additiv zu sein und viele davon zu haben, bedeutet, dass wir mehrere Quads übereinander haben und dasselbe Pixel wiederholt schattieren würden. Im Zentrum der Galaxie hat das gleiche Pixel Hunderte von Quads übereinander und dies hatte enorme Kosten, wenn der Vollbildmodus ausgeführt wird.

Vollbildwolken zu machen und zu versuchen, sie zu verwischen, wäre eine schlechte Idee gewesen, also entschieden wir uns stattdessen, die Hardware die Arbeit für uns erledigen zu lassen.

Zunächst ein wenig Kontext

Wenn Sie Texturen in einem Spiel verwenden, entspricht die Texturgröße selten dem Bereich, in dem sie verwendet werden soll, aber wir können verschiedene Arten der Texturfilterung verwenden, um die Grafik Karte zu erhalten, um die gewünschte Farbe aus den Pixeln der Textur zu interpolieren (Texturfilterung). Die Filterung, die uns interessiert, ist die bilineare Filterung , die den Wert eines beliebigen Pixels mit den 4 nächsten Nachbarn berechnet.

Original vor dem Filtern

Ergebnis nach dem Filtern

Mit dieser Eigenschaft sehen wir, dass jedes Mal, wenn wir versuchen, eine Textur in einen doppelt so großen Bereich zu zeichnen, das Ergebnis weich wird.

Anstatt in einen Vollbildmodus zu rendern und diese wertvollen Millisekunden zu verlieren, die wir für etwas anderes ausgeben könnten, rendern wir in einer winzigen Version des Bildschirms. Wenn wir diese Textur kopieren und mehrmals um den Faktor 2 strecken, kehren wir wieder zum Vollbildmodus zurück, während der Inhalt dabei verschwommen wird.

x3 hochskaliert wieder auf die vollständige Auflösung.

x3 hochskaliert wieder auf die vollständige Auflösung.

Dies ermöglichte es uns, den Cloudteil mit nur einem Bruchteil der ursprünglichen Kosten zu erhalten. Anstatt Wolken auf der vollständigen Auflösung hinzuzufügen, zeichnen wir nur 1/64 der Pixel und strecken die Textur einfach wieder auf die volle Auflösung.

Links, mit einer Aufwärtsskalierung von 1/8 bis zur vollständigen Auflösung; und richtig, mit 3 hochskaliert unter Verwendung von Leistung von 2.

Links, mit einer Aufwärtsskalierung von 1/8 bis zur vollständigen Auflösung; und richtig, mit 3 hochskaliert unter Verwendung von Leistung von 2.

Beachten Sie, dass der Versuch, in einem Schritt von 1/64 der Größe zur vollständigen Größe zu wechseln, völlig anders aussehen würde, da die Grafik Karte in unserem Setup immer noch 4 Pixel verwenden würde, um einen größeren Bereich zu schattieren und Artefakte zu erscheinen beginnen.

Wenn wir dann Sterne mit voller Auflösung mit kleineren Karten hinzufügen, erhalten wir die vollständige Galaxie:

Nahezu Endergebnis des Galaxienrenderings mit Sternen mit voller Auflösung

Nachdem wir mit der Form auf dem richtigen Weg waren, fügten wir eine Schicht von Wolken hinzu, tauschten die temporären Punkte mit denen aus, die wir in Photoshop gemalt haben, und fügten zusätzliche Farbe hinzu. Das Ergebnis war eine Milky Way Galaxy, für die sich unsere Kunst- und Entwicklungsteams beide gut fühlten und unsere Ziele erfüllten: Tiefe, Volumen und Bewegung – alles, ohne die CPU zu belasten.

Unsere letzte Milchstraße in 3D.

Unsere letzte Milchstraße in 3D.

Weitere Informationen

Wir haben den Code für die Galaxy Explorer-App im Open Source-Code erstellt und auf GitHub für Entwickler zur Verfügung gestellt, auf der sie aufbauen können.

Möchten Sie mehr über den Entwicklungsprozess für Galaxy Explorer erfahren? Sehen Sie sich alle unsere früheren Projektupdates auf dem Microsoft HoloLens YouTube-Kanal an.

Über die Autoren

Bild von Karim Luccin an seinem Schreibtisch Karim Luccin ist Ein Software-Ingenieur und Fan von Visuals. Er war Grafikingenieur für Galaxy Explorer.
Foto von Kunstleiter Andy Zibits Andy Zibits ist Art Lead und Space-Enthusiast, der das 3D-Modellierungsteam für Galaxy Explorer leitete und für noch mehr Partikel kämpfte.

Weitere Informationen