1 Punkte von GN⁺ 2 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Der Browser-Shader kombiniert Rayleigh-Streuung, Mie-Streuung und Ozonabsorption, um blauen Himmel sowie Sonnenuntergänge und Sonnenaufgänge in Echtzeit zu rendern.
  • Er akkumuliert die optische Tiefe der Kamerastrahlen und die Transmittanz nach dem Beer's Law und berechnet mit Phasenfunktionen die Streuverteilung abhängig von der Sonnenrichtung.
  • Der Sonnenuntergangseffekt führt an jedem Sample einen separaten Light-March in Sonnenrichtung aus, um zu berücksichtigen, wie viel Sonnenlicht beim Durchqueren der Atmosphäre verloren geht.
  • Ein flacher Himmels-Shader wird durch Depth Buffer und Rekonstruktion der Weltkoordinaten zu einem Post-Processing-Effekt, der sogar atmosphärischen Nebel zwischen Szenenobjekten verarbeitet.
  • Im planetaren Maßstab wird dies mit logarithmischem Depth Buffer, Ray-Sphere-Intersection sowie LUT-basierter Transmittance, Sky-view und Aerial Perspective erweitert.

Ziel und Referenzmaterialien für Atmospheric-Scattering-Shader

  • Ziel ist es, mit einem Browser-Shader Schichten nachzubilden wie auf Fotos eines Sonnenuntergangs im niedrigen Orbit der Raumfähre Endeavour, mit dem Übergang von dunklem Orange, Blau und dem Schwarz des Weltraumhintergrunds in der oberen Erdatmosphäre.
  • Der Implementierungsumfang beginnt bei einer realistischen Sky Dome aus der Kombination von Raymarching, Rayleigh-Streuung, Mie-Streuung und Ozonabsorption und wird auf eine Atmosphärenhülle um einen Planeten sowie LUT-basierte Optimierungen erweitert.
  • Wichtige Referenzen sind Three Geospatial, Sébastien Hillaires A Scalable and Production Ready Sky and Atmosphere Rendering Technique und Atmospheric Scattering (and also just faking it).

Grundmodell des Himmels-Renderings

  • Warum ein einfacher Gradient nicht ausreicht

    • Die Himmelsfarbe ist nicht einfach ein blauer Hintergrund, sondern muss als Ergebnis der Wechselwirkung von Licht mit der Luft und ihren Bestandteilen behandelt werden.
    • Variablen wie Beobachterhöhe, Staubmenge und Tageszeit müssen berücksichtigt werden, und die Berechnung findet in einem Volumen (volume) statt.
  • Sampling der Atmosphärendichte

    • Die Atmosphäre wird wie bei volumetric clouds oder volumetric light per Raymarching gesampelt.
    • Von der Kameraposition aus wird ein Strahl ausgesendet und entlang des transparenten Mediums vorangetrieben; dabei werden die Transmittanz, also das Licht, das die Atmosphäre beim Durchqueren überlebt, und die Streuung, die an jedem Sample zur Kamera hin umgelenkt wird, berechnet.
    • Als Auffrischung zu Raymarching kann Painting with Math: A Gentle Study of Raymarching dienen.
  • Rayleigh-Dichte und optische Tiefe

    • Um die Transmittanz zu bestimmen, muss die Atmosphärendichte, auf die der Strahl trifft, entlang seines Weges akkumuliert werden, um die optische Tiefe (optical depth) zu berechnen.
    • Die Rayleigh-Dichtefunktion gibt an, wie viel „Luft“ sich in der Höhe h befindet, und bildet den Effekt ab, dass die Atmosphäre mit zunehmender Höhe dünner wird.
    • Die Beispielimplementierung verwendet RAYLEIGH_SCALE_HEIGHT = 8.0 km, ATMOSPHERE_HEIGHT = 100.0 km, VIEW_DISTANCE = 200.0 km und PRIMARY_STEPS = 24.
    • rayleighDensity(h) ist exp(-max(h, 0.0) / RAYLEIGH_SCALE_HEIGHT), und in der Schleife wird mit viewOpticalDepth += dR * stepSize akkumuliert.
  • Beer's Law und das Blau des Tageshimmels

    • Aus der optischen Tiefe wird die Transmittanz T an einem bestimmten Punkt berechnet; T=1.0 bedeutet keinen Lichtverlust, T=0.0, dass das Licht vollständig verschwunden ist.
    • Die Transmittanz wird nach dem Beer's Law berechnet; der Beispielcode verwendet vec3 transmittance = exp(-rayleighBeta * viewOpticalDepth).
    • rayleighBeta ist der Rayleigh-Streukoeffizient und wird im Shader als vec3(0.0058, 0.0135, 0.0331) gespeichert.
    • Der Winkel zwischen Sonnenlichtrichtung und Sichtstrahl wird mit einer Rayleigh-Phasenfunktion der Form 3.0 / (16.0 * PI) * (1.0 + mu * mu) modelliert.
    • Wegen des Rayleigh-Streukoeffizienten wird Rot kaum gestreut, Grün etwas stärker und Blau am stärksten, weshalb der Himmel am Tag blau erscheint.
    • Auf einen Strahl pro Pixel erweitert, durchquert der Horizont mehr Atmosphäre und erscheint deshalb wie heller weißer Dunst, während die Farbe mit zunehmender Höhe in ein tieferes, dunkleres Blau übergeht.

