Himmel, Sonnenuntergänge und Planeten rendern
(blog.maximeheckel.com)- 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
hbefindet, und bildet den Effekt ab, dass die Atmosphäre mit zunehmender Höhe dünner wird. - Die Beispielimplementierung verwendet
RAYLEIGH_SCALE_HEIGHT = 8.0km,ATMOSPHERE_HEIGHT = 100.0km,VIEW_DISTANCE = 200.0km undPRIMARY_STEPS = 24. rayleighDensity(h)istexp(-max(h, 0.0) / RAYLEIGH_SCALE_HEIGHT), und in der Schleife wird mitviewOpticalDepth += dR * stepSizeakkumuliert.
-
Beer's Law und das Blau des Tageshimmels
- Aus der optischen Tiefe wird die Transmittanz
Tan einem bestimmten Punkt berechnet;T=1.0bedeutet 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). rayleighBetaist der Rayleigh-Streukoeffizient und wird im Shader alsvec3(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.
- Aus der optischen Tiefe wird die Transmittanz
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,viewODMundviewODOakkumuliert. - An jedem Sample werden
dR = rayleighDensity(h),dM = mieDensity(h)unddO = ozoneDensity(h)berechnet, undtauwird aus der Summe vonBETA_R * viewODR,BETA_M_EXT * viewODMundBETA_OZONE_ABS * viewODOgebildet. - Die Transmittanz wird mit
exp(-tau)berechnet, und insumR,sumMundsumOwerden jeweils Dichte, Transmittanz undstepSizeakkumuliert. - Die finale Streuung wird in der Form
SUN_INTENSITY * (phaseR * BETA_R * sumR + phaseM * BETA_M_SCATTER * sumM + BETA_OZONE_SCATTER * sumO)berechnet.
- In einer Implementierung, die Rayleigh, Mie und Ozon zusammen verwendet, werden die jeweiligen optischen Tiefen in
-
Wichtige Konstanten und ihre Effekte
MIE_SCALE_HEIGHTentspricht für Aerosole demRAYLEIGH_SCALE_HEIGHT; da sich diese Partikel meist in Horizontnähe konzentrieren, wird der Wert kleiner auf1.2kmgesetzt.MIE_BETA_SCATTERsteuert, wie stark Partikel Licht zur Kamera hin streuen; da dies meist weitgehend wellenlängenunabhängig ist, wirdvec3(0.003)verwendet.MIE_BETA_EXTist der Mie-Extinktionskoeffizient, der angibt, wie viel Licht aus dem Pfad entfernt wird, und lässt ferne Atmosphäre diesiger erscheinen.MIE_Gsteuert die Anisotropie;0.0bedeutet gleichmäßige Streuung,1.0eine stärkere Vorwärtsstreuung.OZONE_BETA_ABShat die Wertevec3(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)iteriertLIGHTMARCH_STEPS-mal und akkumuliert dabeiodR,odM,odO- Zur optischen Tiefe der bisherigen Implementierung
viewODR,viewODM,viewODOwird die optische Tiefe in SonnenrichtungsunODaddiert - Das endgültige
tauwird aus der Summe vonBETA_R * (viewODR + sunOD.x),BETA_M_EXT * (viewODM + sunOD.y)undBETA_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 anglelä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,matrixWorldundpositionder Kamera; diese Werte werden als Uniforms an den Post-Processing-Effekt übergeben getWorldPosition(vec2 uv, float depth)erzeugtclipZmitdepth * 2.0 - 1.0, erstellt NDC-Koordinaten mituv * 2.0 - 1.0und wendet anschließendprojectionMatrixInverseundviewMatrixInversean- Dasselbe Verfahren wird auch im Volumetric-Lighting-Post-Processing-Effekt von On Shaping Light verwendet
- Nachdem die
worldPositiondes aktuellen Pixels bestimmt wurde, wirdrayOriginals Kameraposition gesetzt undrayDiralsnormalize(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
stepSizedurch den Depth-Buffer bestimmt werden - Mit
sceneDepth = depthToRayDistance(uv, depth)wird die Szenentiefe entlang des Strahls ermittelt - Hintergrundpixel werden über
depth >= 1.0 - 1e-7erkannt; für „sky pixels“ wirdsceneDepth = atmosphereHeight * SKY_MARCH_DISTANCE_MULTIPLIERangewendet - Zeigt der Strahl nach unten, wird die Bodenüberschneidung mit
tGround = observerAltitude / max(-rayDir.y, 1e-4)berechnet und perrayEnd = min(rayEnd, tGround)begrenzt - Die endgültige
stepSizewird als(rayEnd - rayStart) / float(PRIMARY_STEPS)berechnet - Strahlen, die nahe Objekte oder den Boden treffen, werden mit kleinerer
stepSizepräziser gesampelt, während weiter reichende Strahlen dieselbe Anzahl Samples über eine längere Strecke verteilen
- Um die Szenengeometrie zu berücksichtigen, sollte der Raymarch-Bereich des aktuellen Strahls statt über eine feste
-
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
Raycasterziehbaren 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 dasCanvasvon React Three Fiber umschließt, wirdlogarithmicDepthBuffer: truegesetzt - Eine Beispielkonfiguration hat die Form
<Canvas shadows gl={{ alpha: true, logarithmicDepthBuffer: true }}> - Im Shader wird die Berechnung von
sceneDepthneu definiert, um den logarithmic depth buffer wieder in die Entfernung entlang des Strahls zurückzurechnen logDepthToViewZ(depth)verwendetpow(2.0, depth * log2(cameraFar + 1.0)) - 1.0und gibt-dzurü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
raySphereIntersectorientiert 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.0erkannt undatmosphereFar = 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
lightMarchdie FunktionsunVisibilityaufgerufen 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.0liegt, verdeckt der Mond die Sonne; wenn sie orthogonal sind und nahe0.0liegen, 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
sunVisibilitybehandelt 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
sunVisibilityund 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: 3390atmosphereRadius: 3500, etwa110 kmDickerayleighScaleHeight: 11.1rayleighBeta: new THREE.Vector3(0.019, 0.013, 0.0057)mieScaleHeight: 1.5mieBeta: 0.04mieBetaExt: 0.044mieG: 0.65ozoneCenterHeight: 0.0ozoneWidth: 1.0ozoneBetaAbs: new THREE.Vector3(0.0, 0.0, 0.0)sunIntensity: 15.0planetSurfaceColor: '#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 verschachteltenlightmarching-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.
- Der bisherige Shader kann Atmosphären in kleinem wie großem Maßstab anschaulich rendern, verursacht aber wegen der Raymarching-Schleife mit vielen
-
Transmittance LUT
- Im bisherigen Shader rief jeder Sample-Punkt
lightmarchauf, 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 SzenetransmittanceLUTScenean und übergibt die gerenderte Textur als Uniform an nachgelagerte LUTs. - Für jedes Pixel wird ab
vec3(0.0, radius, 0.0)geraymarcht, wobeiradiusentlang der KoordinatevUv.yvonplanetRadiusbisatmosphereRadiusanwä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.
- Im bisherigen Shader rief jeder Sample-Punkt
-
Sky-view LUT
- Die Sky-view LUT berechnet, welche Farbe der Himmel hat, wenn man vom Boden aus in eine bestimmte Richtung blickt.
getSkyViewRayDirbildetvUv.xauf den Azimut[-PI, PI]undvUv.yauf 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) * PIverwendet; 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 mitgetWorldPosition(vUv, depth)die World-Space-Position des Bildschirm-Pixels und berechnet dannrayDirvon 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
rayDirin 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.rgbberechnet wird. - Für Hintergrund-Pixel wird die Sky-view LUT gesampelt; bei
depth >= 1.0 - 1e-7wird der Pixel als Hintergrund behandelt undcolor = inputColor.rgb + sampleSkyViewLUT(rayDir, planetCenter)angewendet. - Abschließend werden
ACESFilm(color)undpow(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
Hacker-News-Kommentare
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
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...
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_...
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
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
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
https://spaceengine.org/
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
Ein großartiges Spiel, und obwohl es schon ziemlich alt ist, habe ich noch nichts gesehen, das so gut ist
Dieser Artikel hat mich ebenfalls sofort an SpaceEngine denken lassen
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
Ich habe wahrscheinlich nur etwa 5 % verstanden, war aber wirklich sehr beeindruckt
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
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
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
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