1 Punkte von GN⁺ 2024-06-06 | 1 Kommentare | Auf WhatsApp teilen
  • Monde und Gasplaneten für Himmelhintergründe können Oberfläche, Rotation und Atmosphäre allein mit einer kreisförmigen Scheibe und einem Pixel-Shader darstellen, ohne echtes Kugel-Mesh
  • Eine klassische UV-Sphere macht 2×1-Textur-Mapping einfach, bringt aber zugleich kantige Silhouetten, Verluste beim Mipmap-Sampling, Wiederholungsmuster, Seams, Verzerrungen an den Polen und Grenzen beim atmosphärischen Halo mit sich
  • Beim Scheibenansatz wird aus den zentralen UVs die Position auf der Kugeloberfläche rekonstruiert; anschließend erzeugen sphärische UVs auf Basis von asin, eine Rotationsmatrix und Spin durch Verschieben der u-Koordinate eine kugelartig wirkende Oberfläche pro Pixel
  • Das Shading beginnt mit Lambert-Beleuchtung, bei der die Oberflächenposition als Normal verwendet wird; für felsige Oberflächen kommen TBN-Matrix und Normal Map hinzu, während die Atmosphäre über Halo-Helligkeit und kanalweise Absorption angenähert wird
  • Statische Substance-Designer-Texturen und dynamische Render-Texturen aus Unreal lassen sich auf dieselbe Weise behandeln, wodurch sich mit kleineren dynamischen Texturen und angepasstem Tiling die Performance-Belastung reduzieren lässt

Visuelles Ziel der Skybox-Planeten

  • Die Skybox des Projekts konzentriert sich darauf, mit einem animierten Mond und einem Gasplaneten einen außerirdisch wirkenden Himmel zu erzeugen
  • Beide Planeten rotieren, und der Gasplanet enthält außerdem bewegte atmosphärische Strömungen
    • Die tatsächliche Bewegung ist subtil, wurde für den visuellen Effekt aber beschleunigt
  • Die Art Direction folgt einem von SF-Kunst der 1970er- und 1980er-Jahre inspirierten semi-realistischen Stil

Eingaben und Einschränkungen für Oberflächentexturen

  • Die Oberfläche des Gasplaneten wird per Pixel-Shader und Render-to-Texture-Verfahren in Echtzeit erzeugt
  • Die Mondtextur entsteht in Substance Designer; durch Ändern der Farbpalette und des Material-Seeds lassen sich verschiedene Looks ausprobieren
  • Der dynamische Gasplanet muss in jedem Frame eine Textur rendern, was hohe Performance-Kosten verursacht
    • Verdoppelt man die Texturauflösung, vervierfachen sich die Rendering-Kosten
  • Gesucht ist eine Lösung, die sowohl mit statischen Substance-Designer-Texturen als auch mit in Unreal dynamisch gerenderten Texturen konsistent funktioniert
  • Eine Cubemap wurde anfangs erwogen, kann gegenüber einer quadratischen Textur aber sechsmal so viele Ressourcen verbrauchen; außerdem unterstützt Substance Designer keine Cubemap-Erzeugung, weshalb sie unpraktisch ist