Mie-Streuung und Ozonabsorption

  • Effekte, die Rayleigh allein nicht abdeckt

    • Mit Rayleigh-Streuung allein lassen sich bereits brauchbare Ergebnisse erzielen, für einen realistischeren Himmel sind aber zusätzliche atmosphärische Effekte nötig.
    • Mie-Streuung beschreibt die Wechselwirkung von Licht mit größeren Partikeln wie Staub oder Aerosolen und besitzt eine Dichtefunktion sowie eine Phasenfunktion, die die richtungsabhängige Umverteilung beschreibt.
    • Ozonabsorption entfernt bestimmte Wellenlängen des Lichts auf seinem Weg durch die obere Atmosphäre, ohne sie zu streuen.
    • Ozonabsorption vertieft besonders am Horizont, bei Sonnenuntergang, Sonnenaufgang und in der Dämmerung davor und danach die Himmelsfarbe und verschiebt die Farbtöne.
  • Akkumulation von Mie und Ozon

    • In einer Implementierung, die Rayleigh, Mie und Ozon zusammen verwendet, werden die jeweiligen optischen Tiefen in viewODR, viewODM und viewODO akkumuliert.
    • An jedem Sample werden dR = rayleighDensity(h), dM = mieDensity(h) und dO = ozoneDensity(h) berechnet, und tau wird aus der Summe von BETA_R * viewODR, BETA_M_EXT * viewODM und BETA_OZONE_ABS * viewODO gebildet.
    • Die Transmittanz wird mit exp(-tau) berechnet, und in sumR, sumM und sumO werden jeweils Dichte, Transmittanz und stepSize akkumuliert.
    • Die finale Streuung wird in der Form SUN_INTENSITY * (phaseR * BETA_R * sumR + phaseM * BETA_M_SCATTER * sumM + BETA_OZONE_SCATTER * sumO) berechnet.
  • Wichtige Konstanten und ihre Effekte

    • MIE_SCALE_HEIGHT entspricht für Aerosole dem RAYLEIGH_SCALE_HEIGHT; da sich diese Partikel meist in Horizontnähe konzentrieren, wird der Wert kleiner auf 1.2km gesetzt.
    • MIE_BETA_SCATTER steuert, wie stark Partikel Licht zur Kamera hin streuen; da dies meist weitgehend wellenlängenunabhängig ist, wird vec3(0.003) verwendet.
    • MIE_BETA_EXT ist der Mie-Extinktionskoeffizient, der angibt, wie viel Licht aus dem Pfad entfernt wird, und lässt ferne Atmosphäre diesiger erscheinen.
    • MIE_G steuert die Anisotropie; 0.0 bedeutet gleichmäßige Streuung, 1.0 eine stärkere Vorwärtsstreuung.
    • OZONE_BETA_ABS hat die Werte vec3(0.00065, 0.00188, 0.00008) und absorbiert stärker Grün sowie Gelb-Orange, wodurch sich die Himmelsfarbe in Richtung Blau, Rot und Violett verschiebt.
    • Durch die Integration von Mie und Ozon entstehen ein natürlicheres „Sky Blue“ und ein diesiger Lichtkranz um die Sonne; besonders wenn die Sonne nahe am Horizont steht, tritt der Mie-Streuungseffekt deutlicher hervor.

