DEV Community

Cover image for Instancing en WebGL: cómo mostrar 526.000 asteroides sin colapsar
lu1tr0n
lu1tr0n

Posted on Originally published at elsolitario.org

Instancing en WebGL: cómo mostrar 526.000 asteroides sin colapsar

Cargar 526.000 objetos en una escena 3D debería colapsar cualquier navegador: esa cifra multiplicada por una llamada de dibujo por objeto satura a cualquier GPU en el primer fotograma. Space Now, el visualizador de sistema solar del proyecto Belle Lune 2, anuncia ese catálogo (526.000 asteroides y satélites, al 30 de septiembre de 2026) y lo muestra con cámara libre y sin caída de cuadros gracias a una técnica de la GPU conocida como instancing en WebGL.

La misma técnica sostiene bosques de miles de árboles en videojuegos y sistemas de partículas, y ahora catálogos astronómicos enteros dentro de una pestaña del navegador. Entender cómo funciona explica por qué una laptop con GPU integrada mueve más objetos en pantalla que un servidor dedicado a fuerza bruta.

TL;DR

  • El instancing en WebGL agrupa miles de objetos idénticos en una sola llamada de dibujo en vez de una por objeto.- Three.js expone la técnica con InstancedMesh: una geometría, un material y una matriz por instancia.- Space Now anuncia 526.000 asteroides y satélites al 30 de septiembre de 2026 en un único canvas WebGL.- renderer.info.render.calls confirma en consola cuántas llamadas de dibujo emite tu escena.- Una textura de datos reemplaza al buffer de matrices cuando las instancias superan el límite práctico de un atributo.

¿Qué es el instancing en WebGL?

El instancing en WebGL es una técnica de renderizado que pide a la tarjeta gráfica dibujar múltiples copias de una misma geometría, como un asteroide o un satélite, en una sola llamada de dibujo, diferenciando cada copia solo por una matriz de posición, rotación y escala.

La idea central es simple. La CPU sube una sola vez la forma del objeto, por ejemplo los vértices de una esfera o de un icosaedro irregular, y después sube aparte una lista de transformaciones con la posición, el tamaño y la orientación de cada copia. La GPU combina ambas cosas en paralelo y dibuja todo de una vez.

WebGL hereda esta capacidad de OpenGL ES. En WebGL 1 llega vía la extensión ANGLE_instanced_arrays; en WebGL 2 ya es parte del núcleo de la API, sin pedir permiso extra al navegador para usarla.

Por qué importa renderizar así en el navegador

Cada llamada de dibujo (draw call) tiene un costo fijo que no depende del tamaño del objeto. El motor de JavaScript arma el comando, el driver de la GPU lo valida, y el navegador cruza la frontera entre el hilo de JavaScript y el proceso de composición gráfica, y ese costo se paga aunque el objeto sea un cubo de un solo triángulo.

Con un asteroide por llamada, un catálogo de algunas decenas de miles de objetos ya empieza a comerse el presupuesto de un fotograma completo, que en un navegador a 60 cuadros por segundo dura apenas 16,6 milisegundos. El cuello de botella no es la GPU dibujando triángulos: es la comunicación CPU-GPU repetida miles de veces por segundo.

El instancing convierte el problema en uno solo. Reduce todo a una llamada y un cruce de frontera, y deja que la carga de posicionar cientos de miles de copias se resuelva dentro de la GPU, que está diseñada para paralelizar exactamente ese tipo de trabajo.

💭 Clave: subir 526.000 matrices completas en cada fotograma sería casi tan costoso como el problema original; la optimización real está en actualizar solo las matrices que cambiaron, no todo el buffer de golpe.
La franja de asteroides entre Marte y Júpiter concentra la mayoría de los objetos catalogados del sistema solar.

Cómo funciona el renderizado por instancias

En un render normal, cada objeto recorre el pipeline de la GPU con su propia llamada, activando su geometría, subiendo su matriz de transformación y disparando drawArrays o drawElements. Repetir esto 526.000 veces por fotograma es, en la práctica, imposible a 60 cuadros por segundo.

El renderizado por instancias cambia el flujo: la geometría y el material se activan una sola vez, y junto a los atributos normales de cada vértice (posición, normal, UV) se agrega un atributo extra que varía por instancia y no por vértice. Ese atributo suele ser una matriz 4x4 de transformación, guardada en un InstancedBufferAttribute.

Dentro del vertex shader, la GPU multiplica la posición de cada vértice por la matriz de la instancia que le toca, identificada con la variable interna gl_InstanceID. Como resultado, la misma malla de un asteroide se repite 526.000 veces, cada una en su propio lugar, tamaño y orientación, todo dentro de una sola llamada drawArraysInstanced o drawElementsInstanced.