Grenzen der UV-Sphere

  • Eine UV-Sphere wirkt wie die naheliegende Standardwahl, weil sie sich leicht auf eine rechteckige 2×1-Textur und deren Koordinaten abstimmen lässt
  • Da immer nur eine Seite der Kugel auf einmal sichtbar ist, ließe sich statt einer vollständig einzigartigen rechteckigen Textur auch eine gekachelte quadratische Textur verwenden
  • In der praktischen Anwendung überlagern sich mehrere Probleme
    • Die Silhouette wirkt sichtbar kantig und muss durch mehr Subdivision oder eine Maske im Pixel-Shader kaschiert werden
    • Ein Großteil der Kugeloberfläche wird schräg betrachtet und aus niedrigen Mipmaps gesampelt; bei dynamischen Texturen wird ein erheblicher Teil der pro Frame erzeugten Pixel für Bereiche verwendet, die kaum sichtbar sind
    • Tiling muss in ganzzahligen Vielfachen erhöht werden, damit sich das Gesamtmuster fortsetzt; schon eine Verdopplung macht Wiederholungen auffällig
    • Fractional Tiling erzeugt einen Seam, der vom Nordpol bis zum Südpol verläuft
    • An den Polen entstehen Texture Pinching und Verzerrungen durch Dreiecks-Mapping, ähnlich den wackelnden Texturverzerrungen der PSX
    • Es lässt sich nur die Oberfläche zeichnen; für einen atmospheric halo ist ein separates Modell nötig
  • Eine UV-Sphere kann weiterhin für Planetenmodelle genutzt werden, erfordert für Skybox-Zwecke aber viele Korrekturen und Hacks

Ansatz mit kreisförmiger Scheibe und Pixel-Shader

  • Skybox-Planeten werden aus der Ferne von einer festen Position aus betrachtet, daher ist ein komplexes 3D-Kugel-Mesh nicht zwingend nötig
  • Stattdessen wird eine einfache gefüllte kreisförmige Polygon-Scheibe verwendet, während der Pixel-Shader das Textur-Mapping übernimmt
  • Im selben Mesh kann auch der atmospheric halo gezeichnet werden
  • Der UV-Ursprung der Scheibe muss exakt im Zentrum liegen
    • Betrachtet man den Planetenradius als 1, müssen die UV-Koordinaten über diesen Wert hinausreichen, um Platz für die Atmosphäre zu schaffen

Rekonstruktion der Kugeloberfläche

  • Das Koordinatensystem ist ein zu DirectX konsistentes left-handed, Y-up-System
    • Auch Unreal Engine ist left-handed, verwendet aber Z als Up-Richtung; daher müssen Ausrichtung und Normal-Map-Format geprüft werden
  • Die Oberflächengleichung bildet die 2D-Position in der Scheibenebene auf eine 3D-Position auf der Kugeloberfläche ab
  • Zunächst wird geprüft, ob ein Pixel innerhalb der Kugeloberfläche liegt, indem getestet wird, ob die Länge des UV-Vektors kleiner als der Radius ist
float CircleMask( float2 uv, float radius)
{
    return length(uv) < radius? 1.0: 0.0;
}
  • Dieser Ansatz funktioniert nur korrekt, wenn die UV-Koordinaten am Mesh-Zentrum ausgerichtet sind
  • Die lokale Position der Kugeloberfläche übernimmt x und y aus den UVs unverändert und rekonstruiert nur z
float3 ReconstructSurface(float2 uv)
{
    float zSquared = 1.0 - dot(uv, uv);
    float z = sqrt(zSquared);
    return float3(uv, z);
}

Erzeugung sphärischer UVs

  • Das Mapping einer quadratischen Textur auf die Kugeloberfläche lässt sich in drei Schritte aufteilen
    • Auf einen Zylinder wickeln
    • Dasselbe entlang der y-Achse wiederholen
    • Das Ergebnis in eine Kreisform verformen
  • Die x-Texturkoordinate ist proportional zum Winkel, der den Zylinder umspannt; dazu wird arcsine(x) berechnet und anschließend [-π, π] wieder auf [0, 1] gemappt
  • Mithilfe der lokalen Breite der Kugel, der Generatrix, werden die Koordinaten an den Polen zusammengezogen
float2 GenerateSphericalUV(float3 position)
{
    float width = sqrt(1.0 - position.y * position.y);
    float generatrixX = position.x / width * sign(position.z);
    float2 generatrix = float2(generatrixX, position.y);
    float2 uv = asin(generatrix) / 3.14159 + float2(0.5, 0.5);
    return float2(uv);
}