Lichtpfade und Sonnenuntergänge/-aufgänge

  • Grenzen der bisherigen Implementierung

    • Der Sky-Fragment-Shader kann in unterschiedlichen Höhenlagen natürliche Farben rendern und Mie-, Rayleigh- und Ozon-Transmissionsmodelle berücksichtigen
    • Wird die Sonne jedoch nahe an den Horizont verschoben, erscheint ohne Lichtabschwächung oder Sonnenuntergangs-/Sonnenaufgangseffekte nur ein weißlicher, diffuser Lichtschleier
    • Der bisherige Raymarching-Loop berechnete die Lichtabschwächung nur entlang des Sichtstrahls von der Kamera bis zu jedem Sample
    • Es muss jedoch auch berechnet werden, wie stark das Sonnenlicht auf seinem Weg durch die Atmosphäre abgeschwächt wird, bevor es den Sample-Punkt erreicht
  • Verschachtelter Light-March-Loop

    • An jedem Sample-Punkt wird in Richtung der Lichtquelle ein separater verschachtelter Loop ausgeführt, um die Transmission entlang dieses Pfads zu sampeln
    • Ein ähnlicher Ansatz wird auch in real-time cloudscapes und volumetric lighting verwendet
    • lightMarch(float start, float sunY) iteriert LIGHTMARCH_STEPS-mal und akkumuliert dabei odR, odM, odO
    • Zur optischen Tiefe der bisherigen Implementierung viewODR, viewODM, viewODO wird die optische Tiefe in Sonnenrichtung sunOD addiert
    • Das endgültige tau wird aus der Summe von BETA_R * (viewODR + sunOD.x), BETA_M_EXT * (viewODM + sunOD.y) und BETA_OZONE_ABS * (viewODO + sunOD.z) gebildet
    • Mit dieser Implementierung lassen sich Himmel für Sonnenuntergang, Sonnenaufgang, Sonne im Zenit und die dazwischenliegenden Lichtverhältnisse rendern
    • Mit dem Uniform sun angle lässt sich die Veränderung des Himmelblaus im Tagesverlauf erzeugen, und die Mie-Streuung mischt das Licht bei Sonnenuntergang und Sonnenaufgang natürlich mit dem Horizont
    • Steht die Sonne tief, verleiht Ozon dem Himmel einen violetten Ton

Erweiterung auf eine planetare Atmosphäre

  • Vom flachen Hintergrund zum Post-Processing-Effekt

    • Der zuvor erstellte Shader liefert zwar einen guten Himmelshintergrund, kommt in einer React-Three-Fiber-Szene aber eher einem flachen Hintergrund gleich
    • Der nächste Schritt ist, ihn in einen Post-Processing-Effekt umzuwandeln, der das Szenentiefen berücksichtigt und als Volumen sowie als Atmosphärenhülle um das Planeten-Mesh rendert
    • Dafür werden aus den screenUV-Koordinaten Weltkoordinaten rekonstruiert und der Depth-Buffer der Szene in das Raymarching einbezogen
  • Rekonstruktion des Weltraums und 3D-Strahlen

    • Um atmosphärische Streuung auf die Szene anzuwenden, reicht es nicht, nur den Himmel zu zeichnen; vielmehr muss der Raum zwischen Kamera und den auf dem Bildschirm gerenderten Objekten gefüllt werden
    • Benötigt werden der Depth-Buffer der Szene sowie projectionMatrixInverse, matrixWorld und position der Kamera; diese Werte werden als Uniforms an den Post-Processing-Effekt übergeben
    • getWorldPosition(vec2 uv, float depth) erzeugt clipZ mit depth * 2.0 - 1.0, erstellt NDC-Koordinaten mit uv * 2.0 - 1.0 und wendet anschließend projectionMatrixInverse und viewMatrixInverse an
    • Dasselbe Verfahren wird auch im Volumetric-Lighting-Post-Processing-Effekt von On Shaping Light verwendet
    • Nachdem die worldPosition des aktuellen Pixels bestimmt wurde, wird rayOrigin als Kameraposition gesetzt und rayDir als normalize(worldPosition - rayOrigin) berechnet, um entlang eines pixelweisen 3D-Strahls auf dem Bildschirm voranzuschreiten
  • Raymarch-Bereich mit dem Depth-Buffer anpassen

    • Um die Szenengeometrie zu berücksichtigen, sollte der Raymarch-Bereich des aktuellen Strahls statt über eine feste stepSize durch den Depth-Buffer bestimmt werden
    • Mit sceneDepth = depthToRayDistance(uv, depth) wird die Szenentiefe entlang des Strahls ermittelt
    • Hintergrundpixel werden über depth >= 1.0 - 1e-7 erkannt; für „sky pixels“ wird sceneDepth = atmosphereHeight * SKY_MARCH_DISTANCE_MULTIPLIER angewendet
    • Zeigt der Strahl nach unten, wird die Bodenüberschneidung mit tGround = observerAltitude / max(-rayDir.y, 1e-4) berechnet und per rayEnd = min(rayEnd, tGround) begrenzt
    • Die endgültige stepSize wird als (rayEnd - rayStart) / float(PRIMARY_STEPS) berechnet
    • Strahlen, die nahe Objekte oder den Boden treffen, werden mit kleinerer stepSize präziser gesampelt, während weiter reichende Strahlen dieselbe Anzahl Samples über eine längere Strecke verteilen
  • Atmosphärischer Nebel in der Szene

    • Der als Post-Processing-Effekt implementierte Shader wendet atmosphärische Streuung auf das gesamte Volumen der Szene an und kann dabei unter Berücksichtigung der Szenengeometrie den Sky-Shader als Hintergrund nutzen
    • Objekte nahe an der Kamera erscheinen klarer, während weiter entfernte Objekte stärker verschwimmen
    • Ein interaktives Beispiel mit per Raycaster ziehbaren Himmelskörpern ist im Tweet von MaximeHeckel zu sehen

