Meccanismo di rendering 3DGS e gestione della memoria video
Questo documento descrive come i dati entrano nella memoria video per partecipare al rendering, insieme alle regole di allocazione e ai limiti di capacità della memoria video. Per la regolazione dei parametri dell'immagine, vedere Impostazioni visive. Per le operazioni di caricamento, vedere Avvio rapido.
Terminologia: la più piccola unità di caricamento e rendering ottenuta dalla suddivisione dei dati si chiama nodo (Node). La precisione di ogni nodo è identificata dal suo Level: il Level 0 ha la precisione più alta e un Level maggiore indica precisione minore. Queste definizioni corrispondono a quelle di Parametri di prestazione.
Rendering a blocchi e rendering a caricamento completo
Una volta caricati i dati, esistono due modi per renderizzarli: caricare i nodi visibili su richiesta (rendering a blocchi) oppure caricare l'intero dataset in una sola volta (rendering a caricamento completo). Il comportamento predefinito varia per pipeline:
| Pipeline | Formati | Modalità predefinita |
|---|---|---|
| LCC2 | .lcc2 / .ply / .spz / .sog | Rendering a blocchi |
| LCC | .lcc | Rendering a caricamento completo; ripiega sul rendering a blocchi quando i dati superano la soglia |
Rendering a blocchi
I dati vengono suddivisi in nodi spaziali e ogni nodo contiene diversi Level. Ogni fotogramma viene elaborato così:
Traversal (filtra i nodi visibili in base a posizione e orientamento della camera, scegliendo un Level per ogni nodo)
│
▼
Load (legge i dati dei nodi selezionati in memoria)
│
▼
Upload (invia i dati alla memoria video)
│
▼
Render
I nodi vicini usano un Level basso, quelli lontani un Level alto e i nodi non visibili non vengono caricati. Un nodo che esce dalla vista non viene rilasciato immediatamente: resta nella cache per poter essere riutilizzato direttamente quando rientra nella vista. Solo quando la memoria video si esaurisce i nodi usati meno recentemente vengono rimossi, in base a criteri come l'ultimo istante di rendering. L'uso della memoria video dipende principalmente dall'area visibile corrente, ed è per questo che si possono gestire scene di grandi dimensioni.
Le due pipeline inviano i draw in modo diverso:
| Pipeline | Invio dei draw |
|---|---|
| LCC | Ogni nodo viene inviato come draw separato, quindi le draw call crescono con il numero di nodi |
| LCC2 | I dati di tutti i nodi visibili vengono uniti in un unico Buffer e inviati con un solo draw |
Costi intrinseci del rendering a blocchi:
- Nodi adiacenti possono ricevere Level diversi, quindi densità e nitidezza differiscono al confine e i bordi dei nodi diventano visibili
- I Level cambiano continuamente durante il movimento della camera, con possibile comparsa improvvisa dei dettagli
Rendering a caricamento completo
Il passo di filtraggio dei nodi in base alla camera viene saltato. L'intero dataset viene caricato in una sola volta e resta residente in memoria video, quindi muovere la camera non innesca più traversal né selezione dei Level.
Vantaggi:
- Tutta la scena usa un solo Level, quindi non ci sono differenze di Level tra nodi né bordi dei nodi visibili
- I dati restano residenti in memoria video, quindi muovere la camera non provoca caricamenti, rilasci o comparse improvvise di dettaglio
- Il costo di traversal e upload per fotogramma viene eliminato, con un frame rate più stabile
Il costo è che tutti i dati restano residenti in memoria video e l'uso cresce linearmente con le dimensioni della scena, quindi è adatto solo a scene di piccole dimensioni.
Criteri del rendering a caricamento completo
L'uso del rendering a caricamento completo viene deciso al momento del caricamento in base all'interruttore Use Full Load e al numero di splat del Level 0:
Load(file dati)
│
▼
Legge il numero di splat del Level 0
│
▼
Use Full Load abilitato? ── No ──▶ Rendering a blocchi
│ Sì
▼
Numero di splat ≤ Full Load Splat Number? ── No ──▶ Rendering a blocchi
│ Sì
▼
Rendering a caricamento completo
Entrambe le proprietà si trovano nella categoria Performance del pannello Details dell'Actor:
| Proprietà | Descrizione |
|---|---|
| Use Full Load | Se il rendering a caricamento completo è consentito |
| Full Load Splat Number | Limite massimo del numero di splat del Level 0 che consente il rendering a caricamento completo (unità: 10.000, predefinito 1500) |
I valori predefiniti variano in base al tipo di Actor:
| Actor | Valore predefinito di Use Full Load | Motivo |
|---|---|---|
| ALCCActor | Abilitato | Le differenze di Level tra i nodi sono evidenti e i bordi dei nodi sono visibili, e ogni nodo costa una draw call, quindi il rendering a caricamento completo appare migliore nelle scene piccole |
| ALCC2Actor / APlyActor / ASpzActor / ASogActor | Disabilitato | Il cambio di Level avviene in modo continuo in base all'errore in spazio schermo e i bordi dei nodi sono poco evidenti, e serve una sola draw call, quindi il caricamento su richiesta risparmia più memoria video per impostazione predefinita |
Quando abilitare manualmente il rendering a caricamento completo su LCC2
La pipeline LCC2 disabilita il rendering a caricamento completo per impostazione predefinita. Abilitarlo manualmente in questi casi:
- La scena ha dimensioni contenute, la memoria video è sufficiente e sia qualità dell'immagine sia frame rate devono restare stabili
- La camera si muove rapidamente o si teletrasporta spesso, il caricamento dei nodi non riesce a stare al passo e compaiono buchi o comparse improvvise di dettaglio
- Presentazioni di livello prodotto, registrazioni dello schermo, rendering di sequenze e altri casi in cui variazioni dell'immagine durante il caricamento non sono accettabili
- Le differenze di densità ai confini dei nodi sono ancora visibili
Se dopo l'abilitazione la memoria video diventa scarsa o il frame rate cala, la scena ha superato l'ambito adatto al rendering a caricamento completo, quindi tornare al rendering a blocchi.
Nota: il rendering a caricamento completo resta soggetto alla distanza massima di rendering. Nulla viene visualizzato quando la distanza tra camera e dati supera tale valore.
Budget e limiti della memoria video
La pipeline LCC2 unisce i dati splat residenti in un unico Buffer per il rendering, quindi la quantità di dati caricabile da un singolo modello dipende dai limiti di capacità di questi Buffer.
Struttura dei Buffer e occupazione per splat
I dati sono memorizzati in due Buffer:
| Buffer | Occupazione per splat | Contenuto |
|---|---|---|
| Buffer dei dati gaussiani di base | 40 byte | Posizione, rotazione, colore, scala |
| Buffer delle armoniche sferiche | 48 byte (fino alla banda 3) | Coefficienti delle armoniche sferiche, allocati solo per i dati che li contengono |
Limite fisso di un singolo Buffer
Unreal registra le dimensioni dei Buffer come interi a 32 bit, con un limite teorico di 4 GB. Il plugin usa effettivamente 4095 MB (1 MB in meno di 4 GB) e il margine riservato evita overflow nei calcoli dei byte durante l'upload. Questo limite si applica a ciascun Buffer separatamente e non può essere aumentato tramite impostazioni.
Impostazione del budget di memoria video
La memoria video disponibile per un singolo modello è controllata da LCC2 GPU Memory Budget (MB) in ProjectSettings > Plugins > LCC4Unreal:
| Voce | Valore |
|---|---|
| Predefinito | 2048 MB |
| Intervallo regolabile | 2048 ~ 8192 MB |
| Applicazione | Riavviare l'editor dopo la modifica |
Il budget è il totale per entrambi i Buffer, non il limite di uno solo. I dati con armoniche sferiche contano quindi 88 byte per splat (40 + 48), quelli senza armoniche 40 byte.
Costo aggiuntivo del Buffer di ordinamento
Prima del rendering 3DGS gli splat vengono ordinati per distanza dalla camera. Il risultato dell'ordinamento dipende dalla vista, quindi ogni vista richiede il proprio insieme di Buffer di ordinamento. Questa parte non è conteggiata nel budget precedente: ogni splat residente costa 16 byte aggiuntivi per vista (due copie ciascuna di chiave di ordinamento e indice, usate per letture e scritture alternate durante l'ordinamento).
Con il budget di 2048 MB e dati con armoniche sferiche possono restare residenti circa 24,4 milioni di splat, e il Buffer di ordinamento di una singola vista occupa circa 372 MB.
L'occhio sinistro e destro del rendering stereo, ogni giocatore in split screen e ogni SceneCapture contano come una vista. I Buffer di ordinamento crescono al crescere del numero di viste e non si riducono quando il numero di viste diminuisce, quindi riservare memoria video per il numero massimo di viste raggiunto.
Limiti di caricamento dei formati a file singolo
I formati a file singolo sono .ply / .spz / .sog, cioè formati che contengono l'intero dataset in un solo file senza suddivisione in nodi né informazioni di Level. Questi dati non possono essere caricati parzialmente: il plugin può solo decodificarli come singolo nodo in un unico passaggio e mantenerli interamente residenti in memoria video, quindi esiste un limite preciso al numero di splat. .lcc e .lcc2 completano già la suddivisione in nodi in fase di esportazione e non sono formati a file singolo.
Come viene calcolato il limite
Il plugin calcola il limite prima del caricamento e rifiuta i dati che lo superano:
| Budget | Senza armoniche sferiche | Con armoniche sferiche |
|---|---|---|
| 2048 MB (predefinito) | Circa 53 milioni | Circa 24,4 milioni |
| 4096 MB | Circa 107 milioni | Circa 44,7 milioni |
| 8192 MB | Circa 107 milioni | Circa 44,7 milioni |
Il limite è il minore delle tre condizioni seguenti, e il budget di memoria video è solo una di esse:
| Condizione | Descrizione |
|---|---|
| Budget di memoria video | Il budget diviso per i byte per splat (40 B senza armoniche sferiche, 88 B con esse) |
| Limite del singolo Buffer | 4095 MB diviso per lo stride massimo (40 B senza armoniche sferiche, 48 B con esse) |
| Capacità dell'array delle armoniche sferiche | Si applica solo con armoniche sferiche; il risultato decodificato usa indici a 32 bit, con un limite di circa 44,7 milioni |
Per questo il numero non cresce oltre 4096 MB: senza armoniche sferiche si applica il limite del singolo Buffer, con esse si applica la capacità dell'array delle armoniche sferiche. Inoltre la memoria video effettivamente disponibile sulla scheda grafica riduce ulteriormente la quantità caricabile.
Nota: problema noto. L'impostazione del budget accetta valori fino a 8192 MB, ma la quantità caricabile per i formati a file singolo raggiunge già il proprio limite a 4096 MB, quindi aumentarlo ulteriormente non ha effetto e non produce messaggi. Quando i dati superano ancora il limite con un budget di 4096 MB, convertirli in
.lcc2o passare a dati senza armoniche sferiche. Questa limitazione verrà migliorata in una versione successiva.
Cosa accade oltre il limite
Quando i dati a file singolo superano il limite, il caricamento viene rifiutato e l'Output Log riporta il numero effettivo di splat, il limite corrente e il numero di splat supportato con il budget massimo. Procedere in questo ordine di preferenza:
- Convertire in
.lcc2per far caricare al plugin su richiesta invece di mantenere tutto residente. - Aumentare LCC2 GPU Memory Budget a 4096 MB e riprovare dopo il riavvio. Questo aiuta solo quando il budget è il collo di bottiglia corrente; portarlo a 8192 MB non ha effetto sui formati a file singolo.
- Passare a dati senza armoniche sferiche, riducendo i byte conteggiati sul budget da 88 a 40 per splat.
Passare a .sog o .spz non aiuta a superare il limite: la compressione di questi formati riguarda solo il file su disco e ogni splat occupa esattamente la stessa quantità di memoria video dopo la decodifica.
Il formato LCC2 non è soggetto a questo limite
.lcc2 suddivide i dati in più nodi e ogni nodo viene decodificato e caricato separatamente. Ogni singolo nodo resta molto al di sotto dei limiti precedenti, quindi la quantità totale di dati non ha un limite fisso e può superare sensibilmente il budget di memoria video. Restano invece vincolate due quantità a runtime:
| Quantità vincolata | Origine del limite | Comportamento al superamento |
|---|---|---|
| Splat residenti contemporaneamente | Budget di memoria video e limite del singolo Buffer | I nodi usati meno recentemente vengono rimossi e scambiati dentro e fuori |
| Splat renderizzati in un fotogramma | Circa 89 milioni (4095 MB ÷ 48 B) | I nodi lontani vengono scartati per distanza |
La quantità renderizzata per fotogramma è limitata anche da Max Splat Num sull'Actor, e vale il minore dei due.
Gestione e ottimizzazione della memoria video
Rilascio automatico quando la memoria video si esaurisce
A runtime il plugin monitora costantemente l'uso effettivo della memoria video della scheda grafica. Quando l'uso supera la percentuale impostata da Max GPU Usage Percentage For Release in ProjectSettings, i nodi vengono ordinati combinando ultimo istante di rendering, frequenza di accesso, dimensione dei dati e Level, e quelli usati meno recentemente vengono rilasciati per primi. I nodi con Level alto sono protetti per evitare grandi buchi quando la vista ruota rapidamente.
Impostazioni correlate:
| Impostazione | Descrizione |
|---|---|
| Max GPU Usage Percentage For Release | Percentuale di uso della memoria video che innesca il rilascio automatico (50 ~ 100%) |
| GPU Release Percentage | Percentuale di dati rilasciati a ogni attivazione (10 ~ 100%) |
| LCC2 GPU Memory Budget (MB) | Budget di memoria video per i dati splat di un singolo modello |
Diagnosticare e correggere un budget insufficiente
Un budget troppo piccolo non provoca errori di caricamento: i dati vengono invece scambiati dentro e fuori ripetutamente. Sintomi comuni:
- Compaiono buchi mentre la camera si muove o ruota, che si riempiono gradualmente dopo l'arresto
- La stessa area si alterna tra nitido e sfocato mentre la vista oscilla avanti e indietro
- Un'area locale resta sfocata e non diventa nitida nemmeno avvicinandosi
- Il frame rate oscilla con la quantità di contenuto in vista e cala visibilmente nelle viste aperte
- Il comportamento è normale a scena statica, ma si verificano scatti in movimento
La sfocatura locale viene facilmente confusa con dati di qualità scadente. Quando i dati a Level basso non possono essere caricati in tempo, il plugin colma il vuoto con i dati a Level alto già presenti in memoria video, così nessuna parte dell'immagine risulta mancante, e li sostituisce quando i dati a Level basso sono pronti. Quando la memoria video insufficiente causa la rimozione ripetuta dei dati a Level basso, quell'area resta a Level alto. La verifica consiste nel controllare se diventa nitida avvicinandosi: normalmente uno o due secondi bastano per il riempimento e, se non cambia mai, i dati a Level basso non riescono a entrare in memoria video.
Conferma: controllare il costo previsto del modello riportato nell'Output Log al caricamento e confrontarlo con il budget corrente. Se il costo previsto di un singolo modello si avvicina o supera il budget, lo spazio residente è insufficiente. Il numero di rilasci può essere osservato anche con stat LCC; un valore che continua a crescere indica rimozioni frequenti.
Aumentare il budget funziona solo se la scheda grafica ha margine. Il budget è solo il limite superiore richiesto dal plugin; una volta superata la memoria video effettivamente disponibile sulla scheda, il driver sposta i dati nella memoria di sistema e il calo di frame rate è peggiore dello scambio dentro e fuori. Procedere nel seguente ordine:
- Verificare la memoria video disponibile sulla scheda. Dopo aver aggiunto il costo del Buffer di ordinamento, il budget deve lasciare margine per il motore stesso e per le altre risorse.
- Aumentare il budget gradualmente (2048 → 4096 → 8192) e controllare dopo ogni riavvio se buchi e frame rate migliorano.
- Se il miglioramento è ridotto, il collo di bottiglia non è il budget, quindi ridurre invece il volume dei dati: abbassare Max Splat Num, abilitare il limite Max Distance o passare a dati senza armoniche sferiche.
Nota: il budget viene calcolato in modo indipendente per ogni modello. Quando in una scena sono presenti più LCC Actor, l'uso della memoria video si accumula con il numero di modelli, quindi ridurre di conseguenza il budget per modello.