Achsneigung und Rotation

  • Für eine natürlichere Planetenkomposition wird die Planetenachse per Pitch und Roll geneigt
    • Planeten in SF-Kunst sind meist geneigt, wodurch die Komposition dynamischer wirkt und polare Ice Caps sichtbar werden können
  • Yaw entspricht der Rotationsbewegung und wird in einem separaten Schritt behandelt, um Seam-Probleme zu vermeiden
  • Bei Mesh-Kugeln wird der Planet üblicherweise per Matrix transformiert; beim Scheibenansatz definiert aber jedes Pixel seine Oberflächenposition, sodass eine einfache 3×3-Matrix nur für die Rotation genügt
float3x3 CreateRotationMatrix(float pitch, float roll) {
    float cosPitch = cos(pitch);
    float sinPitch = sin(pitch);
    float cosRoll = cos(roll);
    float sinRoll = sin(roll);

    return (float3x3)(
        cosRoll, -sinRoll * cosPitch, sinRoll * sinPitch,
        sinRoll, cosRoll * cosPitch, -cosRoll * sinPitch,
        0.0, sinPitch, cosPitch
    );
}
  • Der Material Editor von Unreal unterstützt Matrizen nicht als Datentyp, es gibt aber Workarounds

Behandlung von Scale, Seam und Spin

  • Ändert man den Textur-Scale, wird der UV-Seam sichtbar, der ursprünglich an den Texturrändern verborgen war
  • Der Seam lässt sich nicht völlig einfach entfernen, wirkt aber weniger auffällig, wenn man ihn auf die Rückseite verschiebt
  • Die Kugel wird anhand des Positionsvorzeichens in Quadranten geteilt; auf den Back-left- und Back-right-Quadranten wird ein Offset in u-Richtung angewendet
float2 GenerateSphericalUV(float3 position, float spin, float scale)
{
    float leftRightSign = sign(position.x);
    float frontBackSign = sign(position.z);
    float width = sqrt(1.0 - position.y * position.y);
    float generatrixX = position.x / width * frontBackSign;
    float2 generatrix = float2 (generatrixX, position.y);
    float2 uv = asin(generatrix) / 3.14159 + float2 (spin, 0.5);
    if(frontBackSign < 0.0)
    {
        uv = float2 (uv.x + 1.0 * leftRightSign, uv.y);
    }
    return float2 (uv / scale);
}
  • Der Seam verbindet weiterhin Nord- und Südpol, liegt nun aber auf der Rückseite und ist deutlich weniger sichtbar
  • Die Rotation wird nicht durch Drehen der Kugel selbst umgesetzt, sondern durch Verschieben der Oberflächentextur entlang der u-Koordinate
float2 GenerateSphericalUV(float3 position, float scale, float spin)
{
    float width = sqrt(1.0 - position.y * position.y);
    float generatrixX = position.x / width * sign(position.z);
    float2 generatrix = float2(generatrixX, position.y);
    float2 uv = asin(generatrix) / 3.14159 * scale + float2(0.5 + spin, 0.5);
    return float2(uv);
}
float2 sphericalVU = GenerateSphericalUV(position, scale, time*speed)

Oberflächen-Shading und Normal Map

  • Damit ein Planet rund wirkt, ist Shading entscheidend
  • Anders als beim üblichen Engine-Shading hat ein Planet zwei Layer, Oberfläche und Atmosphäre; Beleuchtung und Blending beider Layer müssen daher manuell definiert werden
  • Bei einem Radius von 1 kann die Oberflächenposition vor der Rotation direkt als Surface Normal verwendet werden
  • Die Beleuchtung nutzt ein einfaches Lambert-Modell
    • Ziel ist keine vollständige Realitätstreue, sondern eine stilisierte SF-Cover-Ästhetik, inspiriert von NASA-Fotos