Planeten rendern

  • Zwei notwendige Schritte

    • Um eine realistische Atmosphäre um einen Planeten zu rendern, braucht man einen logarithmic depth buffer für große Skalen sowie eine kugelförmige Atmosphärenhülle, die definiert, wo Strahlen in der Atmosphäre beginnen und enden
  • Logarithmic depth buffer

    • Im planetaren Maßstab kann der Shader aus großer Entfernung den Tiefenunterschied zwischen Atmosphäre und Planetenhülle nur schwer unterscheiden, wodurch depth fighting auftreten kann
    • Da die Höhe der Atmosphäre nur einige km beträgt, müssen sowohl die Definition des depth buffer der Szene als auch die Art, wie er in Post-Processing-Effekten gelesen wird, angepasst werden
    • Im gl-Prop, das das Canvas von React Three Fiber umschließt, wird logarithmicDepthBuffer: true gesetzt
    • Eine Beispielkonfiguration hat die Form <Canvas shadows gl={{ alpha: true, logarithmicDepthBuffer: true }}>
    • Im Shader wird die Berechnung von sceneDepth neu definiert, um den logarithmic depth buffer wieder in die Entfernung entlang des Strahls zurückzurechnen
    • logDepthToViewZ(depth) verwendet pow(2.0, depth * log2(cameraFar + 1.0)) - 1.0 und gibt -d zurück
  • Atmosphärenbereich mit ray-sphere intersection finden

    • Um die Punkte zu finden, an denen der Sichtstrahl in die Atmosphärenkugel (atmospheric sphere) eintritt und sie wieder verlässt, wird ein Ray-Sphere-Intersection-Test verwendet
    • Wenn man die beiden Schnittpunkte erhält, kann man vermeiden, Samples außerhalb der Atmosphäre zu verschwenden, und die Raymarching-Schleife nur auf diesen Bereich begrenzen
    • Da der Planet ein kugelförmiges Mesh ist, das von einer etwas größeren Atmosphärenkugel umgeben wird, wird derselbe Intersection-Test auch auf den Planeten selbst angewendet
    • Wenn der Strahl den Boden trifft, bevor er die Atmosphäre verlässt, wird der Bodenschnittpunkt als Ende des Raymarching-Bereichs verwendet
    • Die verwendete Implementierung von raySphereIntersect orientiert sich an Inigo Quilez’ Ray-Surface intersection functions
  • Szenenobjekte und Bedingungen für das Ende der Atmosphäre

    • Die Atmosphäre muss enden, wenn sie die Planetenoberfläche erreicht oder vor dem Auftreffen auf den Boden auf ein anderes Szenenobjekt trifft
    • Beim Auftreffen auf den Planeten wird standardmäßig mit atmosphereFar = min(atmosphereFar, planetHit.x) am Boden gestoppt
    • Wenn ein anderes Mesh vor dem Boden gerendert wird, wird dies über die Bedingung sceneDepth < planetHit.x - 2.0 erkannt und atmosphereFar = min(atmosphereFar, sceneDepth) angewendet
    • Ohne diese Logik entsteht das Problem, dass die Planetenoberfläche vor dem Objekt erscheint
  • React-Three-Fiber-Demo und verbleibende Glitches

    • Wenn diese beiden Anpassungen im Code umgesetzt werden, lässt sich atmosphärische Streuung als Post-Processing-Effekt implementieren und die Atmosphäre um einen Planeten rendern
    • Die Demo-Szene rendert in React Three Fiber ein einfaches „Sun - Earth system“ und wendet den benutzerdefinierten Effekt an
    • Wenn man die Position der Sonne anpasst und herauszoomt, kann man den vom Shader erzeugten Himmelsfarbton aus verschiedenen Blickwinkeln vom Boden bis in den Orbit sehen
    • Derselbe Effekt wurde auch für das Posterbild zur Ankündigung des Artikels Anfang April verwendet, und das gerenderte Bild wurde als Tweet geteilt
    • Der Torus in der Szene kann auch nach Sonnenuntergang weiterhin „lit-up“ erscheinen
    • Die Ursache ist, dass die shadow-map oder shadow-camera des primären directional light zu klein skaliert ist und daher den zu weit entfernten Torus nicht abdecken kann
    • Als Workaround könnte der Shadow-Mapping-Ansatz aus dem Artikel über volumetric lighting wiederverwendet werden, wurde aber tatsächlich nicht ausprobiert

