Mecanismo de renderizado 3DGS y gestión de la memoria de vídeo
Este documento describe cómo entran los datos en la memoria de vídeo para participar en el renderizado, junto con las reglas de asignación y los límites de capacidad de la memoria de vídeo. Para el ajuste de los parámetros de imagen, consultar Ajustes visuales. Para las operaciones de carga, consultar Inicio rápido.
Terminología: la unidad mínima de carga y renderizado después de dividir los datos se denomina nodo (Node). La precisión de cada nodo se identifica por su Level: el Level 0 tiene la precisión más alta, y un Level mayor implica menor precisión. Estas definiciones coinciden con las de Parámetros de rendimiento.
Renderizado por bloques y renderizado de carga completa
Una vez cargados los datos, hay dos formas de renderizarlos: cargar los nodos visibles a demanda (renderizado por bloques), o cargar todo el conjunto de datos de una vez (renderizado de carga completa). El comportamiento predeterminado difiere según el pipeline:
| Pipeline | Formatos | Modo predeterminado |
|---|---|---|
| LCC2 | .lcc2 / .ply / .spz / .sog | Renderizado por bloques |
| LCC | .lcc | Renderizado de carga completa; recurre al renderizado por bloques cuando los datos superan el umbral |
Renderizado por bloques
Los datos se dividen en nodos espaciales, y cada nodo contiene varios Levels. Cada fotograma se procesa así:
Recorrido (filtrar los nodos visibles según la posición y la orientación de la cámara, elegir un Level para cada nodo)
│
▼
Carga (leer los datos de los nodos seleccionados en memoria)
│
▼
Subida (enviar los datos a la memoria de vídeo)
│
▼
Renderizado
Los nodos cercanos usan un Level bajo, los lejanos usan un Level alto, y los nodos no visibles no se cargan. Un nodo que sale de la vista no se libera de inmediato; permanece en la caché para poder reutilizarse directamente cuando vuelve a entrar en la vista. Solo cuando falta memoria de vídeo se expulsan los nodos usados menos recientemente, según condiciones como el último momento de renderizado. El uso de memoria de vídeo depende principalmente del radio visible actual, y por eso se pueden admitir escenas grandes.
Los dos pipelines envían los dibujados de forma distinta:
| Pipeline | Envío de dibujado |
|---|---|
| LCC | Cada nodo se envía como un dibujado independiente, por lo que las draw calls crecen con el número de nodos |
| LCC2 | Los datos de todos los nodos visibles se combinan en un único Buffer y se envían en un solo dibujado |
Costes inherentes al renderizado por bloques:
- A los nodos adyacentes se les pueden asignar Levels distintos, por lo que la densidad y la nitidez difieren en el límite y los bordes de los nodos se hacen visibles
- Los Levels cambian continuamente a medida que se mueve la cámara, lo que puede producir saltos de detalle
Renderizado de carga completa
Se omite el paso de filtrado de nodos basado en la cámara. Todo el conjunto de datos se carga de una vez y permanece residente en memoria de vídeo, por lo que mover la cámara ya no provoca recorrido ni selección de Level.
Ventajas:
- La escena completa usa un solo Level, por lo que no hay diferencia de Level entre nodos ni bordes de nodo visibles
- Los datos permanecen residentes en memoria de vídeo, por lo que mover la cámara no provoca cargas, liberaciones ni saltos de detalle
- Se elimina la sobrecarga de recorrido y subida por fotograma, lo que da una tasa de fotogramas más estable
El coste es que todos los datos permanecen residentes en memoria de vídeo, y el uso crece de forma lineal con el tamaño de la escena, por lo que esto solo es adecuado para escenas pequeñas.
Criterios del renderizado de carga completa
El uso del renderizado de carga completa se determina en el momento de la carga mediante el interruptor Use Full Load y el número de splats de Level 0:
Load(archivo de datos)
│
▼
Leer el número de splats de Level 0
│
▼
¿Use Full Load activado? ── No ──▶ Renderizado por bloques
│ Sí
▼
¿Número de splats ≤ Full Load Splat Number? ── No ──▶ Renderizado por bloques
│ Sí
▼
Renderizado de carga completa
Ambas propiedades se encuentran en la categoría Performance del panel Details del Actor:
| Propiedad | Descripción |
|---|---|
| Use Full Load | Si se permite el renderizado de carga completa |
| Full Load Splat Number | Límite superior del número de splats de Level 0 que permite el renderizado de carga completa (unidad: 10.000, predeterminado 1500) |
Los valores predeterminados difieren según el tipo de Actor:
| Actor | Valor predeterminado de Use Full Load | Motivo |
|---|---|---|
| ALCCActor | Activado | Las diferencias de Level entre nodos son evidentes y los bordes de los nodos son visibles, y cada nodo cuesta una draw call, por lo que el renderizado de carga completa se ve mejor en escenas pequeñas |
| ALCC2Actor / APlyActor / ASpzActor / ASogActor | Desactivado | El cambio de Level realiza una transición continua según el error en espacio de pantalla y los bordes de los nodos son poco perceptibles, y solo se necesita una draw call, por lo que la carga a demanda ahorra más memoria de vídeo por defecto |
Cuándo activar manualmente el renderizado de carga completa en LCC2
El pipeline LCC2 desactiva el renderizado de carga completa por defecto. Activarlo manualmente en estos casos:
- La escena tiene un tamaño limitado, la memoria de vídeo es suficiente y tanto la calidad de imagen como la tasa de fotogramas deben mantenerse estables
- La cámara se mueve rápido o se teletransporta con frecuencia, la carga de nodos no puede seguir el ritmo y aparecen huecos o saltos de detalle
- Presentaciones de nivel de producto, grabaciones de pantalla, renderizado de secuencias y otros casos en los que los cambios de imagen durante la carga son inaceptables
- Las diferencias de densidad en los límites de los nodos siguen siendo visibles
Si la memoria de vídeo se queda justa o la tasa de fotogramas cae después de activarlo, la escena ha superado el rango para el que resulta adecuado el renderizado de carga completa, por lo que conviene volver al renderizado por bloques.
Nota: el renderizado de carga completa sigue estando sujeto a la distancia máxima de renderizado. No se muestra nada cuando la distancia entre la cámara y los datos supera ese valor.
Presupuesto de memoria de vídeo y límites de capacidad
El pipeline LCC2 combina los datos de splats residentes en un único Buffer para el renderizado, por lo que la cantidad de datos que puede cargar un mismo modelo depende de los límites de capacidad de esos Buffer.
Estructura del buffer y coste por splat
Los datos se almacenan en dos Buffer:
| Buffer | Coste por splat | Contenido |
|---|---|---|
| Buffer de datos gaussianos base | 40 bytes | Posición, rotación, color, escala |
| Buffer de armónicos esféricos | 48 bytes (hasta la banda 3) | Coeficientes de armónicos esféricos, asignado solo para los datos que los incluyen |
Límite absoluto de un único buffer
Unreal registra los tamaños de Buffer como enteros de 32 bits, lo que da un límite teórico de 4 GB. El plugin usa en realidad 4095 MB (1 MB menos que 4 GB), y el margen reservado evita que los cálculos de bytes se desborden durante la subida. Este límite se aplica a cada Buffer por separado y no se puede aumentar mediante ajustes.
Ajuste del presupuesto de memoria de vídeo
La memoria de vídeo disponible para un mismo modelo se controla con LCC2 GPU Memory Budget (MB) en ProjectSettings > Plugins > LCC4Unreal:
| Elemento | Valor |
|---|---|
| Predeterminado | 2048 MB |
| Rango ajustable | 2048 ~ 8192 MB |
| Cuándo surte efecto | Reiniciar el editor después de cambiarlo |
El presupuesto es el total de ambos Buffer, no el límite de uno solo. Por tanto, los datos con armónicos esféricos cuentan como 88 bytes por splat (40 + 48), y los datos sin ellos cuentan como 40 bytes.
Coste adicional del buffer de ordenación
Antes de renderizar el 3DGS, los splats se ordenan según su distancia a la cámara. El resultado de la ordenación depende de la vista, por lo que cada vista necesita su propio conjunto de Buffer de ordenación. Esta parte no se incluye en el presupuesto anterior: cada splat residente cuesta 16 bytes adicionales por vista (dos copias de la clave de ordenación y del índice, usadas para lecturas y escrituras alternas durante la ordenación).
Con el presupuesto de 2048 MB y datos que incluyen armónicos esféricos, pueden permanecer residentes unos 24,4 millones de splats, y el Buffer de ordenación de una sola vista ocupa unos 372 MB.
El ojo izquierdo y el derecho del renderizado estéreo, cada jugador en pantalla partida y cada SceneCapture cuentan como una vista. Los Buffer de ordenación crecen a medida que crece el número de vistas, y no se reducen de nuevo cuando el número de vistas disminuye, por lo que conviene reservar memoria de vídeo para el número máximo de vistas que se haya alcanzado.
Límites de carga de los formatos de archivo único
Los formatos de archivo único son .ply / .spz / .sog, es decir, formatos que guardan todo el conjunto de datos en un solo archivo, sin subdivisión en nodos ni información de Level. Estos datos no se pueden cargar por partes: el plugin solo puede decodificarlos como un único nodo en una pasada y mantenerlos residentes por completo en memoria de vídeo, por lo que existe un límite definido del número de splats. .lcc y .lcc2 ya completan la subdivisión en nodos en el momento de la exportación y no son formatos de archivo único.
Cómo se calcula el límite
El plugin calcula el límite antes de la carga y rechaza los datos que lo superan:
| Presupuesto | Sin armónicos esféricos | Con armónicos esféricos |
|---|---|---|
| 2048 MB (predeterminado) | Unos 53 millones | Unos 24,4 millones |
| 4096 MB | Unos 107 millones | Unos 44,7 millones |
| 8192 MB | Unos 107 millones | Unos 44,7 millones |
El límite es el menor de las tres condiciones siguientes, y el presupuesto de memoria de vídeo es solo una de ellas:
| Condición | Descripción |
|---|---|
| Presupuesto de memoria de vídeo | El presupuesto dividido por los bytes por splat (40 B sin armónicos esféricos, 88 B con ellos) |
| Límite de un único Buffer | 4095 MB divididos por el stride máximo (40 B sin armónicos esféricos, 48 B con ellos) |
| Capacidad del array de armónicos esféricos | Se aplica solo con armónicos esféricos; el resultado decodificado usa índices de 32 bits, lo que da un límite de unos 44,7 millones |
Por eso el número deja de crecer más allá de 4096 MB: sin armónicos esféricos se aplica el límite de un único Buffer, y con ellos se aplica la capacidad del array de armónicos esféricos. Además, la memoria de vídeo realmente disponible en la tarjeta gráfica reduce aún más la cantidad que se puede cargar.
Nota: problema conocido. El ajuste del presupuesto acepta valores de hasta 8192 MB, pero la cantidad que se puede cargar en los formatos de archivo único ya alcanza su límite en 4096 MB, por lo que aumentarlo más no tiene efecto y no genera ningún mensaje. Cuando los datos siguen superando el límite con un presupuesto de 4096 MB, convertirlos a
.lcc2o pasar a datos sin armónicos esféricos. Esta limitación se mejorará en una versión posterior.
Qué ocurre al superar el límite
Cuando los datos de archivo único superan el límite, la carga se rechaza y el Output Log muestra el número real de splats, el límite actual y el número de splats admitido con el presupuesto máximo. Tratarlo en este orden de preferencia:
- Convertir a
.lcc2para que el plugin cargue a demanda en lugar de mantener todo residente. - Aumentar LCC2 GPU Memory Budget a 4096 MB y volver a intentarlo después de reiniciar. Esto solo ayuda cuando el presupuesto es el cuello de botella actual; aumentarlo a 8192 MB no tiene efecto en los formatos de archivo único.
- Pasar a datos sin armónicos esféricos, lo que reduce los bytes contabilizados en el presupuesto de 88 a 40 por splat.
Cambiar a .sog o .spz no ayuda a superar el límite: la compresión de estos formatos solo se aplica al archivo en disco, y cada splat ocupa exactamente la misma cantidad de memoria de vídeo después de decodificarse.
El formato LCC2 no está sujeto a este límite
.lcc2 divide los datos en varios nodos, y cada nodo se decodifica y se sube por separado. Cualquier nodo individual queda muy por debajo de los límites anteriores, por lo que la cantidad total de datos no tiene un límite absoluto y puede superar con holgura el presupuesto de memoria de vídeo. Lo que sí queda restringido son dos cantidades en tiempo de ejecución:
| Cantidad restringida | Origen del límite | Comportamiento al superarlo |
|---|---|---|
| Splats residentes a la vez | Presupuesto de memoria de vídeo y límite de un único Buffer | Los nodos usados menos recientemente se expulsan y se intercambian |
| Splats renderizados en un fotograma | Unos 89 millones (4095 MB ÷ 48 B) | Los nodos lejanos se descartan por distancia |
La cantidad renderizada por fotograma también está limitada por Max Splat Num en el Actor, y se aplica el menor de los dos valores.
Gestión y ajuste de la memoria de vídeo
Liberación automática cuando falta memoria de vídeo
En tiempo de ejecución, el plugin supervisa continuamente el uso real de memoria de vídeo de la tarjeta gráfica. Cuando el uso supera el porcentaje establecido en Max GPU Usage Percentage For Release de ProjectSettings, los nodos se clasifican mediante una combinación del último momento de renderizado, la frecuencia de acceso, el tamaño de los datos y el Level, y se liberan primero los usados menos recientemente. Los nodos de Level alto están protegidos para evitar huecos grandes cuando la vista se gira rápidamente.
Ajustes relacionados:
| Ajuste | Descripción |
|---|---|
| Max GPU Usage Percentage For Release | Porcentaje de uso de memoria de vídeo que provoca la liberación automática (50 ~ 100%) |
| GPU Release Percentage | Porcentaje de datos liberados en cada activación (10 ~ 100%) |
| LCC2 GPU Memory Budget (MB) | Presupuesto de memoria de vídeo para los datos de splats de un mismo modelo |
Diagnosticar y ajustar un presupuesto insuficiente
Un presupuesto demasiado pequeño no provoca un fallo de carga; en su lugar, los datos se intercambian repetidamente. Síntomas habituales:
- Aparecen huecos mientras la cámara se mueve o gira, y se rellenan gradualmente al detenerse
- La misma zona se vuelve nítida y borrosa repetidamente mientras la vista oscila de un lado a otro
- Una zona local permanece borrosa y no se vuelve nítida ni al acercarse
- La tasa de fotogramas fluctúa según la cantidad de contenido a la vista y cae notablemente en vistas abiertas
- El comportamiento es normal con la escena estática, pero se producen tirones al moverse
La falta de nitidez local se confunde fácilmente con una calidad deficiente de los datos. Cuando los datos de Level bajo no se pueden cargar a tiempo, el plugin rellena el hueco con los datos de Level alto que ya están en memoria de vídeo para que no falte ninguna parte de la imagen, y los sustituye una vez que los datos de Level bajo están listos. Cuando la falta de memoria de vídeo provoca la expulsión repetida de los datos de Level bajo, esa zona permanece en un Level alto. La comprobación consiste en ver si se vuelve nítida al acercarse: normalmente uno o dos segundos bastan para completarla, y si no cambia nunca, los datos de Level bajo no logran entrar en memoria de vídeo.
Confirmación: consultar el coste previsto del modelo que se muestra en el Output Log en el momento de la carga y compararlo con el presupuesto actual. Si el coste previsto de un mismo modelo se acerca al presupuesto o lo supera, el espacio residente es insuficiente. El número de liberaciones también se puede observar con stat LCC; un valor que sigue creciendo indica expulsiones frecuentes.
Aumentar el presupuesto solo funciona si la tarjeta gráfica tiene margen. El presupuesto es únicamente el límite superior que solicita el plugin; en cuanto supera la memoria de vídeo realmente disponible en la tarjeta, el controlador traslada los datos a la memoria del sistema, y la caída de la tasa de fotogramas es peor que el intercambio. Seguir este orden:
- Confirmar la memoria de vídeo disponible en la tarjeta. Después de añadir el coste del Buffer de ordenación, el presupuesto debe dejar margen para el motor en sí y para otros recursos.
- Aumentar el presupuesto de forma progresiva (2048 → 4096 → 8192) y comprobar después de cada reinicio si mejoran los huecos y la tasa de fotogramas.
- Si la mejora es pequeña, el cuello de botella no es el presupuesto, así que reducir en su lugar el volumen de datos: bajar Max Splat Num, activar el límite de Max Distance o pasar a datos sin armónicos esféricos.
Nota: el presupuesto se calcula de forma independiente para cada modelo. Cuando se colocan varios LCC Actors en una escena, el uso de memoria de vídeo se acumula con el número de modelos, por lo que conviene reducir el presupuesto por modelo en consecuencia.