float LambertianLight(float3 normal, float3 lightDirection) {
    float NdotL = max(dot(normal, lightDirection), 0.0);
    return NdotL;
}
  • Für glatte Oberflächen wie bei einem Gas Giant kann eine einfache Normal ausreichen
  • Bei felsigen Planeten ist es sinnvoll, Gelände wie Gebirge, Grate und Krater mit einer Normal Map zu simulieren
  • Für Normal Maps wird eine aus Tangent, Bitangent und Normal bestehende TBN-Matrix benötigt
    • Auf der Kugel muss diese Berechnung pro Pixel erfolgen
    • Die TBN-Matrix wird verwendet, um die Normal der Normal Map in World Space Coordinates zu transformieren

UV-Diskontinuitäten und Mipmap-Artefakte

  • Bei im Pixel-Shader erzeugten UVs können Diskontinuitäten auftreten
  • Der rückseitige Seam sollte bei der horizontalen UV-Komponente an der Grenze vollständig von 0.0 nach 1.0 übergehen; tatsächlich entstehen entlang des Seams jedoch blockige Artefakte
  • Diese Linie entspricht dem DDX der UVs
    • DDX und DDY messen die Änderungsrate der UVs entlang der Screen-Space-Achsen
    • Der Textur-Sampler nutzt diese Werte, um die zu verwendende Mipmap zu bestimmen
    • Niedrige UV-Derivatives entsprechen hochauflösenden Mipmaps, hohe Derivatives niedrigauflösenden Mipmaps
  • Springen die Werte links und rechts des Seams abrupt, wird DDX groß, und es wird aus der niedrigsten Mipmap gesampelt, was wie eine graue Linie aussehen kann
  • Beim Planeten soll dies durch einen Polar Patch verdeckt werden und ist daher kein großes Problem; falls es auffällt, müssen DDX und DDY am Seam manuell korrigiert und an den Sampler übergeben werden
  • Der beispielhafte Fix dient nur der Erklärung; die tatsächlichen Werte müssen passend zum Mapping selbst hergeleitet werden

Polar Patch und Korrektur des Pinching

  • Verzerrungen an den Polen werden mit einem bewährten Polar Patch überdeckt
  • Bei Planeten oder Monden sind die Polargebiete häufig vereist und visuell von anderen Regionen abgesetzt; ein separater Textur-Patch verbirgt das Problem und sorgt zugleich für mehr visuelles Interesse
  • Ein Polar Patch kann ergänzt werden, indem er auf eine zur Rotationsachse senkrechte Ebene gemappt und die Ergebnis-Koordinaten rotiert werden
float2 PolarPatchMapping(float3 position, float scale, float spin)
{
    float cosSpin = cos(spin);
    float sinSpin = sin(spin);
    float scale = 0.4;

    float2 uv = float2(position.x, position.z) * scale;
    float2 spinningUV = float2(uv.x * cosSpin - uv.y * sinSpin,
                               uv.x * sinSpin + uv.y * cosSpin);

    return spinningUV;
}
  • Weil die UV-Map nicht in Vertices gespeichert, sondern pro Pixel erzeugt wird, ist die Korrektur des Pinchings an den Polen flexibler
  • Mit einer einfachen Division lässt sich das Seitenverhältnis der Poltextur korrigieren
uv = float2(uv.x, uv.y/pow(width, 1/3));
  • Das korrigierte Ergebnis kann wie ein anderes Artefakt wirken, schließt in realistischen Szenen aber glatter an den Polar Patch an und reduziert visuelle Unstimmigkeiten