Eklipsen behandeln

  • Wenn ein großer Himmelskörper die Sonne verdeckt, kann dies ergänzt werden, indem nach lightMarch die Funktion sunVisibility aufgerufen und der Rückgabewert [0, 1] mit der Transmission multipliziert wird
  • Die Grundidee ist, am aktuellen Sample-Punkt das Skalarprodukt der Mondrichtung und der Sonnenrichtung zu vergleichen
  • Wenn beide Richtungen nahezu gleich sind und das Skalarprodukt nahe 1.0 liegt, verdeckt der Mond die Sonne; wenn sie orthogonal sind und nahe 0.0 liegen, gibt es keine Verdeckung
  • Mit dem bloßen Skalarprodukt allein lassen sich jedoch Größe und Skalierung von Objekten in der Szene nicht berücksichtigen, daher vergleicht die Implementierung den Winkelabstand zwischen Sonne und Mond sowie ihre jeweiligen Winkelradien
  • sunVisibility behandelt Fälle, in denen der Mond die Sonne nicht verdeckt, sie verdeckt, während er aus Kamerasicht größer oder ähnlich groß wie die Sonne erscheint, und sie verdeckt, während er aus Kamerasicht innerhalb des Sonnenradius liegt
  • Die Demo ergänzt das bestehende Beispiel zur atmosphärischen Streuung um sunVisibility und ein Mond-Mesh, sodass der Atmospheric-Scattering-Shader die Lichtknappheit verarbeitet, wenn der Mond mit der Sonne ausgerichtet wird
  • Eine aufwendigere Simulation von Finsternissen und Korona wird im Paper Physically Based Real-Time Rendering of Eclipses behandelt; dessen Implementierung wurde nicht nach WebGL portiert

Atmosphären anderer Planeten

  • Das verwendete Modell für Atmosphärendichte und Streuung wird größtenteils durch einige Konstanten bestimmt, etwa den Radius von Planet und Atmosphäre sowie RayleighScaleHeight, RayleighBeta, MieScaleHeight, MieBeta, mieBetaExt, mieG, OzoneHeight, OzoneWidth
  • Durch Anpassen dieser Werte lassen sich Ergebnisse erzielen, die einer Marsatmosphäre oder den Atmosphären anderer Planeten näherkommen
  • Die für den Mars verwendeten Werte sind Näherungen
    • planetRadius: 3390
    • atmosphereRadius: 3500, etwa 110 km Dicke
    • rayleighScaleHeight: 11.1
    • rayleighBeta: new THREE.Vector3(0.019, 0.013, 0.0057)
    • mieScaleHeight: 1.5
    • mieBeta: 0.04
    • mieBetaExt: 0.044
    • mieG: 0.65
    • ozoneCenterHeight: 0.0
    • ozoneWidth: 1.0
    • ozoneBetaAbs: new THREE.Vector3(0.0, 0.0, 0.0)
    • sunIntensity: 15.0
    • planetSurfaceColor: '#8B4513'
  • Ersetzt man die bestehenden Konstanten durch diese Werte, erhält man eine staubigere, orangefarbene Atmosphäre sowie den charakteristischen blauen Farbton bei Sonnenuntergang des Mars
  • Ein zugehöriges Paper ist Physically Based Rendering of the Martian Atmosphere

