Casestudy - Een melkweg maken in mixed reality

Voordat Microsoft HoloLens verzonden, vroegen we onze ontwikkelaarscommunity wat voor soort app ze graag een ervaren interne teambuild voor het nieuwe apparaat willen zien. Meer dan 5000 ideeën werden gedeeld, en na een 24-uurs Twitter-peiling was de winnaar een idee genaamd Galaxy Explorer.

Andy Zibits, de kunstleider van het project, en Resource Luccin, de grafisch ingenieur van het team, vertellen over de samenwerking tussen kunst en techniek die heeft geleid tot de creatie van een nauwkeurige, interactieve weergave van het Melkwegstelsel in Galaxy Explorer.

The Tech

Ons team , bestaande uit twee ontwerpers, drie ontwikkelaars, vier artiesten, een producent en één tester, had zes weken de tijd om een volledig functionele app te bouwen waarmee mensen de weidsheid en schoonheid van onze Melkweg kunnen leren en verkennen.

We wilden optimaal profiteren van de mogelijkheid van HoloLens om 3D-objecten rechtstreeks in uw leefruimte weer te geven, dus besloten we een realistisch ogende melkweg te creëren waar mensen van dichtbij kunnen inzoomen en afzonderlijke sterren kunnen zien, elk op hun eigen traject.

In de eerste week van ontwikkeling kwamen we met een paar doelen voor onze representatie van de Melkweg: Het moest diepte, beweging en gevoel volumetrische hebben- vol sterren die de vorm van het melkwegstelsel zouden helpen creëren.

Het probleem met het maken van een geanimeerde melkweg met miljarden sterren was dat het enorme aantal afzonderlijke elementen dat moet worden bijgewerkt, te groot per frame zou zijn om HoloLens te animeren met behulp van de CPU. Onze oplossing betrof een complexe mix van kunst en wetenschap.

Achter de schermen

Om mensen in staat te stellen individuele sterren te verkennen, was onze eerste stap om erachter te komen hoeveel deeltjes we in één keer konden weergeven.

Deeltjes weergeven

Huidige CPU's zijn ideaal voor het verwerken van seriële taken en maximaal een paar parallelle taken tegelijk (afhankelijk van het aantal kernen dat ze hebben), maar GPU's zijn veel effectiever bij het parallel verwerken van duizenden bewerkingen. Omdat ze echter meestal niet hetzelfde geheugen delen als de CPU, kan het uitwisselen van gegevens tussen CPU<>GPU snel een knelpunt worden. Onze oplossing was om een melkweg te maken op de GPU en het moest volledig op de GPU leven.

We zijn stresstests gestart met duizenden puntdeeltjes in verschillende patronen. Hierdoor konden we het stelsel op HoloLens krijgen om te zien wat werkte en wat niet.

De positie van de sterren maken

Een van onze teamleden had al de C#-code geschreven waarmee sterren op hun oorspronkelijke positie zouden worden gegenereerd. De sterren bevinden zich op een ellips en hun positie kan worden beschreven door (curveOffset, ellipseSize, hoogte) waarbij curveOffset de hoek van de star langs de ellips is, ellipseSize de dimensie van de ellips langs X en Z is, en de juiste verhoging van de star binnen het stelsel. We kunnen dus een buffer (ComputeBuffer van Unity) maken die wordt geïnitialiseerd met elk star kenmerk en deze verzenden naar de GPU waar deze zich voor de rest van de ervaring bevindt. Om deze buffer te tekenen, gebruiken we DrawProcedural van Unity , waarmee een shader (code op een GPU) kan worden uitgevoerd op een willekeurige set punten zonder dat er een werkelijke mesh is die de melkweg vertegenwoordigt:

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];
 …

}

We begonnen met onbewerkte cirkelpatronen met duizenden deeltjes. Dit gaf ons het bewijs dat we nodig hadden dat we veel deeltjes konden beheren EN het met hoge snelheden konden uitvoeren, maar we waren niet tevreden met de algehele vorm van het stelsel. Om de vorm te verbeteren, hebben we verschillende patronen en deeltjessystemen met rotatie geprobeerd. Deze waren in eerste instantie beloftevol omdat het aantal deeltjes en de prestaties constant bleven, maar de vorm brak in de buurt van het midden af en de sterren kwamen naar buiten, wat niet realistisch was. We hadden een emissie nodig die ons in staat zou stellen om de tijd te manipuleren en de deeltjes realistisch te laten bewegen, steeds dichter bij het centrum van het melkwegstelsel.