Annäherung des atmosphärischen Halos

  • Da die Planetenoberfläche auf der Scheibe nicht die gesamte Fläche einnimmt, kann im freien Raum ein atmospheric halo gezeichnet werden
  • Wird der Halo im selben Shader gezeichnet, lässt er sich natürlich mit der Atmosphäre über dem Planeten verbinden
  • Physically Based Atmospheric Rendering nutzt üblicherweise Rayleigh scattering und Raymarching, wird hier aber nicht verwendet
  • Stattdessen kommt eine Annäherung zum Einsatz, die einem Trick für dynamische Objektbeleuchtung in Quake 1 und 2 ähnelt
    • Das Static Lighting von Quake wurde vorab als Lightmap berechnet
    • Dynamische Monster sampelten die darunterliegende Lightmap, um ihre Farbe anzupassen; das war physikalisch nicht korrekt, ließ sie aber mit der Umgebung harmonieren
  • Auf die gleiche Weise wird die Planetenoberfläche genutzt, um die Beleuchtung des atmosphärischen Halos anzunähern
    • Die Surface Normal vor dem Bump Mapping wird erweitert, um die Halo-Helligkeit zu berechnen
    • Werden die Helligkeiten pro Kanal unterschiedlich remappt, lässt sich die Absorption verschiedener Lichtwellenlängen einfach simulieren
    • Da die Atmosphäre unter flachen Blickwinkeln besser sichtbar ist, wird die Z-Komponente der Surface Normal remappt
    • Für den Halo-Fade-Effekt wird außerdem der Abstand zur Kugeloberfläche berechnet
  • Am Ende werden Oberfläche und Atmosphäre per Alpha Blending zusammengeführt

Endergebnis und Nutzung

  • Es waren mehr Schritte nötig als erwartet, doch das Endergebnis wirkt wie eine vollständige Kugel und unterstützt die gewünschten Textur- und Shader-Manipulationen
  • Mit einem einzigen Substance-Designer-Graph lässt sich die Oberflächentextur erstellen, ohne dass für das Ergebnis zusätzliche Nachbearbeitung nötig ist
  • Auch animierte Texturen lassen sich auf dieselbe Weise behandeln
    • Kleinere Texturgrößen können genutzt werden, um Performance zu sparen
    • Das Tiling kann nach Bedarf angepasst werden
  • Das Ergebnisvideo ist in Video 3 zu sehen