LUT-basierte atmosphärische Streuung

  • Ansatz und abgekürzte Teile

    • Der bisherige Shader kann Atmosphären in kleinem wie großem Maßstab anschaulich rendern, verursacht aber wegen der Raymarching-Schleife mit vielen PRIMARY_STEPS, der verschachtelten lightmarching-Schleife und der Berechnung in Full-Screen-Auflösung hohe Laufzeitkosten.
    • Sebastian Hillaire schlägt in A Scalable and Production Ready Sky and Atmosphere Rendering Technique einen auf Look Up Tables (LUTs) basierenden Ansatz vor, der kostspielige Streuberechnungen in Texturen speichert und im finalen Rendern vorberechnete Texturen sampelt und kombiniert.
    • Die behandelten LUTs sind die Transmittance LUT, die speichert, wie viel Licht beim Durchqueren der Atmosphäre übrig bleibt, die Sky-view LUT, die die Himmelsfarbe für eine bestimmte Kameraposition speichert, und die Aerial Perspective LUT, die atmosphärischen Dunst und Streulicht zwischen Kamera und sichtbarer Szenengeometrie speichert.
    • Die vollständige Paper-Implementierung wurde nicht 1:1 übernommen; LUTs passen zwar gut zu den Compute Shadern von WebGPU, aber aus Zeitgründen und wegen der Kontinuität des Artikels bleibt die Implementierung bei WebGL.
    • Im Paper ist die Aerial Perspective LUT eine 3D texture, in der Implementierung wird jedoch ein 2D-Render-Target verwendet.
    • Bei diesem Ansatz müssen die Texturen bei jeder Kamerabewegung neu erzeugt werden, damit die Pixelwerte korrekt bleiben; ein Vorberechnen ist daher schwierig.
    • Multi-Scattering wurde aus Zeitmangel weggelassen.
  • Transmittance LUT

    • Im bisherigen Shader rief jeder Sample-Punkt lightmarch auf, um zu berechnen, wie viel Sonnenlicht ihn erreicht; dieser Prozess ist teuer.
    • Die Transmittance LUT speichert diese Daten vorab in niedriger Auflösung, damit spätere LUTs sie bei Bedarf auslesen können.
    • Die Implementierung definiert ein dediziertes Frame Buffer Object mit einer Auflösung von 250 x 64, wendet ein benutzerdefiniertes Shader-Material auf ein Full-Screen-Quad der dedizierten Szene transmittanceLUTScene an und übergibt die gerenderte Textur als Uniform an nachgelagerte LUTs.
    • Für jedes Pixel wird ab vec3(0.0, radius, 0.0) geraymarcht, wobei radius entlang der Koordinate vUv.y von planetRadius bis atmosphereRadius anwächst.
    • Die x-Achse der LUT steht für den Lichtwinkel, die y-Achse für die Höhe; reines Weiß bedeutet 100% Transmittanz, schwarze oder farbige Bereiche stehen für den Boden oder die dichtesten Luftschichten.
    • Spätere LUTs können damit die „Menge des Lichts, die bei gegebenem Winkel und gegebener Höhe nach dem Durchqueren der Atmosphäre übrig bleibt“ per Textur-Lookup abrufen.
  • Sky-view LUT

    • Die Sky-view LUT berechnet, welche Farbe der Himmel hat, wenn man vom Boden aus in eine bestimmte Richtung blickt.
    • getSkyViewRayDir bildet vUv.x auf den Azimut [-PI, PI] und vUv.y auf die Elevation [-PI/2, PI/2] ab und definiert so die Raymarching-Richtung.
    • Für die Elevation wird ein quadratisches Mapping (vUv.y * vUv.y - 0.5) * PI verwendet; das ist ein Workaround, um zu starkes Flackern der Sky View in großer Entfernung zu vermeiden.
    • Wenn ein Ray nicht in die Atmosphäre eintritt, wird Schwarz zurückgegeben; Rays, die den Planeten treffen, werden nur über den sichtbaren Atmosphärenabschnitt geraymarcht und beim Auftreffen auf den Planeten vorzeitig beendet.
    • Die Streuschleife bleibt wie zuvor, läuft aber entlang der Sky-View-Richtung und verwendet für das Sonnenlicht die Transmittance LUT.
  • Aerial Perspective LUT

    • Anders als im Paper von Hillaire ist das Implementierungsergebnis eine 2D-Textur, bei der jedes Pixel genau einem sichtbaren Bildschirm-Pixel entspricht.
    • Mithilfe des Depth Buffers der Szene wird bestimmt, wie weit entlang des jeweiligen Rays gemarscht und Streuung akkumuliert werden soll.
    • Der bisherige Streucode wird fast vollständig wiederverwendet, wobei jedes Sample die Sichtbarkeit des Sonnenlichts aus der Transmittance LUT holt.
    • Die Ausgabe speichert im RGB-Kanal die akkumulierte atmosphärische Streuung und im Alpha-Kanal einen gepackten View-Transmittance-Wert für das spätere Compositing.
    • Der Implementierungsablauf liest die Tiefe aus depthBuffer, rekonstruiert mit getWorldPosition(vUv, depth) die World-Space-Position des Bildschirm-Pixels und berechnet dann rayDir von der Kameraposition zur World-Position.
    • Anschließend wandelt logDepthToRayDistance(vUv, depth) die Szenentiefe in Ray-Distanz um, berechnet die Schnittpunkte mit Atmosphäre und Planet und marcht nur über den sichtbaren Atmosphärenabschnitt.
  • Compositing

    • Nachdem Sky-view LUT und Aerial Perspective LUT erzeugt wurden, werden beide im letzten Post-Processing-Pass kombiniert.
    • Die zentrale Aufgabe ist, das aktuelle rayDir in Sky-View-UV-Koordinaten umzuwandeln.
    • Auf Szenengeometrie wird die Aerial Perspective LUT angewendet; der Alpha-Kanal dient als View-Transmittance, der RGB-Kanal als Streulicht, sodass color = color * aerialPerspective.a + aerialPerspective.rgb berechnet wird.
    • Für Hintergrund-Pixel wird die Sky-view LUT gesampelt; bei depth >= 1.0 - 1e-7 wird der Pixel als Hintergrund behandelt und color = inputColor.rgb + sampleSkyViewLUT(rayDir, planetCenter) angewendet.
    • Abschließend werden ACESFilm(color) und pow(color, vec3(1.0 / 2.2)) angewendet.
    • Der vollständige Code der LUT-basierten Atmosphärenimplementierung ist unter Github link zu finden.

