fsplat

reinhard@finalmedia.de cafeface
Fri Jul 10 05:10:55 PM CEST 2026
Public Domain

fsplat liest auf stdin .fsplat files sowie auf descriptor 4 steuerungsbefehle (z.B. kamerapositionsbefehle) und gibt auf stdout fdraw befehle aus.

Sourcecode

  • fsplat.c
  • Makefile

    Sourcecode für die Konverter von Szenen Dateien

  • splat2fsplat.c
  • ply2fsplat.c

    Binaries

  • fsplat (76kb ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), static-pie linked, stripped binary) 6a3622106f68e874a148b76990907d73ad6b8dc61bf78fa6907fa141a1d31923
  • fsplat.znver2 (76kb ELF 64-bit LSB pie executable, march=znver2 (ZEN2) setzt AVX2,BMI2,FMA3 voraus, version 1 (SYSV), static-pie linked, stripped binary) - optimal für steamdeck - c355eb8330e3d2a68fd57371433b622f937e4ad3eda6dc61974d107ce00301ab
  • fsplat.x86-64-v3 (76kb ELF 64-bit LSB pie executable, march=x86-64-v3 setzt AVX2 voraus, version 1 (SYSV), static-pie linked, stripped binary) - für moderne cpus - 2e987d5adf72358377d8db1b50096f7729ce9a782ca33986bbbe8bd10fbb2e41
  • splat2fsplat (31kb ELF 64-bit LSB pie executable, march=x86-64, version 1 (SYSV), static-pie linked, stripped binary) - 3723da08dfbdb8e75ed43c65e0b78d3d80528b8c7ae03197fc2a7d4f8816952f
  • ply2fsplat (27kb ELF 64-bit LSB pie executable, march=x86-64, version 1 (SYSV), static-pie linked, stripped binary) - 4e368aca607b927cb66a26acae5c1609c7b3d410d9d74b9b19d4bf1f3e236605

    Example Usage

  • rendernow.sh
  • generate.sh

    Beschreibung

    fsplat ist ein minimalistischer multi-threaded Software-Rasterizer, der mehrere .fsplat-Dateien über die Standardeingabe streamt und parallel auf CPU-Kernen verarbeitet.

    Er benötigt somit keine GPU, sondern arbeitet komplett auf CPU, ist ohne externe Abhängigkeiten in C geschrieben und lässt sich gegen die musl libc statisch gelinkt als 76kb große Binary bauen. Ein ideales Werkzeug zum Experimentieren mit Gaussian Splats unter Beibehaltung der Unix-Philosophie.

    Das Tool liest dabei von stdin die Szenendaten im fsplat Format ein. Auf stdin kommen also bei Bedarf mehrere konkatentierte und vorsortierte fsplat Szenen zum Einsatz, man kann also eine sehr große Szenerie einfach nacheinander streamen und zusammensetzen. Die Reihenfolge ist dabei relevant!

    Auf Descriptor 4 werden die Kameraposition, Winkel, Zoom etc. als reine ASCII Befehlszeilen übertragen. Das können statische Zeilen sein, dynamisch erzeugte, von Mausposition abhängig oder als Animation.

     export SCREEN_WIDTH=120 SCREEN_HEIGHT=80
     zcat szene1.fsplat.gz szene2.fsplat.gz | ./fsplat 4< <(./kameraflug.awk) | fdrawterm
    

    Jeder Thread innerhalb von fsplat führt dann die rechenintensive SH-Evaluierung, das Frustum Culling und den schnellen Bit-Radix-Sort vollkommen unabhängig auf seinen eigenen CPU-Kernen aus.

    Jede Szene bringt dabei ihr eigenes Codebook mit, mit jeweils bis zu 256 Formeln für die SH Pixelfarbe. (oder auch weniger bei Bedarf) Wir benötigen also auch nur 30 bytes pro Splat statt 32 bytes, da die Farbinformationen nur im SH Codebook referenziert vorliegt und dabei nur ein Byte Speicherplatz pro Splat benötigt. Details dazu in den FAQ auf dieser Seite.

    Konzept: Der Einlese-Algorithmus befüllt nacheinander die isolierten Slots in scenes. Sobald die Kamera-Steuerung über Descriptor 4 rendert, werden alle Worker parallel angestoßen und anschließend sequentiell in den Puffer geblendet. Der Performance-Vorteile dieser Architektur ist klar: Es gibt kein Thread-Overhead beim Blending: Da wir im Hauptthread sequentiell blenden, entfallen komplexe Locking-Mechanismen (pthread_mutex_t), Atomics oder Race Conditions auf dem globalen Framebuffer komplett. Zudem haben wir ein optimiertes Speicher-Layout. Da sh_idx im RAM ein schlankes 1-Byte-Feld bleibt, profitiert das s-Array von maximaler Cache-Dichte während der Transformation im Thread.

    Steuerungbefehle werden auf Descriptor 4 zeilenweise übergeben. Man kann P für die Kamerapositionen übergeben (nach jedem Absetzen eines P Befehls, wird auch das Frame neu gerendert) oder man übergibt einen O Befehl mit Offset-Koordinaten. Dann werden alle, ab diesem Zeitpunkt auf stdin einströmenden Daten mit diesem Offset kalkuliert.

    Die Ausgabe erfolgt auf stdout als fdraw Befehle. Du kannst das also in beliebigen Tools weiterverarbeiten, zwischenspeichern oder analysieren.

    Zur Ansicht kannst du z.B. fdraw, fdrawterm oder fbdraw nutzen. In den Demos empfehle ich fdrawterm, da es dir auf dem Terminal eine schöne und schnelle Vorschau gibt, die du auch remote via ssh nutzen kannst! Du findest es hier

    Ausgabe im Terminal als ANSI Grafik

     export SCREEN_WIDTH=120 SCREEN_HEIGHT=80
     zcat szene.fsplat.gz | ./fsplat 4< <(./kamera_animation.sh) | fdrawterm
    

    und wild gemische Szenen nur zu Demostrationszwecken

     export SCREEN_WIDTH=120 SCREEN_HEIGHT=80
     zcat szene.fsplat.gz train.fsplat.gz  | ./fsplat 4< <(./kamera_animation.sh) | fdrawterm
    

    Prozessierung mittels ffmpeg/ffplay

     export SCREEN_WIDTH=640 SCREEN_HEIGHT=480
     zcat original.splat.gz | ./splat2fsplat | ./fsplat 4< <(./kamera_animation.sh) | fdraw | ffplay -loglevel 8 -f rawvideo -pixel_format rgba -video_size ${SCREEN_WIDTH}x${SCREEN_HEIGHT} -framerate 30 -i -
    

    Hier noch meine Beispielszene glas.fsplat als Rendering mit fsplat, fdraw und ffmpeg

     export SCREEN_WIDTH=640 SCREEN_HEIGHT=480
     zcat glas.fsplat.gz | ./fsplat 4< <(echo P 4 -4 -2 180 35 80) | fdraw | ffmpeg -loglevel 8 -f rawvideo -pixel_format rgba -video_size ${SCREEN_WIDTH}x${SCREEN_HEIGHT} -framerate 30 -i - glas%d.png
    

    Und diese Szene kombiniert mit dem bereits passend skalierten chair.fsplat.gz als Miniaturobjekt. (Dank an Alfredo M - quinszouls für das Modell)

     export SCREEN_WIDTH=120 SCREEN_HEIGHT=80
     zcat chair.fsplat.gz glas.fsplat.gz | ./fsplat 4< glasline | fdrawterm
    
    
     export SCREEN_WIDTH=640 SCREEN_HEIGHT=480
     zcat chair.fsplat.gz glas.fsplat.gz | ./fsplat 4< <(echo P 4 -4 -2 180 35 80) | fdraw | ffmpeg -loglevel 8 -f rawvideo -pixel_format rgba -video_size ${SCREEN_WIDTH}x${SCREEN_HEIGHT} -framerate 30 -i - combined%d.png
    

    Tipps: Wenn nach dem Laden von chair.fsplat im descriptor 4 ein passenden O Befehl mit Offset angegeben werden wird, bevor die glas.fsplat Szene geladen wird, kann der Stuhl im Raum, bzw. der Raum zum Stuhl, verschoben werden.

    Wird die Reihenfolge chair.fsplat.gz und glas.fsplat.gz vertauscht, ist der Stuhl nicht mehr sichtbar, da er überzeichnet wird.

    Zu beachten ist natürlich, dass jede .fsplat datei eine eigene SH-Tabelle aufweist.

    Hier noch als Video gerechnet mit kleiner Kameraanimation

    export SCREEN_WIDTH=320 SCREEN_HEIGHT=240
    zcat chair.fsplat.gz glas.fsplat.gz | ./fsplat 4< glasline | fdraw | ffmpeg -loglevel 8 -f rawvideo -pixel_format rgba -video_size ${SCREEN_WIDTH}x${SCREEN_HEIGHT} -framerate 300 -i - -y glas_chair_combined.mp4
    

    Live-Steuerung über Keyboard

     export SCREEN_WIDTH=640 SCREEN_HEIGHT=480
     test ! -e /dev/shm/controlpipe && mkfifo /dev/shm/controlpipe
    
    
     zcat szene.fsplat.gz | ./fsplat 4< /dev/shm/controlpipe | fdraw | ffplay -loglevel 8 -f rawvideo -pixel_format rgba -video_size ${SCREEN_WIDTH}x${SCREEN_HEIGHT} -framerate 30 -i - &
     ./flycontrol.sh > /dev/shm/controlpipe
    

    Stream from Web

     export SCREEN_WIDTH=80 SCREEN_HEIGHT=50
     curl -Ls https://finalmedia.de/code/fsplat/szene.fsplat.gz | zcat | ./fsplat 4< <(curl -Ls https://finalmedia.de/code/fsplat/camlines) | fdrawterm
    

    Proceduraler Kameraflug (3D-Lissajous)

     # Empfehlungen zum Erkunden einer Szene
     export SCREEN_WIDTH=120 SCREEN_HEIGHT=80
     zcat szene.fsplat.gz | ./fsplat 4< <(./kameraflug.awk | pv -qlL 25) | fdrawterm
     zcat bicycle.fsplat.gz | ./fsplat 4< <(./kameraflug.awk) | fdrawterm
    

    Beispiel Splat Szenen

  • glas.fsplat.gz (1.5 MB) (Joerg Reinhard Public Domain, smartphone capture)
  • chair.fsplat.gz (1.3 MB) (Alfredo M - quinszouls) CC4 CC Attribution) konvert von ply nach splat und dann nach fsplat und passend zur glas szene positioniert
  • original.splat.gz (32.4 MB) (original Szene Splat Demo-File) zum Test des Converters splat nach fsplat
  • szene.fsplat.gz (30 MB) (konvertiert)
  • train.fsplat.gz (29 MB) (konvertiert)
  • bicycle.fsplat.gz (41 MB) (konvertiert und reduziert aus original Splat Demo-File bicycle.splat.gz 181MB)

    Beispiel Kamerapositionen

  • flycontrol.sh
  • kamera_animation.sh (benötigt das tool bc)
  • kameraflug.awk
  • camlines (statische datei)
  • glasline (statische datei kameraposition zu szene glas.fsplat)

    Benchmark

    Test auf einem sehr langsamen Celeron CPU J1900 bei 2GhZ. Kein AVX, InOrder. Ganz simple embedded CPU. Ich messe dazu mittels tai64n letztlich die vergangengen Nanosekunden zwischen den erzeuten frames. bei jedem neuen Frame geben wir ja einen . in stderr aus. Damit kann man schon den Verlauf und das Delta erkennen. Ich messe die vollständige Kette mit niedriger und höherer Auflösung.

    export SCREEN_WIDTH=120 SCREEN_HEIGHT=80; zcat szene.fsplat.gz | ./fsplat 4< <(./kamera_animation.sh) 2>&1 >/dev/null | tai64n | tai64nlocal
    2026-07-02 18:37:13.755091500 SCREEN: 120x80
    2026-07-02 18:37:14.442751500 1065994 Splats geladen.
    2026-07-02 18:37:14.624989500 .
    2026-07-02 18:37:14.800078500 .
    2026-07-02 18:37:14.971646500 .
    2026-07-02 18:37:15.145416500 .
    2026-07-02 18:37:15.314967500 .
    2026-07-02 18:37:15.486919500 .
    2026-07-02 18:37:15.659503500 .
    2026-07-02 18:37:15.832940500 .
    2026-07-02 18:37:16.006537500 .
    2026-07-02 18:37:16.181396500 .
    2026-07-02 18:37:16.357425500 .
    2026-07-02 18:37:16.534471500 .
    2026-07-02 18:37:16.713569500 .
    2026-07-02 18:37:16.895258500 .
    2026-07-02 18:37:17.077460500 .
    2026-07-02 18:37:17.259299500 .
    2026-07-02 18:37:17.442589500 .
    2026-07-02 18:37:17.624338500 .
    ^C
    
    export SCREEN_WIDTH=640 SCREEN_HEIGHT=480; zcat szene.fsplat.gz | ./fsplat 4< <(./kamera_animation.sh) 2>&1 >/dev/null | tai64n | tai64nlocal
    2026-07-02 18:37:27.041403500 SCREEN: 640x480
    2026-07-02 18:37:27.757438500 1065994 Splats geladen.
    2026-07-02 18:37:30.051540500 .
    2026-07-02 18:37:32.338945500 .
    2026-07-02 18:37:34.637367500 .
    2026-07-02 18:37:36.940418500 .
    2026-07-02 18:37:39.270503500 .
    2026-07-02 18:37:41.602458500 .
    2026-07-02 18:37:43.929949500 .
    

    Bei VGA (640x480) rendert das Programm auf dem Testsystem konstant in rund 2,29 Sekunden pro Frame (30.051 -> 32.338 -> 34.637 -> 36.940).

    Bei der Vorschau (120x80) liegt der Durchlauf auf dem Testsystem konstant bei rund 172 Millisekunden pro Frame (14.624 -> 14.800 -> 14.971 -> 15.145). Das System läuft also mit 5,8 FPS.

    Auf aktuellen modernen CPUs ist da natürlich sehr viel mehr drin. Es zeigt nur, wie gut das Verfahren auch auf einer so schwachbrüstigen CPU und ohne jede GPU Unterstützung noch performed!

    Wenn du das pv tool installiert hast, kannst du auch die FPS messen:

    export SCREEN_WIDTH=100 SCREEN_HEIGHT=50; zcat szene.fsplat.gz | ./fsplat 4< <(./kamera_animation.sh) 2>&1 >/dev/null | pv -rlX

    Designentscheidungen

    Entwicklung im djb stil. kein libc, kein printf etc. minimale abhängigkeiten

    Statt qsort der Originalimplementierung ein radixsort.

    Kein malloc, kein calloc, also keine dynamische Speicher-Allokation, damit auch kein Heap-Overhead.

    Die Allokation beim Systemstart ist somit vollständig statisch. Es gibt keine Speicherfragmentierung und keine Betriebssystem-Aufrufe während des Renderings.

    Keine Speicher-Leaks: Da kein Puffer dynamisch erzeugt wird, kann zur Laufzeit physisch kein Memoryleak entstehen und der Stack bleibt sauber.

    Optimierte Cache-Lokalität: Die CPU verarbeitet die Arrays im BSS-Segment in exakt der Reihenfolge, in der sie im RAM liegen. Der Hardware-Prefetcher des hiesigen Demo Celerons kann die Datenströme ohne jede Latenz in die Ausführungseinheiten füttern.

    Wir legen Puffer für jede maximal mögliche Szene fest (z.B. ein Limit von 1.500.000 Splats pro Szene und eine maximale Bildschirmauflösung von 1920x1080). Diese Limits sind im Quellcode einfach anpassbar!

    Im Beispiel mit der musl-libc gebaut, statisch gelinked.

    Beim Compilieren ist zwingend -mcmodel=large zu verwenden, da wir für sehr große Szenen auch 128GB RAM etc. mehr verwenden werden.

    Sonstiges

    Erstellen einer netpbm Grafik aus den fdraw Befehlen. Mit Standardtools.

     (echo P3 640 480 255; export SCREEN_WIDTH=640 SCREEN_HEIGHT=480; zcat szene.fsplat.gz | ./fsplat 4< <(head -n 1 camlines) | grep p | sort -V -k2,3 | cut -d " " -f4- | tr -dc "0-9 \n" | tr "\n" " ") > demo.ppm
    

    Steamdeck Demo

    Hier ein CPU Performancetest für deine Steamdeck. Es werden keine GPU Beschleunigung verwendet, wir nutzen nur die ZEN2 CPU und die Grafikausgabe auf dem Terminal:

    Öffne auf der SteamDeck im Desktop Modus mittels STRG+ALT+T ein Terminal und vergößere es auf Vollbild mittels der Taste F11. Dann:

    curl -s https://finalmedia.de/code/fsplat/steamdeck.txt | sh
    

    Fragen und Antworten

    fsplat beendet sich, sobald es alle Splats der Szene geladen hat. Was mache ich falsch?

    Vermutlich hast du den Descriptor 4 nicht übergeben. Dann beendet sich das tool, da es ja keine Kameraposition bekommt, die es rendern soll.

    Was du zum schnellen Testen tun kannst, ist ein

    ... | fsplat 4< /proc/self/status | ...
    zu verwenden.

    Der Trick funktioniert, da grep ^P /proc/self/status immer ein Pid und PPid liefert, wenngleich nicht mit gültigen Kamerapositionen, aber es erzwingt ein Framerendering.

    Wenn du partout keine named pipe nutzen willst, wie in den Beispielen vorgesehen, kannst du auch einen ewiger hook an descriptor 4 binden, in den du dann später on demand Daten injectest. Konkret

    ... | fsplat 4< <(tail -f /dev/null) | ...
    und dann in einem separaten Terminal in den descriptor 4 die Kamerapositionen injecten, nachdem du die PID des Prozesses herausgefunden hast. Konkret also:
    curl -Ls finalmedia.de/code/fsplat/camlines > /proc/$(pgrep fsplat)/fd/4

    Ich will eigene fsplat Files haben. Wie?

    Mach dir welche. Du findest online diverse weitere Demo-Szenen hiltree, etc. Und du kannst mit den gängigen Tools auch Szenen exportieren. Oft auch mit Apps deines Smartphones etc. Oben findest du meine Konvertierungs-Tools, um aus einer .ply oder .splat Datei eine .fsplat Datei zu erzeugen: ply2fsplat und splat2fsplat.

    Diese arbeiten als Filter und man kann damit einfach bestehende splat oder ply Szenen nach fsplat umwandeln. Da das original splat ohnehin kein SH hat, gibt das kein Mehrgewinn. Aber ply hat SH Support und da bietet sich dann das fsplat Format an. Beispielnutzung:

     zcat erbsen.ply.gz | ply2fsplat | gzip -9 erbsen.fsplat.gz
     zcat linsen.splat.gz | splat2fsplat | gzip -9 linsen.fsplat.gz
     splat2fsplat < bohnen.splat | gzip -9 bohnen.fsplat.gz
     splat2fsplat < import.splat > export.fsplat
    

    Und weil es filter sind, muss man auch keine Dateien zwischenspeichern. Man kann direkt

    zcat linsen.splat.gz | splat2fsplat | fsplat 4< ....
    als Kette nutzen

    Beachte: Im Quellcode sind Limits definiert für max Splat sizes etc. Die musst du ggf. anheben. Die Limits sind sehr konservativ gesetzt. Zudem habe ich in allen Tools bewusst auf malloc und calloc etc. verzichtet, damit Verifizierbarkeit und Vorhersagbarkeit der Speicherbelastung bei der Anwendung erhalten bleiben.

    Was ist Gaussian Splatting überhaupt?

    Anstatt eine 3D-Szene mühsam aus Millionen von Dreiecken (Polygonen) oder starren Blöcken (Voxeln) aufzubauen, repräsentiert Gaussian Splatting die Welt durch eine riesige Wolke aus dreidimensionalen, weichen "Gauß-Glocken" (3D Gaussians).

    Jeder einzelne Punkt (Splat) besitzt:
  • Eine 3D-Position (x, y, z).
  • Eine Größe und Streckung in drei Achsen (Skalierungs-Ellipsoid).
  • Eine Rotation im Raum (Splatting-Ausrichtung).
  • Einen Farbwert (RGB) inklusive Opazität (Alpha/Transparenz).
  • Optional aber für Fotoralisismus wichtig: Mathematische Richtungsdaten (Spherical Harmonics), abgekürzt SH, wodurch sich die Farbe je nach Blickwinkel leicht verändert. So werden Reflexionen und Glanz simuliert.

    Beim Rendern wird jede dieser weichen Kugeln flach auf den 2D-Bildschirm projiziert (das namensgebende Splatting, ähnlich wie ein Schneeball, der an einer Wand zerplatzt) und erzeugt einen sanften Farbverlauf von innen nach außen.

    Wer hats erfunden?

    Es wurde bereits in den 1990er Jahren von Lee Westover erfunden, bzw. als "Splatting" eingeführt, verschwand dann in der Schubladen, u.a. weil die Rechenleistung noch nicht so weit war. Und im August 2023 wurde es von einem Forscherteam um Bernhard Kerbl, Georgios Kopanas, Thomas Leimkühler und George Drettakis (Inria / Université Côte d'Azur) zusammen mit Rahul Dhakad (Max-Planck-Institut) wiederentdeckt, um weitere sehr wichtige Elemente ergänzt auf der renommierten Computergrafik-Messe SIGGRAPH vorgestellt.

    Wie entsteht so eine Szene?

    Mit einer klassischen Kamera fertigst du z.B. tausende Fotos von Objekten an. Diese werden analysiert daraus die Punktwolken gerechnet. Manchmal nimmt man auch lidar zur Hilfe, rechnet aus Videos und Bewegung und zieht auch KI hinzu. Letztlich werden daraus nun die Splats gerechnet, also durch Pruning all das entfernt, was übereinander liegt und man eh nicht sehen kann. Die Splats werden so im Raum angeordnet, gedreht, überlappt etc, dass eine fotorealisistische Szene entsteht, die sich aus jedem Blickwinkel betrachten lässt. Also so wird die Szenerie Schritt für Schritt aufgeräumt und das Ergebnis dann sortiert. Zudem wird erkannt, ob es spiegelnde, transparente oder opake Oberflächen gibt, irisierende Farben wie bei Schmetterlingsflügel, Seifenblasen, Ölfilm oder Prachtkäfern, queteleschen Ringen. Man kann also je nach Bedarf mehr oder weniger nötige auch Information über Sphärische Harmonische (SH) sichern. Und das bringt besondere Realitätsnähe. In diesem Fall blickwinkelabhängige Farbeffekte (Anisotropie), Inferenzen, Beugung, Lichtstreckung. Gerade SH dritter Ordnung bringen diese Möglichkeiten der Darstellung:

  • Irisieren (Schillern): Das klassische Farbenspiel auf Perlmutt (Muschelinnenseiten), Pfauenfedern oder den Panzern von bestimmten Insekten (z. B. Rosenkäfern). Die Farbe ändert sich hierbei drastisch, sobald sich die Kamera minimal bewegt.
  • Glanzlichter und Spiegelungen (Specular Highlights): Hochfrequente Lichtreflexionen auf metallischen Oberflächen (wie poliertem Chrom, Gold oder Aluminium), glatten Kunststoffen sowie nassen oder lackierten Objekten (z. B. Autolack).
  • Glas- und Brechungseffekte: Das scharfkantige Funkeln und die Lichtbrechung an geschliffenen Edelsteinen, Diamanten, Kristallgläsern oder Wassertropfen.
  • Streuungseffekte an feinen Strukturen: Das Schimmern von Spinnweben im Gegenlicht oder das matte, samtartige Leuchten von bestimmten Pflanzenblättern und Blütenblättern (oft bedingt durch mikroskopisch kleine Härchen oder Wachsschichten).
  • Gonioptische Oberflächenphänomene: Das winkelabhängige Reflexionsverhalten von modernen strukturierten Materialien wie Carbonfasern, gebürstetem Metall oder holografischen Folien (z. B. Sicherheitsmerkmale auf Geldscheinen und Kreditkarten).

    Die 0. Ordnung (ein einzelner Koeffizient) speichert nur eine feste, richtungsunabhängige Grundfarbe (diffuse Reflexion). Erst die 3. Ordnung liefert insgesamt 16 Koeffizienten pro Farbkanal (insgesamt 48 Parameter). Diese mathematischen Schwingungen auf der Kugeloberfläche erlauben es dem Algorithmus, komplexere hochfrequente Helligkeits- und Farbänderungen exakt abzubilden, wenn sich der Blickwinkel der Kamera verändert. Sphärische Harmonische (SH) funktionieren wie eine Fourier-Transformation, jedoch nicht auf einer flachen Linie, sondern auf der Oberfläche einer Kugel.

    Braucht das nicht normalerweise sehr viel Speicherplatz?

    Ja. Die Wahl der SH-Ordnung ist der wichtigste Hebel für die finale Dateigröße eines Gaussian Splatting Modells. Da eine Szene oft aus Millionen von einzelnen Gaussians besteht, multipliziert sich jeder einzelne Parameter millionenfach.

    Ein einzelner 3D-Gaussian Splat benötigt für seine geometrischen Eigenschaften (Position, Skalierung, Rotation, Opazität) standardmäßig 14 Parameter. Schauen wir uns an, wie die SH-Koeffizienten die Gesamtgröße pro Gaussian verändern (unter Annahme von Standard-32-Bit-Fließkommazahlen / 4 Byte pro Parameter):

    SH-Ordnung	SH-Parameter (RGB)	Geometrie-Parameter	Gesamtparameter		Bytes pro Gaussian Splat
    Ordnung 0	3			14			17			68 Bytes
    Ordnung 1	3 + 9 = 12		14			26			104 Bytes
    Ordnung 2	12 + 15 = 27		14			41			164 Bytes
    Ordnung 3	27 + 21 = 48		14			62			248 Bytes
    

    Das Praxisbeispiel (Szene mit 5 Millionen Gaussians): Mit Ordnung 0 (keine winkelabhängige Farbe) wiegt die unkomprimierte Datei ca. 340 MB. Mit Ordnung 3 (für Irisieren und Glanz) wiegt dieselbe Szene stolze 1,24 GB.

    Der Sprung zur dritten Ordnung verdreifacht also die benötigten Farbparameter im Vergleich zur 2. Ordnung fast. Deshalb nutzen viele Echtzeit-Anwendungen in der Praxis Kompressionsverfahren (wie Quantisierung) oder stoppen standardmäßig bei Ordnung 1 oder 2, außer die Szene lebt explizit von komplexen Lichteffekten.

    Warum nutzt du für Szenen ein eigenes Dateiformat "fsplat", statt einfach splat, gsplat, ksplat, spz, ply, pop, sog, gltg, glb oder andere Formate? Noch eine neue Suppe?!?!

    KISS Prinzip und weil es hier um die Darstellung geht. Eine .splat Datei, also das von Kevoin Kwok entwickelte Original-Format, ist bereits ein sehr brauchbares Format. Es enthält keine Header und keine Metadaten. Die Datei besteht ausschließlich aus einer fortlaufenden Kette von 32 Byte Blöcken (ein Block pro Gauß Splat). Vorsortiert und Optimiert. Sie ist also sehr gut und technisch einfach zu verarbeiten.

    Die Daten selbst sind verlustbehaftet gespeichert. quantisiert. Die Datei hat exakt die Struktur, die auch eine GPU erwartet. Es gibt kein Entpacken und kein Parsen. Die Bytes können direkt im Grafikspeicher landen. Eine .splat Datei ist im Grund also ein reines, lineares Binär-Array. Die Daten sind exakt so angeordnet, wie man sie braucht: Für das korrekte Rendering von transparenten Objekten müssen Splats von hinten nach vorne (bezogen auf die Kamera) gezeichnet werden. Die Daten in .splat files sind daher oft vorsortiert nach Cluster-Logik oder Wichtigkeit, damit bereits die wichtigsten Strukturen angezeigt werden können, während der Rest noch läd.

    Anstattt 32bit floats für jeden Wert zu nutzen, werden die Daten komprimiert. Spherical Harmonics werden dabei komplett entfernt oder auf die Basis-Farbe nullter Ordnung reduziert. Die verbleibende RGB-Farbe wird in ein kompaktes 8-Bit Format (uint8) umgewandelt. Rotation und Skalierung werden ebenfalls in kleinere Ganzzahlformate (8bit oder 16bit) gequetscht, da das mesnchliche Auge minimale Abweichungen bei der Ellipsoid Form gar nicht wahrnimmt.

    Eine .splat file ist im Grunde also das, was C-Entwickler unter einem Raw Binary Dump verstehen. Keine komplexen Header, keine Kompression. Rein Wiederholung desselben Datenblocks. Die Komprimierung der gesamten Datei macht man vorher, also gzip der gesamten Datei, .splat.gz und kann sie daher auch separat prozessieren. Praktisch ist das ebenso ideal beim Ausliefern statischer Files, da ja browser und webserver .gz files im Hintergrund transparent verwenden können.

    splat Dateikopf Format
    keiner
    splat Daten-Format
    Offset	Datentyp	Beschreibung
    0-11	float[3]	Position (x,y,z) je 4 Bytes standard IEEE 754 float
    12-23	float[3]	Skalierung sx,sy,sz des Ellipsoids je 4 bytes
    24-27	uint8_t[4]	Farbe und Opazität rgba 8bit Farbwerte 0-255
    28-31	uint8_t[4]	Rotation Quaternion qx,qy,qz,qw
    

    Das einzige Manko einer original splat file ist also: Es fehlen die SH. Nur die Grundfarbe des Objekst ist als RGB Wert gespeichert, daher kann keine blickwinkelabhängige Farbe beim Rendering berücksichtig werden... und gerade diese blickwinkelabhängigkeit schafft ja normalerweise bei Gaussian Splatting erst die Realitätsnähe (Spiegelungen im Glas, irisierende Effekte etc).

    Generell bietet das originale splat Format jedoch bereits perfekte Streaming-Eigenschaften und Unix-Pipeline-Tauglichkeit. Da die Punkte unstrukturiert sind - sprich, kein Punkt muss wissen, wer sein Nachbar ist (im Gegensatz zu klassischer 3D Polygon-Grafik aus Dreiecken) - kann man die Datei als reinen Datenstrom betrachten. Man kann sie von vorne nach hinten lesen, mitten im Stream abbrechen oder beliebig umsortieren, ohne dass die Pipeline kollabiert. Das macht es ideal für den Streaming-Ansatz in reinem C.

    Aus diesem Grund setzt meine Eigenentwicklung fsplat als Dateiformat auch auf dem splat Format auf. Die 3 Farb-Byte RGB pro Splat habe ich dabei jedoch komplett entfernt und durch ein einziges Byte (sh_idx) ersetzt. Die echten 48 SH Werte für die Farben liegen jetzt ausschließlich ganz am Anfang im Codebook im Dateiheader. Wenn wir nun eine .splat Datei nur die Grundfarbe der 0. Ordnung hat) nach .fsplat konvertieren, setzen wir im Codebook die restlichen 45 Koeffizienten (die Ordnungen 1,2 und 3) auf exakt 0.0f.

    Der eigentliche Datenteil ist damit nun schlanker, und die Datei wird kleiner, erhält aber weiterhin die relevante Information. Damit die 0.0f Bytes aus der Datei exakt an die richtige Stelle im RAM kommen, muss das Struct im C-Renderer zwingend ein festes Array aus 48 Floats (192 Bytes) sein.

    Ich habe die Datenstruktur also einfach ergänzt um einen 1 Byte Index für einen SH-Codebook-Lookup (Vektorquantisierung) und der Datei einen header mit dem codebook zugeteilt. Die fsplat Datei sieht also im Gegensatz zu einer splat Datei nun so aus und ist auch deutlich kompakter, dennoch bleibt die Komplexität überschaubar:

    fsplat Dateikopf Format
    Offset	Datentyp	Beschreibung
    0-3	uint8		Anzahl Muster N: Gibt an, wieviele Codebook Einträge folgen (standard 256)
    4-49155	float[256][48]	SH-Codebook 256x48 IEEE 754 Floats (insgesamt 49.152 Bytes). Enthält je 16 Koeffizienten für den r, g und b Kanal. Bei Konvertierung aus .splat sind die Koeffizienten 1-15 pro Kanel mit 0.0f Nullbytes gefüllt. Was sich auch sehr gut komprimieren lässt.
    49156 - 49159		Anzahl Splats M: Gibt an, wieviele FSplat Datenblöcke im Anschluss folgen
    
    fsplat Daten-Format
    0-11	float[3]	Position (x,y,z) je 4 Bytes standard IEEE 754 float
    12-23	float[3]	Skalierung sx,sy,sz des Ellipsoids je 4 bytes
    24	uint8		Transparenz/Alpha: Der Dichtewert des Splat 0 bis 255
    25-28	uint8_t[4]	Rotation Quaternion qx,qy,qz,qw
    29	uint8		Muster-Index sh_idx der in das FSplat SH-Wörterbuch 8bit (0-255) auf das zugehörige Lichtmuster im Header-Codebook. (statt direkte Angabe von RGB-Bytes)
    

    Was macht deine Codebook Lookup-Funktion?

    Nun, die Idee ist Folgende: Bei einer Szene gibt es für unterschiedliche Splats häufig ähnlich Änderungen von Farbwechseln aus unterschiedlichem Blickwinkel. Man kann also im Vorfeld bereits die komplette Szene auf deren Vorkommen analysieren und diese Vorkommen in einer Formel abbilden. Dann sammelt man nun diese Formeln in einem Codebook, nicht mehr die einzelnen Farben. In unserem Fall hat das Codebook 255 mögliche Slots für unterschiedliche Formeln. Und statt der vollständigen SH Informationen speichern wir nun zu jedem Splat nicht mehr die Farbe, sondern nur dieses eine Byte, dass auf die entsprechende Formel im Codebook verweist. Dann kann der Renderer diese Formeln beim Start passend zur Szenerie bereits vorberechnen und hat dann zur Laufzeit eine sehr schnelle Lookup-Table, um die passende Farbe zu finden. Wiederum je nach Blickwinkel. Für die meisten Szenen funktioniert das hinreichend. Eine Szene müsste extrem komplexe Unterschiede aufweisen, damit wir mehr als 255 Formeln bräuchten. Man muss also pro Splat nur 48 Einzelwerte zu je 3 Byte speichern, sondern man braucht nur insgesamt ein Byte mehr pro Splat.

    Das Tool fsplat braucht also als Eingabeformat auch fsplat files.

    Daher gibt es ja auch meine zwei Converter Tools, die die Farbvorkommen in der Szenerie auch analysieren.

    Und wenn man mehr als 256 Formeln für eine Szene braucht?

    Generell wäre es auch möglich, wiederum weitere Codebooks zu hinterlegen, dann brauchen wir aber pro splat ein weiteres byte, um auch das codebook zu referenzieren. Das ist aber nicht nötig. Es ist dann der elegantere Ansatz, mehrere szenenfiles zu erzeugen und im renderer pro szenenfile ein neues codebook und auch andere speicherbereiche anzusprechen, wenn man szenen über threads verteilt. Das ist hier so realisiert. Mehrere oder größere Codebooks müssen also nicht Bestandteil des .fsplat files sein. Grund: Im header der splatfiles habe ich auch die Anzahl der in der Szene enthaltenen splats definiert. Das hat den Vorteil, dass man mehrere Szenenfiles nun konkatenieren kann und damit ja auch der Beginn einer neuen Szene und neue Codebücher erkennbar bleibt, weil wir wissen, wann eine Szene "endet". Mehrere Szenenfiles werden also sequentiell gelesen und jede bringt bei Bedarf wieder eigene codebücher mit. Und alle wirken zusammen in einem gemeinsamen virtuellen 3D Raum den der Renderer dafür aufbaut. Soweit das Konzept. Man könnte Szenen also einfach nach belieben aufteilen, ohne Chunks o.ä, durch geschicktes vorsortieren der eigentlichen szenen files, vor dem konkatenieren. Dennoch bleibt alles streambar und extern komprimierbar.

    Desweiteren ist 256 ja nur das obere Ende. Du kannst auch einige kleinere Szenen erstellen, die jeweils nur 64 Slots belegen etc. Es wirkt ja alles auf den gemeinsamen virtuellen 3D Raum

    Damit verlagere ich auch sinnvolle Aufgaben zum Preprocessing. Mittel separater Skripte und Tools kann man also im Vorfeld Szenen erst einmal alle analysieren, aufteilen, zerteilen, die jeweils sinnvollste SH Tabellen erkennen und in separaten .fsplat-files hinterlegen. Da es einzelne Files sind, kann man sie auch einzeln prüfen, betrachten etc, oder eben in Summe konkateniert und als stream auswerten, erst on demand laden etc.

    Hast du das Rad dabei nicht neuerfunden? Welche Konkurrenzformate existieren?

    Mir ist kein Format bekannt, dass die gleiche Einfachheit wie splat beibehält, dennoch als Ganzes mit beliebigen Komprimierungstools packbar bleibt und dennoch SH abbilden kann. Daher das fsplat Format.

    Folgende Formate sind geläufig:

    .ply ist eigentlich ein klassisches Datenformat für 3D Punktwohlen. Im Kontext von Gaussian Splatting speichert es jeden Splat als einen Punkt mit einer Vielzahl von Attributen: Position (x,y,z) also das Zentrum des Ellipsois im 3D Raum. Die Skalierung (Sx,Sy,Sz), also die Ausdehnung des Splats in drei Richtungen und meist als Log-Skala gespeichert. Die Rotation (qw,qx,qz,qz), also ein Quaternion, das die Ausrichtung des Ellipsoids im Raum beschreibt, die Opazität alpha, was als Dichte oder Durchsichtigkeit des Splats vor der Aktivierungsfunktion zu verstehen ist, sowie die Spherical Harmonics (SH-Koeffinzienten), ein Satz von meist 3 bis zu 45 Werten pro RGB Farbkanel. Diese mathematischen Koeffizienten sorgen dafür, dass sich ie Farbe des Splats je nach Blickwinkel des Betrachters ändert. Für photorealisistische Spiegelungen, Lichteffekte und ist das essentiell. Vorteil des Formats ist, dass es keine Informationsverluste gibt. Um die Originalszene also zu speichern ist das ein gutes Format. Die Datein sind dann aber riesig und für ein Streaming ungeeignet.

    .pop (Popcorn Splats) Progressive Octree Points ist ein Ansatz hierarchischer Speicherung. Die Splats werden in einer Baumstruktur (Octree) organisiert. Vorteil ist, dass es für sehr große Szenen gut geeinet iest. Wenn die Kamera weit weg ist, werden nur wenige Splats gerendert, Kommt man näher, werden die restlichen für diesen spezifischen Bereich geladen. Das spart ernom Grafikspeicher. Die Daten sind nicht linear gespeichert, sondern in einer Baumstruktur. Der Datei-Header enthält Zeiger oder Indizes auf verschienende Knoten im 3D Raum. Jeder Knoten enthält eine Teilmenge der Splats für ein bestimmtes Detaillevel (LOD). Das reine Einlesen der Datei in dern Speicher ist zwar mit wenigen fread Befehlen getan, aber die Verarbeitung ist komplex. Die Baumstruktur muss im Speicher verwaltet werden, Pointer Arithmetik für die Child-Nodes und zur Laufzeit muss algorithmisch entschieden werden, welche Äste des Baums gerade geladen oder gezeichnet werden müssen. Komplexer Code. Komplexität ist der Feind.

    .gltg mit .glb ist ein Standard der Khronos Group. Aber er ist komplex. gltf basiert auf JSON für die Szenenstruktur, und binäre Puffer für die eigentlichen Geometriedaten. Man benötigt einen JSON Partner, muss verschachtelte Logik von Szenen, Nodes, Meshes etc. auflösen, um an die eigentlichen Splat Rohdaten zu kommen. Das bringt bloat und Abhängigkeit - und Komplexität ist der Freind.

    .spz ist SplatZip von Niantic Scaniverse und sehr komplex aber auch stark komprimiert. Im Gegensatz zu .splat files liegen die Daten nicht als Kette von Blöcken vor, stattdessen nutzt es ein planare, kanalorientiertes layout. Alle X Koordinaten liegen hintereinander, alle Rotationen hintereinander, alle Farben hintereinander. Positionen werden auf 24 bit Ganzzahlen heruntergefbrochem Rotationen in ein normalisiertes Quaternion gequetscht und SH werden komprimiert. Die neuste Version des Formats (spz4) teilt die Kanäle auch in sechs unabhängige Streams auf, die parallel mit zstd kompromiert sind. Vorab liefert ein kleiner Datei-Header (als Table of Contents) die Offsets für diese Streams. Man braucht also zwingend einen ZSTD Dekomprimierungsroutine um die Daten im RAM zu entpacken und kann dafür nicht einfach ein bestehendes Entpacker-Programm in der pipeline vorwegschalten. Mittels Bit-Shifting werden die quantisierten Bit-Weerte (die 24 Bit Ganzzahlen) wieder in Standard 32 Bit Floats umgerechnet, damit die GPU was damit anfangen kann.

    .ksplat ist darauf optimiert, dass Szenen bereits während des Downloads progressiv gerendert werden können. Es kommt ein JSON artiger oder binärer header zum Einsatz, der die Metadaten zur Szene enthält. Dann werden die Splats in räumliche Cluster (blöcke) aufgeteilt und anstatt absoluter 3D Koordinaten, speichert der Block Header eine Basis-Position im Raum. Die einzelnen Splats innerhalb des Blocks speichern nur noch ein kleineres komprimiertes Vektor-Offset, also realtiv zur Basis-Position. Für die Rotationen wird ein cleverer Trick genutzt, man muss die größte Komponenten nicht speichern, da sie berechnet werden kann. Es werden nur die 3 kleinsten Komponenten in einem extrem dichten Bitfeld (10+10+10 Bits+2 Bits für den Index) abgelegt. Das Format ist zwar stark komprimiert und platzsparende, aber alles in allem verlangt das eine Menge algorithmischer Logik auf der CPU. Header parsen, Chunks durchlaufen, relative Positionierung mathematisch auflösen, das Entpacken der Rotationen erfordert komplexe Bit Maskierungen und das ales macht es fehleranfällig und komplex. Und Komplexität ist der Feind.

    .gsplat nutzt moderne Quantisierung und Clustering. Es behält reduzierte Spherical Harmonics (Vektortabellen) bei, berechnet statt Rotation/Skalierung oft direkt die 3D-Kovarianzmatrizen im Voraus und besitzt einen dedizierten Header zur Konfiguration der Streams. Es kommt dem Ansatz schon sehr nahe, definiert aber 2 bytes für einen SH-Index. Das gsplat-Format verlagert also diese Berechnung vom Shader in den Export-Prozess. Es speichert die resultierende Matrix direkt ab und muss nur 6 statt 9 Kovarianzwerte im Datenstrom unterbringen. Problem dabei: Durch die Vorausberechnung ist auch das nachträgliche verändern der Splats erschwert, wenn nicht gleich unmöglich. Beim Rendering kommt die Zwicker Projektion zum Einsatz. Zudem ist der Header deutlich aufwendiger konstruiert.

    .sog (Spatially Ordered Gaussians) ist ein hochentwickeltes, extrem komprimiertes Open-Source-Format, das ursprünglich auf einer Forschungsarbeit des Fraunhofer HHI (ECCV 2024) basiert und von PlayCanvas für das Web standardisiert wurde. Es transformiert 3D-Geometriedaten in 2D-Bildtexturen (meistens WebP). Dadurch können moderne Bildkompressions-Algorithmen genutzt werden, was zu einer 95-prozentigen Reduktion der Dateigröße (bis um den Faktor 20 kleiner als ply) führt. sog sortiert alle Gaussians im Raum nach einer raumfüllenden Kurve (Morton-Code / Z-Kurve) oder einem 2D-Gitter, sodass 3D-Nachbarn auch in der Datei nebeneinander liegen. Diese sortierten Werte werden dann pixelweise in 2D-Bildkanäle codiert. Jedes Pixel (x, y) in den Texturen beschreibt einen Gaussian Splat. Eine .sog-Datei ist im Wesentlichen ein komprimiertes ZIP-Archiv, bestehend aus einer meta.json, means_l.webp, means_u.webp, quats.webp, scales.webp, sh0.webp, etc. also mehrerer aufeinander abgestimmten verlustfreien WebP-Bildern besteht. Die Struktur ist somit viel zu aufwendig und nichts für einen einfachen Parser.

    Mein Ansatz ist daher die Datenhaltung in .ply Files, das vorherige Konvertieren in .fsplat Files zur eigentlichen Nutzung. fsplat ist dabei als guter Kompromiss gewählt, um SH zu erhalten und dennoch keine komplexe Parser zu benötigen.

    Wohin geht die Reise?

    Gaussian Splats ist eine zukunftsweisende und vielversprechende Strategie.

    Derzeitige Entwicklung geht auch in Richtung 4DGS, also auch Spatial Animations, Bewegungsinformationen und Video in der fotorealisitischen 3D-Szenerie. Ganz ohne Polygone. Durch die Techniken WebGPU wird es einfach im Browser nutzbar.

    radsplat ist zudem performancetechnisch beeindruckend.