1 Kommentare

 
GN⁺ 2024-06-06
Hacker-News-Kommentare
  • Der Autor scheint Cube Maps etwas zu schnell verworfen zu haben; ich habe sie beim Rendern eines dynamischen Gasplaneten in einem privaten Projekt ausprobiert, und sie waren die einfachste Lösung.
    Mit Cube Maps vervielfacht sich der Speicher nicht auf das Sechsfache; statt einer großen rechteckigen Textur hat man nur sechs kleinere rechteckige Flächen, daher bleibt die gesamte Texturdetailtiefe gleich.
    Der Vorteil ist, dass man sich nicht um die Stauchung an den Polen kümmern muss, und dass man mit einer 3D- oder 4D-Noise-Funktion leicht nahtlose Strömungsfelder für Texturanimationen/-verzerrungen erzeugen kann.
    https://www.junkship.net/News/2016/06/09/jupiter-jazz

    • Um seltsame Nahtartefakte zu vermeiden, könnte es besser sein, zwei stereografische Halbkugeln zu verwenden.
      Jede Halbkugel wird auf eine Scheibe projiziert, aber man kann die Textur bis an den quadratischen Rand füllen und Teile jeder Halbkugel an den Rändern der jeweils anderen Textur überlappen lassen.
      Zwischen Scheibenmitte und Rand gibt es einen Skalierungsunterschied von 1:2, sodass man das als Pixelverschwendung gemessen am Mindestdetail sehen kann, aber da es sich um eine winkeltreue Projektion handelt, wird es viel weniger knifflig, bei starker Perspektive die Zielfarbe eines Pixels zu samplen, und Vorwärts-/Rückwärtsberechnung brauchen pro Projektionspunkt nur eine Division und ein paar Additionen und Multiplikationen, also sogar weniger als die Zentralprojektion einer Cube Map.
      Wenn man eine winkeltreue Methode möchte, die die Skalierungsänderung und die Pixelverschwendung an den Rändern weiter reduziert, ohne konzeptionell zu kompliziert zu werden, kann man zwei leicht überlappende Mercator-Projektionen im rechten Winkel zueinander verwenden, um die Kugel wie zwei Lederstücke eines Baseballs zu bedecken.
      Jedes Stück kann eine rechteckige Textur sein, und es gibt sogar NOAA-Arbeiten, die diesen Ansatz für Gitter von Differentialgleichungen in Wettersimulationen der Erde vorschlagen.
      Die pixeleffizienteste Projektion, die ich kenne, teilt die Kugel in Oktanten eines Oktaeders und bedeckt jeden Oktanten mit einem hexagonalen Pixelraster auf Basis von „spherical area coordinates“.
      Jeder Oktant kann in einem normalen Bild mit quadratischen Pixeln als halbes Quadrat, also als 45-45-90-Rechtwinkeldreieck, dargestellt werden, was zu Ergebnissen wie https://observablehq.com/@jrus/sac-quincuncial führt, und man kann auch Hex-Raster wie https://observablehq.com/@jrus/sphere-resample verwenden.
      Allerdings ist die Detailbehandlung beim Sampling über Grenzen hinweg viel aufwendiger als beim Ansatz mit zwei stereografischen Projektionen, und es können ebenfalls Nahtartefakte entstehen.
    • https://en.wikipedia.org/wiki/Peirce_quincuncial_projection
    • Wenn man die Verzerrung weiter reduzieren möchte, kann man auch die Dreiecksflächen eines Ikosaeders zu zehn Rauten zusammenfassen und jede Raute mit einer eigenen quadratischen Textur belegen.
      Das erfordert mehr Mathematik, lohnt sich aber meist nicht, sofern man die Oberfläche nicht aus anderen Gründen stärker unterteilen will.
    • Für die Wolkenerzeugung in dem Planetenspiel, an dem ich gerade arbeite, könnte ich dieses Curl Noise ausprobieren.
  • Die Texturstauchung an den Polen ist eigentlich nur eine extreme Ausprägung einer Verzerrung, die auf der gesamten Oberfläche vorhanden ist.
    Meist fällt sie nur an den Polen deutlich auf, aber wenn man eine Kugel adaptiv in Dreiecke unterteilt, kann die Verzerrung auch anderswo sichtbar werden, weil sie sich mit dem Wechsel der Unterteilungsstufe verändert.
    Das Problem ist, dass die Kugel in Vierecke aufgeteilt wird und jedes Viereck im UV-Raum als zwei Dreiecke mit gleicher Fläche dargestellt wird, im 3D-Raum aber ein Dreieck kleiner ist als das andere, besonders dasjenige, dessen horizontale Kante näher am Pol liegt.
    Trotzdem werden die UVs innerhalb des Dreiecks linear interpoliert, sodass die Hälfte der Textur gestaucht und die andere Hälfte gedehnt wird.
    An den Polen wird die 3D-Fläche eines Dreiecks tatsächlich zu null, sodass nur die halbe Textur gerendert wird und die Naht zwischen den Dreiecken deutlich sichtbar wird.
    Die richtige Lösung ist, die UV-Koordinaten pro Pixel im Pixel-Shader zu berechnen statt mit linearer Interpolation pro Vertex, und wenn man es richtig macht, lassen sich auch die Pole nahtlos behandeln.

    • Ich habe gerade erst angefangen, mich ein wenig mit Rendering, Shadern und Spieleentwicklung zu beschäftigen; falls es Hinweise, Stichwörter oder Links gibt, nach denen ich weiter suchen kann, würde ich die gern kennen.
    • Würde dieses Problem nicht behoben, wenn man im XYZ-Eintrag W skaliert statt W auf 1 zu setzen?
    • Gibt es Fälle, in denen man eine Kugel mit mehreren Rendering-Polen rendert, um solche Stauchungen zu reduzieren?
      Zum Beispiel so, dass man bei einer Kugel mit einem „wahren Nordpol“ von der Seite aus einen Rendering-Pol nahe diesem Nordpol verwendet und beim Blick aus der Nähe des Nordpols stattdessen ein äquatoriales Rendering bei 0',0’?
  • Ich bekomme Lust, mir Displacement Mapping noch einmal anzusehen.
    Es ist vielleicht kein Ersatz für das Problem, das der Autor lösen will, aber es ist einfacher und ziemlich unterhaltsam.
    Vor etwa 25 Jahren habe ich für SoundJam einen Musikvisualisierer namens „Eclipse“ gebaut; die Eingabe war ein Array von Pegeln im hörbaren Frequenzbereich sowie linke und rechte Kanäle.
    Das Ziel war ein Bild wie eine verfinsterte Sonne mit koronalen Auswürfen, und die Musikdaten wurden zu diesen „Auswürfen“.
    Die Frequenz der Daten bestimmte, an welcher Stelle rund um die Sonnenscheibe sie erschienen.
    Mit der Zeit entfernten sich die Auswürfe von der Sonne und „kühlten ab“, bis sie in Schwarz verschwanden; starke Signale begannen als Weiß und wurden mit abnehmender Intensität gelb, orange, rot und braun.
    Ich musste ein Array von Audiodatenwerten in einem Ringpuffer halten, der groß genug war, um die Zeit bis zum Verschwinden ins Schwarze zu erfassen.
    Die gesamte Anzeige von „Eclipse“ und der Auswürfe war letztlich eine Displacement Map, und ich hatte vorab eine Bitmap berechnet, in der jeder Pixelwert ein Offset in den Audiopuffer war.
    Pixel nahe der Sonnenoberfläche hatten den Offset zu neu eintreffenden Daten, während weiter außen liegende Pixel Offsets zum auslaufenden Ende des Puffers hatten.
    Weil ich kreisförmige, radiale Auswürfe auf kartesische Koordinaten abbilden musste, war etwas Mathematik nötig, um die Displacement-Werte zu erzeugen.
    Die Hauptschleife nahm neue Audio-Werte auf, überschieb die ältesten Werte und lief dann Zeile für Zeile und Spalte für Spalte durch die Displacement Map, holte die entsprechenden Audiodaten, ordnete ihnen Farben aus einer festen Palette zu und schrieb sie in den Ausgabepuffer.
    Es war nicht so spektakulär wie andere Visualisierer, hatte aber eine ruhige Schönheit und spiegelte die Musikdaten ziemlich gut wider.
    Es war anders als spätere Visualisierer, die selbst bei eingefügter Stille nicht stillhalten konnten.

  • Der Artikel ist toll, aber je weiter man nach unten scrollt, desto mehr Shader werden geladen, sodass der Browser auf allem außer einem sehr leistungsstarken Rechner fast einfrieren kann.

    • Was ist mit „sehr leistungsstarkem Rechner“ ungefähr gemeint? Ist so etwas selbst auf der integrierten Grafik eines Laptops aus den letzten 10 Jahren schwer zu rendern?
  • Es ist interessant, wie unterschiedlich Menschen „Realismus“ interpretieren.
    Bei Spielen aus einer Zeit mit viel stärkeren Ressourcenbeschränkungen war dieser Unterschied besonders deutlich, und manche Spiele wirkten allein mit eckiger, pixeliger Grafik und kleiner Farbpalette immersiver als heutige Big-Budget-Spiele.

    the gas giant’s surface texture will be generated in realtime using a pixel
    shader and a render-to-texture approach
    Es ist spannend zu sehen, wie man von Anfang an einen unpraktischen, grenzenlosen Ansatz verfolgt, nach Wegen sucht, Performance-Bottlenecks zu erzeugen, und dann immer tiefer in den Kaninchenbau hinabsteigt.
    Am Ende frage ich mich, ob der Autor des Originalbeitrags das tatsächlich zum Laufen gebracht hat.
    Vielleicht ist das einfach der Unterschied zwischen einem Projekt mit realen Zwängen und einem Hobbyprojekt.
    Man könnte für den Gasplaneten auch eine vorgerenderte animierte Textur verwenden, dann mit dem Rest des Projekts weitermachen und später, wenn alles läuft, im Slartibartfast-Cosplay darauf zurückkommen.

    • Vielleicht waren Spiele mit geringer Wiedergabetreue immersiver, weil sie mehr Raum für die Fantasie ließen.
      Dadurch hat man sich womöglich aktiver darauf eingelassen.
  • Dieser Artikel mit einem ähnlichen Ansatz fällt mir dazu ein: https://bgolus.medium.com/rendering-a-sphere-on-a-quad-13c92...

  • Gibt es in GPUs oder 3D-Bibliotheken keine Funktion, die jede horizontale Zeile eines Kreises abtastet und dabei X,Y der Textur auf eine „3D“-Position auf der Kugel abbildet?
    Eine Kugel ist ein Kreis, und Algorithmen zum Zeichnen eines Kreises sind simpel; dafür eine Million Vertices für Dreiecke zu rotieren und zu projizieren, wirkt wie eine enorme Verschwendung von Ressourcen.
    Man kann horizontal abtasten, eine Zeile nach unten gehen und so alle Zeilen zeichnen.
    Ich erinnere mich, so etwas Ende der 80er gemacht zu haben, um Dinge wie die Vergrößerungslinse in Second Reality vorab zu berechnen.

    • Vermutlich liegt es daran, dass sphärische lineare Interpolation langsamer ist und spezialisiertere Verarbeitung erfordert als lineare Interpolation.
      Die gesamte Rendering-Pipeline ist bereits darauf ausgelegt, lineare Interpolation zu verarbeiten.
      Mit nur einem Shader-Programm und einem einzigen Dreieck lässt sich bereits eine perfekt anti-aliaste Kugel zeichnen.
    • GPUs rasterisieren nicht zeilenweise wie alte Software-Renderer, sondern kennen im Grunde nur Dreiecks-Rasterisierung.
      Wenn man auf der GPU rendert, wirkt der Ansatz aus dem Artikel trotz seiner scheinbaren Komplexität ziemlich vernünftig.
      Es gibt eine hervorragende Artikelreihe über den Aufbau GPU-basierter Rendering-Strukturen: https://fgiesen.wordpress.com/2011/07/09/a-trip-through-the-...
      Teil 6 behandelt die Rasterisierung.
    • Der von GPT gelieferte Code berechnete bei jedem Pixel im Kreis zunächst z, rotierte dann und ermittelte mit atan2 und asin die Texturkoordinaten, um die Farbe zu setzen; beim Ausführen schien es zu funktionieren.
  • 2D-Pixel-Shader-Impostor-Planeten über einem Hintergrund sind bei Szenen mit vielen Planeten, etwa in einem prozeduralen Weltraum, wirklich sehr hilfreich.
    Wegen Speicher und GPU-Bandbreite beschränke ich kugelförmige, würfelbasierte Planeten auf eine einzelne Instanz und zeichne nähere Himmelskörper mit der Hintergrundtechnik.
    Im Weltraum gibt es eine Entfernung etwa zwischen zwei Himmelskörpern, ab der beide als Impostors gerendert werden; danach wird der nächstgelegene Himmelskörper mit einer sphärischen Quadtree-Methode verarbeitet.
    Es ist nicht perfekt, aber die Illusion ist kaum zu beanstanden.
    Und weil alles auf einer Fläche liegt, lassen sich auch leichte physikalische Effekte einer Kameralinse für entfernte Galaxien oder Himmelskörper leichter einbauen.
    Zum Beispiel, dass der Mond beim Aufgehen größer erscheint als er wirklich ist oder dass eine ferne Galaxie durch Gravitation verzerrt aussieht.

  • Diese Seite zu Gasplaneten gefällt mir: https://emildziewanowski.com/flowfields/

  • Die Icosphere lässt sich viel glatter abwickeln, und auch Vertex-Positionen und Elementgrößen sind regelmäßiger.
    Das Abwickeln ist nicht trivial, aber machbar.