100.000 Sterne verdienen

Michael Chang
Michael Chang

Hallo! Mein Name ist Michael Chang und ich arbeite für das Data Arts-Team bei Google. Vor Kurzem haben wir 100.000 Sterne durchgeführt, ein Chrome-Experiment zur Visualisierung von Sternen in der Nähe. Das Projekt wurde mit THREE.js und CSS3D erstellt. In dieser Fallstudie werde ich den Entdeckungsprozess skizzieren, einige Programmiertechniken vorstellen und mit einigen Gedanken für zukünftige Verbesserungen abschließen.

Die hier behandelten Themen sind ziemlich breit gefasst und erfordern Kenntnisse von THREE.js. Wir hoffen jedoch, dass Sie dies als technische Nachbesprechung nutzen können. Über die Schaltfläche „Inhaltsverzeichnis“ auf der rechten Seite können Sie zu einem Bereich springen, der Sie interessiert. Zuerst zeige ich den Rendering-Teil des Projekts, dann die Shader-Verwaltung und schließlich, wie CSS-Textlabels in Kombination mit WebGL verwendet werden.

100.000 Sterne, ein Chrome-Experiment des Data Arts-Teams
100.000 Sterne nutzen THREE.js,um Sterne in der Nähe der Milchstraße zu visualisieren.

Den Weltraum entdecken

Kurz nach der Fertigstellung von Small Arms Globe experimentierte ich mit einer THREE.js-Partikeldemo mit Schärfentiefe. Mir ist aufgefallen, dass ich den interpretierten „Maßstab“ der Szene ändern konnte, indem ich die Intensität des angewendeten Effekts anpasste. Als der Tiefenschärfeeffekt sehr extrem war, wurden entfernte Objekte sehr verschwommen, ähnlich wie bei der Tilt-Shift-Fotografie, die den Anschein erweckt, eine mikroskopische Szene zu betrachten. Umgekehrt erschien das Reduzieren des Effekts so, als würden Sie in den Weltraum blicken.

Ich habe mich auf die Suche nach Daten gemacht, mit denen ich Partikelpositionen einschleusen könnte. Ein Pfad, der mich zur HYG-Datenbank von astronexus.com führt, einer Zusammenstellung der drei Datenquellen Hipparcos, Yale Bright Star Catalog und Gliese/Jahreiss-Katalog sowie vorberechnete kartesische Koordinaten aus xyz. Los gehts!

Sterndaten darstellen
Der erste Schritt besteht darin, jeden Stern im Katalog als einzelnen Partikel darzustellen.
Die benannten Sterne.
Einige Sterne im Katalog tragen Eigennamen, die hier gekennzeichnet sind.

Es dauerte etwa eine Stunde, bis die Sterndaten im 3D-Raum platziert wurden. Das Dataset enthält genau 119.617 Sterne,sodass es bei einer modernen GPU kein Problem ist, jeden Stern mit einem Partikel darzustellen. Es gibt auch 87 einzeln identifizierte Sterne. Deshalb habe ich mit derselben Technik, die ich in Small Arms Globe beschrieben habe, ein CSS-Markierungs-Overlay erstellt.

In dieser Zeit hatte ich gerade die Serie Mass Effect beendet. Der Spieler wird eingeladen, die Galaxie zu erforschen, verschiedene Planeten zu scannen und mehr über die fiktive, von Wikipedia klingende Geschichte zu erfahren: welche Spezies auf dem Planeten gedeihen, ihre geologische Geschichte usw.

Wenn man die Fülle der realen Daten zu Sternen kennt, kann man vorsichtshalber auch reale Informationen über die Galaxie auf die gleiche Weise präsentieren. Das Ziel dieses Projekts ist es, diese Daten zum Leben zu erwecken, den Betrachter die Galaxie à la Mass Effect zu erforschen, mehr über Sterne und ihre Verteilung zu erfahren und hoffentlich ein Gefühl von Ehrfurcht und Wunder des Weltraums zu wecken. Geschafft!

Ich sollte den Rest dieser Fallstudie wahrscheinlich folgendermaßen beginnen: Ich bin keineswegs Astronom, sondern die Arbeit der Amateurforschung, gestützt durch Ratschläge externer Experten. Dieses Projekt sollte definitiv als künstlerische Interpretation des Raums verstanden werden.

Eine Galaxie erschaffen

