Respuesta corta: qué logra 3D Gaussian Splatting
3D Gaussian Splatting representa una escena con millones de gaussianas 3D y las dibuja con un rasterizador en GPU. En el dataset Mip-NeRF360 renderiza a 134 fps frente a 0.06 de Mip-NeRF360, con calidad parecida (PSNR 27.21 frente a 27.69) y 41m33s de entrenamiento contra 48 horas (Kerbl et al., 2023).
Motivación: por qué 3D Gaussian Splatting busca otra forma de renderizar campos de radiancia
La síntesis de nuevas vistas (novel-view synthesis) parte de varias fotos de una escena y genera la imagen que vería una cámara en una posición que nadie fotografió. Desde NeRF, la forma dominante de hacerlo es un campo de radiancia (radiance field): una función continua que dice qué color y qué densidad hay en cada punto del espacio.
Para dibujar una imagen con NeRF se lanza un rayo por cada píxel y se consulta esa función en muchos puntos del rayo. Ese muestreo, llamado ray-marching, es caro y mete ruido. El método con mejor calidad cuando salió el paper, Mip-NeRF360, "requires up to 48 hours of training time", según los autores.
Los métodos rápidos, como InstantNGP (que guarda rasgos en una tabla hash) y Plenoxels (que usa una rejilla de vóxeles sin red neuronal), bajan el entrenamiento a minutos. El costo es la calidad, y su renderizado se queda en tiempos interactivos de 10 a 15 cuadros por segundo según la escena. Según los autores, para escenas completas y abiertas ningún método lograba tiempo real a 1080p.
Bernhard Kerbl, Georgios Kopanas, Thomas Leimkühler y George Drettakis (Inria, Université Côte d'Azur y Max-Planck-Institut für Informatik) publicaron en 2023, en ACM Transactions on Graphics, una alternativa. En lugar de una función continua, representan la escena con millones de gaussianas 3D explícitas y las dibujan con un rasterizador, como en un videojuego. La pregunta del paper es si así se conserva la calidad de Mip-NeRF360 sin pagar sus horas de entrenamiento ni sus segundos por cuadro.
Cómo funciona 3D Gaussian Splatting, paso a paso
La entrada son fotos de una escena estática y las cámaras calibradas con Structure-from-Motion (SfM), el proceso que estima desde dónde se tomó cada foto. SfM produce, de paso, una nube de puntos dispersa. 3D Gaussian Splatting crea una gaussiana 3D en cada uno de esos puntos. En el caso sintético de Blender también funciona con un inicio aleatorio.
La primitiva: una gaussiana 3D
Cada gaussiana tiene una posición (media $\mu$), una matriz de covarianza $\Sigma$ que define su forma de elipsoide, una opacidad $\alpha$ y unos coeficientes de armónicos esféricos (spherical harmonics, SH) que dan su color según desde dónde la mires. Su valor en el espacio es $G(x) = e^{-\frac{1}{2}(x)^T\Sigma^{-1}(x)}$, la campana de siempre en tres dimensiones. Para dibujarla, se proyecta a la pantalla con $\Sigma' = J W \Sigma W^T J^T$, donde $W$ es la transformación de la cámara y $J$ el jacobiano de la proyección. El resultado es una mancha elíptica en el plano, un splat.
Cómo se pinta un píxel
Los autores parten de una observación: NeRF y el renderizado por puntos usan el mismo modelo de formación de imagen. El color de un píxel se arma mezclando, de adelante hacia atrás, los splats que lo cubren. Es la ecuación (3) del paper:
$$C = \sum_{i \in \mathcal{N}} c_i \alpha_i \prod_{j=1}^{i-1} (1 - \alpha_j)$$
- $C$: el color final del píxel.
- $\mathcal{N}$: el conjunto ordenado de gaussianas que cubren el píxel, de la más cercana a la más lejana.
- $c_i$: el color de la gaussiana $i$ visto desde esa cámara, calculado con sus armónicos esféricos.
- $\alpha_i$: la gaussiana proyectada en la pantalla evaluada en el píxel, multiplicada por la opacidad aprendida.
- $\prod_{j=1}^{i-1} (1 - \alpha_j)$: la transmitancia, la fracción de luz que dejan pasar las gaussianas de enfrente.
La intuición: si una gaussiana cercana es opaca, la transmitancia cae a casi cero y las de atrás ya no aportan. Si es transparente, deja ver lo que hay detrás. La diferencia con NeRF no es la fórmula, es el algoritmo: aquí no hay que buscar muestras al azar a lo largo del rayo, porque las gaussianas ya están en la lista.
Una covarianza que el gradiente no puede romper
Una covarianza solo tiene sentido físico si es semidefinida positiva. El descenso de gradiente no respeta esa condición por sí solo, así que los autores no optimizan $\Sigma$ directo. La escriben como un elipsoide, ecuación (6):
$$\Sigma = R S S^T R^T$$
- $S$: matriz diagonal de escala, guardada como un vector 3D $s$ (el tamaño del elipsoide en cada eje).
- $R$: matriz de rotación, guardada como un cuaternión $q$ que se normaliza en cada paso.
La intuición: cualquier $s$ y cualquier $q$ dan una covarianza válida. Si una escala se hace muy pequeña, la gaussiana se aplana como un disco y sirve para representar superficies; si las tres son iguales, es una esfera. Los autores derivan todos los gradientes a mano (apéndice A) para no pagar el costo de la diferenciación automática.
Qué se optimiza y con qué pérdida
En cada iteración se renderiza una vista de entrenamiento, se compara con la foto real y se ajustan posiciones, covarianzas, opacidades y colores con Adam. La pérdida es la ecuación (7):
$$\mathcal{L} = (1 - \lambda)\mathcal{L}_1 + \lambda\mathcal{L}_{\text{D-SSIM}}$$
- $\mathcal{L}_1$: el error absoluto promedio píxel por píxel.
- $\mathcal{L}_{\text{D-SSIM}}$: un término basado en SSIM, que mide la similitud estructural (bordes, texturas) y no solo el color de cada píxel.
- $\lambda$: el peso entre los dos; los autores usan 0.2 en todas sus pruebas.
La intuición: con $\lambda$ en cero solo importa el color exacto; al subirlo, la optimización cuida más que la textura se vea igual.
Control adaptativo de densidad
La nube de SfM es escasa, así que hay que crear gaussianas. Cada 100 iteraciones, 3D Gaussian Splatting revisa las que tienen un gradiente de posición en pantalla grande (mayor a 0.0002). Si la gaussiana es pequeña, la clona: falta geometría. Si es grande, la divide en dos y reduce su escala por un factor de 1.6: una sola mancha cubre demasiado. Además elimina las casi transparentes y, cada 3000 iteraciones, pone las opacidades cerca de cero para limpiar manchas flotantes.
El rasterizador por mosaicos
La rapidez viene del rasterizador. Divide la pantalla en mosaicos (tiles) de 16×16 píxeles y descarta las gaussianas fuera de la cámara. A cada copia de gaussiana por mosaico le asigna una llave de hasta 64 bits: los 32 bits bajos guardan la profundidad y los altos el número de mosaico. Un solo radix sort en la GPU ordena todo a la vez. Luego cada mosaico mezcla sus gaussianas de adelante hacia atrás y se detiene cuando el píxel se satura. A diferencia de trabajos previos, no limita cuántas gaussianas reciben gradiente.
Ejemplo visual: velocidad y calidad de 3D Gaussian Splatting
La primera pregunta es la velocidad. La Tabla 1 del paper reporta los cuadros por segundo (FPS) al renderizar las escenas del dataset Mip-NeRF360. Las dos barras azules son 3D Gaussian Splatting tras 7K y 30K iteraciones de entrenamiento. Mip-NeRF360 aparece casi en cero: 0.06 cuadros por segundo.
La segunda pregunta es si esa velocidad cuesta calidad. La gráfica compara el PSNR (peak signal-to-noise ratio, en decibeles: más alto es más parecido a la foto real) en los tres datasets reales. Contra Mip-NeRF360, 3D Gaussian Splatting pierde un poco en su dataset, gana en Tanks&Temples y empata en Deep Blending.
Resultados de 3D Gaussian Splatting frente a Mip-NeRF360, InstantNGP y Plenoxels
Los autores evaluaron 13 escenas reales: todas las del dataset Mip-NeRF360, dos de Tanks&Temples y dos de Deep Blending. Separaron una de cada 8 fotos para prueba, como propone Mip-NeRF360. Usaron los mismos hiperparámetros en todo y midieron en una GPU A6000, excepto Mip-NeRF360. Los números de los otros métodos salen de correr el código de sus autores, salvo las métricas de Mip-NeRF360 en su propio dataset, copiadas de su paper.
Calidad y tiempo en las escenas reales
En el dataset Mip-NeRF360, 3D Gaussian Splatting con 30K iteraciones obtiene SSIM de 0.815, PSNR de 27.21 y LPIPS de 0.214. Mip-NeRF360 reporta 0.792, 27.69 y 0.237. Es decir, gana en SSIM y LPIPS (donde menos es mejor) y queda abajo en PSNR. La diferencia está en el costo: 41m33s de entrenamiento y 134 FPS frente a 48h y 0.06 FPS.
Ese tiempo de 48h de Mip-NeRF360 es una equivalencia: lo entrenaron en un nodo de 4 GPU A100 durante 12 horas. En Tanks&Temples, 3D Gaussian Splatting alcanza PSNR de 23.14 frente a 22.22 de Mip-NeRF360, en 26m54s. En Deep Blending, 29.41 frente a 29.40, en 36m2s.
Contra los métodos rápidos, la versión de 7K iteraciones es la comparación justa. En el dataset Mip-NeRF360 entrena en 6m25s y logra PSNR de 25.60 a 160 FPS. InstantNGP Big tarda 7m30s, logra 25.59 y renderiza a 9.43 FPS. Plenoxels tarda 25m49s, logra 23.08 y va a 6.79 FPS. Según los autores, con más entrenamiento 3D Gaussian Splatting llega a la calidad de Mip-NeRF360, algo que los otros métodos rápidos no logran.
Escenas sintéticas y tamaño del modelo
En el dataset sintético de Blender, 3D Gaussian Splatting arranca con 100K gaussianas aleatorias, sin SfM. Su PSNR promedio es 33.32, frente a 33.30 de Point-NeRF, 33.18 de InstantNGP Base y 33.09 de Mip-NeRF. Las escenas entrenadas se renderizan de 180 a 300 FPS.
La memoria es el costo visible: el modelo de 30K iteraciones ocupa 734MB en el dataset Mip-NeRF360, frente a 48MB de InstantNGP Big y 8.6MB de Mip-NeRF360.
Qué aporta cada pieza
La Tabla 3 quita un componente a la vez en tres escenas (Truck, Garden y Bicycle). Con el método completo el PSNR promedio tras 30K iteraciones es 26.05. Iniciar con puntos aleatorios lo baja a 20.42, quitar la división de gaussianas a 23.90 y usar gaussianas isotrópicas (esferas) a 25.23. El caso extremo es limitar a 10 las gaussianas que reciben gradiente por píxel, como hacían métodos previos: el promedio cae a 19.19 y en Truck se pierden 11dB.
Limitaciones de 3D Gaussian Splatting: qué no prueba el paper
- Artefactos donde faltan fotos. Los autores reconocen manchas alargadas, gaussianas "splotchy" y saltos visuales (popping) cuando se crean gaussianas grandes, sobre todo en zonas poco observadas. No aplican regularización, y para escenas muy grandes, como las urbanas, dicen que hay que bajar la tasa de aprendizaje.
- Mucha memoria. El pico de memoria de GPU al entrenar escenas grandes puede pasar de 20 GB en su prototipo. Para renderizar hay que cargar el modelo completo (cientos de MB) más de 30 a 500 MB del rasterizador. NeRF guarda la escena en mucho menos espacio.
- Comparaciones con matices. En el dataset de Mip-NeRF360, el PSNR de Mip-NeRF360 sigue arriba. Sus métricas en ese dataset son copiadas y su hardware es otro (A100). La Tabla 1 mide FPS a la resolución nativa de cada dataset, sin desglosar resoluciones, así que la promesa de tiempo real a 1080p del resumen no se puede revisar fila por fila.
- Alcance. Solo trata escenas estáticas, todos los números son de los propios autores y las ablaciones usan tres escenas.
Qué significa para ti: cómo aprovechar 3D Gaussian Splatting hoy
Si trabajas con visión por computadora, reconstrucción 3D o contenido para web y realidad virtual, 3D Gaussian Splatting es una opción práctica para pasar de fotos a una escena navegable en tiempo real. Los autores publicaron su código y sus datos en la página del proyecto (repo-sam.inria.fr/fungraph/3d-gaussian-splatting). Está escrito en Python con PyTorch, más kernels de CUDA para el rasterizador, así que necesitas una GPU NVIDIA.
El flujo es parecido al de NeRF: tomas fotos alrededor de la escena, calibras las cámaras con una herramienta de SfM y entrenas. La diferencia está en el resultado: no es una red neuronal, es una lista de gaussianas con posición, forma, opacidad y color. Eso se puede guardar, recortar y renderizar sin consultar un modelo.
Las dos ideas centrales caben en pocas líneas de NumPy. Este código arma una covarianza válida a partir de una escala y un cuaternión (ecuación 6) y mezcla dos gaussianas ordenadas para obtener el color de un píxel (ecuación 3):
import numpy as np
def covarianza(escala, q):
"""Sigma = R S S^T R^T a partir de un vector de escala y un cuaternión (ecuación 6)."""
w, x, y, z = q / np.linalg.norm(q) # se normaliza para tener una rotación válida
R = np.array([
[1 - 2 * (y * y + z * z), 2 * (x * y - w * z), 2 * (x * z + w * y)],
[2 * (x * y + w * z), 1 - 2 * (x * x + z * z), 2 * (y * z - w * x)],
[2 * (x * z - w * y), 2 * (y * z + w * x), 1 - 2 * (x * x + y * y)],
])
S = np.diag(escala)
return R @ S @ S.T @ R.T
def mezcla_alfa(colores, alfas):
"""Color de un píxel con gaussianas ya ordenadas de adelante hacia atrás (ecuación 3)."""
C, T = np.zeros(3), 1.0 # T: luz que todavía pasa
for c, a in zip(colores, alfas):
C += T * a * c
T *= 1 - a
if T < 1e-4: # el píxel ya se saturó: se deja de mezclar
break
return C
Sigma = covarianza(np.array([0.5, 0.05, 0.05]), np.array([0.92, 0.0, 0.38, 0.0]))
print(np.linalg.eigvalsh(Sigma)) # siempre >= 0: covarianza válida
print(mezcla_alfa([np.array([1.0, 0.2, 0.2]), np.array([0.2, 0.2, 1.0])], [0.6, 0.9]))
Los valores propios de la covarianza son las escalas al cuadrado, así que nunca son negativos, sin importar qué haga el optimizador. El color del píxel mezcla la gaussiana roja de enfrente con la azul de atrás según cuánta luz deja pasar la primera.
Lo que no cambia: si tu restricción es espacio en disco o una GPU con poca memoria, un método tipo NeRF o InstantNGP sigue siendo más ligero. Y si tu escena se mueve, este paper no lo cubre.