flowchart LR
subgraph SinInstancing["Sin instancing"]
A1["Objeto 1"] --> D1["Draw call 1"]
A2["Objeto 2"] --> D2["Draw call 2"]
A3["Objeto N"] --> D3["Draw call N"]
end
subgraph ConInstancing["Con instancing"]
B1["Geometria + N matrices"] --> D4["1 draw call"]
end
Enter fullscreen mode Exit fullscreen mode
sequenceDiagram
participant JS as JavaScript
participant Buf as Buffer de instancias
participant GPU as GPU
JS->>Buf: escribe una matriz 4x4 por asteroide
Buf->>GPU: sube el buffer completo una vez
JS->>GPU: drawElementsInstanced(count)
GPU->>GPU: multiplica cada vertice por su matriz via gl_InstanceID
GPU-->>JS: un solo draw call renderiza todas las instancias
Enter fullscreen mode Exit fullscreen mode

Ejemplos prácticos y cómo empezar

El ejemplo más simple posible: cinco cubos idénticos en cinco posiciones distintas, todos en una sola llamada de dibujo.

import * as THREE from 'three';

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x8899ff });
const count = 5;
const mesh = new THREE.InstancedMesh(geometry, material, count);

const dummy = new THREE.Object3D();
for (let i = 0; i Las megaconstelaciones de satélites en órbita baja multiplicaron la cantidad de objetos que un rastreador orbital debe dibujar en tiempo real.
## Errores comunes y buenas prácticas

Olvidar marcar `instanceMatrix.needsUpdate = true` después de modificar las matrices es el error más frecuente. Sin ese flag, la GPU sigue usando el buffer viejo y los objetos no se mueven en pantalla aunque el código de posición sea correcto.

Un solo draw call no significa gratis. El vertex shader y el fragment shader igual se ejecutan por cada vértice y cada píxel de cada instancia, así que una geometría con demasiados triángulos por asteroide, multiplicada por 526.000, sigue pesando aunque el conteo de llamadas de dibujo sea uno. La solución habitual es un nivel de detalle bajo para los objetos lejanos o pequeños.

InstancedMesh no aplica frustum culling por instancia de forma automática. Three.js calcula un bounding box que cubre todas las instancias juntas, así que si la cámara mira una fracción del catálogo, igual se procesan todas salvo que el desarrollador implemente su propio descarte, por ejemplo dividiendo el catálogo en una grilla u octree y actualizando solo el buffer de la región visible.

Mezclar materiales transparentes con instancing sin ordenar por profundidad genera artefactos visuales. Los objetos translúcidos se dibujan en el orden del buffer, no en el orden de distancia a la cámara.

Cada matriz de instancia ocupa cuatro vectores de cuatro componentes, 16 floats en total. A partir de cierto volumen, ese atributo empieza a competir por el límite de memoria de vértice de la tarjeta gráfica, y ahí conviene mover las transformaciones a una textura de datos que el shader lee por índice en vez de por atributo fijo.

## Comparativa con alternativas de renderizado masivo

Elegir la técnica correcta depende del volumen de objetos, de si necesitan geometría real con iluminación y de si sus posiciones cambian todo el tiempo o una sola vez al cargar la escena.
TécnicaCuándo usarlaVentajaLimitaciónInstancedMesh (buffer attribute)Hasta cientos de miles de instancias con geometría simpleUn solo draw call, API directa en Three.jsCada matriz consume memoria de vértice; sin culling individual por defectoPoints / SpritesMillones de puntos sin volumen 3D realMemoria mínima por partículaSin geometría real: no sirve para objetos con forma o iluminación propiaGeometría fusionada (merge) estáticaObjetos que nunca cambian de posición entre síUn solo draw call sin overhead de atributos por instanciaMover una sola copia implica reconstruir toda la geometría fusionadaTextura de datos (DataTexture) + instancingMillones de instancias con transformaciones calculadas en GPUEvita el límite de atributos de vértice, mueve el cálculo a la tarjeta gráficaRequiere shaders personalizados; mayor complejidad de código
## Profundizando: qué pasa dentro de la GPU

Cuando el driver recibe `drawElementsInstanced`, reparte el trabajo entre los miles de núcleos paralelos de la GPU en vez de lanzar 526.000 hilos de ejecución secuenciales, procesando al mismo tiempo un vértice de una instancia distinta en cada núcleo. Es el mismo modelo de paralelismo masivo que usa cualquier tarjeta gráfica para sombrear millones de píxeles por fotograma, aplicado ahora a repetir geometría en vez de calcular color.

El vertex shader recibe, además de los atributos normales de cada vértice, la matriz de instancia correspondiente a través de `gl_InstanceID`. Three.js genera este código automáticamente cuando detecta un [InstancedMesh](https://threejs.org/docs/#api/en/objects/InstancedMesh), agregando una línea equivalente a multiplicar la posición del vértice por `instanceMatrix` antes de aplicar la matriz de cámara.

Para catálogos que superan el millón de objetos, algunos proyectos mueven directamente el cálculo de posición al shader. En vez de que la CPU calcule la órbita de cada asteroide cuadro a cuadro y suba una matriz nueva, el vertex shader recibe los elementos orbitales (semieje mayor, excentricidad, inclinación) como datos por instancia y calcula la posición en tiempo real dentro de la GPU.

WebGPU, el sucesor de WebGL que ya corre en navegadores basados en Chromium y en Firefox, añade compute shaders de propósito general: código que corre en la GPU fuera del pipeline de dibujo, útil para calcular qué instancias son visibles antes de que el pipeline de render las toque siquiera. Es el paso natural después del instancing para quien busca escalar de cientos de miles a millones de objetos.

Enter fullscreen mode Exit fullscreen mode

flowchart TD
F["Nuevo fotograma"] --> C["Calcular que instancias entran en el frustum de camara"]
C --> U["Actualizar solo esas matrices en el buffer"]
U --> D["1 draw call instanciado"]
D --> R["Frame renderizado"]
R --> F




📖 Resumen en Telegram: [Ver resumen](https://telegra.ph/Instancing-en-WebGL-cómo-mostrar-526000-asteroides-sin-colapsar-09-30)

Tu próximo paso: cloná el ejemplo de 50.000 asteroides de este artículo, subí el conteo a 500.000 cambiando una sola línea (`const count = 500000`) y mirá en la consola cuánto tarda `renderer.info.render.calls` en seguir devolviendo 1.

## Preguntas frecuentes

### ¿Qué es el instancing en WebGL y en qué se diferencia de dibujar cada objeto por separado?

Es una técnica que dibuja múltiples copias de la misma geometría en una sola llamada a la GPU, en vez de emitir una llamada de dibujo distinta por cada objeto. La diferencia entre uno y 526.000 draw calls es, en la práctica, la diferencia entre un fotograma fluido y un navegador congelado.

### ¿Cuántas instancias soporta InstancedMesh de Three.js sin perder fluidez?

Depende de la cantidad de triángulos por instancia, de si hay culling implementado y de la GPU del usuario. No hay un número universal; la forma correcta de saberlo es medir con `renderer.info.render.calls` y `renderer.info.render.triangles` en el hardware objetivo, no asumir una cifra de otro proyecto.

### ¿Necesito WebGL2 para usar geometría instanciada?

No. WebGL1 la soporta a través de la extensión `ANGLE_instanced_arrays`; WebGL2 la incluye de forma nativa en el núcleo de la API, sin pedir la extensión aparte.

### ¿Puedo animar cada asteroide de forma independiente con renderizado por instancias?

Sí, actualizando la matriz de cada instancia cuadro a cuadro y marcando `instanceMatrix.needsUpdate = true`. A partir de cierto volumen conviene mover ese cálculo al propio shader en vez de recalcularlo en JavaScript cada fotograma.

### ¿Sirve el renderizado por instancias para satélites en órbita real, no solo para asteroides estáticos?

Sí, es el mismo patrón: cada satélite es una instancia con su propia matriz de transformación, recalculada según sus elementos orbitales en cada actualización de tiempo.

### ¿Qué motor de renderizado usa Space Now para mostrar 526.000 objetos?

El sitio no publica su código fuente, así que no puede confirmarse el motor exacto desde afuera. Lo verificable es el comportamiento: cámara fluida sobre un catálogo de esa escala en un único canvas WebGL, algo que en la práctica exige instancing o una técnica equivalente basada en GPU.

## Referencias

- [Space Now, Belle Lune 2](https://space.bl2.net/): visualizador 3D del sistema solar que sirve de caso de estudio para este artículo.- [Documentación oficial de InstancedMesh en Three.js](https://threejs.org/docs/#api/en/objects/InstancedMesh): API completa de la clase usada en los ejemplos de código.- [MDN: extensión ANGLE_instanced_arrays](https://developer.mozilla.org/en-US/docs/Web/API/ANGLE_instanced_arrays): referencia de la extensión que habilita instancing en WebGL1.- [Repositorio de Three.js en GitHub](https://github.com/mrdoob/three.js/): código fuente de la librería usada en los ejemplos.

📱 **¿Te gusta este contenido?** Únete a nuestro canal de Telegram [@programacion](https://t.me/programacion) donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)