Abschluss

  • Das Ergebnis der LUT-basierten atmosphärischen Streuung kann fast gleich aussehen wie die frühere vollständig raymarchende Version, aber der interne Ablauf ist anders.
  • Die Arbeit wird in kleinere LUTs aufgeteilt und im finalen Effekt zusammengesetzt; dabei wird nicht mehr für jedes Sample wiederholt in Richtung Sonne geraymarcht, um das einfallende Licht zu berechnen.
  • Da die Beleuchtungsinformationen direkt aus der Transmittance LUT geholt werden, werden kostspielige verschachtelte Schleifen durch einfache Textur-Lookups ersetzt, was in der finalen Szene zu einem spürbaren Performance-Gewinn führt.
  • Verglichen mit Implementierungen von Sébastian Hillaire und anderen in diesem Bereich ist die Umsetzung noch unzureichend; insbesondere gibt es in der Sky View Banding und Flickering, und die abgekürzten Teile gehen auf Kosten der Optimalität.
  • Möglicherweise hätte von Anfang an WebGPU verwendet werden sollen.
  • Als echte production-grade Implementierung wird three-geospatial von Shoda Matsuda (@shotamatsuda) empfohlen.
  • Zusätzlich wurde auch mit volumetrischen Wolken experimentiert, aber das Ergebnis ist noch ein mixed bag und noch nicht zufriedenstellend genug, um es im Artikel zu zeigen; es braucht mehr Arbeit.