Mein Plan war es, ein Modell der Galaxie zu erstellen, das die Sterndaten in einen Kontext stellt und hoffentlich einen großartigen Blick auf unseren Ort in der Milchstraße bietet.

Ein früher Prototyp der Galaxie.
Ein früher Prototyp des Milchstraßen-Partikelsystems.

Zur Erzeugung der Milchstraße sprang ich 100.000 Partikel und platzierte sie in einer Spirale, indem ich die Art und Weise nachahmte,wie galaktische Arme gebildet werden. Die Besonderheiten der Spiralenbildung machten mir keine allzu großen Sorgen, denn dies wäre eher ein Darstellungsmodell als ein mathematisches Modell. Ich habe aber versucht, die Anzahl der Spiralarme mehr oder weniger zu korrigieren und in die richtige Richtung zu drehen.

In späteren Versionen des Milchstraße-Modells habe ich die Verwendung von Partikeln zugunsten eines planaren Bilds einer Galaxie abgeschwächt, das die Partikel begleitet, was dem Ganzen hoffentlich einen eher fotografischen Look verliehen hat. Das eigentliche Bild zeigt die Spiralgalaxie NGC 1232, die etwa 70 Millionen Lichtjahre von uns entfernt ist. Die Bilder wurden so verändert, dass sie wie die Milchstraße aussehen.

Den Maßstab der Galaxie ermitteln
Jede GL-Einheit ist ein Lichtjahr. In diesem Fall ist die Kugel 110.000 Lichtjahre breit und umfasst das Partikelsystem.

Ich beschloss schon früh, eine GL-Einheit, im Grunde ein Pixel in 3D, für ein Lichtjahr darzustellen – eine Konvention, bei der alle visualisierten Elemente vereinheitlicht werden. Leider gab es später erhebliche Präzisionsprobleme.

Eine weitere Konvention, die ich beschloss, die gesamte Szene zu drehen, anstatt die Kamera zu bewegen, war das auch bei einigen anderen Projekten. Ein Vorteil besteht darin, dass alles auf einer "Turntable" platziert wird, sodass das betreffende Objekt durch Ziehen mit der Maus nach links und rechts gedreht wird. Beim Heranzoomen wird jedoch nur camera.position.z geändert.

Das Sichtfeld der Kamera ist ebenfalls dynamisch. Wenn sich eine Person nach außen zieht, vergrößert sich das Sichtfeld und nimmt immer mehr von der Galaxie ein. Das Gegenteil gilt, wenn Sie sich zu einem Stern hineinbewegen, wird das Sichtfeld verkleinert. Auf diese Weise kann die Kamera Dinge erfassen, die (im Vergleich zur Galaxie) unendlich klein sind, indem sie das Sichtfeld auf eine Art götterähnliches Lupensymbol herunterdrücken, ohne Probleme durch Beschneidung auf der Fläche zu lösen.

Verschiedene Darstellungen einer Galaxie.
(oben) Frühe Teilchengalaxie. (unten) Partikel in Verbindung mit einer Bildebene.

Von hier aus konnte ich die Sonne in einer bestimmten Anzahl von Einheiten vom galaktischen Kern „platzieren“. Außerdem konnte ich die relative Größe des Sonnensystems visualisieren, indem ich den Radius des Kuiper-Klippens abbildete. Schließlich entschied ich mich dafür, stattdessen die Oort Cloud zu visualisieren. Innerhalb dieses Sonnensystems konnte ich auch eine vereinfachte Umlaufbahn der Erde darstellen und den tatsächlichen Radius der Sonne im Vergleich darstellen.

Das Sonnensystem.
Die Sonne, die von Planeten umkreist wird, und eine Kuiperkugel, die den Kuipergürtel darstellt.

Die Sonne war schwer darzustellen. Ich musste so viele Echtzeit-Grafiktechniken betrügen, wie ich wusste. Die Sonnenoberfläche besteht aus heißem Plasmaschaum, das pulsieren und sich im Laufe der Zeit verändern muss. Dies wurde anhand einer Bitmaptextur eines Infrarotbilds der Sonnenoberfläche simuliert. Der Oberflächen-Shader führt eine Farbsuche basierend auf der Graustufe dieser Textur durch und führt einen Suchvorgang in einer separaten Farbrampe durch. Wenn sich dieses Loch im Laufe der Zeit verändert, entsteht diese lavaähnliche Verzerrung.

