3DGS-Rendering-Mechanismus und Verwaltung des Grafikspeichers
Dieses Dokument beschreibt, wie Daten in den Grafikspeicher gelangen und am Rendering teilnehmen, samt der Zuweisungsregeln und Kapazitätsgrenzen des Grafikspeichers. Zur Anpassung der Bildparameter siehe Bildeinstellungen. Zu den Ladevorgängen siehe Schnellstart.
Begriffe: Die kleinste Einheit von Laden und Rendering nach der Aufteilung der Daten heißt Node. Die Genauigkeit jedes Nodes wird über sein Level angegeben: Level 0 hat die höchste Genauigkeit, ein größeres Level bedeutet geringere Genauigkeit. Diese Definitionen entsprechen denen in Leistungsparameter.
Chunk-Rendering und Rendering mit vollständigem Laden
Nach dem Laden der Daten gibt es zwei Wege, sie zu rendern: sichtbare Nodes bei Bedarf laden (Chunk-Rendering) oder den gesamten Datensatz auf einmal laden (Rendering mit vollständigem Laden). Das Standardverhalten unterscheidet sich je Pipeline:
| Pipeline | Formate | Standardmodus |
|---|---|---|
| LCC2 | .lcc2 / .ply / .spz / .sog | Chunk-Rendering |
| LCC | .lcc | Rendering mit vollständigem Laden; fällt auf Chunk-Rendering zurück, wenn die Daten den Schwellwert überschreiten |
Chunk-Rendering
Die Daten werden in räumliche Nodes aufgeteilt, und jeder Node enthält mehrere Levels. Jedes Bild wird folgendermaßen verarbeitet:
Traversierung (sichtbare Nodes nach Kameraposition und -ausrichtung filtern, je Node ein Level wählen)
│
▼
Laden (Daten der gewählten Nodes in den Arbeitsspeicher lesen)
│
▼
Upload (Daten in den Grafikspeicher übergeben)
│
▼
Rendering
Nahe Nodes verwenden ein niedriges Level, ferne Nodes ein hohes Level, und unsichtbare Nodes werden nicht geladen. Ein Node, der das Blickfeld verlässt, wird nicht sofort freigegeben; er bleibt im Cache und kann direkt wiederverwendet werden, wenn er erneut ins Blickfeld kommt. Erst wenn der Grafikspeicher knapp wird, werden die am längsten nicht genutzten Nodes verdrängt, anhand von Kriterien wie dem letzten Rendering-Zeitpunkt. Der Grafikspeicherbedarf hängt vor allem vom aktuell sichtbaren Bereich ab, weshalb große Szenen unterstützt werden können.
Die beiden Pipelines übergeben Draws unterschiedlich:
| Pipeline | Übergabe der Draws |
|---|---|
| LCC | Jeder Node wird als eigener Draw übergeben, die Draw Calls wachsen also mit der Anzahl der Nodes |
| LCC2 | Die Daten aller sichtbaren Nodes werden in einem einzigen Buffer zusammengefasst und in einem Draw übergeben |
Inhärente Kosten des Chunk-Renderings:
- Benachbarte Nodes können unterschiedliche Levels erhalten, dadurch unterscheiden sich Dichte und Schärfe an der Grenze und Node-Ränder werden sichtbar
- Levels wechseln fortlaufend, während sich die Kamera bewegt, was zu Detail-Popping führen kann
Rendering mit vollständigem Laden
Der Schritt der kamerabasierten Node-Filterung entfällt. Der gesamte Datensatz wird auf einmal geladen und bleibt im Grafikspeicher resident, Kamerabewegungen lösen also keine Traversierung und keine Level-Auswahl mehr aus.
Vorteile:
- Die gesamte Szene nutzt ein einziges Level, es gibt keine Level-Unterschiede zwischen Nodes und keine sichtbaren Node-Ränder
- Die Daten bleiben im Grafikspeicher resident, Kamerabewegungen verursachen also kein Laden, kein Freigeben und kein Detail-Popping
- Der Aufwand für Traversierung und Upload pro Bild entfällt, was die Bildrate stabiler macht
Der Preis: Alle Daten bleiben im Grafikspeicher resident, und der Bedarf wächst linear mit der Szenengröße, was nur für kleine Szenen geeignet ist.
Kriterien für das Rendering mit vollständigem Laden
Ob mit vollständigem Laden gerendert wird, entscheidet sich beim Laden anhand des Schalters Use Full Load und der Splat-Anzahl auf Level 0:
Load(Datendatei)
│
▼
Splat-Anzahl auf Level 0 lesen
│
▼
Use Full Load aktiviert? ── Nein ──▶ Chunk-Rendering
│ Ja
▼
Splat-Anzahl ≤ Full Load Splat Number? ── Nein ──▶ Chunk-Rendering
│ Ja
▼
Rendering mit vollständigem Laden
Beide Eigenschaften liegen im Details-Panel des Actors unter der Kategorie Performance:
| Eigenschaft | Beschreibung |
|---|---|
| Use Full Load | Ob Rendering mit vollständigem Laden erlaubt ist |
| Full Load Splat Number | Obergrenze der Splat-Anzahl auf Level 0, die vollständiges Laden erlaubt (Einheit: 10.000, Standard 1500) |
Die Standardwerte unterscheiden sich je Actor-Typ:
| Actor | Standard für Use Full Load | Grund |
|---|---|---|
| ALCCActor | Aktiviert | Level-Unterschiede zwischen Nodes sind deutlich und Node-Ränder sichtbar, und jeder Node kostet einen Draw Call, deshalb sieht Rendering mit vollständigem Laden in kleinen Szenen besser aus |
| ALCC2Actor / APlyActor / ASpzActor / ASogActor | Deaktiviert | Der Level-Wechsel geht über den Screen-Space-Fehler stufenlos ineinander über und Node-Ränder sind kaum sichtbar, außerdem ist nur ein Draw Call nötig, deshalb spart das bedarfsgesteuerte Laden standardmäßig mehr Grafikspeicher |
Wann bei LCC2 vollständiges Laden manuell aktiviert wird
In der LCC2-Pipeline ist das Rendering mit vollständigem Laden standardmäßig deaktiviert. In diesen Fällen manuell aktivieren:
- Die Szene ist begrenzt groß, der Grafikspeicher reicht aus, und Bildqualität sowie Bildrate müssen stabil bleiben
- Die Kamera bewegt sich schnell oder springt häufig, das Laden der Nodes kommt nicht mit, und es treten Löcher oder Detail-Popping auf
- Produktpräsentationen, Bildschirmaufnahmen, Sequenz-Rendering und andere Fälle, in denen Bildänderungen während des Ladens nicht akzeptabel sind
- Dichteunterschiede an Node-Grenzen sind weiterhin sichtbar
Wenn nach der Aktivierung der Grafikspeicher knapp wird oder die Bildrate sinkt, hat die Szene den für vollständiges Laden geeigneten Rahmen überschritten; dann zurück zum Chunk-Rendering wechseln.
Hinweis: Auch beim Rendering mit vollständigem Laden gilt die maximale Renderdistanz. Überschreitet der Abstand zwischen Kamera und Daten diesen Wert, wird nichts dargestellt.
Grafikspeicher-Budget und Kapazitätsgrenzen
Die LCC2-Pipeline fasst die residenten Splat-Daten für das Rendering in einem einzigen Buffer zusammen; wie viele Daten ein einzelnes Modell laden kann, hängt daher von den Kapazitätsgrenzen dieser Buffer ab.
Buffer-Struktur und Bedarf pro Splat
Die Daten liegen in zwei Buffern:
| Buffer | Bedarf pro Splat | Inhalt |
|---|---|---|
| Buffer der Gaussian-Basisdaten | 40 Byte | Position, Rotation, Farbe, Skalierung |
| Buffer der Kugelflächenfunktionen | 48 Byte (bis Band 3) | Koeffizienten der Kugelflächenfunktionen, nur für Daten belegt, die sie mitbringen |
Harte Grenze eines einzelnen Buffers
Unreal erfasst Buffer-Größen als 32-Bit-Ganzzahlen, was eine theoretische Grenze von 4 GB ergibt. Das Plugin nutzt tatsächlich 4095 MB (1 MB weniger als 4 GB), und der reservierte Rest verhindert einen Überlauf bei der Byte-Berechnung während des Uploads. Diese Grenze gilt je Buffer separat und lässt sich nicht über Einstellungen erhöhen.
Einstellung des Grafikspeicher-Budgets
Der einem einzelnen Modell verfügbare Grafikspeicher wird über LCC2 GPU Memory Budget (MB) unter ProjectSettings > Plugins > LCC4Unreal gesteuert:
| Punkt | Wert |
|---|---|
| Standard | 2048 MB |
| Einstellbarer Bereich | 2048 ~ 8192 MB |
| Wird wirksam | Nach der Änderung den Editor neu starten |
Das Budget ist die Summe für beide Buffer, nicht die Grenze eines einzelnen. Daten mit Kugelflächenfunktionen zählen daher mit 88 Byte pro Splat (40 + 48), Daten ohne sie mit 40 Byte.
Zusätzlicher Aufwand des Sortier-Buffers
Bevor 3DGS gerendert wird, werden die Splats nach ihrem Abstand zur Kamera sortiert. Das Sortierergebnis hängt von der Ansicht ab, jede Ansicht benötigt daher eigene Sortier-Buffer. Dieser Teil zählt nicht in das obige Budget: Jeder residente Splat kostet zusätzlich 16 Byte pro Ansicht (je zwei Kopien von Sortierschlüssel und Index, für abwechselndes Lesen und Schreiben während der Sortierung).
Mit dem Budget von 2048 MB und Daten mit Kugelflächenfunktionen können rund 24,4 Millionen Splats resident bleiben, und der Sortier-Buffer einer einzelnen Ansicht liegt bei etwa 372 MB.
Linkes und rechtes Auge beim Stereo-Rendering, jeder Spieler im Splitscreen und jedes SceneCapture zählen jeweils als eine Ansicht. Sortier-Buffer wachsen mit der Anzahl der Ansichten und schrumpfen nicht wieder, wenn die Anzahl sinkt; daher Grafikspeicher für die höchste aufgetretene Anzahl an Ansichten einplanen.
Ladegrenzen von Einzeldateiformaten
Einzeldateiformate sind .ply / .spz / .sog, also Formate, die den gesamten Datensatz in einer Datei halten, ohne Node-Aufteilung und ohne Level-Informationen. Solche Daten lassen sich nicht in Teilen laden: Das Plugin kann sie nur als einen einzigen Node in einem Durchgang dekodieren und muss sie vollständig im Grafikspeicher resident halten, es gibt daher eine feste Grenze für die Splat-Anzahl. .lcc und .lcc2 schließen die Node-Aufteilung bereits beim Export ab und sind keine Einzeldateiformate.
Wie die Grenze berechnet wird
Das Plugin berechnet die Grenze vor dem Laden und weist Daten ab, die sie überschreiten:
| Budget | Ohne Kugelflächenfunktionen | Mit Kugelflächenfunktionen |
|---|---|---|
| 2048 MB (Standard) | Etwa 53 Millionen | Etwa 24,4 Millionen |
| 4096 MB | Etwa 107 Millionen | Etwa 44,7 Millionen |
| 8192 MB | Etwa 107 Millionen | Etwa 44,7 Millionen |
Die Grenze ist das Minimum der folgenden drei Bedingungen, das Grafikspeicher-Budget ist nur eine davon:
| Bedingung | Beschreibung |
|---|---|
| Grafikspeicher-Budget | Das Budget geteilt durch die Byte pro Splat (40 B ohne Kugelflächenfunktionen, 88 B mit ihnen) |
| Grenze eines einzelnen Buffers | 4095 MB geteilt durch den maximalen Stride (40 B ohne Kugelflächenfunktionen, 48 B mit ihnen) |
| Kapazität des Arrays der Kugelflächenfunktionen | Gilt nur mit Kugelflächenfunktionen; das dekodierte Ergebnis nutzt 32-Bit-Indizes, was eine Grenze von etwa 44,7 Millionen ergibt |
Deshalb wächst die Zahl über 4096 MB hinaus nicht weiter: ohne Kugelflächenfunktionen greift die Grenze des einzelnen Buffers, mit ihnen die Kapazität des Arrays der Kugelflächenfunktionen. Zusätzlich senkt der tatsächlich auf der Grafikkarte verfügbare Grafikspeicher die ladbare Menge weiter.
Hinweis: Bekanntes Problem. Die Budget-Einstellung nimmt Werte bis 8192 MB an, die ladbare Menge bei Einzeldateiformaten erreicht ihre Grenze aber schon bei 4096 MB; eine weitere Erhöhung hat daher keine Wirkung und erzeugt keine Meldung. Überschreiten Daten bei einem Budget von 4096 MB weiterhin die Grenze, sie nach
.lcc2konvertieren oder auf Daten ohne Kugelflächenfunktionen wechseln. Diese Einschränkung wird in einer späteren Version verbessert.
Was jenseits der Grenze passiert
Überschreiten Einzeldateidaten die Grenze, wird das Laden abgewiesen, und das Output Log gibt die tatsächliche Splat-Anzahl, die aktuelle Grenze und die beim maximalen Budget unterstützte Splat-Anzahl aus. Behandlung in dieser Vorzugsreihenfolge:
- Nach
.lcc2konvertieren, damit das Plugin bedarfsgesteuert lädt statt alles resident zu halten. - LCC2 GPU Memory Budget auf 4096 MB erhöhen und nach einem Neustart erneut versuchen. Das hilft nur, wenn das Budget der aktuelle Engpass ist; eine Erhöhung auf 8192 MB hat bei Einzeldateiformaten keine Wirkung.
- Auf Daten ohne Kugelflächenfunktionen wechseln, wodurch die auf das Budget gerechneten Byte pro Splat von 88 auf 40 sinken.
Ein Wechsel nach .sog oder .spz hilft nicht, die Grenze zu überschreiten: Die Kompression dieser Formate betrifft nur die Datei auf dem Datenträger, und jeder Splat belegt nach dem Dekodieren exakt gleich viel Grafikspeicher.
Das LCC2-Format unterliegt dieser Grenze nicht
.lcc2 teilt Daten in mehrere Nodes auf, und jeder Node wird separat dekodiert und hochgeladen. Ein einzelner Node bleibt weit unter den oben genannten Grenzen, die Gesamtmenge der Daten hat daher keine harte Grenze und kann das Grafikspeicher-Budget deutlich überschreiten. Begrenzt sind zwei Laufzeitgrößen:
| Begrenzte Größe | Ursprung der Grenze | Verhalten bei Überschreitung |
|---|---|---|
| Gleichzeitig residente Splats | Grafikspeicher-Budget und Grenze eines einzelnen Buffers | Die am längsten nicht genutzten Nodes werden verdrängt und ein- und ausgelagert |
| In einem Bild gerenderte Splats | Etwa 89 Millionen (4095 MB ÷ 48 B) | Ferne Nodes werden nach Distanz verworfen |
Die pro Bild gerenderte Menge ist außerdem durch Max Splat Num am Actor begrenzt, der kleinere der beiden Werte greift.
Verwaltung und Optimierung des Grafikspeichers
Automatische Freigabe bei knappem Grafikspeicher
Zur Laufzeit überwacht das Plugin fortlaufend den tatsächlichen Grafikspeicherbedarf der Grafikkarte. Übersteigt der Bedarf den durch Max GPU Usage Percentage For Release in den ProjectSettings gesetzten Prozentwert, werden Nodes anhand einer Kombination aus letztem Rendering-Zeitpunkt, Zugriffshäufigkeit, Datengröße und Level bewertet, und die am längsten nicht genutzten werden zuerst freigegeben. Nodes mit hohem Level werden geschützt, damit beim schnellen Drehen der Ansicht keine großen Löcher entstehen.
Zugehörige Einstellungen:
| Einstellung | Beschreibung |
|---|---|
| Max GPU Usage Percentage For Release | Prozentwert des Grafikspeicherbedarfs, der die automatische Freigabe auslöst (50 ~ 100%) |
| GPU Release Percentage | Prozentanteil der Daten, der bei jedem Auslösen freigegeben wird (10 ~ 100%) |
| LCC2 GPU Memory Budget (MB) | Grafikspeicher-Budget für die Splat-Daten eines einzelnen Modells |
Ein zu kleines Budget erkennen und anpassen
Ein zu kleines Budget führt nicht zu einem Ladefehler, stattdessen werden die Daten fortlaufend ein- und ausgelagert. Häufige Symptome:
- Löcher treten auf, während sich die Kamera bewegt oder dreht, und füllen sich nach dem Anhalten allmählich
- Derselbe Bereich wird beim Hin- und Herschwenken der Ansicht wiederholt scharf und unscharf
- Ein lokaler Bereich bleibt unscharf und wird auch beim Annähern nicht scharf
- Die Bildrate schwankt mit der Menge des sichtbaren Inhalts und sinkt in offenen Ansichten deutlich
- Das Verhalten ist bei statischer Szene normal, ruckelt aber bei Bewegung
Lokale Unschärfe wird leicht für schlechte Datenqualität gehalten. Wenn Daten mit niedrigem Level nicht rechtzeitig geladen werden können, füllt das Plugin die Lücke mit den bereits im Grafikspeicher liegenden Daten hohen Levels, damit kein Bildteil fehlt, und ersetzt sie, sobald die Daten niedrigen Levels bereit sind. Wenn wegen zu wenig Grafikspeicher die Daten niedrigen Levels wiederholt verdrängt werden, bleibt dieser Bereich auf einem hohen Level. Der Test ist, ob er beim Annähern scharf wird: normalerweise genügen eine bis zwei Sekunden zum Auffüllen, und wenn sich nichts ändert, gelangen die Daten niedrigen Levels nicht in den Grafikspeicher.
Bestätigung: Den beim Laden im Output Log ausgegebenen prognostizierten Bedarf des Modells prüfen und mit dem aktuellen Budget vergleichen. Nähert sich der prognostizierte Bedarf eines einzelnen Modells dem Budget oder übersteigt es, reicht der residente Platz nicht aus. Die Anzahl der Freigaben lässt sich außerdem mit stat LCC beobachten; ein fortlaufend steigender Wert deutet auf häufiges Verdrängen hin.
Das Budget zu erhöhen wirkt nur, wenn die Grafikkarte Reserven hat. Das Budget ist lediglich die Obergrenze, die das Plugin anfordert; überschreitet es den tatsächlich auf der Karte verfügbaren Grafikspeicher, lagert der Treiber Daten in den Systemspeicher aus, und der Bildraten-Einbruch ist schlimmer als das Ein- und Auslagern. Folgende Reihenfolge abarbeiten:
- Den verfügbaren Grafikspeicher der Karte prüfen. Nach Hinzurechnen des Aufwands für den Sortier-Buffer sollte das Budget Reserven für die Engine selbst und weitere Ressourcen lassen.
- Das Budget schrittweise erhöhen (2048 → 4096 → 8192) und nach jedem Neustart prüfen, ob sich Löcher und Bildrate verbessern.
- Fällt die Verbesserung gering aus, ist das Budget nicht der Engpass; dann stattdessen die Datenmenge senken: Max Splat Num verringern, die Begrenzung Max Distance aktivieren oder auf Daten ohne Kugelflächenfunktionen wechseln.
Hinweis: Das Budget wird je Modell unabhängig berechnet. Sind mehrere LCC-Actors in einer Szene platziert, summiert sich der Grafikspeicherbedarf mit der Anzahl der Modelle; das Budget pro Modell entsprechend senken.