1 Kommentare

 
GN⁺ 2 시간 전
Hacker-News-Kommentare
  • Ich weiß nicht, ob das absichtlich ausgelassen wurde, aber beim Sonnenuntergangsmodell ist es erwähnenswert, dass der Himmel nicht schwarz werden sollte, sobald die Sonne unter den Horizont sinkt.
    Auch nach Sonnenuntergang werden die Atmosphäre über dem Kopf und der Bereich über dem Horizont noch eine Weile von Sonnenlicht getroffen, und in der Erdatmosphäre bleibt eine deutlich sichtbare Dämmerung bestehen, bis die Sonne 18 Grad unter dem Horizont steht. Per Raytracing ist das vielleicht nicht praktisch umzusetzen, aber es gibt gängige Algorithmen, um das zu modellieren
  • Solche guten Grafikartikel sind immer willkommen. Ich habe an etwas Ähnlichem in einem prozeduralen Weltraum-/Planetengenerator gearbeitet, und das Schöne an atmosphärischer Streuung ist, dass sie zusammen mit volumetrischem Wolken-Rendering großartige Sonnenuntergänge und Himmelsszenen erzeugen kann
    https://www.threads.com/@mrsharpoblunto/post/DVS4wfYiG8f?xmt...
    https://www.threads.com/@mrsharpoblunto/post/C6Vc-S1O9mX?xmt...
    https://www.threads.com/@mrsharpoblunto/post/C6apksDRa8q?xmt...
  • Es ist wirklich erstaunlich, was Handys und Browser heute leisten können
    Ich erinnere mich daran, „Display of The Earth Taking into Account Atmospheric Scattering“ von Nishita et al. implementiert zu haben, eine Arbeit von 1993, die dem Ursprung dieses Themas sehr nahekommt und sehr leicht zu lesen ist: https://www.researchgate.net/publication/2933032_Display_of_...
    • Ich musste bei einem anderen Kommentar an die Arbeit denken, die ich damals gelesen habe, als ich Rayleigh-Streuung und Mie-Streuung implementiert habe, und genau die ist es
      Als ich es zum Laufen gebracht hatte, gab es diesen Moment: „Dieses komplexe Phänomen der realen Welt lässt sich mit ein paar relativ einfachen Berechnungen erstaunlich gut modellieren.“ Es ging im Handumdrehen von einem statischen blauen Skybox-Himmel zu einem vollständigen Tag-Nacht-Zyklus
  • Wirklich großartig
    Ich hatte früher einmal überlegt, ob man den Himmel im Web durch das Übereinanderlegen mehrerer Verläufe rendern könnte. Vielleicht hätte man damit ein halbwegs ordentliches Ergebnis erzielt, aber mit dem, was hier gemacht wurde, ist das nicht zu vergleichen. Das Resultat ist beeindruckend und inspirierend
    • Ich habe in einer Hobby-Game-Engine einmal Rayleigh-Streuung und Mie-Streuung implementiert
      Schon das allein lieferte einen ziemlich überzeugenden Sonnenuntergangs-/Sonnenaufgangs-Zyklus, und wenn ich mich richtig erinnere, kam sogar die Sonne selbst irgendwie ganz natürlich mit heraus. Ich nutzte XNA, Microsofts C#-Plattform für Spieleentwicklung, und folgte Riemers hervorragender Tutorial-Reihe; ein Archiv davon gibt es hier: https://github.com/SimonDarksideJ/XNAGameStudio/wiki/Riemers...
      Allerdings sehe ich dort nichts zur Streuung, also könnte ich diesen Teil woanders hergeholt haben. Ich erinnere mich aber daran, Arbeiten mit Formeln gelesen zu haben
  • SpaceEngine ist ebenfalls dafür bekannt, in diesem Bereich viel Arbeit investiert zu haben, daher eine klare Empfehlung: https://www.youtube.com/watch?v=_4TjdVAbXks
    https://spaceengine.org/
    • Ich mag die FAQ solcher Dinge, weil sie den Maßstab und die Vielfalt der Fragen zeigen
      Auf „Wie viele Objekte gibt es in SpaceEngine?“ lautet die Antwort sinngemäß: der komplette Hipparcos-Sternkatalog, alle bekannten Exoplaneten, mehr als zehntausend Galaxien und die meisten Objekte im Sonnensystem, zusammen 130.000, dazu noch mehr Galaxien und Sternsysteme, als im beobachtbaren Universum tatsächlich existieren. Auf „Wie kann ein Wasserplanet heiß sein?“ wird geantwortet, dass das Wasser in der oberen Atmosphäre heißer Wasserdampf ist, weiter unten aber unter hohem Druck sanft in Flüssigkeit übergeht und in noch größerer Tiefe zum festen Zustand ice VII wird. Die Antwort auf „Wie bewegt man sich?“ lautet: mit den WASD-Tasten
    • Auf einem Tab Wikipedia und auf dem anderen SpaceEngine offen zu haben, ist für mich eine der schönsten halb-bildenden Spielerfahrungen
      Ein großartiges Spiel, und obwohl es schon ziemlich alt ist, habe ich noch nichts gesehen, das so gut ist
    • Es ist seit vielen Jahren hervorragende Software, und nicht nur bei diesem Thema, sondern in vielen Bereichen ist die Liebe zum Detail enorm
      Dieser Artikel hat mich ebenfalls sofort an SpaceEngine denken lassen
  • Streuung ist seit Langem ein Schlüssel zu realistisch wirkenden Rendering-Bildern
    Eines meiner Lieblingspapers: http://www.graphics.stanford.edu/papers/bssrdf/bssrdf.pdf
    Ich glaube, damals habe ich zum ersten Mal gelernt, dass das Rendern von Milch ein kniffliges Problem ist
  • Wow, das war eine ziemlich beeindruckende Reise
    Ich habe wahrscheinlich nur etwa 5 % verstanden, war aber wirklich sehr beeindruckt
    • Geht mir genauso. Schon allein wegen der Visualisierungen war es lesenswert
  • Oh, das ist wirklich ein wunderschöner und gut lesbarer Artikel
    Und wenn es unter der MIT-Lizenz steht, ist mein Skybox-Problem für mein Spiel praktisch gelöst. Die Perspektive wird fest sein, also brauche ich nur ein Rendering, in dem die Sonne über den Himmel wandert, und könnte das dann mit einer Sinusperiode um die jahreszeitliche Veränderung des Sonnenwinkels erweitern
  • Ich habe das schon einmal gesehen, also ist es vielleicht nicht ganz direkt relevant, aber Sebastian Lague hatte auch ein wirklich interessantes Video über Atmosphären-Rendering in einem Experiment zur Planetengenerierung https://www.youtube.com/watch?v=DxfEbulyFcY
    Es hat einen besonderen Reiz, visuelle Effekte zu entwickeln und zuzusehen, wie sie immer realistischer werden, und ich würde dieses Feld irgendwann gern selbst ausprobieren
    • Das Erstaunlichste an Sebastian Lague ist, wie sehr der YouTube-Algorithmus Menschen ruinieren kann
      Früher hatten seine Videos mehrere Millionen Aufrufe, heute kommen sie kaum noch über 500.000. Es könnte auch daran liegen, dass während Corona alle zu Hause saßen und sich für zufällige Dinge interessiert haben
    • Meine einzige Beschwerde über Sebastian Lague ist, dass es nicht annähernd genug Videos gibt
      Ich lasse sie meistens zum Einschlafen laufen, und weil ich mir mehr von solchen ruhigen, aber tiefgehenden technischen Inhalten wünsche, habe ich schon darüber nachgedacht, selbst so etwas zu machen