We hebben verschillende patronen en deeltjessystemen geprobeerd die gedraaid zijn, zoals deze.

We hebben verschillende patronen en deeltjessystemen geprobeerd die gedraaid zijn, zoals deze.

Ons team heeft onderzoek gedaan naar de manier waarop melkwegstelsels werken en we hebben speciaal voor de melkweg een aangepast deeltjessysteem gemaakt, zodat we de deeltjes op beletseltekens kunnen verplaatsen op basis van de 'dichtheidsgolftheorie', die theoretiseert dat de armen van een melkweg gebieden zijn met een hogere dichtheid, maar in constante flux, zoals een verkeersopstopping. Het lijkt stabiel en solide, maar de sterren bewegen in en uit de armen terwijl ze langs hun respectieve beletseltekens bewegen. In ons systeem bestaan de deeltjes nooit op de CPU. We genereren de kaarten en richten ze allemaal op de GPU, dus het hele systeem is gewoon initiële status + tijd. De voortgang is als volgt verlopen:

Progressie van deeltjessysteem met GPU-rendering

Progressie van deeltjessysteem met GPU-rendering

Zodra er voldoende ellipsen zijn toegevoegd en zijn ingesteld om te draaien, begonnen de melkwegstelsels "armen" te vormen waar de beweging van sterren convergeert. De afstand van de sterren langs elk elliptisch pad kreeg enige willekeurigheid, en elke star kreeg een beetje positionele willekeurigheid toegevoegd. Dit creëerde een veel natuurlijker ogende verdeling van star beweging en armvorm. Ten slotte hebben we de mogelijkheid toegevoegd om kleur te bepalen op basis van de afstand tot het centrum.

De beweging van de sterren maken

Om de algemene star beweging te animeren, moesten we een constante hoek voor elk frame toevoegen en sterren met een constante radiale snelheid langs hun weglatingstekens laten bewegen. Dit is de primaire reden voor het gebruik van curveOffset. Dit is technisch niet juist omdat sterren sneller langs de lange zijden van de beletseltekens bewegen, maar de algemene beweging voelde goed aan.

Sterren bewegen sneller op de lange boog, langzamer aan de randen.

Sterren bewegen sneller op de lange boog, langzamer aan de randen.

Hiermee wordt elk star volledig beschreven door (curveOffset, ellipseSize, verhoging, Leeftijd), waarbij Leeftijd een accumulatie is van de totale tijd die is verstreken sinds de scène is geladen.

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

Hierdoor konden we tienduizenden sterren genereren aan het begin van de toepassing, waarna we een enkele set sterren langs de gevestigde curven hebben geanimeerd. Omdat alles zich op de GPU bevindt, kan het systeem alle sterren parallel animeren zonder kosten voor de CPU.

Zo ziet het eruit bij het tekenen van witte quads.

Zo ziet het eruit bij het tekenen van witte quads.

Om elke quad naar de camera te laten kijken, hebben we een geometrie-shader gebruikt om elke star positie te transformeren naar een 2D-rechthoek op het scherm die onze star textuur bevat.

Diamanten in plaats van quads.

Diamanten in plaats van quads.

Omdat we de overtekening (het aantal keren dat een pixel wordt verwerkt) zoveel mogelijk wilden beperken, hebben we de quads gedraaid zodat ze minder overlappen.

Clouds toevoegen

Er zijn veel manieren om een volumetrische gevoel met deeltjes te krijgen: van straal die binnen een volume marcheert tot het tekenen van zoveel mogelijk deeltjes om een wolk te simuleren. Real-time ray marching zou te duur en moeilijk zijn om te schrijven, dus we hebben eerst geprobeerd een bedriegersysteem te bouwen met behulp van een methode voor het weergeven van bossen in games, met veel 2D-afbeeldingen van bomen die naar de camera gericht zijn. Wanneer we dit in een game doen, kunnen we structuren weergeven van een camera die ronddraait, al die afbeeldingen opslaan en tijdens runtime voor elke billboardkaart de afbeelding selecteren die overeenkomt met de weergaverichting. Dit werkt niet zo goed als de afbeeldingen hologrammen zijn. Het verschil tussen het linkeroog en het rechteroog zorgt ervoor dat we een veel hogere resolutie nodig hebben, anders ziet het er gewoon plat, aliased of herhalend uit.

Bij onze tweede poging probeerden we zoveel mogelijk deeltjes te hebben. De beste beelden werden bereikt wanneer we deeltjes extra tekenden en vervaagden voordat we ze aan de scène toevoegden. De typische problemen met deze benadering hadden betrekking op het aantal deeltjes dat we op één moment konden tekenen en hoeveel schermgebied ze bedekten terwijl ze nog steeds 60fps behouden. Het vervagen van de resulterende afbeelding om dit cloudgevoel te krijgen, was meestal een zeer dure bewerking.

