Elegir entre las cuatro técnicas de texto
La tabla comparativa completa y el árbol de decisión que lleva de los requisitos reales a una de las cuatro opciones, sin pasar por la intuición.
La comparativa, eje por eje
| Eje | TextGeometry | Sprite de canvas | troika con SDF | CSS2D o CSS3D |
|---|---|---|---|---|
| Nitidez al acercarse | perfecta | se pixela | perfecta | perfecta |
| Draw calls por etiqueta | una | una | una | ninguna en WebGL |
| Texturas en VRAM | ninguna | una por etiqueta | un atlas compartido | ninguna |
| Coste de cambiar el texto | reconstruir la malla | redibujar el canvas | un sync en worker |
asignar textContent |
| Escrituras complejas | no | sí, el motor del navegador | parcial | sí, el motor del navegador |
| Texto seleccionable y accesible | no | no | no | sí |
| Se ocluye con la geometría | sí | sí | sí | no, hay que programarlo |
| Recibe luz y proyecta sombra | sí | no | sí, con material propio | no |
| Participa del post-procesado | sí | sí | sí | no |
Merece la pena detenerse en tres filas.
La de texturas en VRAM es la que hunde a los sprites de canvas cuando hay muchos: cada etiqueta arrastra su propia imagen, y cincuenta etiquetas legibles rondan los cuarenta megas. Troika comparte un único atlas para toda la tipografía.
La de oclusión es la única en la que el HTML pierde de forma estructural, no por falta de implementación. El compositor del navegador y el búfer de profundidad de WebGL viven en universos separados.
La de luz y sombra es la que salva a TextGeometry de la irrelevancia. Un rótulo metálico en el que un reflejo especular recorre el canto de la letra al girar la cámara solo se consigue con geometría real. Un cuadrado con SDF puede tener material PBR, pero es plano: no hay canto que recorrer.
El árbol
Las dos preguntas de arriba son eliminatorias y por eso van primero. La accesibilidad no admite sustituto: si el texto tiene que llegar a un lector de pantalla o al buscador de la página, ninguna de las tres técnicas que rasterizan sirve. Y el volumen físico tampoco: no hay forma de simular con un cuadrado el bisel de una letra recibiendo luz rasante.
La tercera pregunta separa lo que cambia de lo que no. Un contador de fotogramas que se actualiza sesenta veces por segundo con TextGeometry reconstruye decenas de miles de triángulos por cuadro; con troika lanza un sync por cuadro que satura el worker; con un canvas reutilizado es un fillText y un needsUpdate, que es lo que esa técnica hace mejor que nadie.
Las tres combinaciones que aparecen siempre
Troika para el mundo, CSS2D para la interfaz. El texto que forma parte de la escena —nombres de calles sobre un mapa, etiquetas de piezas dentro de un modelo— va en troika, porque se ocluye correctamente y aguanta el zoom. El texto con el que el usuario interactúa —un panel de información, un menú contextual— va en CSS2D, porque necesita eventos, foco y accesibilidad. Cada uno hace lo que el otro no puede.
TextGeometry para el titular, troika para el resto. Una portada con tres palabras en relieve metálico y todo el texto secundario en SDF. Las tres palabras cuestan lo que cuestan pero son tres, y el resto no paga.
Canvas para lo que cambia, troika para lo que no. Un panel de telemetría donde las etiquetas son fijas y los valores cambian diez veces por segundo: las etiquetas en troika con un solo sync al arrancar, los valores en un canvas que se redibuja. El truco está en no destruir la textura del canvas: se reutiliza el mismo CanvasTexture limpiando y repintando, con textura.needsUpdate = true.
// Un valor numerico que cambia sin reconstruir nadaconst canvas = document.createElement( 'canvas' );canvas.width = 256;canvas.height = 64;const ctx = canvas.getContext( '2d' );const textura = new THREE.CanvasTexture( canvas );textura.colorSpace = THREE.SRGBColorSpace;const sprite = new THREE.Sprite( new THREE.SpriteMaterial( { map: textura, transparent: true } ));sprite.scale.set( 1.0, 0.25, 1 );function actualizarValor( v ) { ctx.clearRect( 0, 0, 256, 64 ); ctx.font = '600 40px system-ui, sans-serif'; ctx.fillStyle = '#a6e3a1'; ctx.textBaseline = 'middle'; ctx.fillText( v.toFixed( 1 ) + ' rpm', 8, 32 ); textura.needsUpdate = true; // el unico coste es una subida de 64 KB}Esa subida de textura por cambio es el precio, y es lineal en el número de píxeles del canvas, no en la longitud del texto. Un canvas de 256 por 64 son 64 KB por actualización; a diez actualizaciones por segundo son 640 KB por segundo, perfectamente asumible. El mismo canvas a 2048 por 512 serían 40 MB por segundo, que no lo es.