Für die Sonnenkorona wurde eine ähnliche Technik verwendet, mit der Ausnahme, dass es sich um eine flache Sprite-Karte handelt, die mit https://github.com/mrdoob/three.js/blob/master/src/extras/core/Gyroscope.js immer in die Kamera gerichtet ist.

Renderinglösung
Frühversion der Sonne.

Die Leuchtfackeln wurden mithilfe von Scheitel- und Fragment-Shadern erstellt, die auf einen Torus angewendet wurden und sich genau um den Rand der Sonnenoberfläche drehen. Der Vertex-Shader hat eine Rauschfunktion, durch die er Blob-ähnliche gewebt wird.

An diesem Punkt traten aufgrund der GL-Genauigkeit Probleme beim Z-Fighting auf. Alle Variablen für die Precision waren in THREE.js vordefiniert, sodass ich sie ohne großen Aufwand nicht realistischerweise erhöhen konnte. Die Genauigkeitsprobleme waren in der Nähe des Ursprungs nicht so schlimm. Doch als ich anfing, andere Sternsysteme zu modellieren, wurde es zu einem Problem.

Sternmodell.
Der Code zum Rendern der Sonne wurde später verallgemeinert, um andere Sterne zu rendern.

Es gab ein paar Hacks, die ich genutzt habe, um das Z-Fighting zu verringern. Material.polygonoffset von THREE ist eine Eigenschaft, mit der Polygone an einer anderen wahrgenommenen Position gerendert werden können, soweit ich dies verstehe. Dadurch wurde die Koronaebene immer wieder auf der Sonnenoberfläche gerendert. Darunter wurde ein Sonnenschein dargestellt, der scharfe Lichtstrahlen erzeugt, die sich von der Kugel wegbewegen.

Ein weiteres Problem im Hinblick auf die Genauigkeit bestand darin, dass die Sternmodelle beim Heranzoomen zu wackeln begannen. Um dieses Problem zu beheben, musste ich die Szenendrehung „ausgleichen“ und das Sternmodell und die Umgebungskarte separat drehen, um den Eindruck zu erwecken, Sie würden den Stern umkreisen.

Lensflare wird erstellt

Starke Leistung bringt große Verantwortung mit sich.
Große Leistungsfähigkeit birgt große Verantwortung.

Raumvisualisierungen sind die Orte, an denen ich das Gefühl habe, mit übermäßiger Verwendung von Lensflare davonzukommen. THREE.LensFlare dient genau diesem Zweck. Alles, was ich tun musste, war ein anamorphes Sechseck und eine Prise JJ Abrams. Das folgende Snippet zeigt, wie sie in Ihrer Szene erstellt werden.

// This function returns a lesnflare THREE object to be .add()ed to the scene graph
function addLensFlare(x,y,z, size, overrideImage){
var flareColor = new THREE.Color( 0xffffff );

lensFlare = new THREE.LensFlare( overrideImage, 700, 0.0, THREE.AdditiveBlending, flareColor );

// we're going to be using multiple sub-lens-flare artifacts, each with a different size
lensFlare.add( textureFlare1, 4096, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );

// and run each through a function below
lensFlare.customUpdateCallback = lensFlareUpdateCallback;

lensFlare.position = new THREE.Vector3(x,y,z);
lensFlare.size = size ? size : 16000 ;
return lensFlare;
}

// this function will operate over each lensflare artifact, moving them around the screen
function lensFlareUpdateCallback( object ) {
var f, fl = this.lensFlares.length;
var flare;
var vecX = -this.positionScreen.x _ 2;
var vecY = -this.positionScreen.y _ 2;
var size = object.size ? object.size : 16000;

var camDistance = camera.position.length();

for( f = 0; f < fl; f ++ ) {
flare = this.lensFlares[ f ];

flare.x = this.positionScreen.x + vecX * flare.distance;
flare.y = this.positionScreen.y + vecY * flare.distance;

flare.scale = size / camDistance;
flare.rotation = 0;

}
}

Eine einfache Möglichkeit, Texturen zu scrollen

Inspiriert von Homeworld.
Eine kartesische Ebene zur räumlichen Ausrichtung im Weltraum

Für die „räumliche Ausrichtungsebene“ wurde eine gigantische DREI.CylinderGeometry() erstellt und auf die Sonne zentriert. Um die nach außen zu fächernde „Lichtwelle“ zu erzeugen, habe ich den Textur-Offset im Laufe der Zeit wie folgt modifiziert:

mesh.material.map.needsUpdate = true;
mesh.material.map.onUpdate = function(){
this.offset.y -= 0.001;
this.needsUpdate = true;
}