Zonder textuur zouden de wolken er als volgt uitzien met een dekking van 2%.

Zonder textuur zouden de wolken er als volgt uitzien met een dekking van 2%.

Omdat we additief zijn en er veel van hebben, betekent dit dat we verschillende quads op elkaar zouden hebben, waarbij we herhaaldelijk dezelfde pixel arceren. In het midden van het melkwegstelsel heeft dezelfde pixel honderden quads boven op elkaar en dit had enorme kosten wanneer het volledig scherm werd gedaan.

Het uitvoeren van clouds op volledig scherm en het vervagen ervan zou een slecht idee zijn geweest, dus in plaats daarvan hebben we besloten om de hardware het werk voor ons te laten doen.

Eerst een beetje context

Wanneer u patronen gebruikt in een game, komt de patroongrootte zelden overeen met het gebied waarin we het willen gebruiken, maar we kunnen verschillende soorten patroonfilters gebruiken om de grafische kaart te krijgen om de gewenste kleur van de pixels van het patroon te interpoleren (Patroonfiltering). De filtering die ons interesseert, is bilineaire filtering waarmee de waarde van een pixel wordt berekend met behulp van de vier dichtstbijzijnde buren.

Origineel voor filteren

Resultaat na filteren

Met behulp van deze eigenschap zien we dat telkens wanneer we een patroon in een gebied proberen te tekenen dat twee keer zo groot is, het resultaat vervaagt.

In plaats van naar een volledig scherm te renderen en die kostbare milliseconden te verliezen die we aan iets anders kunnen uitgeven, maken we een kleine versie van het scherm. Door dit patroon vervolgens te kopiëren en meerdere keren met een factor 2 uit te rekken, gaan we terug naar het volledige scherm terwijl de inhoud in het proces wordt vervaagd.

x3 opgeschaald terug naar volledige resolutie.

x3 opgeschaald terug naar volledige resolutie.

Hierdoor konden we het cloudonderdeel ophalen met slechts een fractie van de oorspronkelijke kosten. In plaats van wolken toe te voegen aan de volledige resolutie, schilderen we slechts 1/64e van de pixels en strekken we het patroon terug naar de volledige resolutie.

Links, met een opstaande van 1/8e tot volledige resolutie; en juist, met 3 luxe met behulp van kracht van 2.

Links, met een opstaande van 1/8e tot volledige resolutie; en juist, met 3 luxe met behulp van kracht van 2.

Houd er rekening mee dat proberen om in één stap van 1/64e van de grootte naar de volledige grootte te gaan er heel anders uit zou zien, omdat de grafische kaart nog steeds 4 pixels in onze installatie zou gebruiken om een groter gebied te arceren en artefacten beginnen te verschijnen.

Als we vervolgens sterren met volledige resolutie met kleinere kaarten toevoegen, krijgen we het volledige melkwegstelsel:

Bijna het uiteindelijke resultaat van galaxy rendering met behulp van sterren met volledige resolutie

Zodra we op het juiste spoor waren met de vorm, hebben we een laag wolken toegevoegd, de tijdelijke stippen verwisseld met de punten die we in Photoshop hebben geschilderd en wat extra kleur toegevoegd. Het resultaat was een Melkwegstelsel waar onze kunst- en engineeringteams zich allebei goed over voelden en het voldeed aan onze doelstellingen om diepte, volume en beweging te hebben, allemaal zonder de CPU te belasten.

Onze laatste Melkweg in 3D.

Onze laatste Melkweg in 3D.

Meer om te verkennen

We hebben de code voor de Galaxy Explorer-app open source gemaakt en beschikbaar gemaakt op GitHub voor ontwikkelaars om op voort te bouwen.

Wilt u meer weten over het ontwikkelingsproces voor Galaxy Explorer? Bekijk al onze eerdere projectupdates op het YouTube-kanaal Microsoft HoloLens.

Over de auteurs

Afbeelding van Karim Luccin aan zijn bureau Karim Luccin is een software-engineer en liefhebber van visuele elementen. Hij was grafisch ingenieur voor Galaxy Explorer.
Foto van art lead Andy Zibits Andy Zibits is een Art Lead en ruimteliefhebber die het 3D-modelleringsteam voor Galaxy Explorer beheerde en vocht voor nog meer deeltjes.

Zie ook