map ist die Textur des Materials, das eine onUpdate-Funktion erhält, die Sie überschreiben können. Wenn das Offset festgelegt wird, wird die Textur entlang dieser Achse „gescrollt“. Spamming needUpdate = true würde eine Schleife erzwingen.

Farbrampen verwenden

Jeder Stern hat basierend auf einem „Farbindex“, den Astronomen ihm zugewiesen haben, eine andere Farbe. Rote Sterne sind in der Regel kühler und blaue/lila Sterne sind heißer. Bei diesem Farbverlauf ist ein Streifen in Weiß und einem orangefarbenen Zwischenton zu sehen.

Beim Rendern der Sterne wollte ich jedem Partikel basierend auf diesen Daten eine eigene Farbe geben. Dazu wurden „Attribute“ für das auf die Partikel angewendete Shader-Material verwendet.

var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: datastarUniforms,
attributes: datastarAttributes,
/_ ... etc _/
});
var datastarAttributes = {
size: { type: 'f', value: [] },
colorIndex: { type: 'f', value: [] },
};

Durch das Ausfüllen des Arrays colorIndex würde jedem Partikel seine eigene Farbe im Shader zugewiesen werden. Normalerweise würde man die Farbe vec3 übergeben, aber in diesem Fall übergebe ich einen Float für den abschließenden Farb-Rand-Lookup.

Farbabstufung.
Eine Farbrampe, mit der die sichtbare Farbe aus dem Farbindex eines Sterns nachgeschlagen wird.

Die Farbrampe sah so aus, aber ich musste über JavaScript auf die Bitmap-Farbdaten zugreifen. Dazu lade ich zuerst das Bild in DOM, zeichne es in ein Canvas-Element und greife dann auf die Canvas-Bitmap zu.

// make a blank canvas, sized to the image, in this case gradientImage is a dom image element
gradientCanvas = document.createElement('canvas');
gradientCanvas.width = gradientImage.width;
gradientCanvas.height = gradientImage.height;

// draw the image
gradientCanvas.getContext('2d').drawImage( gradientImage, 0, 0, gradientImage.width, gradientImage.height );

// a function to grab the pixel color based on a normalized percentage value
gradientCanvas.getColor = function( percentage ){
return this.getContext('2d').getImageData(percentage \* gradientImage.width,0, 1, 1).data;
}

Auf dieselbe Weise werden dann einzelne Sterne in der Sternmodell-Ansicht eingefärbt.

Meine Augen!
Mit derselben Technik wird eine Farbsuche für die Spektralklasse eines Sterns durchgeführt.

Shader-Wrangling

Im Verlauf des Projekts habe ich festgestellt, dass ich immer mehr Shader schreiben musste, um alle visuellen Effekte zu erzielen. Ich habe zu diesem Zweck einen benutzerdefinierten Shader Loader geschrieben, da ich es leid war, Shader in der Datei „index.html“ live zu haben.

// list of shaders we'll load
var shaderList = ['shaders/starsurface', 'shaders/starhalo', 'shaders/starflare', 'shaders/galacticstars', /*...etc...*/];

// a small util to pre-fetch all shaders and put them in a data structure (replacing the list above)
function loadShaders( list, callback ){
var shaders = {};

var expectedFiles = list.length \* 2;
var loadedFiles = 0;

function makeCallback( name, type ){
return function(data){
if( shaders[name] === undefined ){
shaders[name] = {};
}

    shaders[name][type] = data;

    //  check if done
    loadedFiles++;
    if( loadedFiles == expectedFiles ){
    callback( shaders );
    }

};

}

for( var i=0; i<list.length; i++ ){
var vertexShaderFile = list[i] + '.vsh';
var fragmentShaderFile = list[i] + '.fsh';

//  find the filename, use it as the identifier
var splitted = list[i].split('/');
var shaderName = splitted[splitted.length-1];
$(document).load( vertexShaderFile, makeCallback(shaderName, 'vertex') );
$(document).load( fragmentShaderFile,  makeCallback(shaderName, 'fragment') );

}
}

Die Funktion „loadShaders()“ verwendet eine Liste von Shader-Dateinamen (in Erwartung von .fsh für Fragment und .vsh für Vertex-Shader), versucht, ihre Daten zu laden, und ersetzt dann nur die Liste durch Objekte. Das Endergebnis ist Ihre THREE.js-Uniformen, an die Sie Shader so übergeben können:

var galacticShaderMaterial = new THREE.ShaderMaterial( {
vertexShader: shaderList.galacticstars.vertex,
fragmentShader: shaderList.galacticstars.fragment,
/_..._/
});

Ich hätte wahrscheinlich "need.js" verwenden können, obwohl hierfür ein gewisser Code erforderlich gewesen wäre, um den Code neu zusammenzustellen. Diese Lösung ist zwar viel einfacher, könnte aber meiner Meinung nach verbessert werden, vielleicht sogar als THREE.js-Erweiterung. Wenn Sie Verbesserungsvorschläge oder Vorschläge haben, lassen Sie es mich wissen.

CSS-Textlabels zusätzlich zu THREE.js

Bei unserem letzten Projekt, Small Arms Globe, habe ich versucht, Textlabels über einer THREE.js-Szene einzublenden. Die von mir verwendete Methode berechnet die absolute Modellposition an der Stelle, an der der Text erscheinen soll, löst dann die Bildschirmposition mit THREE.Projector() auf und verwendet schließlich CSS "top" und "left", um die CSS-Elemente an der gewünschten Position zu platzieren.

In frühen Iterationen dieses Projekts wurde dieselbe Technik verwendet, aber ich würde gerne diese andere von Luis Cruz beschriebene Methode ausprobieren.

Die Grundidee: Gleicht die CSS3D-Matrixtransformation der Kamera und der Szene von DREE ab, sodass ihr CSS-Elemente in 3D so "platziert" könnt, als befände sie sich oberhalb der Szene von THREE. Hierbei gibt es jedoch Einschränkungen. Beispielsweise können Sie keinen Text unter einem THREE.js-Objekt platzieren. Dies ist immer noch viel schneller als der Versuch, ein Layout mit den CSS-Attributen „top“ (oben) und „left“ (links) durchzuführen.

Textlabels:
Mit CSS3D-Transformationen Textlabels über WebGL platzieren

Die Demo (und den Code in der Ansicht im Quelltext) findest du hier. Allerdings habe ich festgestellt, dass sich die Matrixreihenfolge für THREE.js geändert hat. Die aktualisierte Funktion:

/_ Fixes the difference between WebGL coordinates to CSS coordinates _/
function toCSSMatrix(threeMat4, b) {
var a = threeMat4, f;
if (b) {
f = [
a.elements[0], -a.elements[1], a.elements[2], a.elements[3],
a.elements[4], -a.elements[5], a.elements[6], a.elements[7],
a.elements[8], -a.elements[9], a.elements[10], a.elements[11],
a.elements[12], -a.elements[13], a.elements[14], a.elements[15]
];
} else {
f = [
a.elements[0], a.elements[1], a.elements[2], a.elements[3],
a.elements[4], a.elements[5], a.elements[6], a.elements[7],
a.elements[8], a.elements[9], a.elements[10], a.elements[11],
a.elements[12], a.elements[13], a.elements[14], a.elements[15]
];
}
for (var e in f) {
f[e] = epsilon(f[e]);
}
return "matrix3d(" + f.join(",") + ")";
}

Da alles transformiert wird, ist der Text nicht mehr in die Kamera gerichtet. Die Lösung war die Verwendung von THREE.Gyroscope(), wodurch ein Object3D seine übernommene Ausrichtung von der Szene „verliert“. Diese Technik wird als „Billboard“ bezeichnet und Gyroskop ist dafür perfekt geeignet.

Besonders praktisch ist, dass das gesamte normale DOM und der gesamte CSS-Code weiterhin mitgespielt haben, so als wäre es möglich, den Mauszeiger über ein 3D-Textlabel zu bewegen und es mit Schlagschatten zum Leuchten zu bringen.

Textlabels:
Wenn Textlabels immer in die Kamera gerichtet sind, werden sie an eine DREI.Gyroskop() angebracht.

Beim Heranzoomen stellte ich fest, dass die Skalierung der Typografie Probleme bei der Positionierung verursachte. Ist das möglicherweise auf die Unterschneidung und den Abstand im Text zurückzuführen? Ein weiteres Problem bestand darin, dass der Text beim Heranzoomen verpixelt wurde, da der DOM-Renderer den gerenderten Text als texturiertes Viereck behandelte, was bei Verwendung dieser Methode zu beachten ist. Im Nachhinein hätte ich einfach riesigen Text in Schriftgröße verwenden können. Vielleicht ist das etwas für die zukünftige Erkundung. In diesem Projekt habe ich auch die zuvor beschriebenen CSS-Textlabels "top/left" für sehr kleine Elemente verwendet, die Planeten im Sonnensystem begleiten.

Musikwiedergabe und Schleife

Das Musikstück, das während der „Galaktischen Karte“ von Mass Effect gespielt wurde, stammt von den Bioware-Komponisten Sam Hulick und Jack Wall und hatte die Art von Emotion, die ich den Besuchern wollte. Wir wollten Musik in unser Projekt einbauen, weil wir das Gefühl hatten, dass sie ein wichtiger Teil der Atmosphäre ist und die Ehrlichkeit und die Verwunderung erzeugt, die wir anstreben.

Unser Produzent Valdean Klump hat Sam kontaktiert, der eine Menge „Cutefloor“-Musik von Mass Effect hatte, die er uns freundlicherweise zur Verfügung ließ. Der Titel heißt „In a Strange Land“.

Ich habe das Audio-Tag für die Musikwiedergabe verwendet, aber selbst in Chrome war das Attribut "loop" unzuverlässig. Manchmal scheiterte die Wiedergabe einfach. Am Ende wurde mit diesem Hacking durch zwei Audio-Tags das Ende der Wiedergabe überprüft und der Wechsel zum anderen Tag für die Wiedergabe durchgeführt. Enttäuschend war, dass die Schleife noch nicht immer perfekt gelaufen ist.

var musicA = document.getElementById('bgmusicA');
var musicB = document.getElementById('bgmusicB');
musicA.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playB = function(){
musicB.play();
}
// make it wait 15 seconds before playing again
setTimeout( playB, 15000 );
}, false);

musicB.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playA = function(){
musicA.play();
}
// otherwise the music will drive you insane
setTimeout( playA, 15000 );
}, false);

// okay so there's a bit of code redundancy, I admit it
musicA.play();

Verbesserungspotenzial

Nachdem ich eine Weile mit THREE.js gearbeitet habe, habe ich das Gefühl, dass ich so weit gekommen bin, dass sich meine Daten zu viel mit meinem Code vermischen. Wenn ich beispielsweise Materialien, Texturen und Geometrieanweisungen inline definiert habe, war ich im Wesentlichen „3D-Modellierung mit Code“. Das fühlte sich sehr schlecht an und ist ein Bereich, in dem zukünftige Bemühungen mit THREE.js noch weiter verbessert werden könnten, zum Beispiel die Definition von Materialdaten in einer separaten Datei, die vorzugsweise in einem bestimmten Kontext sichtbar und optimiert werden kann und wieder in das Hauptprojekt eingebracht werden kann.

Unser Kollege Ray McClure hat auch einige Zeit damit verbracht, einige generative „Weltraumgeräusche“ zu erzeugen, die ausgeschnitten werden mussten, weil das Web Audio API instabil war und Chrome gelegentlich abstürzte. Schade, aber das hat uns definitiv zum Nachdenken angeregt, um bei zukünftigen Projekten mehr über den Sound zu erfahren. Zum Zeitpunkt der Abfassung dieses Texts werde ich darüber informiert, dass das Web Audio API gepatcht wurde. Es ist also möglich, dass es jetzt funktioniert und wir in Zukunft darauf achten sollten.

Typografische Elemente in Kombination mit WebGL sind nach wie vor eine Herausforderung und ich bin mir nicht ganz sicher, ob wir hier richtig vorgehen. Es fühlt sich immer noch wie ein Hack an. Vielleicht könnten zukünftige Versionen von THREE mit dem aufstrebenden CSS Renderer dazu verwendet werden, diese beiden Welten besser zu verknüpfen.

Guthaben

Danke an Aaron Koblin, dass ich mit diesem Projekt in die Stadt gehen durfte. Jono Brandel für das hervorragende Design und die Implementierung der Benutzeroberfläche, die Schriftbearbeitung und die Tourimplementierung. Valdean Klump, weil er dem Projekt einen Namen und den gesamten Text gegeben hat. Sabah Ahmed für das Klärung der unzähligen Nutzungsrechte für die Daten- und Bildquellen. Clem Wright für die Kontaktaufnahme mit den richtigen Personen zur Veröffentlichung. Doug Fritz für technische Exzellenz. George Brower hat mir JS und CSS beigebracht. Und natürlich Mr. Doob für DREE.js.

Verweise